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: 35/114
Findings: 1
Award: $304.58
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: rbserver
Also found by: 0xnev, ABAIKUNANBAEV, Audit_Avengers, Aymen0909, BGSecurity, BRONZEDISC, Bason, DadeKuma, GG_Security, Jerry0x, Jorgect, MohammedRizwan, REACH, Sathish9098, Shogoki, T1MOH, UniversalCrypto, aviggiano, ayden, berlin-101, bytes032, codeslide, descharre, fatherOfBlocks, hals, kaveyjoe, kodyvim, lfzkoala, lukris02, nadin, naman1778, patitonar, pontifex, sakshamguruji, squeaky_cactus, teawaterwire, wonjun, yjrwkk
304.5822 USDC - $304.58
Total 04 Low and 06 Non-Critical
In the implementation GrantFund.state
, expired proposals are not check on the state function. Thus expired proposals still can be executed
function state( uint256 proposalId_ ) external view override returns (ProposalState) { FundingMechanism mechanism = findMechanismOfProposal(proposalId_); return mechanism == FundingMechanism.Standard ? _standardProposalState(proposalId_) : _getExtraordinaryProposalState(proposalId_); }
standardProposalState(proposalId)
Proposal memory proposal = _standardFundingProposals[proposalId_]; if (proposal.executed) return ProposalState.Executed; else if (_distributions[proposal.distributionId].endBlock >= block.number) return ProposalState.Active; else if (_standardFundingVoteSucceeded(proposalId_)) return ProposalState.Succeeded; else return ProposalState.Defeated; }
getExtraordinaryProposalState(proposalId)
function _getExtraordinaryProposalState(uint256 proposalId_) internal view returns (ProposalState) { ExtraordinaryFundingProposal memory proposal = _extraordinaryFundingProposals[proposalId_]; bool voteSucceeded = _extraordinaryProposalSucceeded(proposalId_, uint256(proposal.tokensRequested)); if (proposal.executed) return ProposalState.Executed; else if (proposal.endBlock >= block.number && !voteSucceeded) return ProposalState.Active; else if (voteSucceeded) return ProposalState.Succeeded; else return ProposalState.Defeated; }
It is recommended to check expired proposals on the standardProposalState(proposalId) and getExtraordinaryProposalState(proposalId) function.
Using the SafeCast library can help prevent unexpected errors in your Solidity code and make your contracts more secure
File: ajna-core/src/RewardsManager.sol 222: stakeInfo.stakingEpoch = uint96(curBurnEpoch); 225: stakeInfo.lastClaimedEpoch = uint96(curBurnEpoch); 236: bucketState.lpsAtStakeTime = uint128(positionManager.getLP( 241: bucketState.rateAtStakeTime = uint128(IPool(ajnaPool).bucketExchangeRate(bucketId)); 594: stakeInfo_.lastClaimedEpoch = uint96(epochToClaim_);
RewardsManager.sol#L222-L225 RewardsManager.sol#L236-L241 RewardsManager.sol#L594
Consider using OpenZeppelin’s SafeCast library to prevent unexpected overflows when casting from uint256.
File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol 23: uint256 internal constant MAX_EFM_PROPOSAL_LENGTH = 216_000; // number of blocks in one month
https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L23
216_000 number of blocks in one month equal 7200 blocks per day. At the time of writing this report there are only 7107 blocks per day . MAX_EFM_PROPOSAL_LENGTH
is used to check the proposal length is within limits of 1 month maximum in ExtraordinaryFunding.proposExtraordinary()
. This can lead to unintended reverts.
File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol 100: if (block.number + MAX_EFM_PROPOSAL_LENGTH < endBlock_) revert InvalidProposal();
File: ajna-grants/src/grants/base/StandardFunding.sol: 34: uint256 internal constant CHALLENGE_PERIOD_LENGTH = 50400; 40: uint48 internal constant DISTRIBUTION_PERIOD_LENGTH = 648000; 46: uint256 internal constant FUNDING_PERIOD_LENGTH = 72000;
This is a best-practice to protect against reentrancy in other modifiers.
File: ajna-core/src/PositionManager.sol 264: ) external override mayInteract(params_.pool, params_.tokenId) nonReentrant {
file: ajna-core/src/PositionManager.sol 69: ERC20PoolFactory private immutable erc20PoolFactory; 71: ERC721PoolFactory private immutable erc721PoolFactory;
file: ajna-core/src/RewardsManager.sol 87: address public immutable ajnaToken; 89: IPositionManager public immutable positionManager;
file: ajna-grants/src/grants/base/Funding.sol 21: address public immutable ajnaTokenAddress = 0x9a96ec9B57Fb64FbC60B423d1f4da7691Bd35079;
File: ajna-core/src/PositionManager.sol 52: mapping(uint256 => address) public override poolKey; 55: mapping(uint256 => mapping(uint256 => Position)) internal positions; 57: mapping(uint256 => uint96) internal nonces; 59: mapping(uint256 => EnumerableSet.UintSet) internal positionIndexes;
File: ajna-core/src/RewardsManager.sol 70: mapping(uint256 => mapping(uint256 => bool)) public override isEpochClaimed; 72: mapping(uint256 => uint256) public override rewardsClaimed; 74: mapping(uint256 => uint256) public override updateRewardsClaimed; 77: mapping(address => mapping(uint256 => mapping(uint256 => uint256))) internal bucketExchangeRates; 80: mapping(uint256 => StakeInfo) internal stakes;
File: ajna-grants/src/grants/base/ExtraordinaryFunding.sol 49: mapping(uint256 => mapping(address => bool)) public hasVotedExtraordinary;
File: ajna-grants/src/grants/base/StandardFunding.sol 69: mapping(uint24 => QuarterlyDistribution) internal _distributions; 75: mapping(uint256 => Proposal) internal _standardFundingProposals; 82: mapping(uint256 => uint256[]) internal _topTenProposals; 88: mapping(bytes32 => uint256[]) internal _fundedProposalSlates; 94: mapping(uint256 => mapping(address => QuadraticVoter)) internal _quadraticVoters; 100: mapping(uint256 => bool) internal _isSurplusFundsUpdated; 106: mapping(uint256 => mapping(address => bool)) public hasClaimedReward; 112: mapping(uint256 => mapping(address => uint256)) public screeningVotesCast;
The highest tier of smart contract behavior assurance is formal mathematical verification. All assertions that are made are guaranteed to be true across all inputs → The quality of your asserts is the quality of your verification
https://twitter.com/0xOwenThurm/status/1614359896350425088?t=dbG9gHFigBX85Rv29lOjIQ&s=19
Since this is a low level language that is more difficult to parse by readers, include extensive documentation, comments on the rationale behind its use, clearly explaining what each assembly instruction does.
This will make it easier for users to trust the code, for reviewers to validate the code, and for developers to build on or update the code.
Note that using Assembly removes several important security features of Solidity, which can make the code more insecure and more error-prone
File: ajna-grants/src/grants/base/Funding.sol 122: assembly { 132: assembly {
https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-grants/src/grants/base/Funding.sol#L122 https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-grants/src/grants/base/Funding.sol#L132
An important feature of Custom Error is that values such as address, tokenID, msg.value can be written inside the () sign, this kind of approach provides a serious advantage in debugging and examining the revert details of dapps such as tenderly
File: ajna-grants/src/grants/interfaces/IFunding.sol 18: error AlreadyVoted() 25: error InvalidProposal(); 30: error ProposalAlreadyExists(); 35: error ProposalNotFound(); 40: error ProposalNotSuccessful();
File: ajna-grants/src/grants/interfaces/IExtraordinaryFunding.sol 18: error ExtraordinaryFundingProposalInactive(); 23: error ExecuteExtraordinaryProposalInvalid();
IExtraordinaryFunding.sol#L18-L23
File: ajna-grants/src/grants/interfaces/IStandardFunding.sol 18: error DistributionPeriodStillActive(); 23: error ExecuteProposalInvalid(); 28: error FundingVoteWrongDirection(); 33: error InsufficientVotingPower(); 38: error ChallengePeriodNotEnded(); 43: error InvalidProposalSlate(); 48: error DelegateRewardInvalid(); 53: error InvalidVote(); 58: error ScreeningPeriodEnded(); 63: error RewardAlreadyClaimed();
Consider using named parameters in mappings (e.g. mapping(address account => uint256 balance)) to improve readability. This feature is present since Solidity 0.8.18
Cases already listed in [N-02]
#0 - c4-judge
2023-05-18T18:36:18Z
Picodes marked the issue as grade-a