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

0 stars 0 forks source link

Incorrect Minimum Amount Post-Check Leads to Failed Redemptions #100

Closed c4-bot-4 closed 1 month ago

c4-bot-4 commented 2 months ago

Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L338-L343

Vulnerability details

redeemCustom() enables users to redeem their RToken balance for a custom basket of underlying assets in specific proportions. there is an issue with the post-checks performed at the end of the redeemCustom() function. The check for the received amounts of the expected output ERC20 tokens against the specified minimum amounts is incorrect leading to users receiving fewer tokens than expected during consecutive redemptions.

Impact

If a user attempts to perform consecutive redemptions using the same minAmounts values, the second redemption may fail even if the user has sufficient RToken balance. This is because the minAmounts array is not updated between consecutive calls, causing the function to revert when the received amounts do not meet the outdated minimum requirements.

In scenarios where users rely on consecutive redemptions to retrieve their underlying assets, the issue may prevent them from successfully redeeming their tokens. This can lead to funds being stuck in the contract and users being unable to access their assets.

Proof of Concept

In scenario where:

  1. User Alice has a balance of 1000 RTokens and wants to redeem them for a custom basket of assets.

  2. Alice calls the redeemCustom() function with the following parameters:

    • amount: 500 RTokens
    • basketNonces: [1, 2, 3]
    • portions: [0.5, 0.3, 0.2]
    • expectedERC20sOut: [TokenA, TokenB, TokenC]
    • minAmounts: [100, 50, 20]
  3. The function performs the necessary checks and transfers the redeemed tokens to Alice's address. Alice receives:

    • TokenA: 120
    • TokenB: 60
    • TokenC: 30
  4. Alice immediately calls the redeemCustom() function again with the same parameters, intending to redeem the remaining 500 RTokens.

  5. The function performs the post-checks, comparing the received amounts against the minAmounts array.

  6. Since the minAmounts array is not updated, the function expects to receive at least [100, 50, 20] tokens again.

  7. However, the actual received amounts in the second redemption are:

    • TokenA: 80
    • TokenB: 40
    • TokenC: 20
  8. The function reverts because the received amounts do not meet the minimum requirements specified in the minAmounts array.

In the redeemCustom() function: RToken:L338-L343

// RToken.sol: Lines 338-344
// Check post-balances
for (uint256 i = 0; i < expectedERC20sOut.length; ++i) {
    uint256 bal = IERC20(expectedERC20sOut[i]).balanceOf(recipient);
    require(bal - pastBals[i] >= minAmounts[i], "redemption below minimum");
}

Tools Used

Manual review

Recommended Mitigation Steps

Should update the minAmounts array between consecutive calls.

// Check post-balances and update minAmounts
for (uint256 i = 0; i < expectedERC20sOut.length; ++i) {
    uint256 bal = IERC20(expectedERC20sOut[i]).balanceOf(recipient);
+   uint256 receivedAmount = bal - pastBals[i];
-   require(bal - pastBals[i] >= minAmounts[i], "redemption below minimum");
+   require(receivedAmount >= minAmounts[i], "redemption below minimum");
+   minAmounts[i] -= receivedAmount;
}

Assessed type

Math