sherlock-audit / 2023-01-ajna-judging

1 stars 0 forks source link

berndartmueller - Extraordinary proposals can receive more tokens than eligible #124

Open github-actions[bot] opened 1 year ago

github-actions[bot] commented 1 year ago

berndartmueller

medium

Extraordinary proposals can receive more tokens than eligible

Summary

Two extraordinary proposals proposed at similar times (while no other extraordinary proposals are executed) can request the same amount of tokens (proposal.tokensRequested). But if the second proposal is proposed after the first proposal is successfully funded and executed, the second proposal could not request the same amount of tokens. It would have to be lower due to ExtraordinaryFunding.sol#L86.

Vulnerability Detail

Proposing an extraordinary funding proposal with the ExtraordinaryFunding.proposeExtraordinary function verifies that the requested token amount is within (less than) a certain limit in ExtraordinaryFunding.sol#L86. The limit is based on the treasury Ajna token balance and decreases with an increasing number of funded extraordinary proposals. A maximum number of 10 extraordinary proposals can be funded.

If multiple extraordinary proposals are proposed while the number of funded proposals is unchanged, the limit for the tokens requested is the same for those proposals. At a later time, if those proposals pass voting, the proposals are executed and receive the requested (stale) token amount.

Impact

The requested token amount of an extraordinary proposal is not checked when executing if it's within the same limits imposed by L86.

If another extraordinary proposal was successfully funded and executed in the meantime of the 1 month voting period for the proposal, the limit for the requested token amount is already lower than the proposal.tokensRequested amount.

This means that if multiple extraordinary proposals are proposed at a similar time and pass voting, the tokensRequested amount is stale and potentially too much.

Code Snippet

ecosystem-coordination/src/grants/base/ExtraordinaryFunding.sol#L86

067: function proposeExtraordinary(
068:     uint256 endBlock_,
069:     address[] memory targets_,
070:     uint256[] memory values_,
071:     bytes[] memory calldatas_,
072:     string memory description_) external returns (uint256 proposalId_) {
073:
074:     proposalId_ = hashProposal(targets_, values_, calldatas_, keccak256(bytes(description_)));
075:
076:     if (extraordinaryFundingProposals[proposalId_].proposalId != 0) revert ProposalAlreadyExists();
077:
078:     // check proposal length is within limits of 1 month maximum and it hasn't already been submitted
079:     if (block.number + MAX_EFM_PROPOSAL_LENGTH < endBlock_ || extraordinaryFundingProposals[proposalId_].proposalId != 0) {
080:         revert ExtraordinaryFundingProposalInvalid();
081:     }
082:
083:     uint256 totalTokensRequested = _validateCallDatas(targets_, values_, calldatas_);
084:
085:     // check tokens requested is within limits
086:     if (totalTokensRequested > getSliceOfTreasury(Maths.WAD - _getMinimumThresholdPercentage())) revert ExtraordinaryFundingProposalInvalid();
087:
088:     // store newly created proposal
089:     ExtraordinaryFundingProposal storage newProposal = extraordinaryFundingProposals[proposalId_];
090:     newProposal.proposalId      = proposalId_;
091:     newProposal.startBlock      = block.number;
092:     newProposal.endBlock        = endBlock_;
093:     newProposal.tokensRequested = totalTokensRequested;
094:
095:     emit ProposalCreated(
096:         proposalId_,
097:         msg.sender,
098:         targets_,
099:         values_,
100:         new string[](targets_.length),
101:         calldatas_,
102:         block.number,
103:         endBlock_,
104:         description_);
105: }
106:

Tool used

Manual Review

Recommendation

Consider re-checking the tokensRequested amount when executing an extraordinary proposal and make sure it's within the same or similar limits as when proposing.