Platform: Code4rena
Start Date: 24/07/2023
Pot Size: $100,000 USDC
Total HM: 18
Participants: 73
Period: 7 days
Judge: alcueca
Total Solo HM: 8
Id: 267
League: ETH
Rank: 5/73
Findings: 3
Award: $5,746.15
π Selected for report: 2
π Solo Findings: 1
π Selected for report: 0xWaitress
Also found by: 0xWaitress, Nyx, ast3ros, catellatech, kodyvim, nadin
510.8177 USDC - $510.82
https://github.com/code-423n4/2023-07-moonwell/blob/main/src/core/IRModels/JumpRateModel.sol#L66-L75
utilization can be above 100% when reserve is more than cash
utilization is calculated as borrow / (cash + borrow - reserve)
. and the utilization would determine the interest rate in this classic jump/klink model.
Consider:
When reserves becomes more than cash, the formula for utilizationRate would give a utilization rate above 1. This is different from the code comment which said to @return The utilization rate as a mantissa between [0, 1e18]
* @return The utilization rate as a mantissa between [0, 1e18] function utilizationRate(uint cash, uint borrows, uint reserves) public pure returns (uint) { // Utilization rate is 0 when there are no borrows if (borrows == 0) { return 0; } return borrows.mul(1e18).div(cash.add(borrows).sub(reserves)); }
https://github.com/code-423n4/2023-07-moonwell/blob/main/src/core/IRModels/JumpRateModel.sol#L66-L75
Consider put a cap of 1e18 to the final output.
Math
#0 - c4-pre-sort
2023-08-03T14:02:25Z
0xSorryNotSorry marked the issue as duplicate of #135
#1 - ElliotFriedman
2023-08-03T21:42:35Z
reserves can be pulled back to admin address by admin, so this is a non issue
#2 - c4-sponsor
2023-08-03T21:42:38Z
ElliotFriedman marked the issue as sponsor disputed
#3 - c4-judge
2023-08-13T13:43:58Z
alcueca marked the issue as satisfactory
#4 - alcueca
2023-08-13T13:45:27Z
@code423n4, please merge this issue into #40 by the same warden.
#5 - c4-judge
2023-08-13T13:45:39Z
alcueca marked the issue as nullified
#6 - c4-judge
2023-08-21T21:27:58Z
alcueca marked the issue as not nullified
#7 - c4-judge
2023-08-21T21:28:07Z
alcueca marked the issue as satisfactory
#8 - c4-judge
2023-08-21T21:28:13Z
alcueca marked the issue as nullified
#9 - c4-judge
2023-08-21T21:28:30Z
alcueca marked the issue as not nullified
#10 - c4-judge
2023-08-21T21:28:42Z
alcueca marked the issue as satisfactory
π Selected for report: 0xWaitress
Also found by: 0xWaitress, Nyx, ast3ros, catellatech, kodyvim, nadin
510.8177 USDC - $510.82
https://github.com/code-423n4/2023-07-moonwell/blob/main/src/core/MToken.sol#L403
accrueInterest is expected to revert when the rate is higher than the maximum allowed rate, which is possible since the utilization can be more than 1
accrueInterest is an essential function to keep updated of the global mToken interest payment from the borrower to the depositor. The function is called anytime there is a deposit/liquidate/borrow/repay/withdraw.
The function is set to revert when the borrowRateMantissa is bigger than the borrowRateMaxMantissa.
require(borrowRateMantissa <= borrowRateMaxMantissa, "borrow rate is absurdly high");
.
With proper configuration, this check should be fine since the real borrowRate should be calibrated to be within the maxRate. However, since the utilization could actually be bigger than 1 (when reserve is bigger than cash [issue-37] , the actual rate could be bigger than the expected unreachable max rate:
Example: base: 1% multiplierPerTimestamp: 5% APR jumpMultiplier: 50% APR klink: 50% We could reasonably assume the max APR is 1% + 5% * 0.5 + 50% * (1-0.5) = 28.5%
However when reserve is more than cash it would cause the utilization be bigger than 1, let'say utilization is 101%: such that the actual rate would be:
1% + 5% * 0.5 + 50% * (1.01 - 0.5) = 29%
This would cause the accurueInterest to revert.
Impact: all deposit/withdraw/borrow/repay function would fail and severe brick the operation of the protocol and lock user funds. Interest accrual cannot work which impact both borrower for timely exit as well as deposit to collect their interest.
function accrueInterest() virtual override public returns (uint) { /* Remember the initial block timestamp */ uint currentBlockTimestamp = getBlockTimestamp(); uint accrualBlockTimestampPrior = accrualBlockTimestamp; /* Short-circuit accumulating 0 interest */ if (accrualBlockTimestampPrior == currentBlockTimestamp) { return uint(Error.NO_ERROR); } /* Read the previous values out of storage */ uint cashPrior = getCashPrior(); uint borrowsPrior = totalBorrows; uint reservesPrior = totalReserves; uint borrowIndexPrior = borrowIndex; /* Calculate the current borrow interest rate */ uint borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior); require(borrowRateMantissa <= borrowRateMaxMantissa, "borrow rate is absurdly high");
https://github.com/code-423n4/2023-07-moonwell/blob/main/src/core/MToken.sol#L403
Consider to cap the real-time borrowRate as the borrowRateMaxMantissa instead of reverting.
DoS
#0 - c4-pre-sort
2023-08-03T14:02:23Z
0xSorryNotSorry marked the issue as duplicate of #135
#1 - c4-sponsor
2023-08-03T21:42:17Z
ElliotFriedman marked the issue as sponsor disputed
#2 - ElliotFriedman
2023-08-03T21:42:28Z
reserves can be pulled back to admin address by admin, so this is a non issue
#3 - alcueca
2023-08-13T13:20:46Z
@code423n4, if valid, this should be merged with #37 by the same warden, and then the combination would be the one selected for report of this group of duplicates.
#4 - c4-judge
2023-08-19T21:23:27Z
alcueca marked the issue as satisfactory
#5 - c4-judge
2023-08-19T21:24:13Z
alcueca marked the issue as selected for report
π Selected for report: 0xWaitress
5190.4455 USDC - $5,190.45
a single emissionCap is not suitable for different tokens reward if they have different underlying decimals
At MultiRewardDistributor, different rewards token can be set up for each mToken, which is the assetToken or debtToken of the lending protocol. When a reward token is set up, it's emission speed cannot be bigger than the emissionCap (100 * 1e18 as suggested in the code comment). However since multiple reward tokens may be set up, and they may have different decimal places, for example USDT has a decimal of 6 and GUSD even has a decimal of 2 they are virtually not bounded by such emissionCap.
On the contract, tokens with more than 18 decimal would not be practically emitted. This one-size-fit-all emissionCap might create obstacles for the protocol to effectively manage each rewardTokens.
/// @notice The emission cap dictates an upper limit for reward speed emission speed configs /// @dev By default, is set to 100 1e18 token emissions / sec to avoid unbounded /// computation/multiplication overflows uint256 public emissionCap;
function _updateBorrowSpeed( MToken _mToken, address _emissionToken, uint256 _newBorrowSpeed ) external onlyEmissionConfigOwnerOrAdmin(_mToken, _emissionToken) { MarketEmissionConfig storage emissionConfig = fetchConfigByEmissionToken( _mToken, _emissionToken ); uint256 currentBorrowSpeed = emissionConfig .config .borrowEmissionsPerSec; require( _newBorrowSpeed != currentBorrowSpeed, "Can't set new borrow emissions to be equal to current!" ); require( _newBorrowSpeed < emissionCap, "Cannot set a borrow reward speed higher than the emission cap!" );
Consider creating emissionCap for each rewardTokens so they can be adapted based on their own decimals
Context
#0 - c4-pre-sort
2023-08-03T14:11:06Z
0xSorryNotSorry marked the issue as primary issue
#1 - c4-sponsor
2023-08-03T19:23:56Z
ElliotFriedman marked the issue as disagree with severity
#2 - ElliotFriedman
2023-08-03T19:24:55Z
this is a known issue, the amount of tokens created is bounded 100e18 per second. then, reward speeds will be integration tested to ensure they are properly configured.
#3 - c4-sponsor
2023-08-03T19:25:07Z
ElliotFriedman marked the issue as sponsor confirmed
#4 - alcueca
2023-08-12T21:06:47Z
@ElliotFriedman, I'm not sure if you understood the finding. How would you set up the emissions cap if emissions are in USDT (6 decimals) and WETH (18 decimals).
#5 - c4-judge
2023-08-12T21:07:04Z
alcueca marked the issue as satisfactory
#6 - ElliotFriedman
2023-08-21T20:35:48Z
Valid issue, just a question of is it medium severity. I'll leave that to judges to decide.
#7 - alcueca
2023-08-21T20:51:23Z
Function incorrect as to spec would be QA - This means that if the way the code works is wrong but has no real impact in the business it is QA. Function of the protocol or its availability possibly impacted would be Medium - In certain cases, the emissionsCap will either not work or stop emissions, the protocol is impacted in a non-lethal manner, and the finding is medium.
#8 - c4-judge
2023-08-21T21:41:40Z
alcueca marked the issue as selected for report
#9 - lyoungblood
2023-08-21T23:17:44Z
Good finding - disagree with the severity as the cap is just a safety feature that didn't exist at all in the original Compound.
π Selected for report: immeas
Also found by: 0x70C9, 0xAnah, 0xArcturus, 0xComfyCat, 0xWaitress, 0xackermann, 0xkazim, 2997ms, 33audits, Arz, Aymen0909, ChrisTina, JP_Courses, John_Femi, Jorgect, Kaysoft, LosPollosHermanos, MohammedRizwan, Nyx, Rolezn, Sathish9098, Stormreckson, T1MOH, Tendency, Topmark, Udsen, Vagner, albertwh1te, ast3ros, banpaleo5, berlin-101, catellatech, cats, codetilda, cryptonue, eeshenggoh, fatherOfBlocks, hals, jamshed, jaraxxus, josephdara, kankodu, kodyvim, kutugu, lanrebayode77, mert_eren, nadin, naman1778, niki, petrichor, ravikiranweb3, said, solsaver, souilos, twcctop, wahedtalash77
44.8793 USDC - $44.88
claimReward can be called by anyone to trigger reward collection for any particular user. This is fine with security but may cause taxation issue or make some user/contract to collect tokens at the time when the owner does not deem appropriate to call.
function claimReward(address[] memory holders, MToken[] memory mTokens, bool borrowers, bool suppliers) public {
Recommendation: Consider only allow the msg.sender to claimReward, or add a reward allowance delegation function.
function liquidateBorrowVerify( address mTokenBorrowed, address mTokenCollateral, address liquidator, address borrower, uint repayAmount, uint seizeTokens) virtual external;
#0 - c4-judge
2023-08-12T18:19:31Z
alcueca marked the issue as grade-a
#1 - ElliotFriedman
2023-08-15T17:40:06Z
this is expected behavior, when claiming rewards on others behalf, rewards get sent to the person who accrued them.
#2 - c4-sponsor
2023-08-15T17:40:11Z
ElliotFriedman marked the issue as sponsor disputed