Platform: Code4rena
Start Date: 31/08/2023
Pot Size: $55,000 USDC
Total HM: 5
Participants: 30
Period: 6 days
Judge: hickuphh3
Total Solo HM: 2
Id: 282
League: ETH
Rank: 17/30
Findings: 1
Award: $259.08
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: c3phas
Also found by: 0x11singh99, 0x3b, 0xta, JCK, K42, ReyAdmirado, SAQ, Sathish9098, hunter_w3b, kaveyjoe, lsaudit, turvy_fuzz
259.0763 USDC - $259.08
Location: Inside the checkpointTotalActiveStake function.
Description: The function may consume significant gas when there are many total active stake checkpoints, especially if the round provided is not recent.
Code snippet:
uint256[] storage initializedRounds = totalStakeCheckpoints.rounds; uint256 upper = initializedRounds.findUpperBound(_round); if (upper == 0) { return 0; } else if (upper < initializedRounds.length) { uint256 nextInitedRound = initializedRounds[upper]; return totalStakeCheckpoints.data[nextInitedRound]; } else { return bondingManager().nextRoundTotalActiveStake(); }
Recommended Change:
uint256 currentRound = clock(); if (_round == currentRound) { return totalStakeCheckpoints.data[_round]; } else if (_round < currentRound && totalStakeCheckpoints.data[_round] > 0) { return totalStakeCheckpoints.data[_round]; } else if (_round > currentRound) { return bondingManager().nextRoundTotalActiveStake(); }
Location: Inside the getBondingStateAt function.
Description: The function checks _round against the current round twice, which is redundant.
Code snippet:
if (_round > clock() + 1) { revert FutureLookup(_round, clock() + 1); }
Recommended Change:
uint256 currentRound = clock(); if (_round > currentRound + 1) { revert FutureLookup(_round, currentRound + 1); }
Location: Inside the delegatorCumulativeStakeAt function.
Description: The function reads from storage multiple times, which can be optimized to reduce gas consumption.
Code snippet:
BondingCheckpoint storage bond = getBondingCheckpointAt(_account, _round);
Recommended Change:
BondingCheckpoint storage bond = getBodingCheckpointAt(_account, _round); if (bond.bondedAmount == 0) { amount = 0; } else if (isTranscoder) { amount = bond.delegatedAmount; } else { amount = delegatorCumulativeStakeAt(bond, _round); }
Location: Inside the getBondingStateAt function.
Description: The function checks if _account is a transcoder twice, which is redundant.
Code snippet:
bool isTranscoder = delegateAddress == _account; if (isTranscoder) { // ... }
Recommended Change:
bool isTranscoder = delegateAddress == _account; if (isTranscoder) { // ... } else { // ... }
Location: Inside the onBondingCheckpointChanged function.
Description: The function emits events unconditionally, which can lead to redundant event emissions.
Code snippet:
emit DelegateChanged(_account, previousDelegate, newDelegate); emit DelegateVotesChanged(_account, previousDelegateVotes, currentDelegateVotes); emit DelegatorBondedAmountChanged(_account, previous.bondedAmount, currentBondedAmount);
Recommended Change:
if (previousDelegate != newDelegate) { emit DelegateChanged(_account, previousDelegate, newDelegate); }
if (previousDelegateVotes != currentDelegateVotes) { emit DelegateVotesChanged(_account, previousDelegateVotes, currentDelegateVotes); }
if (previous.bondedAmount != currentBondedAmount) { emit DelegatorBondedAmountChanged(_account, previous.bondedAmount, currentBondedAmount); }
Location: Inside updateCumulativeRewardFactor and delegatorCumulativeStakeAndFees functions.
Description: calls PreciseMathUtils.percPoints(1, 1) multiple times. we can save gas by storing this value in a constant variable and reusing it.
Code snippet:
if (_prevEarningsPool.cumulativeRewardFactor != 0) { prevCumulativeRewardFactor = _prevEarningsPool.cumulativeRewardFactor; } else { prevCumulativeRewardFactor = PreciseMathUtils.percPoints(1, 1); }
Recommended Change:
uint256 defaultCumulativeRewardFactor = PreciseMathUtils.percPoints(1, 1); if (_prevEarningsPool.cumulativeRewardFactor != 0) { prevCumulativeRewardFactor = _prevEarningsPool.cumulativeRewardFactor; } else { prevCumulativeRewardFactor = defaultCumulativeRewardFactor; }
Location: Inside the updateCumulativeFeeFactor function.
Description: The prevCumulativeRewardFactor is computed but not used. we can remove this variable to save gas.
Code snippet:
uint256 prevCumulativeFeeFactor = _prevEarningsPool.cumulativeFeeFactor; uint256 prevCumulativeRewardFactor = _prevEarningsPool.cumulativeRewardFactor != 0 ? _prevEarningsPool.cumulativeRewardFactor : PreciseMathUtils.percPoints(1, 1);
Recommended Change:
uint256 prevCumulativeFeeFactor = _prevEarningsPool.cumulativeFeeFactor;
Location: Inside the findLowerBound function.
Description: The current implementation performs a binary search to find the upper bound and then adjusts the result to find the lower bound. we can optimize this by directly performing a binary search to find the lower bound, eliminating the need for adjustments.
Code snippet:
uint256 upperIdx = _array.findUpperBound(_val); // ... return upperIdx - 1;
Recommended Change:
uint256 lowerIdx = _array.findLowerBound(_val); return lowerIdx;
Location: Inside the findLowerBound function.
Description: The current implementation checks if the array is empty at the beginning and again after finding the upper bound. we can reduce gas usage by removing the second check since it's redundant.
Code snippet:
uint256 len = _array.length; if (len == 0) { return 0; } // ... if (upperIdx == 0) { return len; }
Recommended Change:
uint256 len = _array.length; if (len == 0) { return 0; } // ...
Location: Inside the _quorumReached and _voteSucceeded functions.
Description: These functions read from storage to get the proposal's vote counts multiple times. we can optimize by reading the values once and storing them in local variables to reduce storage reads.
Code snippet:
(uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) = proposalVotes(_proposalId);
Recommended Change:
uint256 _proposalIdVotes = proposalVotes(_proposalId); uint256 againstVotes = _proposalIdVotes[0]; uint256 forVotes = _proposalIdVotes[1]; uint256 abstainVotes = _proposalIdVotes[2];
Location: Inside the _handleVoteOverrides function.
Description: Instead of repeatedly calling votes().delegatedAt(_account, timepoint), we can store the result in a local variable to avoid multiple function calls.
Code snippet:
address delegate = votes().delegatedAt(_account, timepoint);
Recommended Change:
address delegate = votes().delegatedAt(_account, timepoint);
#0 - c4-judge
2023-09-21T10:47:09Z
HickupHH3 marked the issue as grade-a
#1 - victorges
2023-09-22T21:38:51Z
@HickupHH3 Not sure if it matters, but this showed up in the C4 plugin for me and I started reviewing it, and most of the reports are invalid:
findLowerBound
doesn't exist, which is exactly why we created it.if
is not checking the array length.Some of the other issues do seem valid tho.