code-423n4 / 2024-07-reserve-validation

0 stars 0 forks source link

Incorrect Exchange Rate Calculation in `_scaleUp()` Function Allows Inflated RToken Supply and Insufficient Collateral Backing #83

Open c4-bot-5 opened 2 months ago

c4-bot-5 commented 2 months ago

Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L483-L497 https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L89-L110

Vulnerability details

The issue() and issueTo() functions allow users to mint new RTokens by depositing the required collateral baskets. The contract tracks the number of collateral baskets needed (basketsNeeded) to fully collateralize the RToken supply and calculates the exchange rate as basketsNeeded / totalSupply.

There is an issue in the _scaleUp() function, which is called by issueTo() to mint new tokens and update the basketsNeeded value. The problem arises when minting tokens from a zero supply state. In this case, the function mints amtBaskets worth of RTokens instead of using the current exchange rate, allowing the effective exchange rate to decrease on subsequent mints.

Impact

Proof of Concept

The exchange rate has decreased from 1 basket per RToken (after Alice's mint) to 0.5 baskets per RToken (after Bob's mint).

Tools Used

Manual review

Recommended Mitigation Steps

The _scaleUp() function should establish a non-zero initial basketsNeeded value when minting from a zero supply state.

function _scaleUp(
    address recipient,
    uint192 amtBaskets,
    uint256 totalSupply
) private {
-   uint256 amtRToken = totalSupply != 0
-       ? amtBaskets.muluDivu(totalSupply, basketsNeeded) // {rTok} = {BU} * {qRTok} * {qRTok}
-       : amtBaskets; // {rTok}
+   uint256 amtRToken;
+   if (totalSupply == 0) {
+       amtRToken = amtBaskets;
+       basketsNeeded = amtBaskets;
+   } else {
+       amtRToken = amtBaskets.muluDivu(totalSupply, basketsNeeded);
+       basketsNeeded += amtBaskets;
+   }
    emit BasketsNeededChanged(basketsNeeded, basketsNeeded + amtBaskets);
-   basketsNeeded += amtBaskets;

    // Mint RToken to recipient
    _mint(recipient, amtRToken);
}

Assessed type

Math