Report generated by auditbase
Issue | Instances | |
---|---|---|
[M001] | The owner is a single point of failure and a centralization risk | 2 |
[M002] | Unsafe use of transfer() /transferFrom() with IERC20 |
2 |
[M003] | Return values of transfer() /transferFrom() not checked |
2 |
[M004] | Missing checks for the L2 Sequencer grace period | 2 |
[M005] | Insufficient oracle validation | 1 |
Total: 9 instances over 5 issues
Issue | Instances | |
---|---|---|
[L001] | Unsafe ERC20 Operation(s) | 6 |
[L002] | abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256() |
4 |
[L003] | Initializers could be front-run | 1 |
[L004] | Loss of precision due to division by large numbers | 2 |
[L005] | Array lengths not checked | 1 |
[L006] | Division by zero not prevented | 2 |
[L007] | require() should be used instead of assert() |
4 |
[L008] | Missing checks for address(0x0) when assigning values to address state variables |
5 |
[L009] | Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions | 1 |
[L010] | Events are missing sender information | 29 |
[L011] | Use Ownable2Step rather than Ownable |
1 |
[L012] | Upgradeable contract not initialized | 1 |
[L013] | Array does not have a pop function |
3 |
[L014] | Approve type(uint256).max not work with some tokens |
1 |
[L015] | Solidity version 0.8.20 may not work on other chains due to PUSH0 |
3 |
[L016] | External call recipient may consume all transaction gas | 2 |
[L017] | Setters should have initial value check | 36 |
[L018] | Empty receive() /payable fallback() function does not authorize requests |
1 |
[L019] | Contracts are designed to receive ETH but do not implement function for withdrawal | 1 |
[L020] | Functions calling contracts/addresses with transfer hooks are missing reentrancy guards | 2 |
[L021] | Some ERC20 revert on zero value transfer | 10 |
Total: 119 instances over 21 issues
Issue | Instances | |
---|---|---|
[NC001] | TODO Left in the code | 2 |
[NC002] | External calls in an unbounded for-loop may result in a DoS | 18 |
[NC003] | Constants should be defined rather than using magic numbers | 38 |
[NC004] | Numeric values having to do with time should use time units for readability | 1 |
[NC005] | Events that mark critical parameter changes should contain both the old and the new value | 6 |
[NC006] | Use scientific notation (e.g. 1e18) rather than exponentiation (e.g. 10**18) | 9 |
[NC007] | Event is not properly indexed | 12 |
[NC008] | Function ordering does not follow the Solidity style guide | 34 |
[NC009] | Imports could be organized more systematically | 1 |
[NC010] | Constants in comparisons should appear on the left side | 50 |
[NC011] | else-block not required | 10 |
[NC012] | Events may be emitted out of order due to reentrancy | 1 |
[NC013] | If-statement can be converted to a ternary | 4 |
[NC014] | Import declarations should import specific identifiers, rather than the whole file | 1 |
[NC015] | Public functions not called by the contract should be declared external instead | 27 |
[NC016] | Constant redefined elsewhere | 1 |
[NC017] | Duplicated require() /revert() checks should be refactored to a modifier or function |
7 |
[NC018] | Use a more recent version of solidity | 2 |
[NC019] | Variable names that consist of all capital letters should be reserved for constant/immutable variables | 2 |
[NC020] | Consider using delete rather than assigning zero to clear values | 1 |
[NC021] | Contract does not follow the Solidity style guide's suggested layout ordering | 11 |
[NC022] | Lines are too long | 108 |
[NC023] | All @param values in a function's NatSpec comment should match the function's parameters | 155 |
[NC024] | NatSpec @return argument is missing | 15 |
[NC025] | Inconsistent spacing in comments | 1 |
[NC026] | Contract does not follow the Solidity style guide's suggested layout ordering | 11 |
[NC027] | Non-external variable and function names should begin with an underscore | 3 |
[NC028] | Consider disabling renounceOwnership() |
1 |
[NC029] | Non-library/interface files should use fixed compiler versions, not floating ones | 3 |
[NC030] | Empty Function Body - Consider commenting why | 2 |
[NC031] | Remove forge-std import |
1 |
[NC032] | Contracts should have full test coverage | 1 |
[NC033] | Large or complicated code bases should implement invariant tests | 1 |
[NC034] | Long functions should be refactored into multiple, smaller, functions | 6 |
[NC036] | Consider using block.number instead of block.timestamp . |
16 |
[NC037] | Large numeric literals should use underscores for readability | 1 |
[NC038] | Cast to bytes or bytes32 for clearer semantic meaning |
4 |
[NC039] | Critical Changes Should Use Two-step Procedure | 3 |
[NC040] | Variable names don't follow the Solidity style guide | 14 |
[NC041] | Consider using named mappings | 1 |
[NC042] | Consider adding a deny-list | 13 |
[NC043] | Use a more recent version of solidity | 1 |
[NC044] | Mixed usage of int /uint with int256 /uint256 |
88 |
[NC045] | Consider moving msg.sender checks to a common authorization modifier |
26 |
Total: 717 instances over 45 issues
Having a single EOA as the only owner of contracts is a large centralization risk and a single point of failure. A single private key may be taken in a hack, or the sole holder of the key may become unable to retrieve the key when necessary. Consider changing to a multi-signature setup, or having a role-based authorization model.
transfer()
/transferFrom()
with IERC20
:Some tokens do not implement the ERC20 standard properly but are still accepted by most code that accepts ERC20 tokens. For example Tether (USDT)'s transfer() and transferFrom() functions on L1 do not return booleans as the specification requires, and instead have no return value. When these sorts of tokens are cast to IERC20, their function signatures do not match and therefore the calls made, revert (see this link for a test case). Use OpenZeppelin’s SafeERC20's safeTransfer()/safeTransferFrom() instead
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L967:967
transfer()
/transferFrom()
not checked:Not all IERC20 implementations revert() when there's a failure in transfer()/transferFrom(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually making a payment
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L967:967
Chainlink recommends that users using price oracles, check whether the grace period has passed in the event the Arbitrum Sequencer goes down. To help your applications identify when the sequencer is unavailable, you can use a data feed that tracks the last known status of the sequencer at a given point in time. This helps you prevent mass liquidations by providing a grace period to allow customers to react to such an event.
There is no freshness check on the timestamp of the prices fetched from the Chainlink oracle, so old prices may be used if OCR was unable to push an update in time. Add a staleness threshold number of seconds configuration parameter, and ensure that the price fetched is from within that time range.
ERC20 operations can be unsafe due to different implementations and vulnerabilities in the standard. It is therefore recommended to always either use OpenZeppelin's SafeERC20
library or at least to wrap each operation in a require
statement. To circumvent ERC20's approve
functions race-condition vulnerability use OpenZeppelin's SafeERC20
library's safe{Increase|Decrease}Allowance
functions.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L967:967
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20.sol#L225:225
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/router/WETHRouter.sol#L26:26
abi.encodePacked()
should not be used with dynamic types when passing the result to a hash function such as keccak256()
:Use abi.encode()
instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456)
=> 0x123456
=> abi.encodePacked(0x1,0x23456)
, but abi.encode(0x123,0x456)
=> 0x0...1230...456
). "Unless there is a compelling reason, abi.encode
should be preferred". If there is only one argument to abi.encodePacked()
it can often be cast to bytes()
or bytes32()
instead. If all arguments are strings and or bytes, bytes.concat()
should be used instead.
Initializers could be front-run, allowing an attacker to either set their own values, take ownership of the contract, and in the best case forcing a re-deployment
Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator.
If the length of the arrays are not required to be of the same length, user operations may not be fully executed due to a mismatch in the number of items iterated over, versus the number of items provided in the second array.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L1028:1053
The divisions below take an input parameter which does not have any zero-value checks, which may lead to the functions reverting when zero is passed.
require()
should be used instead of assert()
:Prior to solidity version 0.8.0, hitting an assert consumes the remainder of the transaction's available gas rather than returning it, as require()/revert() do. assert() should be avoided even past solidity version 0.8.0 as its documentation states that 'The assert function creates an error of type Panic(uint256). … 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-07-moonwell/tree/main/src/core/Comptroller.sol#L329:329
address(0x0)
when assigning values to address state variables:This issue arises when an address state variable is assigned a value without a preceding check to ensure it isn't address(0x0). This can lead to unexpected behavior as address(0x0) often represents an uninitialized address.
This issue arises when an upgradeable contract doesn't include a __gap[50] storage variable. This variable is crucial for facilitating future upgrades and preserving storage layout.
When an action is triggered based on a user's action, not being able to filter based on who triggered the action makes event processing a lot more cumbersome. Including the msg.sender the events of these types of action will make events much more useful to end users. Include msg.sender
in the event output.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L872:872
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L1185:1185
Ownable2Step
rather than Ownable
:Ownable2Step
and Ownable2StepUpgradeable
prevent the contract ownership from mistakenly being transferred to an address that cannot handle it (e.g. due to a typo in the address), by requiring that the recipient of the owner permissions actively accept via a contract call of its own.
Upgradeable contracts are initialized via an initializer function rather than by a constructor. Leaving such a contract uninitialized may lead to it being taken over by a malicious user.
pop
function:Arrays without the pop operation in Solidity can lead to inefficient memory management and increase the likelihood of out-of-gas errors.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L798:798
type(uint256).max
not work with some tokens:Source: https://github.com/d-xo/weird-erc20#revert-on-large-approvals–transfers
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/router/WETHRouter.sol#L26:26
PUSH0
:The compiler for Solidity 0.8.20 switches the default target EVM version to Shanghai, which includes the new PUSH0
op code. This op code may not yet be implemented on all L2s, so deployment on these chains will fail. To work around this issue, use an earlier EVM version. While the project itself may or may not compile with 0.8.20, other projects with which it integrates, or which extend this project may, and those projects will have problems deploying these contracts/libraries.
https://github.com/code-423n4/2023-07-moonwell/tree/main/test/proposals/mips/mip00.sol#L2:2
There is no limit specified on the amount of gas used, so the recipient can use up all of the transaction's gas, causing it to revert. Use addr.call{gas: <amount>}()
or this library instead.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/router/WETHRouter.sol#L59:61
Setters should have initial value check to prevent assigning wrong value to the variable. Assginment of wrong value can lead to unexpected behavior of the contract.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L940:947
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Unitroller.sol#L86:102
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20Delegator.sol#L443:446
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L1459:1484
receive()
/payable fallback()
function does not authorize requests:If the intention is for the Ether to be used, the function should call another function, otherwise it should revert (e.g. require(msg.sender == address(weth))
). Having no access control on the function means that someone may send Ether to the contract, and have no way to get anything back out, which is a loss of funds. If the concern is having to spend a small amount of gas to check the sender against an immutable address, the code should at least have a function to rescue unused Ether.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/router/WETHRouter.sol#L65:65
The following contracts can receive ETH but can not withdraw, ETH is occasionally sent by users will be stuck in those contracts. This functionality also applies to baseTokens resulting in locked tokens and loss of funds.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/router/WETHRouter.sol#L65:65
Even if the function follows the best practice of check-effects-interaction, not using a reentrancy guard when there may be transfer hooks will open the users of this protocol up to read-only reentrancies with no way to protect against it, except by block-listing the whole protocol.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/router/WETHRouter.sol#L46:46
Example: https://github.com/d-xo/weird-erc20#revert-on-zero-value-transfers.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L965:965
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20.sol#L152:152
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/router/WETHRouter.sol#L46:46
TODOs may signal that a feature is missing or not ready for audit, consider resolving the issue and removing the TODO comment
https://github.com/code-423n4/2023-07-moonwell/tree/main/test/proposals/mips/mip00.sol#L210:210
Consider limiting the number of iterations in for-loops that make external calls.
https://github.com/code-423n4/2023-07-moonwell/tree/main/test/proposals/mips/mip00.sol#L799:799
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L1031:1031
Even assembly can benefit from using readable constants instead of hex/numeric literals.
https://github.com/code-423n4/2023-07-moonwell/tree/main/test/proposals/mips/mip00.sol#L819:819
There are units for seconds, minutes, hours, days, and weeks, and since they're defined, they should be used.
This should especially be done if the new value is not required to be different from the old value.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L789:789
While the compiler knows to optimize away the exponentiation, it's still better coding practice to use idioms that do not require compiler optimization, if they exist.
https://github.com/code-423n4/2023-07-moonwell/tree/main/test/proposals/mips/mip00.sol#L579:579
Index event fields make the field more quickly accessible to off-chain tools that parse events. This is especially useful when it comes to filtering based on an address. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Where applicable, each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three applicable fields, all of the applicable fields should be indexed.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L56:56
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Oracles/ChainlinkOracle.sol#L41:41
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Unitroller.sol#L31:31
According to the Solidity style guide, functions should be laid out in the following order :constructor()
, receive()
, fallback()
, external
, public
, internal
, private
, but the cases below do not follow this pattern.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L998:998
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Unitroller.sol#L136:136
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20Delegator.sol#L496:496
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20.sol#L46:46
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L1443:1443
This issue arises when the contract's interface is not imported first, followed by each of the interfaces it uses, followed by all other files.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L9:9
This issue arises when constants in comparisons appear on the right side, which can lead to typo bugs.
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L1077:1077
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20Delegator.sol#L497:497
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/IRModels/JumpRateModel.sol#L70:70
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L1077:1077
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/router/WETHRouter.sol#L53:53
One level of nesting can be removed by not having an else block when the if-block returns
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Comptroller.sol#L614:618
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/IRModels/JumpRateModel.sol#L87:93
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L649:663
Ensure that events follow the best practice of check-effects-interaction, and are emitted before external calls
The code can be made more compact while also increasing readability by converting the following if-statements to ternaries (e.g. foo += (x > y) ? a : b)
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/Oracles/ChainlinkOracle.sol#L79:83
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MToken.sol#L1020:1024
Using import declarations of the form import {<identifier_name>} from 'some/file.sol' avoids polluting the symbol namespace making flattened files smaller, and speeds up compilation
https://github.com/code-423n4/2023-07-moonwell/tree/main/src/core/MErc20.sol#L4:4
Contracts are allowed to override their parents' functions and change the visibility from external to public.