Platform: Code4rena
Start Date: 16/01/2024
Pot Size: $80,000 USDC
Total HM: 37
Participants: 178
Period: 14 days
Judge: Picodes
Total Solo HM: 4
Id: 320
League: ETH
Rank: 90/178
Findings: 1
Award: $72.13
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: juancito
Also found by: 0x3b, 0xBinChook, 0xCiphky, 0xHelium, 0xMango, 0xOmer, 0xRobocop, 0xSmartContractSamurai, 0xWaitress, 0xbepresent, 0xpiken, 7ashraf, Arz, Audinarey, Banditx0x, Bauchibred, Draiakoo, IceBear, J4X, Jorgect, Kaysoft, KingNFT, Rhaydden, Rolezn, The-Seraphs, Tigerfrake, Topmark, Tripathi, Udsen, ZanyBonzy, a3yip6, b0g0, chaduke, codeslide, csanuragjain, dharma09, djxploit, eta, ether_sky, grearlake, inzinko, jasonxiale, jesjupyter, josephdara, lanrebayode77, linmiaomiao, lsaudit, niroh, oakcobalt, peanuts, pina, sivanesh_808, slvDev, t0x1c, vnavascues, wangxx2026
72.1303 USDC - $72.13
The QA report highlights several issues in the codebase. One critical issue ([L-01]) involves the addition of a cooldown period even when the price is not changed, leading to false emissions. Additionally, the report identifies issues such as checking ballot existence before removal ([L-02]), verifying the existence of a country before proposing exclusion ([L-03]), and checking for the existence of a ballotId before counting votes ([L-04]). Other issues include ensuring values are not smaller than a specified threshold ([L-05]), checking for pool whitelisting ([L-06]), and addressing various non-critical concerns ([N-01] to [N-06]). Remedial actions and mitigations are provided for each issue.
Issue Number | Title | Number of Instances |
---|---|---|
L-01 | Cooldown period added without price change | 1 |
L-02 | Check if ballot exists before removal | 1 |
L-03 | Check if country already exists | 1 |
L-04 | Check if ballotId exists before counting votes | 3 |
L-05 | Check if values are smaller than pool.dust | 3 |
L-06 | Check if pool already whitelisted | 1 |
N-01 | Revert with an error message | 3 |
N-02 | Add else and revert on else | 1 |
N-03 | Require increment amount to be > 0 | 1 |
N-04 | Emit vote updated and vote casted events | 1 |
N-05 | Check for token decimals under 18 | 1 |
N-06 | Function requires more comments | 1 |
R-01 | Add msg.sender to proposal created message | 1 |
R-02 | recoverSalt() should be called after unstake | 1 |
R-03 | Emit an event on swap | 1 |
R-04 | Consider adding remaining amount event | 1 |
On numbers greater than 3, the function will have a false cooldown change and a false emit.
else if ( priceFeedNum == 3 ) priceFeed3 = newPriceFeed; priceFeedModificationCooldownExpiration = block.timestamp + priceFeedModificationCooldown; emit PriceFeedSet(priceFeedNum, newPriceFeed);
Add a final else statement and revert or return.
function markBallotAsFinalized( uint256 ballotID ) external nonReentrant { require( msg.sender == address(exchangeConfig.dao()), "Only the DAO can mark a ballot as finalized" ); Ballot storage ballot = ballots[ballotID]; // Remove finalized whitelist token ballots from the list of open whitelisting proposals if ( ballot.ballotType == BallotType.WHITELIST_TOKEN ) _openBallotsForTokenWhitelisting.remove( ballotID ); // Remove from the list of all open ballots _allOpenBallots.remove( ballotID ); ballot.ballotIsLive = false; // Indicate that the user who posted the proposal no longer has an active proposal address userThatPostedBallot = _usersThatProposedBallots[ballotID]; _userHasActiveProposal[userThatPostedBallot] = false; delete openBallotsByName[ballot.ballotName]; emit BallotFinalized(ballotID); }
function proposeCountryExclusion( string calldata country, string calldata description ) external nonReentrant returns (uint256 ballotID) { require( bytes(country).length == 2, "Country must be an ISO 3166 Alpha-2 Code" ); string memory ballotName = string.concat("exclude:", country ); return _possiblyCreateProposal( ballotName, BallotType.EXCLUDE_COUNTRY, address(0), 0, country, description ); }
mapping(Vote=>uint256) storage votes = _votesCastForBallot[ballotID]; Ballot memory ballot = ballots[ballotID];
mapping(Vote=>uint256) storage votes = _votesCastForBallot[ballotID];
mapping(Vote=>uint256) storage votes = _votesCastForBallot[ballotID];
pool.dust
(uint256 reservesA0, uint256 reservesA1) = getPoolReserves( weth, arbToken2);
(uint256 reservesB0, uint256 reservesB1) = getPoolReserves( arbToken2, arbToken3);
(uint256 reservesC0, uint256 reservesC1) = getPoolReserves( arbToken3, weth);
_whitelist.add(poolID);
if ( (reservesWETH < PoolUtils.DUST) || (reservesTokenIn < PoolUtils.DUST) ) return 0;
if ( swapAmountInValueInETH <= PoolUtils.DUST ) return 0;
if ( swapAmountInValueInETH == 0 ) return 0;
Should add an else statement and revert with an error message instead of returning the variable un-initialized with zero value
if (arbitrageAmountIn > 0) arbitrageProfit = _arbitrage(arbToken2, arbToken3, arbitrageAmountIn);
usdsThatShouldBeBurned += usdsToBurn;
if ( lastVote.votingPower > 0 ) _votesCastForBallot[ballotID][lastVote.vote] -= lastVote.votingPower; // Update the votes cast for the ballot with the user's current voting power _votesCastForBallot[ballotID][vote] += userVotingPower; // Remember how the user voted in case they change their vote later _lastUserVoteForBallot[ballotID][msg.sender] = UserVote( vote, userVotingPower ); emit VoteCast(msg.sender, ballotID, vote, userVotingPower);
Tokens with decimals above 18 may break the contract
emit ProposalCreated(ballotID, ballotType, ballotName); function proposeTokenWhitelisting( IERC20 token, string calldata tokenIconURL, string calldata description ) external nonReentrant returns (uint256 _ballotID){ + //require tokens.decimals() <= 18; ... }
add comments to describe function behavior, input, and output
function _possiblyCreateProposal( string memory ballotName, BallotType ballotType, address address1, uint256 number1, string memory string1, string memory string2 ) internal returns (uint256 ballotID)
emit ProposalCreated(ballotID, ballotType, ballotName);
recoverSalt()
should be called after the unstake()
methodif ( usdsBorrowedByUsers[msg.sender] == 0 ) _walletsWithBorrowedUSDS.remove(msg.sender); emit RepaidUSDS(msg.sender, amountRepaid);
#0 - c4-judge
2024-02-03T13:21:59Z
Picodes marked the issue as grade-a
#1 - othernet-global
2024-02-12T21:31:34Z
L5: Already checked in _bisection search: if ( reservesA0 <= PoolUtils.DUST || reservesA1 <= PoolUtils.DUST || reservesB0 <= PoolUtils.DUST || reservesB1 <= PoolUtils.DUST || reservesC0 <= PoolUtils.DUST || reservesC1 <= PoolUtils.DUST ) return 0;
L6: Checked at the time the proposal is made.
#2 - c4-sponsor
2024-02-12T21:31:37Z
othernet-global (sponsor) acknowledged