Skip to content

Not resetting totalBurned in CashManger will break user redemptions #291

Description

@code423n4

Lines of code

https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L721-L724

Vulnerability details

Not resetting totalBurned in CashManger will break user redemptions

The current implementation in CashManager.completeRedemptions is not updating the totalBurned amount in an epoch if there was a refund.
The problem is, that if not all user redemptions can be processed in a single call per epoch it will break the withdraws for all users that are processed in the second call for the epoch as they will receive less tokens.

Scenario:

  • There are lots of requestRedemption's from users in an epoch.
  • For all the requestRedemption there is at least one that will receive a refund
  • As there are too much requestRedemption's to process all in one transaction (or any other reason why the admin want to split it), the admin needs to schedule another transaction with the rest of the users

The users in the second transaction will now get less tokens than the users processed in the first transaction.

Proof of Concept

The following test should succeed, but Bob only gets 130666666 instead of 147000000 tokens and so it fails.

Add Test to File: forge-tests/cash/cash_manager/Redemption.t.sol

function test_redeem_redeemRefund_multiple() public {
    // Seed alice and bob with 200 cash tokens
    _seed(200e18, 200e18, 50e18);

    // Have alice request to withdraw 200 cash tokens
    vm.startPrank(alice);
    tokenProxied.approve(address(cashManager), 200e18);
    cashManager.requestRedemption(200e18);
    vm.stopPrank();

    // Have bob request to withdraw 200 cash tokens
    vm.startPrank(bob);
    tokenProxied.approve(address(cashManager), 200e18);
    cashManager.requestRedemption(200e18);
    vm.stopPrank();

    // Have charlie request to withdraw his tokens
    vm.startPrank(charlie);
    tokenProxied.approve(address(cashManager), 50e18);
    cashManager.requestRedemption(50e18);
    vm.stopPrank();

    // Move forward to the next epoch
    vm.warp(block.timestamp + 1 days);
    vm.prank(managerAdmin);
    cashManager.setMintExchangeRate(2e6, 0);

    // Approve the cashMinter contract from the assetSender account
    _seedSenderWithCollateral(400e6);

    // Airdrop Alice and bob their collateral
    toWithdraw.push(alice);    

    // Issue charlie a refund for whatever reason
    toRefund.push(charlie);

    vm.prank(managerAdmin);
    cashManager.completeRedemptions(
      toWithdraw, // Addresses to issue collateral to
      toRefund, // Addresses to refund cash
      300e6, // Total amount of money to dist incl fees
      0, // Epoch we wish to process
      6e6 // Fee amount to be transferred to ondo
    );

    assertEq(USDC.balanceOf(alice), 147e6);    
    assertEq(tokenProxied.balanceOf(charlie), 50e18);
    toRefund.pop(); // cleanup
    toWithdraw.pop(); // cleanup

    // second redemption call as we had to much in first call and need another transaction to fulfill the rest of the users for the epcoh
    toWithdraw.push(bob);

    vm.prank(managerAdmin);
    cashManager.completeRedemptions(
      toWithdraw, // Addresses to issue collateral to
      toRefund, // Addresses to refund cash
      300e6, // Total amount of money to dist incl fees
      0, // Epoch we wish to process
      6e6 // Fee amount to be transferred to ondo
    );

    assertEq(USDC.balanceOf(alice), 147e6); // not changed    
    assertEq(tokenProxied.balanceOf(charlie), 50e18); // not changed
    
    assertEq(USDC.balanceOf(bob), 147e6); // we expect the same result for bob that alice got
}

Tools Used

Manual review

Recommended Mitigation Steps

Update the totalBurned amount for the epoch after the refunds.

File: contracts/cash/CashManager.sol
721:     uint256 quantityBurned = redemptionInfoPerEpoch[epochToService]
722:       .totalBurned - refundedAmt;
723: 
724:     // @audit update the epoch totalBurned amount
725:     redemptionInfoPerEpoch[epochToService].totalBurned = quantityBurned;
726:     
727:     uint256 amountToDist = collateralAmountToDist - fees;

Activity

  1. added
    3 (High Risk)Assets can be stolen/lost/compromised directly
    bugSomething isn't working
    on Jan 17, 2023
  2. added a commit that references this issue on Jan 17, 2023
  3. c4-judge commented on Jan 23, 2023

    @c4-judge
    Contributor

    trust1995 marked the issue as duplicate of #325

  4. added
    satisfactorysatisfies C4 submission criteria; eligible for awards
    on Jan 23, 2023
  5. c4-judge commented on Jan 23, 2023

    @c4-judge
    Contributor

    trust1995 marked the issue as satisfactory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3 (High Risk)Assets can be stolen/lost/compromised directlybugSomething isn't workingduplicate-325satisfactorysatisfies C4 submission criteria; eligible for awards

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions