Omniscia Convex Finance Due Diligence
Centralization Evaluation
Centralization Evaluation
Project Overview
The contract system of Convex Finance relies on a manual upgrade-path rather than a common upgrade-ability pattern, such as an EIP-1967 based proxy. While this trait prevents the contract logic from being completely replaced and thus taken over, the ability to change sensitive configurational parameters of the protocol can still cause potentially devastating effects to the overall project.
Additionally, the system operates on a permission-based configuration and invocation pattern for many of the sensitive functions of the system. While some level of input sanitization is applied, we will analyze whether any can be circumvented and ultimately what impact the compromisation of a single Externally Owned Account (EOA) can have.
Before analyzing each contract in detail, we need to analyze what entities the project is represented by within the blockchain. We identified two parties; the Convex deployer which is a single externally-owned account and the Convex multi-signature wallet.
Convex Deployer (EOA)
The Convex Deployer is a single Ethereum account controlled by a private key that has been historically used to deploy the Convex solution. Although the team has performed the necessary actions to diminish its control over the project, during our analysis we were able to identify certain contracts that have not been properly updated as well as a contract from which the deployer is able to maliciously acquire 1.4 million USD worth of Convex (CVX) tokens and thus cause a negative price impact.
We believe this to be an oversight by the Convex team and to be the main centralization attack vector we identified across the project.
Convex Gnosis Multi-Signature Wallet (v1.1.1)
The multi-signature wallet implementation used by Convex is the Gnosis v1.1.1 instance. Upon inspection of its configuration, we identified that the contract currently has the following three owners:
- 0x22C8A69089e3BE8007716bF01b942489dd7cF059
- 0xAAc0aa431c237C2C0B5f041c8e59B3f1a43aC78F
- 0xb3DF5271b92e9fD2fed137253BB4611285923f16
The multi-signature wallet employs a 2-out-of-3 scheme which we believe to be the least secure multi-signature system available given that only two private keys need to be compromised to possess full control over the wallet.
The team behind Convex Finance is anonymous and as such, it is not possible to pinpoint which address belongs to whom and whether they are ultimately owned by a single entity. The convex-eth/platform repository contains contributions by three parties one of which is not anonymous and the third being the project itself with zero code contributions.
The repository appears to be under constant development, meaning that the Convex team may be planning a consequent release of either new contracts or the replacement of existing ones.
Code Centralization Analysis
We will now provide a brief description of the administrative status of each of the contracts in the Convex Finance system where applicable and analyze whether an attack by the privileged party is feasible. An overview is as follows:
| Contract | Proper Role Setup | Potential Attack Surface | Private Keys Required | Entity Affected |
|---|---|---|---|---|
| Booster | ✔️ | ✔️ | 2 | Convex Token |
| Voter Proxy | ✔️ | ✔️ | 2 | User Funds |
| Convex Token | ✔️ | ✔️ | 2 | Convex Token (Unlimited Mints Immediately) |
| Convex CRV Token | ✔️ | ❌ | ➖ | ➖ |
| CRV Depositor | ✔️ | ❌ | ➖ | ➖ |
| Reward Factory | ✔️ | ❌ | ➖ | ➖ |
| Token Factory | ✔️ | ❌ | ➖ | ➖ |
| Stash Factory | ✔️ | ❌ | ➖ | ➖ |
| CRV Rewards | ✔️ | ❌ | ➖ | ➖ |
| cvxCRV Rewards | ✔️ | ❌ | ➖ | ➖ |
| Pool Manager | ✔️ | ➖ | 2 | Convex Token |
| Arbitrator Vault | ✔️ | ✔️ | 2 | Bonus Tokens (stkAAVE) |
| Convex Master Chef | ✔️ | ✔️ | 2 | Convex Token ($58.3M Gradually) |
| Vested Escrow | ❌ | ➖ | 1 | Convex Token |
| Merkle Airdrop Factory | ➖ | ❌ | ➖ | ➖ |
| Merkle Airdrop | ❌ | ✔️ | 1 | Convex Token ($1.4M Immediately) |
| Claim Zap | ✔️ | ❌ | ➖ | ➖ |
| Treasury Funds | ✔️ | ✔️ | 2 | Any Token, Convex ($200k Immediately) |
Booster
The booster system contains 7 different role concepts each one being responsible for a different set of functions made available by the contract. Of those 7, 3 are governed by the project and the remaining 4 are governed by smart contract code. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| CRV Rewards | High | Inexistent | ❌ |
| Stash | Medium | Existent | ❌ |
| Pool Manager | Medium | Existent | ➖ |
| Locked Rewards | High | Inexistent | ❌ |
| Owner | Medium | Existent | ✔️ |
| Fee Manager | Low | Inexistent | ✔️ |
| Vote Delegate | Low | Inexistent | ✔️ |
CRV Rewards Role
The crvRewards role is the looked up entry of a particular pool ID that is assigned during a pool's creation and is done so to the result of a rewardFactory clone. This indicates that the code of crvRewards is immutable and thus always behaves as expected. After analyzing the rewardFactory codebase, we have concluded that the functions are only invoked in proper scenarios and cannot be circumvented.
rewardClaimed Access
This function is solely invoked whenever new rewards are meant to be minted. The function's invocation results in the minting of new Convex tokens proportionate to the amassed rewards of an individual in the crvRewards contract.
withdrawTo Access
This function is solely invoked when a user decides that they want to simultaneously withdraw and unwrap their tokens. This results in the deposit being withdrawn and unwrapped to the underlying LP tokens in the user's account.
Stash Role
The stash role is the looked up entry of a particular pool ID that is assigned during a pool's creation and is done so to the result of a stashFactory clone. This indicates that the code of stash is immutable and thus always behaves as expected. After analyzing the stashFactory codebase, we have concluded that a limited degree of manipulation can be applied here by the poolManager role. Additionally, the stashFactory can be replaced by the owner role at will permitting the stash role to perform arbitrary calls.
claimRewards Access
The claimRewards function does not sanitize the input _gauge argument which is relayed as is by all versioned stash implementations generated by the factory. Given that the gauge can be specified by the creator of new pools, an unwarranted claim of rewards can occur for an unrelated gauge. The impact of this action however is negligible.
setGaugeRedirect Access
The setGaugeRedirect function can be manipulated to redirect an existing gauge to a new pool whose LP token is solely owned by the attacker. This can result in a permanent redirect of all V3 gauge payments in case duplicate gauges are maliciously specified during the addition of a new pool.
Pool Manager Role
The poolManager is responsible for adding new pools and performing a shutdown on deprecated ones. Additionally, the pool manager can be adjusted on the fly by itself and thus potentially point to an EOA address in the future. The current manager is entirely controlled by a smart contract. We analyzed the source code of the PoolManager contract and concluded that the only way to take advantage of the role is by compromisation of the Convex Finance multi-signature wallet.
setPoolManager Access
This function permits the currently set poolManager to be set to a new one. The PoolManager implementation contains a revertControl function that transmits control back to the original operator which currently is the Convex Finance multi-signature wallet. As such, in order to perform arbitrary calls as poolManager a revertControl function call followed by a setPoolManager call need to be performed by the multi-signature wallet.
shutdownPool Access
This function permits the removal of a pool and associated gauge from the system by withdrawing all funds and setting it as shut-down changing how the system behaves when interacting with it for withdrawals.
addPool Access
Here, a new pool is created with the input arguments provided. No access control is imposed on the arguments and it is assumed that the caller has imposed sufficient sanitization on them, meaning that if the setPoolManager sequence detailed above unfolds new pools can be created arbitrarily.
Locked Rewards Role
The lockRewards member is set only once by the owner of the contract and currently points to a smart contract implementation. We analyzed the BaseRewardPool implementation and concluded that its normal operation cannot be circumvented.
rewardClaimed Access
The function is invoked by the locked rewards contract to mint new Convex tokens and satisfy the reward rate of the contract. The reward rate of the contract is set by the Booster itself thus enabling a closed loop system that cannot artificially inflate the reward rate.
Owner Role
The owner role is responsible for the early lifecycle of the contract as well as the ability to shutdown the system and adjust certain limited-access configurational values of the protocol. It currently points to the Convex Finance multi-signature wallet. Our analysis of the role's powers indicates that a certain degree of manipulation can be applied by it.
setOwner Access
Replaces the current owner with the supplied one. As a result, this allows the role to perform arbitrary actions if a single vote of the multi-signature wallet of Convex is passed.
setFactories Access
This function is meant to adjust the reward factories once but allow re-setting of the stash factory. As a result, this can allow instances of the stash role to perform arbitrary actions on the contract in case the factory is maliciously replaced.
setArbitrator Access
Replaces the current rewardArbitrator with the supplied one.
shutdownSystem Access
Shutdowns all pools of the system and sets a system-wide flag that the system should not accept deposits anymore. Intended as a failsafe in case the protocol is somehow compromised.
setRewardContracts Access
Meant to be invoked once during the early lifecycle of the contract, it sets the reward contract instances.
Fee Manager Role
The feeManager role is meant to be able to set the treasury that fees go to, adjust the fees of the system and set the lockFees and feeToken contracts based on changes in Curve's system. It currently points to the Convex Finance multi-signature wallet. Our analysis of the role's powers indicates that its powers are limited to the rewards the protocol acquires.
setFeeManager Access
Permits the feeManager to be overwritten. As a result, this allows the role to perform arbitrary actions if a single vote of the multi-signature wallet of Convex is passed.
setTreasury Access
Adjusts the treasury address to a new one where fees are meant to be accrued to. This allows all protocol fees to be redirected to a new wallet and would only negatively impact the project's team if the feeManager role was exploited.
setFees Access
Adjusts the various fees of the protocol. These fees include funds redirected to the various reward contracts and the ranges of these fees are strictly defined by the contract both independently and in sum disallowing circumvention and potentially 100% fees from being applied.
setFeeInfo Access
Updates the fee information of the contract based on changes brought to the Curve registry. Its proper execution relies on the security of Curve.
Vote Delegate Role
This role is able to perform votes on behalf of the accumulated staked Curve funds for adjusting gauge weights on Curve and in general voting for a particular ID in favor or against. It currently points to the Convex Finance multi-signature wallet. Our analysis of the role's powers indicates that its effects relate to Curve and not Convex.
setVoteDelegate Access
Permits the voteDelegate to be overwritten. As a result, this allows the role to perform arbitrary actions if a single vote of the multi-signature wallet of Convex is passed.
vote Access
Allows the voteDelegate to vote on a Curve proposal for or against. Amasses the total Curve voting power held by the protocol and thus can be considered a centralization point in an analysis of Curve.
voteGaugeWeight Access
Allows the voteDelegate to vote for the adjustment of a set of gauges and weights affecting the rewards of Curve. Amasses the total Curve voting power held by the protocol and thus can be considered a centralization point in an analysis of Curve.
Voter Proxy
The Curve voter proxy contains 4 different role concepts each one being responsible for a different set of functions made available by the contract. Of those 4, 1 is governed by the project and the remaining 3 are governed by smart contract code. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Owner | High | Existent | ✔️ |
| Operator | High | Existent | ➖ |
| Depositor | Low | Inexistent | ❌ |
| Stash | Medium | Existent | ➖ |
Owner Role
The owner of the contract is able to adjust all sensitive parameters of the system, including the operator and the depositor. As a result, the owner can be thought of as the most powerful role and able to completely overtake the system. It currently points to the Convex Finance multi-signature wallet. Our analysis of the role's powers indicates that it can lead to a rug-pull scenario in case the project owners act maliciously or have their keys compromised.
setOwner Access
The setOwner function permits the owner of the contract to change and thus a single vote of the multi-signature can permit a more elaborate attack to unfold by the new owner.
setOperator Access
The setOperator function permits the operator of the contract to change. This party possesses full control over the funds held by the contract and thus allows a rug-pull to occur.
setDepositor Access
The setDepositor function permits the depositor of the contract to change. The depositor is responsible for managing the escrowed CRV vote lifecycle and thus has minimal impact to the funds of the contract.
Operator Role
Almost all functions of the contract are guarded by the operator role who is meant to represent a logically functioning contract. As it can be adjusted by the project owners, it can result in a rug-pull should the owner keys be compromised or the owners act maliciously. We will not dive deeper to each respective function of the role as they are numerous, however, the role is capable of withdrawing ERC20 tokens from the contract at will i.e. by malicious execution of claimFees.
Depositor Role
The depositor role is responsible for the escrowed vote lifecycle in the Curve system. It currently points to the CrvDepositor contract implementation. After analyzing the depositor codebase, we have concluded that the functions are only invoked in proper scenarios and cannot be circumvented.
Stash Role
The stash role represents a mapping entry that is set to by the operator. Based on the analysis of the existing operator which is the Booster contract, it is possible for the project to set a malicious stash in case the Stash Factory implementation is replaced by the project's team.
withdraw Access
The stash role is capable of withdrawing any non-protected ERC20 asset from the contract. Given that LP tokens and gauges are protected at the contract-level, the attack surface of this function solely relies on accidentally sent tokens to the contract as well as extra rewards by other protocols that automatically accrue.
Convex Token
The Convex Token contains a single role concept that is responsible for minting new tokens in the system. It is currently governed by smart contract code but can be easily changed by the Convex multi-signature wallet. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Operator | High | Existent | ➖ |
Operator Role
The operator of the contract is capable of minting new Convex tokens in circulation to supplement the various reward mechanisms of the protocol. It currently points to the Booster contract which performs as expected but can be changed to an arbitrary address given that a function exists that newly sets the operator to the one specified in Voter Proxy. As shown above, the multi-signature wallet of Convex can arbitrarily change the operator and thus be able to mint Convex tokens at will.
mint Access
The mint function creates new Convex tokens. This function is privileged by the role and should the role be compromised, an infinite amount of tokens can be minted and consequently sold in the open market.
Convex CRV Token
The Convex CRV Token contains a single role concept that is responsible for minting new tokens in the system and burning them. It is currently governed by smart contract code and can never be changed. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Operator | High | Inexistent | ❌ |
Operator Role
The operator of the contract is capable of minting and burning Convex CRV tokens to aid tracking of the wrapped CRV tokens of the protocol. It currently points to the CrvDepositor contract which performs as expected. A setOperator function exists in the contract that allows the operator to change, however, the CrvDepositor contract is unable to invoke this function and thus the operator role will never change.
CRV Depositor
The Convex CRV Token contains a single role concept that is responsible for managing the fees of the protocol. It is currently governed by an EOA, the Convex Deployer, and can be changed at will. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Fee Manager | Low | Inexistent | ✔️ |
Fee Manager Role
The role is responsible for adjusting the fees of the contract as well as being able to re-initialize the escrowed vote lock by withdrawing the previous one and re-depositing. It is currently controlled by the EOA of the Convex Deployer and thus is handled by a single party.
setFeeManager Access
Permits a new fee manager to be set in the system.
setFees Access
Allows the fee of the contract to be adjusted. The range the fee can fluctuate in is hard-coded in the contract and thus cannot be changed to a harmful percentage.
initialLock Access
Re-initializes (or initializes if it's the first time) the escrow lock of the CRV tokens.
Reward Factory
The reward factory contract contains two role concepts that are responsible for creating new reward contract instances and maintaining the active reward list respectively. The former role points to the Booster contract and cannot change whereas the latter is meant to be set by the Booster contract to a stash instance. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Operator | Low | Inexistent | ❌ |
| Reward Access | Low | Inexistent | ❌ |
Operator Role
The operator is able to create new pool instances from the factory that are meant to be deposited to by it. Given that no funds remain at rest in the factory and it is solely a utility contract, no funds are ever processed by the contract or role.
Reward Access Role
The rewardAccess role is responsible for maintaining an array of active rewards in the contract. Given that no funds remain at rest in the factory and it is solely a utility contract, no funds are ever processed by the contract or role.
Token Factory
The tokens factory contract contains a single role concept that is responsible for creating new token contract instances. The role points to the Booster contract and cannot change. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Operator | Low | Inexistent | ❌ |
Operator Role
The operator is able to create new token instances from the factory. Given that no funds remain at rest in the factory and it is solely a utility contract, no funds are ever processed by the contract or role.
Stash Factory
The stash factory contract contains a single role concept that is responsible for creating new stash contract instances. The role points to the Booster contract and cannot change. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Operator | Low | Inexistent | ❌ |
Operator Role
The operator is able to create new stash instances from the factory. Given that no funds remain at rest in the factory and it is solely a utility contract, no funds are ever processed by the contract or role.
CRV Rewards
The CRV rewards contract contains two role concepts that are responsible for introducing extra rewards and affecting the reward drip rate. The former role points to the Convex Finance Deployer EOA account whereas the latter role points to the Booster contract and cannot change. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Reward Manager | Low | Inexistent | ✔️ |
| Operator | High | Inexistent | ❌ |
Reward Manager Role
The rewardManager is able to introduce a set of new rewards to the contract that are called before each action is performed. Given that the order of execution disallows re-entrancies from manifesting, there is no attack surface introduced by these calls.
addExtraReward Access
As its name implies, adds an extra reward to the contract in the form of a contract. The contract is consequently invoked for withdrawals and deposits but is done so in full compliancy with the Checks-Effects-Interactions pattern by being the uppermost call.
clearExtraRewards Access
Clears the existing extra rewards list.
Operator Role
The operator is able to queue new rewards without supplying tokens. As this directly affects the rate at which funds are dripped, it possesses a degree of power over the contract. It currently points to the Booster contract and cannot be changed; our inspection indicates that the function is solely invoked in correct scenarios.
queueNewRewards Access
The queueNewRewards function affects the drip rate of the contract by adding new rewards to the existing queued ones and enforcing them if a certain time threshold has passed.
cvxCRV Rewards
This contract is equivalent in nature to the CRV Rewards contract in both logic and centralization.
Pool Manager
The pool manager contract contains a single role concept that is responsible for creating new pool instances in the Booster. The role points to the Convex multi-signature wallet and can be arbitrarily changed. Additionally, the contract can request the Booster contract to replace itself with a new address thus allowing the logic it imposes to change as well. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Operator | High | Inexistent | ✔️ |
Operator Role
The role is responsible for shutting down pools in the protocol as well as passing the torch to the next operator. Although no attack surface exists within the contract itself, it contains a function that permits the operator to replace the contract implementation entirely within Booster and thus cause attack surfaces to manifest as detailed in the Booster chapter.
revertControl Access
Invokes the setPoolManager function in the Booster to the operator thus permitting them to re-set the manager to an arbitrary contract or address.
Arbitrator Vault
The Arbitrator Vault implementation contains a single role concept that is able to distribute funds manually when the same reward token exists across multiple pools. It currently points to the Convex multi-signature wallet but can be adjusted to an arbitrary address. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Operator | High | Inexistent | ✔️ |
Operator Role
The role is responsible for supplying the contract with fair PIDs and amounts for a particular token to be distributed amongst them in a fair fashion. Although the token, amounts and PIDs can be dynamically specified they all rely on the depositor which is a smart contract and cannot be manipulated.
distribute Access
Performs the distribution of the specified _token to the various stashes of the supplied pool IDs. Extracts values from depositor which cannot be manipulated.
Convex Master Chef
The contract is a SushiSwap fork that acts as a gradual release of Convex tokens to its stakers. The contract contains a single role concept that handles the lifecycle of the staking pools in the system. It currently points to the Convex multi-signature wallet. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Owner | High | Existent | ✔️ |
Owner Role
The owner is able to adjust the pool allocations of the staking implementation and in general mange the lifecycle of staking pools. As a result, they are able to create a fake pool with a value-less token and assign a decent portion of the allocation to it to continuously drip rewards to themselves, even equivalent to the full drip of the contract.
Although we will not dive into the function set available to the contract, our on-chain analysis indicated that there are two pools that contain a special single-unit token that is only held by the Convex Finance deployer. In essence, this means that the Convex Finance deployer had a decent portion of the rewards assigned to themselves over the course of the contract's lifetime. This can be independently validated by querying the poolInfo of slots 2 and 3 directly on the contract using an interface like Etherscan.
Overall, the owner is in full control of the allocation distributed of a constant drip-rate for 23,603,977.22~ CVX tokens as of 2:00PM EST 21st of July, 2021 amounting to roughly 58,3 million USD.
Vested Escrow
The contract system contains two role concepts that are responsible for managing the funding lifecycle of the contract as well as the provision of new funds to it. Both roles point to the EOA of the Convex Deployer and thus are controlled by a single party. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Admin | High | Inexistent | ✔️ |
| Fund Admin | Medium | Inexistent | ✔️ |
Admin Role
The admin role is responsible for setting the fundAdmin role and is also the only party able to add new tokens for allocation to the contract. As the contract does not have funds available by itself and they need to be manually added, there is no attack surface here.
Fund Admin Role
The fundAdmin role is able to disburse the funds deposited by the admin role based on the release schedule specified by the contract. If the admin and fundAdmin roles were controlled by separate parties, an attack surface could manifest here whereby the malicious party assigns the vesting amounts maliciously however they are both controlled by the Convex deployer EOA. Additionally, there is no unallocated supply within the contract currently meaning that new vesting periods can only be introduced if funds are introduced to the system.
Merkle Airdrop Factory
The contract does not impose any form of access control.
Merkle Airdrop
The airdrop contract contains a single role concept that is the owner of the contract able to define the Merkle parameters of the airdrop. This role currently points to the Convex deployer and allows them to possess full control over the funds of the airdrop contract which are 582,507.80~ CVX tokens as of 2:00PM EST 21st of July, 2021 amounting to roughly 1.4 million USD. We believe this to be a viable centralization attack vector for the project. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Owner | High | Existent | ✔️ |
Owner Role
The owner role is able to define the various Merkle-based parameters of the airdrop. As a result, they are able to change the parameters to a carefully crafted Merkle tree they know the solutions to to get access to the full amount of underlying funds in the contract. Given that the owner does not point to the multi-signature wallet and instead points to the deployer, it is possible to acquire the underlying funds in a relatively straightforward manner for whoever manages to get access to the private key of the Convex deployer.
Claim Zap
The contract does not impose any meaningful form of access control. It possesses role notions however the contract itself is meant to act as a utility contract and the only component that can be adjusted for its functionality is the chefRewards contract an adjustment of which cannot cause misbehaviours.
Treasury Funds
The contract contains a single role concept that is meant to possess full control over the assets held by the contract as well as being able to perform arbitrary calls. It currently points to the Convex multi-signature wallet but can be adjusted. A quick overview can be found below:
| Role | Asset Criticality | Attack Surface | Project Controlled |
|---|---|---|---|
| Owner | High | Existent | ✔️ |
Owner Role
The owner role possesses full control over the funds that are held by the contract. All its functions are guarded by the role and these actions involve dispersing funds to an arbitrary address and performing an arbitrary contract call. The contract possesses 85,906.50~ CVX tokens as of 2:00PM EST 21st of July, 2021 amounting to roughly 200 thousand USD however they are also guarded by a multi-signature wallet implementation instead of a single EOA and thus are not a viable centralization attack vector.