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: 147/178
Findings: 1
Award: $11.69
🌟 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
11.6897 USDC - $11.69
Lines of code https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#L372-L380
Impact In case of tie between Increase and Decrease, No change will be chosen
Proof of Concept
function winningParameterVote( uint256 ballotID ) external view returns (Vote) { mapping(Vote=>uint256) storage votes = _votesCastForBallot[ballotID]; uint256 increaseTotal = votes[Vote.INCREASE]; uint256 decreaseTotal = votes[Vote.DECREASE]; uint256 noChangeTotal = votes[Vote.NO_CHANGE]; if ( increaseTotal > decreaseTotal ) if ( increaseTotal > noChangeTotal ) return Vote.INCREASE; if ( decreaseTotal > increaseTotal ) if ( decreaseTotal > noChangeTotal ) return Vote.DECREASE; return Vote.NO_CHANGE; }
increaseTotal > decreaseTotal which is 100>100 which is false decreaseTotal > increaseTotal which is 100>100 which is false So Vote.NO_CHANGE is returned
Recommendation Document this behavior so that Users are aware
Lines of code https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#L431
Impact Ballot with highest yes vote won't be selected right away, for token whitelisting under certain circumstances. Ballot which are created early are given preference which might not be fair always
Proof of Concept
quorum=100 Ballot 1: yesVotes=80 noVotes=30 Ballot 2: yesVotes=80 noVotes=20 Ballot 3: yesVotes=70 noVotes=30
_finalizeTokenWhitelisting -> proposals.tokenWhitelistingBallotWithTheMostVotes();
tokenWhitelistingBallotWithTheMostVotes
will iterate through each ballot and find the one with most yesVotes like below:if ( (yesTotal + noTotal) >= quorum ) // Make sure that quorum has been reached if ( yesTotal > noTotal ) // Make sure the token vote is favorable if ( yesTotal > mostYes ) // Make sure these are the most yes votes seen { bestID = ballotID; mostYes = yesTotal; }
Ballot 1: (yesTotal + noTotal) >= quorum is true since 80+30>=100 yesTotal > noTotal is true since 80>30 mostYes=80 bestID=Ballot 1 Ballot 2: (yesTotal + noTotal) >= quorum is true since 80+20>=100 yesTotal > noTotal is true since 80>20 mostYes=80 yesTotal > mostYes is false since 80>80 is false bestID remains Ballot 1
uint256 bestWhitelistingBallotID = proposals.tokenWhitelistingBallotWithTheMostVotes(); require( bestWhitelistingBallotID == ballotID, "Only the token whitelisting ballot with the most votes can be finalized" ); // All tokens are paired with both WBTC and WETH, so whitelist both pairings poolsConfig.whitelistPool( pools, IERC20(ballot.address1), exchangeConfig.wbtc() ); poolsConfig.whitelistPool( pools, IERC20(ballot.address1), exchangeConfig.weth() ); bytes32 pool1 = PoolUtils._poolID( IERC20(ballot.address1), exchangeConfig.wbtc() ); bytes32 pool2 = PoolUtils._poolID( IERC20(ballot.address1), exchangeConfig.weth() );
Recommended Mitigation Steps This behavior can be documented
#0 - c4-judge
2024-02-03T13:24:13Z
Picodes marked the issue as grade-b