Platform: Code4rena
Start Date: 22/05/2024
Pot Size: $20,000 USDC
Total HM: 6
Participants: 126
Period: 5 days
Judge: 0xsomeone
Total Solo HM: 1
Id: 379
League: ETH
Rank: 124/126
Findings: 2
Award: $0.00
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: robertodf99
Also found by: 0xAadi, 0xAkira, 0xdice91, 0xhacksmithh, 0xleadwizard, AgileJune, Bauchibred, Bbash, Beosin, Bigsam, Dots, EPSec, EaglesSecurity, Eeyore, Evo, John_Femi, Mahmud, MrPotatoMagic, RotiTelur, Rushkov_Boyan, Sabit, Sentryx, Stormreckson, Topmark, Tychai0s, Utsav, Walter, ZanyBonzy, ZdravkoHr, adam-idarrha, araj, aslanbek, avoloder, bigtone, brevis, brgltd, carrotsmuggler, crypticdefense, dd0x7e8, dhank, djanerch, falconhoof, iamandreiski, joaovwfreire, leegh, merlinboii, mitko1111, pamprikrumplikas, pfapostol, prapandey031, swizz, trachev, twcctop, typicalHuman, unique, xyz
0.0037 USDC - $0.00
Judge has assessed an item in Issue #535 as 2 risk. The relevant finding follows:
PriceFeed can vote both against and in favor of confirming the new price at the same time
Low
src/managers/LockManager.sol
One of the roles can first call disapproveUSDPrice()
and then call approveUSDPrice()
, thus increasing the count of those who agree and disagree for the new token price, since the approveUSDPrice function does not check if one of the roles has voted against it.
function approveUSDPrice( uint256 _price ) external onlyOneOfRoles( [ Role.PriceFeed_1, Role.PriceFeed_2, Role.PriceFeed_3, Role.PriceFeed_4, Role.PriceFeed_5 ] ) { if (usdUpdateProposal.proposer == address(0)) revert NoProposalError(); if (usdUpdateProposal.proposer == msg.sender) revert ProposerCannotApproveError(); if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId) revert ProposalAlreadyApprovedError(); if (usdUpdateProposal.proposedPrice != _price) revert ProposalPriceNotMatchedError(); usdUpdateProposal.approvals[msg.sender] = _usdProposalId; usdUpdateProposal.approvalsCount++; if (usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD) { _execUSDPriceUpdate(); } emit ApprovedUSDPrice(msg.sender); }
function disapproveUSDPrice( uint256 _price ) external onlyOneOfRoles( [ Role.PriceFeed_1, Role.PriceFeed_2, Role.PriceFeed_3, Role.PriceFeed_4, Role.PriceFeed_5 ] ) { if (usdUpdateProposal.proposer == address(0)) revert NoProposalError(); if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId) revert ProposalAlreadyApprovedError(); if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId) revert ProposalAlreadyDisapprovedError(); if (usdUpdateProposal.proposedPrice != _price) revert ProposalPriceNotMatchedError(); usdUpdateProposal.disapprovalsCount++; usdUpdateProposal.disapprovals[msg.sender] = _usdProposalId; emit DisapprovedUSDPrice(msg.sender); if (usdUpdateProposal.disapprovalsCount >= DISAPPROVE_THRESHOLD) { delete usdUpdateProposal; emit RemovedUSDProposal(); } }
it is worth adding to the approveUSDPrice()
function to check usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId
function approveUSDPrice( uint256 _price ) external onlyOneOfRoles( [ Role.PriceFeed_1, Role.PriceFeed_2, Role.PriceFeed_3, Role.PriceFeed_4, Role.PriceFeed_5 ] ) { if (usdUpdateProposal.proposer == address(0)) revert NoProposalError(); if (usdUpdateProposal.proposer == msg.sender) revert ProposerCannotApproveError(); if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId) revert ProposalAlreadyApprovedError(); -> if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId){ revert ProposalAlreadyDisApprovedError(); } if (usdUpdateProposal.proposedPrice != _price) revert ProposalPriceNotMatchedError(); usdUpdateProposal.approvals[msg.sender] = _usdProposalId; usdUpdateProposal.approvalsCount++; if (usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD) { _execUSDPriceUpdate(); } emit ApprovedUSDPrice(msg.sender); }
#0 - c4-judge
2024-06-05T12:29:53Z
alex-ppg marked the issue as duplicate of #495
#1 - c4-judge
2024-06-05T12:29:56Z
alex-ppg marked the issue as partial-25
🌟 Selected for report: MrPotatoMagic
Also found by: 0xAkira, Bauchibred, Deivitto, Walter, bigtone, merlinboii
0 USDC - $0.00
PriceFeed can vote both against and in favor of confirming the new price at the same time
Low
src/managers/LockManager.sol
One of the roles can first call disapproveUSDPrice()
and then call approveUSDPrice()
, thus increasing the count of those who agree and disagree for the new token price, since the approveUSDPrice function does not check if one of the roles has voted against it.
function approveUSDPrice( uint256 _price ) external onlyOneOfRoles( [ Role.PriceFeed_1, Role.PriceFeed_2, Role.PriceFeed_3, Role.PriceFeed_4, Role.PriceFeed_5 ] ) { if (usdUpdateProposal.proposer == address(0)) revert NoProposalError(); if (usdUpdateProposal.proposer == msg.sender) revert ProposerCannotApproveError(); if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId) revert ProposalAlreadyApprovedError(); if (usdUpdateProposal.proposedPrice != _price) revert ProposalPriceNotMatchedError(); usdUpdateProposal.approvals[msg.sender] = _usdProposalId; usdUpdateProposal.approvalsCount++; if (usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD) { _execUSDPriceUpdate(); } emit ApprovedUSDPrice(msg.sender); }
function disapproveUSDPrice( uint256 _price ) external onlyOneOfRoles( [ Role.PriceFeed_1, Role.PriceFeed_2, Role.PriceFeed_3, Role.PriceFeed_4, Role.PriceFeed_5 ] ) { if (usdUpdateProposal.proposer == address(0)) revert NoProposalError(); if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId) revert ProposalAlreadyApprovedError(); if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId) revert ProposalAlreadyDisapprovedError(); if (usdUpdateProposal.proposedPrice != _price) revert ProposalPriceNotMatchedError(); usdUpdateProposal.disapprovalsCount++; usdUpdateProposal.disapprovals[msg.sender] = _usdProposalId; emit DisapprovedUSDPrice(msg.sender); if (usdUpdateProposal.disapprovalsCount >= DISAPPROVE_THRESHOLD) { delete usdUpdateProposal; emit RemovedUSDProposal(); } }
it is worth adding to the approveUSDPrice()
function to check usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId
function approveUSDPrice( uint256 _price ) external onlyOneOfRoles( [ Role.PriceFeed_1, Role.PriceFeed_2, Role.PriceFeed_3, Role.PriceFeed_4, Role.PriceFeed_5 ] ) { if (usdUpdateProposal.proposer == address(0)) revert NoProposalError(); if (usdUpdateProposal.proposer == msg.sender) revert ProposerCannotApproveError(); if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId) revert ProposalAlreadyApprovedError(); -> if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId){ revert ProposalAlreadyDisApprovedError(); } if (usdUpdateProposal.proposedPrice != _price) revert ProposalPriceNotMatchedError(); usdUpdateProposal.approvals[msg.sender] = _usdProposalId; usdUpdateProposal.approvalsCount++; if (usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD) { _execUSDPriceUpdate(); } emit ApprovedUSDPrice(msg.sender); }
Permalink: https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L177-L242
Add a check for minimum lock duration
Low
src/managers/LockManager.sol
In the configureLockdrop()
function, you should add a check that _lockdropData.minLockDuration>=30 days
as specified in the documentation
function configureLockdrop( Lockdrop calldata _lockdropData ) external onlyAdmin { if (_lockdropData.end < block.timestamp) revert LockdropEndedError( _lockdropData.end, uint32(block.timestamp) ); // , "LockManager: End date is in the past"); if (_lockdropData.start >= _lockdropData.end) revert LockdropInvalidError(); lockdrop = _lockdropData; emit LockDropConfigured(_lockdropData); }
function configureLockdrop( Lockdrop calldata _lockdropData ) external onlyAdmin { if (_lockdropData.end < block.timestamp) revert LockdropEndedError( _lockdropData.end, uint32(block.timestamp) ); // , "LockManager: End date is in the past"); if (_lockdropData.start >= _lockdropData.end) revert LockdropInvalidError(); -> if (_locdropData.minLockDuration < 30 days ) { -> revert Error(); } lockdrop = _lockdropData; emit LockDropConfigured(_lockdropData); }
Permalink: https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L98C5-L112C6
STATE VARIABLE VISIBILITY NOT SET
Informational
src/managers/LockManager.sol
It is best practice to set the visibility of state variables explicitly.
uint8 APPROVE_THRESHOLD = 3; uint8 DISAPPROVE_THRESHOLD = 3; ... mapping(address => PlayerSettings) playerSettings; ... USDUpdateProposal usdUpdateProposal;
It is recommended to explicitly specify the visibility of all state variables.
Permalink: https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L16 https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L18 https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L26 https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L30
Unnecessary code repetition
Informational
src/managers/LockManager.sol
in the LockManager::approveUSDPrice
function there is a condition
if (usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD) { _execUSDPriceUpdate(); }
if it is true, the internal
function _execUSDPriceUpdate()
is called, and this function will be executed only if this condition is true.
if ( usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD && usdUpdateProposal.disapprovalsCount < DISAPPROVE_THRESHOLD ) {...}
Consider removing unnecessary repetitive code
Permalink: https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L202-L204 https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L507-L510
Add the getter function
Informational
src/managers/LockManager.sol
In the approveUSDPrice(uint256 _price)
and disapproveUSDPrice(uint256 _price)
functions, one of the roles passes to these functions the price they want to approve or reject. These functions have a check that the input must equal the suggested price. If it is not, it revert the error ProposalPriceNotMatchedError
.
function approveUSDPrice( uint256 _price ) external onlyOneOfRoles( [ Role.PriceFeed_1, Role.PriceFeed_2, Role.PriceFeed_3, Role.PriceFeed_4, Role.PriceFeed_5 ] ) { if (usdUpdateProposal.proposer == address(0)) revert NoProposalError(); if (usdUpdateProposal.proposer == msg.sender) revert ProposerCannotApproveError(); if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId) revert ProposalAlreadyApprovedError(); -> if (usdUpdateProposal.proposedPrice != _price) revert ProposalPriceNotMatchedError(); ...}
Consider adding a getter function
for the convenience of all PriceFeeds
that will return a proposed price
function getProposedPrice() external returns (uint256){ return usdUpdateProposal.proposedPrice; }
Permalink: https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L177C5-L207 https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L177C5-L207
#0 - c4-judge
2024-06-05T12:37:48Z
alex-ppg marked the issue as grade-a