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: 21/69
Findings: 1
Award: $304.58
š Selected for report: 0
š Solo Findings: 0
š 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
304.5822 USDC - $304.58
Issue | |
---|---|
L-01 | CashManager.cash should not be immutable |
L-02 | Additional sanity checks |
L-03 | Use require instead of assert |
L-04 | Avoid leftover ETH in CashFactory |
L-05 | Immutable addresses should have appropriate checks in constructor |
L-06 | Avoid static year variables |
L-07 | static prices should not be used in oracles |
L-08 | CTokenModified.repayBorrowFresh can be DOSed |
Issue | |
---|---|
N-01 | Use the same Solidity version across the protocol |
N-02 | Scientific notation can be used |
N-03 | Redundant cast |
N-04 | Order of functions |
N-05 | Comments consistency |
N-06 | Native time should be used |
N-07 | Avoid floating pragmas |
Cash
is upgradeable. CashManager
should not have cash
has an immutable variable,
as it will not be able to work if Cash
gets upgraded.
https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L43
43: Cash public immutable cash
Add an additional sanity check: correctExchangeRate != 0
, as a value of 0
would make _getMintAmountForEpoch()
revert, leading to claimMint()
not working.
https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L375
375: epochToExchangeRate[epochToSet] = correctExchangeRate;
require
instead of assert
As per Solidity's (documentation)[https://docs.soliditylang.org/en/v0.8.17/control-structures.html?highlight=assert#panic-via-assert-and-error-via-require]:
The assert function creates an error of type Panic(uint256). The same error is created by the compiler in certain situations as listed below. Assert should only be used to test for internal errors, and to check invariants. Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix
https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/factory/CashFactory.sol#L97
97: assert(cashProxyAdmin.owner() == guardian);
CashFactory
Consider adding a check here after the loop that msg.value = Sum(exCallDatavalues)
, to avoid having ETH
leftover in CashFactory
The guardian could use that leftover ETH in future multiexcalls, but it is still better to avoid dust (or large amounts) remaining in CashFactory
123: function multiexcall( 124: ExCallData[] calldata exCallData 125: ) external payable override onlyGuardian returns (bytes[] memory results) { 126: results = new bytes[](exCallData.length); 127: for (uint256 i = 0; i < exCallData.length; ++i) { 128: (bool success, bytes memory ret) = address(exCallData[i].target).call{ 129: value: exCallData[i].value 130: }(exCallData[i].data); 131: require(success, "Call Failed"); 132: results[i] = ret; 133: } 134: }
Add a check to ensure _sanctionsList != address(0)
.
Note: this variable was not mentioned in the C4udit output
https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/kyc/KYCRegistry.sol#L57
57: sanctionsList = ISanctionsList(_sanctionsList);
static year variables will give an incorrect approximation on leap years, where blocksPerYear
should be 2635200, leading to interest rates being higher than expected.
https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/lending/JumpRateModelV2.sol#L29
29: uint public constant blocksPerYear = 2628000;
It is dangerous for oracles to return a price set in storage. Price volatility (or in the case of stablecoins, a depeg) can create arbitrage opportunities, given that this function is used to determine prices of assets in lending markets
https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/lending/OndoPriceOracle.sol#L61-L66
61: function getUnderlyingPrice( 62: address fToken 63: ) external view override returns (uint256) { 64: if (fTokenToUnderlyingPrice[fToken] != 0) { 65: return fTokenToUnderlyingPrice[fToken]; 66: }
99: if (oracleType == OracleType.MANUAL) { 100: // Get price stored in contract storage 101: price = fTokenToUnderlyingPrice[fToken]; 102: }
CTokenModified.repayBorrowFresh
can be DOSedIf a user is trying to repay part of their loan, a malicious payer
can front-run their call with an amount
high enough so that when the borrower call is processed, the amount they are trying to repay is lower than the account's borrowing balance, causing a revert here.
Note that this only arises if the user is trying to repay part of their loan - ie not calling repayAmount == type(uint).max
.
818: uint accountBorrowsNew = accountBorrowsPrev - actualRepayAmount;
A solution would be to compare repayAmount
with accountBorrowsPrev
to prevent underflow from happening.
-796: uint repayAmountFinal = repayAmount == type(uint).max +796: uint repayAmountFinal = repayAmount >= accountBorrowsPrev 797: ? accountBorrowsPrev 798: : repayAmount;
https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L15
15: pragma solidity 0.8.16;
2: pragma solidity ^0.8.10;
https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/lending/OndoPriceOracle.sol#L15
15: pragma solidity 0.6.12;
https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L58
58: uint256 public minimumDepositAmount = 10_000 //@audit - change to 1e4 89: uint256 public constant BPS_DENOMINATOR = 10_000; //@audit - change to 1e4
https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L962
962: (bool success, bytes memory ret) = address(exCallData[i].target).call //@audit = exCallData[i].target is already an address
modifier should be placed before functions, as per the Solidity documentation style guide https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/factory/CashFactory.sol#L152-L155
152: modifier onlyGuardian() { 153: require(msg.sender == guardian, "CashFactory: You are not the Guardian"); 154: _; 155: }
For consistency, comments should specify jumpMultiplierPerYear
and kink_
are scaled by 1e18 - as it is stated for baseRatePerYear
and multiplierPerYear
https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/lending/JumpRateModelV2.sol#L53-L56
53: * @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by 1e18) 54: * @param multiplierPerYear The rate of increase in interest rate wrt utilization (scaled by 1e18) 55: * @param jumpMultiplierPerYear The multiplierPerBlock after hitting a specified utilization point 56: * @param kink_ The utilization point at which the jump multiplier is applied
use Native time units instead of a number of seconds https://github.com/code-423n4/2023-01-ondo/blob/main/lending/OndoPriceOracleV2.sol#L77
77: uint256 public maxChainlinkOracleTimeDelay = 90000; //@audit 25 hours;
2: pragma solidity ^0.8.10;
#0 - c4-judge
2023-01-23T15:00:00Z
trust1995 marked the issue as grade-a