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: 14/73
Findings: 2
Award: $735.16
🌟 Selected for report: 1
🚀 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
_newEndTime
should be in the future. _endTime > block.timestamp + 1
should be used instead of _newEndTime > block.timestamp
Using _newEndTime > block.timestamp
to check if the new end time is in the future is not sufficient because it allows the new end time to be set to the current block timestamp, which is not what you'd want in most cases.To guarantee that _newEndTime
is in the future, you should use the comparison _newEndTime > block.timestamp + 1
instead. This ensures that the new end time is at least one second greater than the current block timestamp, making it a valid future time.
FILE: Breadcrumbs2023-07-moonwell/src/core/MultiRewardDistributor/MultiRewardDistributor.sol 793: require( 794: _newEndTime > block.timestamp, 795: "_newEndTime MUST be > block.timestamp" 796: );
Change the _newEndTime check same like _addEmissionConfig() function . This will ensures the valid future time.
require( _endTime > block.timestamp + 1, "The _endTime parameter must be in the future!" );
safe32(block.timestamp)
returns truncated values after year 2106After the year 2106, the block.timestamp will continue to increment normally, but the truncated timestamp variable will reset to zero after reaching its maximum value (2^32 - 1). As a result, the timestamp variable will behave as if it's cycling every 2^32 seconds (approximately 136 years), causing it to lose track of the actual time
FILE: 2023-07-moonwell/src/core/MultiRewardDistributor/MultiRewardDistributor.sol 436: supplyGlobalTimestamp: safe32( block.timestamp, "block timestamp exceeds 32 bits" ), 442: borrowGlobalTimestamp: safe32( block.timestamp, "block timestamp exceeds 32 bits" ), 919: uint32 blockTimestamp = safe32( block.timestamp, "block timestamp exceeds 32 bits" );
When even handling block.timestamp
use at least uint128
to avoid unexpected behaviors
oracle
for price
Currently protocol only uses Chainlink
Oracle for prices
You should not trust a single oracle for price calculations. There are a number of reasons why this is the case
Single oracles can be attacked
: If a single oracle is attacked, it could be compromised and provide incorrect price information. This could lead to significant losses for users who rely on the price information from the oracle.Single oracles can be unreliable
: Even if a single oracle is not attacked, it could still be unreliable. This could be due to technical problems, such as network outages or hardware failures. It could also be due to human error.Single oracles can be biased
: A single oracle could be biased towards providing certain price information. This could be due to a number of factors, such as the oracle's own financial interests or the interests of its owners.Using multiple oracles for price calculations is to use a weighted average
. A weighted average
is a method of combining the price information
from multiple oracles
. The weight of each oracle is based on its reliability
. This approach can help to ensure that the price information is accurate
and reliable
, even if some of the oracles are unreliable
integer validations
in _setEmissionCap()
function leads unexpected behaviorLack of integer validations in the _setEmissionCap() function can indeed lead to unexpected behavior and potential vulnerabilities due to human errors.Setting a very low emission cap unintentionally could limit the contract's functionality or prevent it from functioning as intended. It could also have adverse effects on token economics if the emission cap is set too low
FILE: Breadcrumbs2023-07-moonwell/src/core/MultiRewardDistributor/MultiRewardDistributor.sol function _setEmissionCap( uint256 _newEmissionCap ) external onlyComptrollersAdmin { uint256 oldEmissionCap = emissionCap; emissionCap = _newEmissionCap; emit NewEmissionCap(oldEmissionCap, _newEmissionCap); }
require(_newEmissionCap > 0, ``Not a zero ``);
require(_newEmissionCap > MIN && _newEmissionCap < MAX, ``Not a zero ``);
total supply limit
in updateMarketSupplyIndexInternal()
There is no explicit check for the total supply limit of the MToken (_mToken). This lack of validation could indeed lead to potential issues if the owner mistakenly sets the total supply to a value less than the actual circulating supply or if the total supply is incorrectly changed.
FILE: Breadcrumbs2023-07-moonwell/src/core/MultiRewardDistributor/MultiRewardDistributor.sol function updateMarketSupplyIndex( MToken _mToken ) external onlyComptrollerOrAdmin { updateMarketSupplyIndexInternal(_mToken); } function updateMarketSupplyIndexInternal(MToken _mToken) internal { MarketEmissionConfig[] storage configs = marketConfigs[ address(_mToken) ]; uint256 totalMTokens = MTokenInterface(_mToken).totalSupply(); // Iterate over all market configs and update their indexes + timestamps for (uint256 index = 0; index < configs.length; index++) { MarketEmissionConfig storage emissionConfig = configs[index]; // Go calculate our new values IndexUpdate memory supplyUpdate = calculateNewIndex( emissionConfig.config.supplyEmissionsPerSec, emissionConfig.config.supplyGlobalTimestamp, emissionConfig.config.supplyGlobalIndex, emissionConfig.config.endTime, totalMTokens ); // Set the new values in storage emissionConfig.config.supplyGlobalIndex = supplyUpdate.newIndex; emissionConfig.config.supplyGlobalTimestamp = supplyUpdate .newTimestamp; emit GlobalSupplyIndexUpdated( _mToken, emissionConfig.config.emissionToken, supplyUpdate.newIndex, supplyUpdate.newTimestamp ); } }
Consider implementing checks in the function to validate that the _mToken's total supply is within a reasonable range and not below the current circulating supply
uint256 currentTotalSupply = MTokenInterface(_mToken).totalSupply(); require(currentTotalSupply >= totalMTokens, "Invalid total supply: Total supply cannot be decreased.");
MultiRewardDistributor
contract inherited the Pausable.sol
but not actually PausableMultiRewardDistributor inherits from Pausable.sol but does not actually implement the pausable functionality, it can lead to confusion and potential security risks. The Pausable pattern is used to add a pausing mechanism to a smart contract, allowing the contract owner to pause and unpause certain functions temporarily. It's used to handle emergency situations, bugs, or unexpected behavior.
FILE: 2023-07-moonwell/src/core/MultiRewardDistributor/MultiRewardDistributor.sol 44: Pausable,
whenNotPaused
can be used to pause and resume certain critical operations
_emissionConfig.supplierIndices[_supplier]
in some cases this will return default uint256 value 0If the _supplier
is a valid address but has not yet interacted with the contract or does not have an associated index in the mapping, accessing _emissionConfig.supplierIndices[_supplier]
will return the default value for uint256
, which is 0
. Depending on how the contract uses this index, it may lead to incorrect calculations or undesired behavior.
FILE: 2023-07-moonwell/src/core/MultiRewardDistributor/MultiRewardDistributor.sol 833: uint256 userSupplyIndex = _emissionConfig.supplierIndices[_supplier];
One way to do this is to use the .contains()
function provided by a mapping library like OpenZeppelin's
calculateSupplyRewardsForUser()
, calculateBorrowRewardsForUser()
functions does not explicitly check for the existence of the _supplier
address in _emissionConfig.supplierIndices
MappingIf the _emissionConfig.supplierIndices mapping should only contain valid addresses that have been added beforehand, it might be necessary to add additional validation to ensure that _supplier is a valid address before accessing its index in the mapping. It directly accesses userSupplyIndex
from the mapping, assuming that the address exists.
Validate that the _supplier address exists in the set before accessing its index in the mapping
_user
address can receive ETHThere is no guarantee that _user
can receive ETH directly in the sendReward
function, especially if _user
is a contract address or an externally owned address that does not implement a fallback function to receive ETH.
Sending ETH directly to a contract or an externally owned address without a payable fallback function will result in a transaction failure, and the transfer will not be successful. There is no address(0) checks
Before sending ETH, you can check if _user is a contract address using the isContract function. If it's a contract, you can check if it has a payable fallback function to receive ETH. If it does, proceed with the ETH transfer. If it doesn't, handle the situation accordingly (e.g., log an event, revert the transaction, etc.).
FILE: Breadcrumbs2023-07-moonwell/src/core/MultiRewardDistributor/MultiRewardDistributor.sol 1215: address payable _user,
sendEthReward
function checks if _user
is a contract and whether it has a payable fallback function before sending ETH.
token.balanceOf(address(this))
every time this will reduce the contract performanceUsing token.balanceOf(address(this))
every time can potentially reduce the contract's performance. Frequent external calls to other contracts, such as reading the balance of the token contract, can be expensive in terms of gas costs and execution time. This can lead to higher transaction costs and slower contract execution.
FILE: Breadcrumbs2023-07-moonwell/src/core/MultiRewardDistributor/MultiRewardDistributor.sol 480: token.balanceOf(address(this)) 1232: uint256 currentTokenHoldings = token.balanceOf(address(this));
Consider State variables instead of token.balanceOf(address(this))
@openzeppelin
Project mostly uses openzeppelin 4.9.0. There is latest version available 4.9.3
There are known issues in this version
{ "name": "openzeppelin-solidity", "version": "4.9.0",
Use latest version v4.9.3
Insufficient coverage
- What is the overall line coverage percentage provided by your tests?: 80%
The code base, the 80% line coverage percentage means that 80% of the lines of code in the contract have been executed by the tests. This is a good start, but it is not enough to ensure that the contract is fully tested.
Achieve 100% Percent lines coverage to avoid risks
There are warnings in the codebase. Try to rectify all warnings to avoid unexpected behaviors in protocol
Warning (2519): Warning: This declaration shadows an existing declaration. --> src/core/Governance/deprecated/MoonwellArtemisGovernor.sol:349:9: | 349 | ProposalState state = state(proposalId); | ^^^^^^^^^^^^^^^^^^^ Note: The shadowed declaration is here: --> src/core/Governance/deprecated/MoonwellArtemisGovernor.sol:372:5: | 372 | function state(uint proposalId) public view returns (ProposalState) { | ^ (Relevant source part starts here and spans across multiple lines). Warning (3628): Warning: This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> src/core/MErc20Delegator.sol:11:1: | 11 | contract MErc20Delegator is MTokenInterface, MErc20Interface, MDelegatorInterface { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> src/core/MErc20Delegator.sol:496:5: | 496 | fallback () external payable { | ^ (Relevant source part starts here and spans across multiple lines). Warning (2462): Warning: Visibility for constructor is ignored. If you want the contract to be non-deployable, making it "abstract" is sufficient. --> src/core/Governance/deprecated/MoonwellArtemisGovernor.sol:259:5: | 259 | constructor( | ^ (Relevant source part starts here and spans across multiple lines). Warning (2462): Warning: Visibility for constructor is ignored. If you want the contract to be non-deployable, making it "abstract" is sufficient. --> src/core/MLikeDelegate.sol:19:3: | 19 | constructor() public MErc20Delegate() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Warning (2072): Warning: Unused local variable. --> test/integration/LiveSystem.t.sol:66:9: | 66 | uint256 startingTokenBalance = token.balanceOf(sender); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
address(0)
should be avoidedThe external function sendReward() ignores the use of address(0).Need to check address(0) even when using safeTransfer
. safeTransfer
is a function that is designed to prevent certain types of errors from occurring when transferring tokens. However, it does not protect against all possible errors.
FILE: 2023-07-moonwell/src/core/MultiRewardDistributor/MultiRewardDistributor.sol function sendReward( address payable _user, uint256 _amount, address _rewardToken ) internal nonReentrant returns (uint256) { // Short circuit if we don't have anything to send out if (_amount == 0) { return _amount; } // If pause guardian is active, bypass all token transfers, but still accrue to local tally if (paused()) { return _amount; } IERC20 token = IERC20(_rewardToken); // Get the distributor's current balance uint256 currentTokenHoldings = token.balanceOf(address(this)); // Only transfer out if we have enough of a balance to cover it (otherwise just accrue without sending) if (_amount > 0 && _amount <= currentTokenHoldings) { // Ensure we use SafeERC20 to revert even if the reward token isn't ERC20 compliant token.safeTransfer(_user, _amount); return 0; } else { // If we've hit here it means we weren't able to emit the reward and we should emit an event // instead of failing. emit InsufficientTokensToEmit(_user, _rewardToken, _amount); // By default, return the same amount as what's left over to send, we accrue reward but don't send them out return _amount; } }
Add address(0)
check before calling safeTransfer()
function
require(_user!=address(0), "Zero Address");
Deprecated
CommentThe comment ```/// @dev this branch should never get called as native tokens are not supported on this deployment`` is outdated and does not match the actual implementation. As mentioned earlier, nativeToken is not used in the contract, so the branch is indeed getting called and can be problematic.
FILE: 2023-07-moonwell/src/core/Oracles/ChainlinkOracle.sol 62: if (keccak256(abi.encodePacked(symbol)) == nativeToken) { /// @dev this branch should never get called as native tokens are not supported on this deployment
nativeToken
variableThis variable is no longer needed, and it could be removed from the contract
/// @param _nativeToken The native token symbol, unused in this deployment so it can be anything
FILE: 2023-07-moonwell/src/core/Oracles/ChainlinkOracle.sol 46: nativeToken = keccak256(abi.encodePacked(_nativeToken));
setDirectPrice
function should be modified to check the validity
of the price
before setting itThis code first checks that the price is greater than zero. It then checks that the price is less than 1e18. If both of these checks pass, the price is set and the PricePosted event is emitted
- The price should be greater than zero. - The price should be a valid decimal number. - The price should not be too large or too small.
#0 - c4-judge
2023-08-12T17:56:27Z
alcueca marked the issue as grade-a
#1 - c4-sponsor
2023-08-15T18:30:14Z
ElliotFriedman marked the issue as sponsor confirmed
🌟 Selected for report: Sathish9098
Also found by: 0xSmartContract, K42, Udsen, berlin-101, catellatech, cryptonue, hals, jaraxxus, kodyvim, kutugu, solsaver
690.2765 USDC - $690.28
List | Head | Details |
---|---|---|
1 | Overview of Moonwell Protocol | overview of the key components and features of Moonwell |
2 | My Thoughts | My own thoughts about future of the this protocol |
3 | Audit approach | Process and steps i followed |
4 | Learnings | Learnings from this protocol |
5 | Possible Systemic Risks | The possible systemic risks based on my analysis |
6 | Code Commentary | Suggestions for existing code base |
7 | Centralization risks | Concerns associated with centralized systems |
8 | Gas Optimizations | Details about my gas optimizations findings and gas savings |
9 | Risks as per Analysis | Possible risks |
10 | Non-functional aspects | General suggestions |
11 | Time spent on analysis | The Over all time spend for this reports |
Moonwell Protocol
is a fork of Compound v2
with features.
Moonwell is an open lending and borrowing protocol built on Base
, Moonbeam
, and Moonriver
.The protocol's intuitive design ensures a seamless user experience, enabling users to easily navigate through various features and perform operations effortlessly.
ChainlinkCompositeOracle
MultiRewardDistributor
Comptroller
TemporalGovernor
Moonwell Protocol may changes Lending and Borrowing
to next level
Moonwell's approach to lending and borrowing in DeFi can be a catalyst for positive changes in the ecosystem. By setting a standard for user experience, security, transparency, and governance, it may inspire other projects to improve and innovate, leading to a more mature, inclusive, and trustworthy DeFi lending and borrowing landscape in the future.
Decentralization and Governance: Moonwell's onchain governance model can pave the way for more decentralized decision-making processes in other DeFi projects. As protocols adopt similar governance mechanisms, the community's voice and participation may play a more significant role in shaping the future of DeFi platforms.
Trust and Transparency: The emphasis on transparency in Moonwell's onchain operations can foster trust among users. As other protocols adopt similar transparency practices, users may feel more confident in interacting with DeFi platforms, leading to increased trust within the ecosystem.
Non-Custodial Solutions: Moonwell's non-custodial approach to lending and borrowing may become a trend in the DeFi space. More protocols might adopt non-custodial models to allow users to retain control over their assets, aligning with the core principles of DeFi.
Global Reach: Moonwell's focus on accessibility and usability may lead to increased global adoption of DeFi lending and borrowing. As the protocol reaches users in different regions, DeFi's impact could become more widespread and inclusive.
I followed below steps while analyzing and auditing the code base.
Analyzed the over all codebase one iterations very fast
Study of documentation to understand each contract purposes, its functionality, how it is connected with other contracts, etc.
Then i read old audits and already known findings. Then go through the bot races findings
Then setup my testing environment things. Run the tests to checks all test passed. I used foundry to test moonwell protocol. I used forge comments for testing
Finally, I started with the auditing the code base in depth way I started understanding line by line code and took the necessary notes to ask some questions to sponsors.
The first stage of the audit
During the initial stage of the audit for Moonwell Protocol, the primary focus was on analyzing gas usage and quality assurance (QA) aspects. This phase of the audit aimed to ensure the efficiency of gas consumption and verify the robustness of the platform.
Found 14 QA Findings
The second stage of the audit
In the second stage of the audit for Moonwell Protocol, the focus shifted towards understanding the protocol usage in more detail. This involved identifying and analyzing the important contracts and functions within the system. By examining these key components, the audit aimed to gain a comprehensive understanding of the protocol's functionality and potential risks.
The third stage of the audit
During the third stage of the audit for Moonwell Protocol, the focus was on thoroughly examining and marking any doubtful or vulnerable areas within the protocol. This stage involved conducting comprehensive vulnerability assessments and identifying potential weaknesses in the system. Found 60-70
vulnerable
and weakness
code parts all marked with @audit tags
.
The fourth stage of the audit
During the fourth stage of the audit for Moonwell Protocol, a comprehensive analysis and testing of the previously identified doubtful and vulnerable areas were conducted. This stage involved diving deeper into these areas, performing in-depth examinations, and subjecting them to rigorous testing, including fuzzing with various inputs. Finally concluded findings after all research's and tests. Then i reported C4 with proper formats
Found one medium risk findings admin
may receive less token than expected because of this check _amount == type(uint256).max
The Moonwell Protocol team can draw lessons from other DeFi protocols that have faced similar challenges. Studying security incidents, best practices, and successful governance mechanisms from other projects can provide valuable insights for building a more robust and resilient platform.
By prioritizing security, conducting thorough risk assessments, and actively involving the community in governance decisions, the Moonwell Protocol can proactively address these areas of concern and strengthen its position as a secure and community-driven DeFi lending and borrowing platform
Smart Contract Vulnerabilities
: The use of inheritance and integration of multiple features can introduce potential smart contract vulnerabilities. Bugs, logic flaws, or improper implementations could lead to exploits and financial losses.
Test Coverage Risks
: With only 80% test coverage, the protocol may have undiscovered vulnerabilities. Inadequate security audits could leave critical issues unnoticed, increasing the risk of attacks
Governance Challenges
: Understanding Moonbeam and Temporal Governance systems may be complex for some users, potentially leading to governance inefficiencies or suboptimal decision-making.
Chainlink Oracle Risks
: Relying on a single oracle provider, like Chainlink, exposes the protocol to potential risks associated with oracle failures, data manipulation, or centralization issues.
Protocol Fork Risks
: Forking from other protocols may inherit vulnerabilities present in the original codebase. Failure to address these risks could result in potential attacks.
Liquidity and Market Risks
: Insufficient liquidity or market manipulation risks can affect the protocol's stability and overall performance.
Use consistant SPDX-License
for all contracts
Inconsistance solidity versions
used
Ensure that variable and function names follow consistent naming conventions
for better readability
and maintainability
. For example, use camelCase or snake_case consistently throughout the contract
In the Status
and DistributionStatus
structs, consider using an enum type
for stage to improve readability and avoid potential bugs caused by using numbers directly
Some functions like updateMarketSupplyIndexAndDisburseSupplierRewards
and updateMarketBorrowIndexAndDisburseBorrowerRewards
are similar. You can consolidate them into a single function
that takes an additional parameter to specify the action (supplier rewards or borrower rewards)
Instead of importing the whole
contract code for IERC20
and MToken
, use interfaces to define only the required functions
. This reduces the gas cost and enhances code readability
Add inline comments to explain complex logic, especially in mathematical calculations, to make the code more understandable
Add appropriate error messages in require statements to provide more informative feedback
to users when a transaction fails
In some functions like exitMarket
, multiple emit statements are used for the same event
. Consolidate
the event emissions to a single location
to reduce code duplication
and improve readability
.
It's a good practice to add more detailed information in the event logs
, such as the account addresses involved in certain actions.
Some functions return error codes (e.g., uint(Error.NO_ERROR))
, but the exact meaning of these error codes
is not explicitly defined. Proper error code documentation or the use of error enums would enhance clarity
and usability
.
In the addressToBytes
function, you can use bytes20(addr)
directly instead of casting
it as a bytes20
. It will not have any impact on gas costs, but it simplifies the code
In the setTrustedSenders
and unSetTrustedSenders
functions, avoid encoding and decoding the same data by directly using the addr parameter as the key for the mapping
Emit
events for significant contract state changes
, as it helps in tracking contract activities and provides transparency to external applications
Consider refactoring
the code to break down complex functions
into smaller, more manageable pieces, following the principles of modularity
Instead of using separate bool variables like guardianPauseAllowed
, you can use an enum to represent different contract states, such as enum ContractState
{ Active, Paused, GuardianRevoked }
. This can help make the contract state more explicit and easier to understand
A single point of failure is not acceptable for this project Centrality risk is high in the project, the role of onlyOwner
detailed below has very critical and important powers
Project and funds may be compromised by a malicious or stolen private key onlyOwner
msg.sender
File: src/core/Governance/TemporalGovernor.sol 266: function fastTrackProposalExecution(bytes memory VAA) external onlyOwner { 274: function togglePause() external onlyOwner {
Using token.balanceOf(address(this))
every time can potentially reduce the contract's performance. Frequent external calls to other contracts, such as reading the balance of the token contract, can be expensive in terms of gas costs and execution time. This can lead to higher transaction costs and slower contract execution
Explicitly initializing default values
for variables consumes additional ga
s during contract deployment. If you rely on the EVM's automatic default initialization, you can save gas costs associated with these unnecessary operations
Minimize the number of state variable reads and writes
. Use local variables
when possible to avoid additional storage operations
Limit
the number of iterations in loops
to prevent excessive gas consumption
. Consider using other patterns like mapping
, where possible, to reduce the need for loops
Whenever applicable, consider batching multiple operations
together to save on gas costs. For example, you can combine multiple token transfers into a single function call
Remove any unused or commented-out code
to save space and simplify contract logic
Check if modifiers
can be combined
or reused across multiple functions
to reduce the number of modifier calls. This can save gas by avoiding redundant checks.
The enterMarkets
function currently loops through the list of mTokens
and emits an event
for each entry. Consider batching
the events
or using other gas-saving techniques to reduce transaction costs
In the addToMarketInternal
function, you can optimize the code by storing markets[address(mToken)]
in a local variable, rather than repeatedly accessing it within the function
Some operations, such as updating supply and borrow indexes
, are performed in a loop for each market in the claimReward
function. This could lead to exceeding the block gas limit if the number of markets is substantial
Evaluate whether certain loops can be simplified or avoided altogether to reduce gas costs. For instance, consider whether iterating over the allMarkets array in the _addMarketInternal
function can be optimized
Reuse variables
instead of creating new ones when possible. This can reduce memory usage
and, in turn, save gas
For functions that don't modify state
, use the view or pure
modifiers to avoid unnecessary gas costs associated with state changes
For small and simple functions
, consider inlining
them within other functions to save gas costs associated with function calls
Use gas-efficient math libraries, if available, to perform arithmetic operations. Check if there are any gas-efficient
replacements for the current math operations
If certain variables, such as admin, borrowCapGuardian, pauseGuardian,
etc., are not intended to be changed after deployment, mark them as immutable
to enhance security
and gas efficiency
Simplify code logic and eliminate redundant calculations
In the executeProposal
function, there are two function calls to trustedSenders[vm.emitterChainId]
.contains(vm.emitterAddress)
. Consider storing the result of this call in a variable and using that variable to avoid calling the function twice
In the setTrustedSenders
and unSetTrustedSenders
functions, you can move the msg.sender == address(this)
check outside the loop to minimize storage access
in each iteration saves gas
In the constructor
, you can use emit to emit the TrustedSenderUpdated
event instead of using emit in the loop. This can reduce the gas cost by avoiding multiple event emissions
In the executeProposal
function, the return value returnData
is not used. You can remove
it if it's not required.
Lack of integer validations in _setEmissionCap()
function leads unexpected behavior
if (_amount == type(uint256).max) check is wrong . There is no gurantee that type(uint256).max
and contract balance or same
updateMarketSupplyIndexInternal()
,updateMarketSupplyIndexInternal()
there is no limits checked when updating totalsupply
Inline assembly
should be used with caution as it can introduce security risks. Whenever possible, prefer using built-in Solidity functions
and libraries
.
MultiRewardDistributor.sol
The contract interacts with external contracts, such as comptroller, IERC20, and MToken
, through external calls. These external calls are not checked for success or failure
, and there is no handling for potential exceptions or revert reasons, which can lead to unexpected results
.
The contract involves various mathematical calculations
, such as index updates
and reward distributions
. It's crucial to ensure that these operations are handled correctly and do not result in integer overflows
or underflows
.
Some of the functions in the contract, such as updateMarketSupplyIndexInternal
, updateMarketBorrowIndexInternal
, disburseSupplierRewardsInternal
, and disburseBorrowerRewardsInternal
, perform calculations and iterations that could potentially consume a significant amount of gas. It's essential to ensure that these functions are optimized to prevent gas limitations and high transaction costs.
The contract uses uint224
data type for some of the index values (_globalSupplyIndex, _globalBorrowIndex, etc.)
. If these indices grow too large
, it could potentially lead to overflow issues and incorrect ``reward calculations```.
The sendReward
function performs a token transfer and is marked as nonReentrant
, but it's essential to ensure that all external contract calls, especially token transfers, are safe against reentrancy attacks
The contract allows the owner to update supply
and borrow emission speeds
. There should be careful consideration of the emission rates and their effects on the overall token economy
to avoid unintended consequences
The contract has two functions (calculateSupplyRewardsForUser and calculateBorrowRewardsForUser)
for calculating rewards for suppliers and borrowers
, respectively. Ensure that both functions use consistent calculations and share the same index
values to prevent discrepancies
Consider using upgradeable contract patterns
to allow for future updates
without redeploying the entire contract
Comptroller.sol
In the transferAllowed function, there's a comment mentioning the use of local vars to avoid stack-depth limits
in calculating account liquidity. While it's a good practice to use local variables
for complex calculations
, it's important to ensure that the overall stack depth
is managed appropriately, especially in functions that could be called in nested scenarios
The contract appears to have some administrative functions like _setPriceOracle, _setCloseFactor, _setCollateralFactor,
etc., which should only be callable by the admin
. However, the access control checks are not consistently applied in all these functions, potentially allowing unauthorized users to modify critical parameters
Several functions call external contracts, like the rewardDistributor
, oracle
, and token contracts, but they do not have proper error handling
for potential failures. Lack of error handling can lead to unexpected behavior and vulnerabilities
The contract has several functions for pausing different actions (_setMintPaused, _setBorrowPaused, _setTransferPaused, _setSeizePaused)
. However, there is no mechanism for time-based unpausing or emergency unpause
, which could be essential for the contract's safety
The liquidateCalculateSeizeTokens
function, used during liquidation, performs arithmetic calculations using external data (e.g., exchangeRateMantissa). Failing to handle or validate external data correctly
could lead to vulnerabilities in the liquidation process
The claimReward
function, responsible for distributing rewards, has a complex design with nested loops, and it calls an external contract multiple times. High complexity increases the risk of errors and makes the contract harder to audit
The getBlockTimestamp
function retrieves the current block timestamp using block.timestamp. It's worth noting that relying solely on block timestamps for time-sensitive operations can be manipulated by miners to some extent
Certain functions that modify contract parameters (e.g., _setPriceOracle, _setCloseFactor, etc.) do not include time limitations
for changes. Adding time restrictions
for critical parameter modifications could prevent malicious or mistaken changes
TemporalGovernor.sol
The contract relies on trusted emitters from the Wormhole bridge using isTrustedSender
. However, relying solely on a chain ID
and a 32-byte emitter address might not be sufficient to ensure the authenticity
of the signed messages. Consider using a more robust signature verification
mechanism to ensure the validity of messages
The contract doesn't have any explicit reentrancy protection
. Ensure that critical state changes are performed before any external calls to prevent potential reentrancy attacks
The contract uses uint248
for lastPauseTime
, which might be susceptible to integer overflow if the pause timestamp becomes larger than the maximum value
The fastTrackProposalExecution function allows the owner to bypass the usual delay for proposal execution. This can be risky as it doesn't have any checks on the validity of the proposal. It should be handled with caution and only used in specific emergency scenarios
The function allTrustedSender
s iterates through the entire trustedSenders set to return the list of trusted senders for a chain. This operation may become inefficient as the size of the set grows. Consider alternative data structures or optimizations if needed
ChainlinkCompositeOracle.sol
The contract relies on Chainlink oracles
to provide accurate price data. If a Chainlink oracle is compromised
or provides incorrect data
, the ChainlinkCompositeOracle contract could also be compromised
The contract does not have any mechanisms in place to prevent front-running
or other forms of market manipulation
. This could allow malicious actors to exploit the contract and profit at the expense of other users
The contract relies on the decimals
value returned by Chainlink oracles. If the decimals value is misaligned with the contract's expectations
, it could result in incorrect price calculations
and pose a vulnerability
15 Hours
13 hours
#0 - c4-pre-sort
2023-08-03T15:38:42Z
0xSorryNotSorry marked the issue as high quality report
#1 - ElliotFriedman
2023-08-03T22:00:46Z
I can't see the findings he linked in his report
#2 - c4-sponsor
2023-08-03T22:21:53Z
ElliotFriedman marked the issue as sponsor acknowledged
#3 - c4-judge
2023-08-11T20:39:21Z
alcueca marked the issue as selected for report
#4 - sathishpic22
2023-08-23T03:59:42Z
Hi @alcueca I think missed to mark the grade . Please mark grade for this report
Thank you
#5 - c4-judge
2023-08-23T20:22:13Z
alcueca marked the issue as grade-a