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: 101/114
Findings: 1
Award: $22.28
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: JCN
Also found by: 0x73696d616f, 0xSmartContract, 0xnev, Audit_Avengers, Aymen0909, Blckhv, Eurovickk, K42, Kenshin, Rageur, Raihan, ReyAdmirado, SAAJ, SAQ, Shubham, Tomio, Walter, ayden, codeslide, descharre, dicethedev, hunter_w3b, j4ld1na, kaveyjoe, okolicodes, patitonar, petrichor, pontifex, yongskiws
22.2767 USDC - $22.28
Scope = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/GrantFund.sol
Possible Optimization 1 = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/GrantFund.sol#L58
-- You could create a state variable for the IERC20 instance in fundTreasury
= IERC20 token = IERC20(ajnaTokenAddress);
to avoid initializing it every time fundTreasury
is called, like so IERC20 public immutable ajnaToken; constructor(address ajnaTokenAddress) { ajnaToken = IERC20(ajnaTokenAddress); }
Then you can modify the function like so treasury += fundingAmount_; emit FundTreasury(fundingAmount_, treasury); ajnaToken.safeTransferFrom(msg.sender, address(this), fundingAmount_);
This will some save gas.
Scope = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/PositionManager.sol
Possible Optimization 1 = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/PositionManager.sol#LL45C1-L45C35
-- As you are already importing IERC20, change using SafeERC20 for ERC20;
to using SafeERC20 for IERC20;
, then change all instances of ERC20 with IERC20. This will import the minimal interface required for the contract, and therefore reduce contract size and lower gas costs.
Possible Optimization 2 = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/PositionManager.sol#LL146C3-L146C3
-- You could use temporary variable for params_.tokenId
in burn
and you can consider implementing this change in other functions as it can avoid multiple SLOAD operations. This will save gas, as SLOAD is relatively expensive, do this like so uint256 tokenId = params_.tokenId; if (positionIndexes[tokenId].length() != 0) revert LiquidityNotRemoved(); delete nonces[tokenId]; delete poolKey[tokenId]; _burn(tokenId); emit Burn(msg.sender, tokenId);
Scope = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol
Possible Optimization 1 = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L426
-- You can reduce state variable reads by using local variables more, in multiple places, state variables like stakes
,positionManager
,ajnaPool
, and others are read multiple times within the same function. Reading state variables consumes more gas than reading local variables. Therefore you can reduce gas usage by storing the state variable values, in a local variable and then use the local variable throughout the function use cases.
For example = in the _calculateNextEpochRewards()
, you can create local variables for stakes[tokenId_]
and positionManager
,
like this StakeInfo storage stakeInfo = stakes[tokenId_]; IPositionManager positionMgr = positionManager;
Then you can replace all instances of stakes[tokenId_]
with stakeInfo
and positionManager
with positionMgr. This will further optimize the contract and save gas.
Scope = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/Funding.sol Possible Optimization 1 = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/Funding.sol#L123
-- In _validateCallDatas
you can use calldatas_[i].length
instead of mload(add(selDataWithSig, 0x20))
Instead of using assembly to get the length of calldatas_[i]
, you can use calldatas_[i].length
, this will save gas.
Possible Optimization 2 = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/Funding.sol#L112
-- You can better optimize the for loop in _validateCallDatas
,
by incrementing i
directly in the for loop, like so for (uint256 i = 0; i < targets_.length; i++)
This will save you some gas.
Scope = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/ExtraordinaryFunding.sol
Possible Optimization 1 = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/ExtraordinaryFunding.sol#L246
-- In _getVotesExtraordinary()
, you could remove if (proposalId_ == 0) revert ExtraordinaryFundingProposalInactive();
, because the proposalId_
is never zero when the function is called from an external function, therefore the condition will never be true when the function is called from an external function, so you can remove the unnecessary check and save gas, like so function _getVotesExtraordinary(address account_, uint256 proposalId_) internal view returns (uint256 votes_) { uint256 startBlock = _extraordinaryFundingProposals[proposalId_].startBlock; votes_ = _getVotesAtSnapshotBlocks( account_, startBlock - VOTING_POWER_SNAPSHOT_DELAY, startBlock ); }
Scope = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/StandardFunding.sol Possible Optimization 1 = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/StandardFunding.sol#LL274C3-L293C6
-- In _getDelegateReward()
Instead of dividing the result of Maths.wdiv() by 10, you can multiply the votingPowerAllocatedByDelegatee directly by 0.1 (as a WAD) to calculate the 10% of GBC distributed as per delegatee voting power allocated. This will save gas by reducing the number of arithmetic operations.
Change like so
function _getDelegateReward( QuarterlyDistribution memory currentDistribution_, QuadraticVoter memory voter_ ) internal pure returns (uint256 rewards_) { // calculate the total voting power available to the voter that was allocated in the funding stage uint256 votingPowerAllocatedByDelegatee = voter_.votingPower - voter_.remainingVotingPower; // if none of the voter's voting power was allocated, they receive no rewards if (votingPowerAllocatedByDelegatee == 0) return 0; // calculate reward // delegateeReward = 10 % of GBC distributed as per delegatee Voting power allocated rewards_ = Maths.wdiv( Maths.wmul( currentDistribution_.fundsAvailable, Maths.wmul(votingPowerAllocatedByDelegatee, 0.1 * 1e18) // Multiply by 0.1 as a WAD ), currentDistribution_.fundingVotePowerCast); }
Possible Optimization 2 = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/StandardFunding.sol#LL300C5-L341C1
-- In updateSlate()
Instead of doing currentSlateHash!= 0 && sum > _sumProposalFundingVotes(_fundedProposalSlates[currentSlateHash])
to check if the new slate is better, you can store the sum of the funded votes for the current slate directly, into the QuarterlyDistribution struct. This will save gas by avoiding the function call to _sumProposalFundingVotes(), and also you can use a local variable for existingSlate instead of accessing storage directly. This will save gas by reducing the number of SSTORE operations.
Change like so
function updateSlate(uint256[] calldata proposalIds_, uint24 distributionId_) external override returns (bool newTopSlate_) { QuarterlyDistribution storage currentDistribution = _distributions[distributionId_]; // store number of proposals for reduced gas cost of iterations uint256 numProposalsInSlate = proposalIds_.length; // check each proposal in the slate is valid, and get the sum of the proposals fundingVotesReceived uint256 sum = _validateSlate(distributionId_, currentDistribution.endBlock, currentDistribution.fundsAvailable, proposalIds_, numProposalsInSlate); // get pointers for comparing proposal slates bytes32 currentSlateHash = currentDistribution.fundedSlateHash; bytes32 newSlateHash = keccak256(abi.encode(proposalIds_)); // check if the slate of proposals is better than the existing slate, and is thus the new top slate newTopSlate_ = currentSlateHash == 0 || (currentSlateHash != 0 && sum > currentDistribution.fundedSlateVoteSum); // if the slate of proposals is a new top slate, update state if (newTopSlate_) { uint256[] storage existingSlate = _fundedProposalSlates[newSlateHash]; uint256[] memory localSlate = new uint256[](numProposalsInSlate); for (uint i = 0; i < numProposalsInSlate; ) { // update list of proposals to fund localSlate[i] = proposalIds_[i]; unchecked { ++i; } } existingSlate = localSlate; // update hash to point to the new leading slate of proposals and update the fundedSlateVoteSum currentDistribution.fundedSlateHash = newSlateHash; currentDistribution.fundedSlateVoteSum = sum; emit FundedSlateUpdated(distributionId_, newSlateHash); }}
Possible Optimization 3 = https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/StandardFunding.sol#LL843C2-L853C6
-- In _sumSquareOfVotesCast()
, you can remove the unchecked block since the function is already safe due to the limited length of votesCast_
array. The compiler will automatically handle the bounds check for you anyway, so removing unchecked { ++i; }
can save you some gas. Other than that, looking optimized.
#0 - c4-judge
2023-05-17T12:27:03Z
Picodes marked the issue as grade-b