Users could spend gas and possibly other off chain resources voting on an ExtraordinaryFunding proposal which would later revert when executing.
Proof of Concept
If an ExtraordinaryFunding proposal requests an amount of tokens between 48.5% and 50% of the treasury (the limits change as ExtraordinaryFunding proposals are funded). 48.5% is 0.5*0.97*100, due to the 3% allocated to the StandardFunding distribution round.
Add the following test to GrantFund.t.sol:
function test_POC_ExtraordinaryFundingFails_DueToNewDistributionRound() external {
// initial minter distributes tokens to test addresses
_transferAjnaTokens(_token, _votersArr, 500_000_000 * 1e18, _tokenDeployer);
for (uint256 i_ = 0; i_ < _votersArr.length; ++i_) {
changePrank(_votersArr[i_]);
_token.delegate(_votersArr[i_]);
}
vm.roll(_startBlock + 33);
// fund treasury
changePrank(_tokenDeployer);
_token.approve(address(_grantFund), 50_000_000 * 1e18);
vm.expectEmit(true, true, false, true);
emit FundTreasury(50_000_000 * 1e18, 50_000_000 * 1e18);
_grantFund.fundTreasury(50_000_000 * 1e18);
// create proposal
address[] memory targets_ = new address[](1);
targets_[0] = address(_token);
uint256[] memory values_ = new uint256[](1);
values_[0] = 0;
bytes[] memory calldatas_ = new bytes[](1);
// The first extraordinary proposal can give up to treasury/2 tokens
calldatas_[0] = abi.encodeWithSignature("transfer(address,uint256)", _tokenHolder1, 50_000_000/2*1e18);
uint256 proposalId_ = _grantFund.proposeExtraordinary(
block.number + 216_000 - 1,
targets_,
values_,
calldatas_,
""
);
// vote on it
_extraordinaryVote(_grantFund, _tokenHolder1, proposalId_, 1);
_extraordinaryVote(_grantFund, _tokenHolder2, proposalId_, 1);
_extraordinaryVote(_grantFund, _tokenHolder3, proposalId_, 1);
// StandardFunding new round
_startDistributionPeriod(_grantFund);
// execute proposal should revert
vm.expectRevert(IExtraordinaryFunding.ExecuteExtraordinaryProposalInvalid.selector);
_grantFund.executeExtraordinary(targets_, values_, calldatas_, keccak256(""));
}
Tools Used
Vscode, Foundry
Recommended Mitigation Steps
If a StandardFunding distribution is coming in the next fund, only allow proposing up to _getMinimumThresholdPercentage()*0.97*treasury. Another option is, when executing, don't revert if tokensRequested_ are bigger than _getMinimumThresholdPercentage()*treasury, but limit it to it.
Lines of code
https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L105 https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L176
Vulnerability details
Impact
Users could spend gas and possibly other off chain resources voting on an
ExtraordinaryFunding
proposal which would later revert when executing.Proof of Concept
If an
ExtraordinaryFunding
proposal requests an amount of tokens between 48.5% and 50% of the treasury (the limits change asExtraordinaryFunding
proposals are funded). 48.5% is0.5*0.97*100
, due to the 3% allocated to theStandardFunding
distribution round.Add the following test to
GrantFund.t.sol
:Tools Used
Vscode, Foundry
Recommended Mitigation Steps
If a
StandardFunding
distribution is coming in the next fund, only allow proposing up to_getMinimumThresholdPercentage()*0.97*treasury
. Another option is, when executing, don't revert iftokensRequested_
are bigger than_getMinimumThresholdPercentage()*treasury
, but limit it to it.Assessed type
Math