Open code423n4 opened 1 year ago
thereksfour marked the issue as primary issue
External requirement with oracle errors
thereksfour changed the severity to 2 (Med Risk)
pmckelvy1 marked the issue as sponsor confirmed
thereksfour marked the issue as satisfactory
thereksfour marked the issue as selected for report
Lines of code
https://github.com/reserve-protocol/protocol/blob/9ee60f142f9f5c1fe8bc50eef915cf33124a534f/contracts/plugins/assets/RTokenAsset.sol#L163-L175 https://github.com/reserve-protocol/protocol/blob/9ee60f142f9f5c1fe8bc50eef915cf33124a534f/contracts/plugins/assets/RTokenAsset.sol#L53-L69 https://github.com/reserve-protocol/protocol/blob/9ee60f142f9f5c1fe8bc50eef915cf33124a534f/contracts/p1/BasketHandler.sol#L329-L351
Vulnerability details
The RTokenAsset is an implementation of interface
IRTokenOracle
to work as a oracle price feed for the little RToken. RTokenAsset implements thelatestPrice
function to get the oracle price and saved time from thecachedOracleData
, which is updated by_updateCachedPrice
function:The
_updateCachedPrice
gets the low and high prices fromprice()
, and updates the oracle price to(low + high) / 2
. And it checkslow != 0 && high != FIX_MAX
.The
RTokenAsset.price
just uses the return oftryPrice
as the low price and high price, iftryPrice
reverts, it will return(0, FIX_MAX)
, which is a invalid pirce range for the oracle price check above. But if there is any underlying collateral's price oracle reverts, for example oracle timeout, theRTokenAsset.price
will return a valid but untrue (low, high) price range, which can be described aslow = true_price * A1
andhigh = FIX_MAX * A2
, A1 isbh.quantity(oracle_revert_coll) / all quantity for a BU
and A2 is theBasketRange.top / RToken totalSupply
.Impact
The RToken oracle price will be about
FIX_MAX / 2
when any underlying collateral's price oracle is timeout. It is significantly more than the actual price. It will lead to a distortion in the price of collateral associated with the RToken, for exampleCurveStableRTokenMetapoolCollateral
:Proof of Concept
RToken.tryPrice
gets the BU (low, high) price frombasketHandler.price()
first.BasketHandler._price(false)
core logic:And the
IAsset.price()
should not revert. If the price oracle of the asset reverts, it just returns(0,FIX_MAX)
. In this case, the branch will enterhigh256 += qty.safeMul(highP, RoundingMode.CEIL);
first. And it won't revert for overflow because the Fixed.safeMul will return FIX_MAX directly if any param is FIX_MAX:So the high price is
FIX_MAX
, and the low price is reduced according to the share of qty.Return to the
RToken.tryPrice
, the following codes usesbasketRange()
to calculate the low and high price for BU:And the only thing has to be proofed is
range.top.mulDiv(highBUPrice, supply, CEIL)
should not revert for overflow in unit192. Now highBUPrice = FIX_MAX, according to theFixed.mulDiv
, ifrange.top <= supply
it won't overflow. And for passing the check in theRToken._updateCachedPrice()
, the high price should be lower than FIX_MAX. So it needs to ensurerange.top < supply
.The max value of range.top is basketsNeeded which is defined in
RecollateralizationLibP1.basketRange(ctx, reg)
:And the basketsNeeded:RToken supply is 1:1 at the beginning. If the RToken has experienced a haircut or the RToken is undercollateralized at present, the basketsNeeded can be lower than RToken supply.
Tools Used
Manual review
Recommended Mitigation Steps
Add a BU price valid check in the
RToken.tryPrice
:Assessed type
Context