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: 46/73
Findings: 1
Award: $44.88
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
Issue | Contexts | |
---|---|---|
LOW‑1 | Contract will stop functioning in the year 2106 | 1 |
LOW‑2 | Large transfers may not work with some ERC20 tokens | 9 |
LOW‑3 | Minting tokens to the zero address should be avoided | 1 |
LOW‑4 | Use SafeCast to safely downcast variables | 5 |
LOW‑5 | Wrong statement msg.sender == 0x0 address | 5 |
Total: 21 contexts over 5 issues
Issue | Contexts | |
---|---|---|
NC‑1 | Avoid variable names that can shade | 1 |
NC‑2 | No need for == true or == false checks | 7 |
NC‑3 | block.timestamp is already used when emitting events, no need to input timestamp | 2 |
NC‑4 | Empty bytes check is missing | 13 |
NC‑5 | Function names should differ to make the code more readable | 8 |
NC‑6 | Function must not be longer than 50 lines | 15 |
NC‑7 | Some functions contain the same exact logic | 2 |
NC‑8 | Generate perfect code headers for better solidity code layout and readability | 1 |
NC‑9 | internal functions not called by the contract should be removed | 1 |
NC‑10 | Redundant Cast | 2 |
NC‑11 | Use named function calls | 1 |
NC‑12 | Use SMTChecker | 1 |
NC‑13 | Use time units directly | 1 |
NC‑14 | Zero as a function argument should have a descriptive meaning | 67 |
Total: 122 contexts over 14 issues
Limiting the timestamp variable to fit in a uint32
will cause the call to start reverting in year 2106 for the following calls.
File: MultiRewardDistributor.sol 919: uint32 blockTimestamp = safe32(
ERC20
tokensSome IERC20
implementations (e.g UNI
, COMP
) may fail if the valued transferred is larger than uint96
. Source
File: Comptroller.sol 965: token.transfer(admin, token.balanceOf(address(this))); 967: token.transfer(admin, _amount);
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L965
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L967
File: MErc20.sol 152: token.transfer(admin, balance);
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20.sol#L152
File: MultiRewardDistributor.sol 478: token.safeTransfer( 483: token.safeTransfer( 1237: token.safeTransfer(
File: MultiRewardDistributor.sol 1237: token.safeTransfer(_user, _amount);
File: WETHRouter.sol 36: IERC20(address(mToken)).safeTransfer(
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/router/WETHRouter.sol#L36
File: WETHRouter.sol 46: IERC20(address(mToken)).safeTransferFrom(
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/router/WETHRouter.sol#L46
</details>The core function mint
is used by users to mint an option position by providing token1 as collateral and borrowing the max amount of liquidity. Address(0)
check is missing in this function, which is triggered to mint the tokens to the recipient
address. Consider applying a check in the function to ensure tokens aren't minted to the zero address.
File: WETHRouter.sol function mint(address recipient) external payable { weth.deposit{value: msg.value}(); require(mToken.mint(msg.value) == 0, "WETHRouter: mint failed"); IERC20(address(mToken)).safeTransfer( recipient, mToken.balanceOf(address(this)) ); }
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/router/WETHRouter.sol#L31
</details>Downcasting int
/uint
s in Solidity can be unsafe due to the potential for data loss and unintended behavior. When downcasting a larger integer type to a smaller one (e.g., uint256
to uint128
), the value may exceed the range of the target type, leading to truncation and loss of significant digits. This data loss can result in unexpected state changes, incorrect calculations, or other contract vulnerabilities, ultimately compromising the contracts functionality and reliability. To prevent these risks, developers should carefully consider the range of values their variables may hold and ensure that proper checks are in place to prevent out-of-range values before performing downcasting. Also consider using OZ SafeCast functionality.
File: TemporalGovernor.sol 284: lastPauseTime = block.timestamp.toUint248();
File: TemporalGovernor.sol 339: queuedTransactions[vm.hash].queueTime = block.timestamp.toUint248(); 366: queuedTransactions[vm.hash].queueTime = block.timestamp.toUint248();
</details>File: ChainlinkCompositeOracle.sol 109: expectedDecimals > uint8(0) && expectedDecimals <= uint8(18), 140: expectedDecimals > uint8(0) && expectedDecimals <= uint8(18),
msg.sender == 0x0 address
The following statement is wrongly written. As how it is now, when the statement is triggered it checks if msg.sender
is address(0) which can never happen.
File: MToken.sol 1170: if (msg.sender != pendingAdmin || msg.sender == address(0)) {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L1170
File: Unitroller.sol 111: if (msg.sender != pendingAdmin || msg.sender == address(0)) {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Unitroller.sol#L111
</details>With global variable names in the form of call{value: value }
, argument name similarities can shade and negatively affect code readability.
File: TemporalGovernor.sol 400: (bool success, bytes memory returnData) = target.call{value: value}(
== true
or == false
checksThere is no need to verify that == true
or == false
when the variable checked upon is a boolean as well.
File: Comptroller.sol 129: if (marketToJoin.accountMembership[borrower] == true) {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L129
File: Comptroller.sol 914: require(msg.sender == admin || state == true, "only admin can unpause");
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L914
File: Comptroller.sol 924: require(msg.sender == admin || state == true, "only admin can unpause");
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L924
File: Comptroller.sol 933: require(msg.sender == admin || state == true, "only admin can unpause");
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L933
File: Comptroller.sol 942: require(msg.sender == admin || state == true, "only admin can unpause");
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L942
File: Comptroller.sol 1038: if (suppliers == true) { 1046: if (borrowers == true) {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L1038
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L1046
</details>Instead simply check for variable
or !variable
File: TemporalGovernor.sol 197: emit GuardianPauseGranted(block.timestamp);
File: TemporalGovernor.sol 258: emit PermissionlessUnpaused(block.timestamp);
bytes
check is missingTo avoid mistakenly accepting empty bytes
as a valid value, consider to check for empty bytes
. This helps ensure that empty bytes
are not treated as valid inputs, preventing potential issues.
File: MErc20.sol 62: function mintWithPermit( uint mintAmount, uint deadline, uint8 v, bytes32 r, bytes32 s ) override external returns (uint) {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20.sol#L62
File: MErc20Delegate.sol 21: function _becomeImplementation(bytes memory data) virtual override public {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20Delegate.sol#L21
File: MErc20Delegator.sol 61: function _setImplementation(address implementation_, bool allowResign, bytes memory becomeImplementationData) override public {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20Delegator.sol#L61
File: MErc20Delegator.sol 98: function mintWithPermit( uint mintAmount, uint deadline, uint8 v, bytes32 r, bytes32 s ) override external returns (uint) {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20Delegator.sol#L98
File: MErc20Delegator.sol 455: function delegateTo(address callee, bytes memory data) internal returns (bytes memory) {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20Delegator.sol#L455
File: MErc20Delegator.sol 471: function delegateToImplementation(bytes memory data) public returns (bytes memory) {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20Delegator.sol#L471
File: MErc20Delegator.sol 482: function delegateToViewImplementation(bytes memory data) public view returns (bytes memory) {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20Delegator.sol#L482
File: TemporalGovernor.sol 87: function isTrustedSender( uint16 chainId, bytes32 addr ) public view returns (bool) {
File: TemporalGovernor.sol 229: function queueProposal(bytes memory VAA) external whenNotPaused {
File: TemporalGovernor.sol 237: function executeProposal(bytes memory VAA) public whenNotPaused {
File: TemporalGovernor.sol 266: function fastTrackProposalExecution(bytes memory VAA) external onlyOwner {
File: TemporalGovernor.sol 295: function _queueProposal(bytes memory VAA) private {
</details>File: TemporalGovernor.sol 344: function _executeProposal(bytes memory VAA, bool overrideDelay) private {
In Solidity, while function overriding allows for functions with the same name to coexist, it is advisable to avoid this practice to enhance code readability and maintainability. Having multiple functions with the same name, even with different parameters or in inherited contracts, can cause confusion and increase the likelihood of errors during development, testing, and debugging. Using distinct and descriptive function names not only clarifies the purpose and behavior of each function, but also helps prevent unintended function calls or incorrect overriding. By adopting a clear and consistent naming convention, developers can create more comprehensible and maintainable smart contracts.
File: Comptroller.sol 998: function claimReward
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L998
File: Comptroller.sol 1006: function claimReward
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L1006
File: Comptroller.sol 1015: function claimReward
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L1015
File: Comptroller.sol 1028: function claimReward
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L1028
File: TemporalGovernor.sol 86: function isTrustedSender
File: TemporalGovernor.sol 96: function isTrustedSender
File: MultiRewardDistributor.sol 231: function getOutstandingRewardsForUser
File: MultiRewardDistributor.sol 256: function getOutstandingRewardsForUser
File: Comptroller.sol 563: function getHypotheticalAccountLiquidityInternal(
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L563
File: MToken.sol 68: function transferTokens(
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L68
File: MToken.sol 385: function accrueInterest(
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L385
File: MToken.sol 615: function redeemFresh(
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L615
File: MToken.sol 750: function borrowFresh(
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L750
File: MToken.sol 865: function repayBorrowFresh(
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L865
File: MToken.sol 968: function liquidateBorrowFresh(
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L968
File: MToken.sol 1074: function seizeInternal(
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L1074
File: TemporalGovernor.sol 344: function _executeProposal(
File: MultiRewardDistributor.sol 231: function getOutstandingRewardsForUser( 256: function getOutstandingRewardsForUser(
File: MultiRewardDistributor.sol 382: function _addEmissionConfig(
File: MultiRewardDistributor.sol 912: function calculateNewIndex(
File: MultiRewardDistributor.sol 1041: function disburseSupplierRewardsInternal(
</details>File: MultiRewardDistributor.sol 1147: function disburseBorrowerRewardsInternal(
These functions might be a problem if the logic changes before the contract is deployed, as the developer must remember to syncronize the logic between all the function instances.
Consider using a single function instead of duplicating the code, for example by using a library
, or through inheritance.
File: JumpRateModel.sol 104: function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) override public view returns (uint) { uint oneMinusReserveFactor = uint(1e18).sub(reserveFactorMantissa); uint borrowRate = getBorrowRate(cash, borrows, reserves); uint rateToPool = borrowRate.mul(oneMinusReserveFactor).div(1e18); return utilizationRate(cash, borrows, reserves).mul(rateToPool).div(1e18); } }
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/IRModels/JumpRateModel.sol#L104
File: WhitePaperInterestRateModel.sol 80: function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) override public view returns (uint) { uint oneMinusReserveFactor = uint(1e18).sub(reserveFactorMantissa); uint borrowRate = getBorrowRate(cash, borrows, reserves); uint rateToPool = borrowRate.mul(oneMinusReserveFactor).div(1e18); return utilizationRate(cash, borrows, reserves).mul(rateToPool).div(1e18); } }
It is recommended to use pre-made headers for Solidity code layout and readability: https://github.com/transmissions11/headers
/*////////////////////////////////////////////////////////////// TESTING 123 //////////////////////////////////////////////////////////////*/
File: TemporalGovernor.sol 11: TemporalGovernor.sol
internal
functions not called by the contract should be removedFile: MErc20.sol function getCashPrior() virtual override internal view returns (uint) {
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20.sol#L171
The type of the variable is the same as the type to which the variable is being cast
File: MultiRewardDistributor.sol 1107: MToken(_mToken)
File: ChainlinkOracle.sol 100: AggregatorV3Interface(feed)
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Oracles/ChainlinkOracle.sol#L100
Code base has an extensive use of named function calls, but it somehow missed one instance where this would be appropriate.
It should use named function calls on function call, as such:
library.exampleFunction{value: _data.amount.value}({ _id: _data.id, _amount: _data.amount.value, _token: _data.token, _example: "", _metadata: _data.metadata });
File: TemporalGovernor.sol 400: (bool success, bytes memory returnData) = target.call{value: value}( data );
The highest tier of smart contract behavior assurance is formal mathematical verification. All assertions that are made are guaranteed to be true across all inputs → The quality of your asserts is the quality of your verification
https://twitter.com/0xOwenThurm/status/1614359896350425088?t=dbG9gHFigBX85Rv29lOjIQ&s=19
The values hours
,days
,weeks
etc, can be used directly instead of using the calculated value as it is not needed.
File: JumpRateModel.sol 20: uint public constant timestampsPerYear = 60 * 60 * 24 * 365;
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/IRModels/JumpRateModel.sol#L20
Consider using descriptive constants or an enum instead of passing zero directly on function calls, as that might be error-prone, to fully describe the caller's intention.
File: Comptroller.sol 344: (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(borrower, MToken(mToken), 0, borrowAmount);
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L344
File: Comptroller.sol 517: (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, MToken(address(0)), 0, 0);
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L517
File: Comptroller.sol 529: return getHypotheticalAccountLiquidityInternal(account, MToken(address(0)), 0, 0);
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L529
File: Comptroller.sol 580: return (Error.SNAPSHOT_ERROR, 0, 0); 588: return (Error.PRICE_ERROR, 0, 0); 617: return (Error.NO_ERROR, 0, vars.sumBorrowPlusEffects - vars.sumCollateral);
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L580
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L588
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L617
File: MErc20.sol 199: returndatacopy(0, 0, 32) 234: returndatacopy(0, 0, 32) 203: revert(0, 0) 238: revert(0, 0)
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20.sol#L199
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20.sol#L234
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20.sol#L203
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20.sol#L238
File: MToken.sol 213: return (uint(Error.MATH_ERROR), 0, 0, 0); 218: return (uint(Error.MATH_ERROR), 0, 0, 0);
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L213
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L218
File: MToken.sol 594: return redeemFresh(payable(msg.sender), 0, redeemAmount);
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L594
File: Unitroller.sol 142: returndatacopy(free_mem_ptr, 0, returndatasize())
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Unitroller.sol#L142
</details>#0 - c4-judge
2023-08-12T18:06:48Z
alcueca marked the issue as grade-a
#1 - c4-sponsor
2023-08-15T18:29:17Z
ElliotFriedman marked the issue as sponsor confirmed