As seen from above, there is no check to ensure that cl.answer does not go below or above a certain price.
Chainlink aggregators have a built in circuit breaker if the price of an asset goes outside of a predetermined price band. Therefore, if CVX experiences a huge drop/rise in value, the CVX / ETH price feed will continue to return minAnswer/maxAnswer instead of the actual price of CVX.
Currently, minAnswer is set to 1e13 and maxAnswer is set to 1e18. This can be checked by looking at the AccessControlledOffchainAggregator contract for the CVX / ETH price feed. Therefore, if CVX ever experiences a flash crash and its price drops to below 1e13 (eg. 100), the cl.answer will still be 1e13.
This becomes problematic as ethPerCvx() is used to determine the price of vAfEth:
If CVX experiences a flash crash, vStrategy.price() will be 1e13, which is much larger than the actual price of CVX. This will cause totalValue to become extremely large, which in turn causes amountToMint to be extremely large as well. Therefore, the caller will receive a huge amount of afEth.
Impact
Due to Chainlink's in-built circuit breaker mechanism, if CVX experiences a flash crash, ethPerCvx() will return a price higher than the actual price of CVX. Should this occur, an attacker can call deposit() to receive a huge amount of afEth as it uses an incorrect CVX price.
This would lead to a loss of funds for previous depositors, as the attacker would hold a majority of afEth's total supply and can withdraw most of the protocol's TVL.
Proof of Concept
Assume the following:
For convenience, assume that 1 safEth is worth 1 ETH.
The AfEth contract has the following state:
ratio = 5e17 (50%)
totalSupply() = 100e18
safEthBalanceMinusPending() = 50e18
vEthStrategy.balanceOf(address(this)) (vAfEth balance) is 50e18
The VotiumStrategy contract has the following state:
Only 50 vAfEth has been minted so far (totalSupply() = 50e18).
The contract only has 50 CVX in total (cvxInSystem() = 50e18).
As seen from above, the attacker will receive 1e30 AfEth, which is huge compared to the remaining 100e18 held by previous depositors before the flash crash.
Therefore, almost all of the protocol's TVL now belongs to the attacker as he holds most of AfEth's total supply. This results in a loss of funds for all previous depositors.
Recommended Mitigation
Consider validating that the price returned by Chainlink's price feed does not go below/above a minimum/maximum price:
Lines of code
https://github.com/code-423n4/2023-09-asymmetry/blob/main/contracts/strategies/votium/VotiumStrategyCore.sol#L173-L181
Vulnerability details
Bug Description
The
ethPerCvx()
function relies on a Chainlink oracle to fetch the CVX / ETH price:VotiumStrategyCore.sol#L158-L169
The return values from
latestRoundData()
are validated as such:VotiumStrategyCore.sol#L173-L181
As seen from above, there is no check to ensure that
cl.answer
does not go below or above a certain price.Chainlink aggregators have a built in circuit breaker if the price of an asset goes outside of a predetermined price band. Therefore, if CVX experiences a huge drop/rise in value, the CVX / ETH price feed will continue to return
minAnswer
/maxAnswer
instead of the actual price of CVX.Currently,
minAnswer
is set to1e13
andmaxAnswer
is set to1e18
. This can be checked by looking at the AccessControlledOffchainAggregator contract for the CVX / ETH price feed. Therefore, if CVX ever experiences a flash crash and its price drops to below1e13
(eg.100
), thecl.answer
will still be1e13
.This becomes problematic as
ethPerCvx()
is used to determine the price of vAfEth:VotiumStrategy.sol#L31-L33
Furthermore, vAfEth's price is used to calculate the amount of AfEth to mint to users whenever they call
deposit()
:AfEth.sol#L162-L166
If CVX experiences a flash crash,
vStrategy.price()
will be1e13
, which is much larger than the actual price of CVX. This will causetotalValue
to become extremely large, which in turn causesamountToMint
to be extremely large as well. Therefore, the caller will receive a huge amount of afEth.Impact
Due to Chainlink's in-built circuit breaker mechanism, if CVX experiences a flash crash,
ethPerCvx()
will return a price higher than the actual price of CVX. Should this occur, an attacker can calldeposit()
to receive a huge amount of afEth as it uses an incorrect CVX price.This would lead to a loss of funds for previous depositors, as the attacker would hold a majority of afEth's total supply and can withdraw most of the protocol's TVL.
Proof of Concept
Assume the following:
AfEth
contract has the following state:ratio = 5e17
(50%)totalSupply() = 100e18
safEthBalanceMinusPending() = 50e18
vEthStrategy.balanceOf(address(this))
(vAfEth balance) is50e18
VotiumStrategy
contract has the following state:totalSupply() = 50e18
).cvxInSystem() = 50e18
).cvxPerVotium()
returns1e18
as:The price of CVX flash crashes from
2e15 / 1e18
ETH per CVX to100 / 1e18
ETH per CVX. Now, if an attacker callsdeposit()
with 10 ETH:priceBeforeDeposit
, which is equal toprice()
, is5e17 + 5e12
as:ratio
is 50%, 5 ETH is staked into safEth:sMinted = 5e18
, since the price of safEth and ETH is equal.VotiumStrategy
:priceBefore
, which is equal tocvxPerVotium()
, is1e18
as shown above.1e16
CVX (according to the price above),cvxAmount = 5e34
.vMinted = 5e34
as:vStrategy.price()
afterVotiumStrategy
'sdeposit()
function is called:ethPerCvx()
returns1e13
, which isminAnswer
for the CVX / ETH price fee.cvxPerVotium()
is still1e18
as:vStrategy.price()
returns1e13
as:As seen from above, the attacker will receive
1e30
AfEth, which is huge compared to the remaining100e18
held by previous depositors before the flash crash.Therefore, almost all of the protocol's TVL now belongs to the attacker as he holds most of AfEth's total supply. This results in a loss of funds for all previous depositors.
Recommended Mitigation
Consider validating that the price returned by Chainlink's price feed does not go below/above a minimum/maximum price:
VotiumStrategyCore.sol#L173-L181
This ensures that an incorrect price will never be used should CVX experience a flash crash, thereby protecting the assets of existing depositors.
Assessed type
Oracle