RSR Stakers Unintentionally Slashed During Collateral Depegging Despite Sufficient Collateral Backing #240
Labels
2 (Med Risk)
Assets not at direct risk, but function/availability of the protocol could be impacted or leak value
bug
Something isn't working
edited-by-warden
🤖_primary
AI based primary recommendation
sufficient quality report
This report is of sufficient quality
Lines of code
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/RecollateralizationLib.sol#L300
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/RecollateralizationLib.sol#L317-L321
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/TradeLib.sol#L163
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/TradeLib.sol#L176
Vulnerability details
Description
A collateral that has depegged, can still be considered SOUND, as long as the current price remains within the boundaries of the ORACLE_ERROR. In other words, if, ORACLE_ERROR is 1% at the peg price of 1e8, a collateral can be SOUND down to 0.99e8 and up to 1.01e8. Because of this, a depegged collateral can be used to
rebalance()
theBackingManager
as long as it is the most-in-surplus.With this, a call to
isEnoughToSell()
onTradeLib.sol
is used to determine if there is a sufficient amount of collateral in theBackingManager.sol
to cover the rebalancing.https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/RecollateralizationLib.sol#L317-L322
The problem is that
isEnoughToSell()
takes the current collateral price (in this case, the depegged price) and uses it as thelow
parameter.e.g. At peg of 1e8, the low may be
990000000000000000
at a 1% oracle error, however, at a depegged price of 0.99e8, the low is now980100000000000000
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/RecollateralizationLib.sol#L300
Because the
low
parameter is now lower that it would normally be, theminTradeSize
, used inisEnoughToSell
will actually be inflated due to dividing by a smaller price:e.g. At-peg,
minTradeSize = 101010101010101010101011 [1.01e23]
De-peg,
minTradeSize = 102030405060708091011122 [1.02e23]
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/TradeLib.sol#L176
Consequently, because
isEnoughToSell
compares whether thectx.bals[i].minus(needed)
is >=minTradeSize
, there are scenarios where at-pegisEnoughToSell
returns true, utilizing the collateral to rebalance, but when depegged it returns false, resulting in the usage of staked RSR to rebalance.Impact
Now, for this to occur, a few things must align:
BackingManager
must be within the specified range.Due to the above conditions, the likelihood of this playing out is low, however, the amount of RSR that is slashed from stakers can be substantial as it is bounded to the
minTradeVolume
.minTradeVolume
is 1e22, the amount seized is approximately 1.02e22 (10,200 RSR),minTradeVolume
is 1e23, the amount seized is approximately 1.02e23 (102,000 RSR),minTradeVolume
is 1e24, the amount seized is approximately 1.02e24 (1,020,000 RSR),As you can see, the amount seized is almost a 1:1 ratio, therefore, the higher the
minTradeVolume
, the more is seized. Being as how these scale by a factor of 10, the amount seized grows exponentially larger as minTradeVolume increases.According to the
deployment-variable.md
:https://github.com/code-423n4/2024-07-reserve/blob/main/docs/deployment-variables.md
The reasonable range of
minTradeVolume
isReasonable range: 1e19 to 1e23
. So in the scenario displayed below in the PoC, we are using the highest reasonable value of 1e23.Note:
minTradeVolume
can be set all the way up to 1e29https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/mixins/Trading.sol#L22
Proof of Concept
To use this PoC, create a new file in
test
and paste the following. Make sure to set the basket asreweightable
infixtures.ts
:https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/test/fixtures.ts#L459
Additionally, this test is designed to pass when depegged, showcasing that RSR is seized. To verify that RSR is not seized when at-peg, but that the collateral (token2) is used to rebalance, just change the oracle price from
0.99e8
to1e8
.The logs when de-pegged:
The logs when at-peg:
Tools Used
Manual Analysis/Hardhat
Recommended Mitigation Steps
My recommendation is to make use of the
issuancePremium()
function that has been added on toBasketHandler.sol
to scale thelow
from the actuallow
de-pegged price to the targetlow
at-peg price. Ideally, this calculation would be done on thenextTradePair()
function, however this will likely result in stack-to-deep errors. The next best option I can think of will be to have the low and the high returned to thenextTradePair()
function already scaled.The
scaledPrice()
function will start with thereg.assets[i].price()
and will utilize apremium
to determine if it is under-pegged, similar to the checks here:https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BasketHandler.sol#L440-L443
and here:
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/BasketHandler.sol#L500-L503
If depegged,
scaledPrice()
will return the scaled values, but if not depegged, it will return the values fromreg.assets[i].price()
Assessed type
Error
The text was updated successfully, but these errors were encountered: