Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not resetting totalBurned in CashManger will break user redemptions #291

Closed
code423n4 opened this issue Jan 17, 2023 · 2 comments
Closed
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-325 satisfactory satisfies C4 submission criteria; eligible for awards

Comments

@code423n4
Copy link
Contributor

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;
@code423n4 code423n4 added 3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working labels Jan 17, 2023
code423n4 added a commit that referenced this issue Jan 17, 2023
@c4-judge
Copy link
Contributor

trust1995 marked the issue as duplicate of #325

@c4-judge c4-judge added the satisfactory satisfies C4 submission criteria; eligible for awards label Jan 23, 2023
@c4-judge
Copy link
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
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-325 satisfactory satisfies C4 submission criteria; eligible for awards
Projects
None yet
Development

No branches or pull requests

2 participants