Platform: Code4rena
Start Date: 11/01/2023
Pot Size: $60,500 USDC
Total HM: 6
Participants: 69
Period: 6 days
Judge: Trust
Total Solo HM: 2
Id: 204
League: ETH
Rank: 7/69
Findings: 2
Award: $2,553.37
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: adriro
Also found by: cccz, minhquanym, peanuts, zaskoh
2517.1267 USDC - $2,517.13
https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L707-L727 https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L755-L757
Users will get less USDC compensation than intended when swapping CASH if completeRedemptions() is called more than once should there be any refunds.
The function completeRedemptions() in CashManager.sol is called by the admin to ensure that users can get back USDC by exchanging their CASH. In the rare occasion that CASH needs to be refunded to a user, a refund function _processRefund() is executed.
In _processRefund(), users that called requestRedemption() to burn their CASH tokens is refunded back the full amount.
function _processRefund( address[] calldata refundees, uint256 epochToService ) private returns (uint256 totalCashAmountRefunded) { uint256 size = refundees.length; for (uint256 i = 0; i < size; ++i) { address refundee = refundees[i]; uint256 cashAmountBurned = redemptionInfoPerEpoch[epochToService] .addressToBurnAmt[refundee]; redemptionInfoPerEpoch[epochToService].addressToBurnAmt[refundee] = 0; cash.mint(refundee, cashAmountBurned); totalCashAmountRefunded += cashAmountBurned; emit RefundIssued(refundee, cashAmountBurned, epochToService); } return totalCashAmountRefunded; }
The total refunded amount from all refundees is calculated in completeRedemptions() and subtracted from quantityBurned.
uint256 refundedAmt = _processRefund(refundees, epochToService); uint256 quantityBurned = redemptionInfoPerEpoch[epochToService] .totalBurned - refundedAmt;
The variable quantityBurned is then passed on as a parameter to _processRedemption to calculate the collateralAmount due for each person.
_processRedemption(redeemers, amountToDist, quantityBurned, epochToService);
The logic flows like this:
The problem lies when the function completeRedemptions() is called multiple times because of gas limit (according to the conversation with the developer, ideally completeRedemptions() is called once per Epoch, but can be called many times if there is gas limit). If the first call is for refundees and some redeemers, the refunded amount will not be saved in redemptionInfoPerEpoch[epochToService].totalBurned. The next few calls will affect the redeemers and they will have a lower compensation because refunded amount is not reflected in the totalBurned amount. Their share of USDC to CASH will be lesser as quantity burned acts as the denominator (the larger the denominator, the lower the collateralAmountDue).
uint256 collateralAmountDue = (amountToDist * cashAmountReturned) / quantityBurned;
Manual Review
Make sure the totalBurned amount is updated.
// Calculate the total quantity of shares tokens burned w/n an epoch uint256 refundedAmt = _processRefund(refundees, epochToService); uint256 quantityBurned = redemptionInfoPerEpoch[epochToService] .totalBurned - refundedAmt; + redemptionInfoPerEpoch[epochToService].totalBurned = quantityBurned uint256 amountToDist = collateralAmountToDist - fees;
#0 - c4-judge
2023-01-22T16:49:47Z
trust1995 marked the issue as duplicate of #325
#1 - c4-judge
2023-01-22T17:55:07Z
trust1995 marked the issue as satisfactory
#2 - c4-judge
2023-02-01T07:28:51Z
trust1995 changed the severity to 3 (High Risk)
🌟 Selected for report: CodingNameKiki
Also found by: 0x1f8b, 0x52, 0x5rings, 0xAgro, 0xSmartContract, 0xcm, 0xkato, 2997ms, Aymen0909, BClabs, BPZ, BRONZEDISC, Bauer, Bnke0x0, Deekshith99, IllIllI, Josiah, Kaysoft, RaymondFam, Rolezn, SaeedAlipoor01988, Tajobin, Udsen, Viktor_Cortess, adriro, arialblack14, betweenETHlines, btk, chaduke, chrisdior4, cryptphi, csanuragjain, cygaar, defsec, descharre, erictee, gzeon, hansfriese, horsefacts, joestakey, koxuan, lukris02, luxartvinsec, nicobevi, oyc_109, pavankv, peanuts, rbserver, scokaf, shark, tnevler, tsvetanovv, zaskoh
36.2377 USDC - $36.24
https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L201 https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L244 https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L668 https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L955
Make sure that protocol cannot set the mint limit as 0
https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L596 https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/CashManager.sol#L609
Event should emit critical parameters, especially lastSetMintExchangeRate and newRate. Also, Each event should use three indexed fields if there are three or more fields.
Initializers in the protocol are front-runnable because there is no admin privilege. Anyone can front run and set and the protocol has to re-initialize the contract, which waste gas and time
https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/cash/token/CashKYCSender.sol#L51 https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/tokens/cToken/CErc20.sol#L40
uint256 public maxChainlinkOracleTimeDelay = 90000; // 25 hours
can be written as maxChainlinkOracleTimeDelay = 25 hours;
Use alternative variants, e.g. allowlist/denylist instead of whitelist/blacklist
#0 - c4-judge
2023-01-23T15:14:33Z
trust1995 marked the issue as grade-b