Ajna Protocol - Jiamin'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: 72/114

Findings: 2

Award: $54.07

🌟 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)
partial-25
edited-by-warden
duplicate-465

Awards

17.8317 USDC - $17.83

External Links

Lines of code

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

Vulnerability details

Impact

A proposal receives no vote can still be updated in slate.

Proof of Concept

Any project can submit a proposal in screening stage, the top ten proposals receiving the most votes in screening stage are then voted upon in the funding stage, once the voting period ends, the winning proposals that can be executed are decided through a one week challenge period. In the challenge period, anyone can submit a set of winning proposals to execute, if the the sum of votes in the submitted proposal slate is larger than the previously submitted slate of proposals, then it becomes the new winning slate. This updating logic is implement in updateSlate function and validation is done through _validateSlate function:

function _validateSlate(uint24 distributionId_, uint256 endBlock, uint256 distributionPeriodFundsAvailable_, uint256[] calldata proposalIds_, uint256 numProposalsInSlate_) internal view returns (uint256 sum_) { // check that the function is being called within the challenge period if (block.number <= endBlock || block.number > _getChallengeStageEndBlock(endBlock)) { revert InvalidProposalSlate(); } // check that the slate has no duplicates if (_hasDuplicates(proposalIds_)) revert InvalidProposalSlate(); uint256 gbc = distributionPeriodFundsAvailable_; uint256 totalTokensRequested = 0; // check each proposal in the slate is valid for (uint i = 0; i < numProposalsInSlate_; ) { Proposal memory proposal = _standardFundingProposals[proposalIds_[i]]; // check if Proposal is in the topTenProposals list if (_findProposalIndex(proposalIds_[i], _topTenProposals[distributionId_]) == -1) revert InvalidProposalSlate(); // account for fundingVotesReceived possibly being negative if (proposal.fundingVotesReceived < 0) revert InvalidProposalSlate(); // update counters sum_ += uint128(proposal.fundingVotesReceived); // since we are converting from int128 to uint128, we can safely assume that the value will not overflow totalTokensRequested += proposal.tokensRequested; // check if slate of proposals exceeded budget constraint ( 90% of GBC ) if (totalTokensRequested > (gbc * 9 / 10)) { revert InvalidProposalSlate(); } unchecked { ++i; } } }

However, system does not check if the funding votes of the submitted proposals is 0, it only checks if the votes being negative:

if (proposal.fundingVotesReceived < 0) revert InvalidProposalSlate();

This could lead to any proposal with 0 votes being updated in the slate and is then be executed.

Tools Used

Manual Review

system should check if the funding votes received by a proposal is 0:

if (proposal.fundingVotesReceived <= 0) revert InvalidProposalSlate();

Assessed type

Context

#0 - c4-judge

2023-05-18T14:10:04Z

Picodes marked the issue as primary issue

#1 - c4-judge

2023-05-18T14:11:21Z

Picodes marked issue #465 as primary and marked this issue as a duplicate of 465

#2 - c4-judge

2023-05-30T20:12:20Z

Picodes marked the issue as partial-25

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