Platform: Code4rena
Start Date: 21/04/2022
Pot Size: $75,000 USDC
Total HM: 7
Participants: 45
Period: 7 days
Judge: 0xean
Total Solo HM: 5
Id: 111
League: ETH
Rank: 37/45
Findings: 1
Award: $71.77
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xkatana
Also found by: 0v3rf10w, 0x1f8b, 0xNazgul, 0xmint, CertoraInc, Dravee, Fitraldys, Funen, IllIllI, NoamYakov, Scocco, Tomio, catchup, csanuragjain, defsec, delfin454000, djxploit, fatima_naz, gzeon, joestakey, joshie, kebabsec, nahnah, oyc_109, rayn, robee, rotcivegaf, saian, samruna, sorrynotsorry, teryanarmen, z3s
71.7655 USDC - $71.77
block.timestamp.safeCastTo32()
, which could be changed to uint32(block.timestamp)
with no practical risk, and saves the require()
checkuint256
should be used in many scenarios inside of the functions or as function parameters instead, and only cast it into a lower size uint, when storing it instead. reference. for example timestamp
is being casted way too early, and then being used, resulting in higher gas.forge snapshot --optimize --optimize-runs 1000000
diff --git a/.gas-snapshot b/.gas-snapshot index c5e3f62..c439cf6 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -53,13 +53,13 @@ ERC20MultiVotesTest:testCanContractExceedMax() (gas: 35031) ERC20MultiVotesTest:testCanContractExceedMaxNonContractFails() (gas: 13535) ERC20MultiVotesTest:testCanContractExceedMaxNonOwnerFails() (gas: 13760) ERC20MultiVotesTest:testDecrementOverWeightFails() (gas: 247398) -ERC20MultiVotesTest:testDecrementUntilFreeDouble() (gas: 350624) -ERC20MultiVotesTest:testDecrementUntilFreeSingle() (gas: 327524) +ERC20MultiVotesTest:testDecrementUntilFreeDouble() (gas: 350352) +ERC20MultiVotesTest:testDecrementUntilFreeSingle() (gas: 327354) ERC20MultiVotesTest:testDecrementUntilFreeWhenFree() (gas: 375129) ERC20MultiVotesTest:testDelegateOverMaxDelegatesApproved() (gas: 509277) ERC20MultiVotesTest:testDelegateOverMaxDelegatesFails() (gas: 413581) ERC20MultiVotesTest:testDelegateOverVotesFails() (gas: 247599) ERC20MultiVotesTest:testDelegateToAddressZeroFails() (gas: 87370) -ERC20MultiVotesTest:testPastVotes() (gas: 351022) +ERC20MultiVotesTest:testPastVotes() (gas: 350952) ERC20MultiVotesTest:testSetMaxDelegatesNonOwnerFails() (gas: 13647) -ERC20MultiVotesTest:testUndelegate() (gas: 211796) +ERC20MultiVotesTest:testUndelegate() (gas: 211684) diff --git a/src/token/ERC20MultiVotes.sol b/src/token/ERC20MultiVotes.sol index 08e2242..bb75374 100644 --- a/src/token/ERC20MultiVotes.sol +++ b/src/token/ERC20MultiVotes.sol @@ -260,13 +260,14 @@ abstract contract ERC20MultiVotes is ERC20, Auth { address delegatee, uint256 amount ) internal virtual { - uint256 newDelegates = _delegatesVotesCount[delegator][delegatee] - amount; + mapping(address => uint256) storage ptr = _delegatesVotesCount[delegator]; + uint256 newDelegates = ptr[delegatee] - amount; if (newDelegates == 0) { require(_delegates[delegator].remove(delegatee)); } - _delegatesVotesCount[delegator][delegatee] = newDelegates; + ptr[delegatee] = newDelegates; userDelegatedVotes[delegator] -= amount; emit Undelegation(delegator, delegatee, amount); @@ -339,19 +340,22 @@ abstract contract ERC20MultiVotes is ERC20, Auth { uint256 totalFreed; // Loop through all delegates - address[] memory delegateList = _delegates[user].values(); + EnumerableSet.AddressSet storage _delegatesUser = _delegates[user]; + address[] memory delegateList = _delegatesUser.values(); // Free delegates until through entire list or under votes amount uint256 size = delegateList.length; for (uint256 i = 0; i < size && (userFreeVotes + totalFreed) < votes; i++) { address delegatee = delegateList[i]; - uint256 delegateVotes = _delegatesVotesCount[user][delegatee]; + + mapping(address => uint256) storage _delegatesVotesCountUser = _delegatesVotesCount[user]; + uint256 delegateVotes = _delegatesVotesCountUser[delegatee]; if (delegateVotes != 0) { totalFreed += delegateVotes; - require(_delegates[user].remove(delegatee)); // Remove from set. Should never fail. + require(_delegatesUser.remove(delegatee)); // Remove from set. Should never fail. - _delegatesVotesCount[user][delegatee] = 0; + _delegatesVotesCountUser[delegatee] = 0; _writeCheckpoint(delegatee, _subtract, delegateVotes); emit Undelegation(user, delegatee, delegateVotes);
diff --git a/.gas-snapshot b/.gas-snapshot index c439cf6..ddddaf1 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -4,17 +4,17 @@ FlywheelTest:testAccrueTwoUsersSeparately() (gas: 330979) FlywheelTest:testFailAddStrategy() (gas: 15092) FlywheelTest:testSetFlywheelBoosterUnauthorized() (gas: 13594) FlywheelTest:testSetFlywheelRewardsUnauthorized() (gas: 13615) -FlywheelGaugeRewardsTest:testGetPriorRewards() (gas: 566359) -FlywheelGaugeRewardsTest:testGetRewards() (gas: 546077) +FlywheelGaugeRewardsTest:testGetPriorRewards() (gas: 566122) +FlywheelGaugeRewardsTest:testGetRewards() (gas: 545907) FlywheelGaugeRewardsTest:testGetRewardsUninitialized() (gas: 12512) -FlywheelGaugeRewardsTest:testIncompletePagination() (gas: 972359) -FlywheelGaugeRewardsTest:testPagination() (gas: 900730) -FlywheelGaugeRewardsTest:testQueue() (gas: 524891) -FlywheelGaugeRewardsTest:testQueueFullPagination() (gas: 527970) -FlywheelGaugeRewardsTest:testQueueSkipCycle() (gas: 345785) -FlywheelGaugeRewardsTest:testQueueSkipCycleFullPagination() (gas: 348443) -FlywheelGaugeRewardsTest:testQueueTwoCycles() (gas: 560780) -FlywheelGaugeRewardsTest:testQueueTwoCyclesFullPagination() (gas: 566759) +FlywheelGaugeRewardsTest:testIncompletePagination() (gas: 972019) +FlywheelGaugeRewardsTest:testPagination() (gas: 900390) +FlywheelGaugeRewardsTest:testQueue() (gas: 524721) +FlywheelGaugeRewardsTest:testQueueFullPagination() (gas: 527800) +FlywheelGaugeRewardsTest:testQueueSkipCycle() (gas: 345700) +FlywheelGaugeRewardsTest:testQueueSkipCycleFullPagination() (gas: 348358) +FlywheelGaugeRewardsTest:testQueueTwoCycles() (gas: 560543) +FlywheelGaugeRewardsTest:testQueueTwoCyclesFullPagination() (gas: 566522) FlywheelGaugeRewardsTest:testQueueWithoutGaugesBeforeCycle() (gas: 13468) FlywheelGaugeRewardsTest:testQueueWithoutGaugesNoGauges() (gas: 69137) FlywheelStaticRewardsTest:testGetAccruedRewards() (gas: 97429) @@ -23,27 +23,27 @@ FlywheelStaticRewardsTest:testGetAccruedRewardsCappedAfterEnd() (gas: 97873) FlywheelStaticRewardsTest:testSetRewardsInfo() (gas: 39879) ERC20GaugesTest:testAddGaugeNonOwner() (gas: 38452) ERC20GaugesTest:testAddGaugeTwice() (gas: 113090) -ERC20GaugesTest:testCalculateGaugeAllocation() (gas: 488805) +ERC20GaugesTest:testCalculateGaugeAllocation() (gas: 488640) ERC20GaugesTest:testCanContractExceedMax() (gas: 35078) ERC20GaugesTest:testCanContractExceedMaxNonContract() (gas: 13603) ERC20GaugesTest:testCanContractExceedMaxNonOwner() (gas: 13917) -ERC20GaugesTest:testDecrement() (gas: 310594) -ERC20GaugesTest:testDecrementAllRemovesGauge() (gas: 300684) -ERC20GaugesTest:testDecrementGauges() (gas: 432470) -ERC20GaugesTest:testDecrementGaugesOver() (gas: 415437) -ERC20GaugesTest:testDecrementGaugesSizeMismatch() (gas: 466673) -ERC20GaugesTest:testDecrementUntilFreeDeprecated() (gas: 447041) -ERC20GaugesTest:testDecrementUntilFreeDouble() (gas: 431097) -ERC20GaugesTest:testDecrementUntilFreeSingle() (gas: 449392) -ERC20GaugesTest:testDecrementUntilFreeWhenFree() (gas: 477079) -ERC20GaugesTest:testDecrementWithStorage() (gas: 324403) -ERC20GaugesTest:testIncrementAtMax() (gas: 380777) -ERC20GaugesTest:testIncrementGauges() (gas: 477454) +ERC20GaugesTest:testDecrement() (gas: 310419) +ERC20GaugesTest:testDecrementAllRemovesGauge() (gas: 300562) +ERC20GaugesTest:testDecrementGauges() (gas: 432171) +ERC20GaugesTest:testDecrementGaugesOver() (gas: 415206) +ERC20GaugesTest:testDecrementGaugesSizeMismatch() (gas: 466503) +ERC20GaugesTest:testDecrementUntilFreeDeprecated() (gas: 446756) +ERC20GaugesTest:testDecrementUntilFreeDouble() (gas: 430812) +ERC20GaugesTest:testDecrementUntilFreeSingle() (gas: 449178) +ERC20GaugesTest:testDecrementUntilFreeWhenFree() (gas: 476909) +ERC20GaugesTest:testDecrementWithStorage() (gas: 324228) +ERC20GaugesTest:testIncrementAtMax() (gas: 380697) +ERC20GaugesTest:testIncrementGauges() (gas: 477289) ERC20GaugesTest:testIncrementGaugesDeprecated() (gas: 282189) -ERC20GaugesTest:testIncrementGaugesOver() (gas: 421785) +ERC20GaugesTest:testIncrementGaugesOver() (gas: 421615) ERC20GaugesTest:testIncrementGaugesSizeMismatch() (gas: 281154) -ERC20GaugesTest:testIncrementOverMax() (gas: 420342) -ERC20GaugesTest:testIncrementWithStorage() (gas: 514952) +ERC20GaugesTest:testIncrementOverMax() (gas: 420170) +ERC20GaugesTest:testIncrementWithStorage() (gas: 514787) ERC20GaugesTest:testRemoveGauge() (gas: 179755) ERC20GaugesTest:testRemoveGaugeNonOwner() (gas: 112716) ERC20GaugesTest:testRemoveGaugeTwice() (gas: 180455) diff --git a/src/token/ERC20Gauges.sol b/src/token/ERC20Gauges.sol index ad8fabe..2b59338 100644 --- a/src/token/ERC20Gauges.sol +++ b/src/token/ERC20Gauges.sol @@ -259,9 +259,10 @@ abstract contract ERC20Gauges is ERC20, Auth { if (cycle - block.timestamp <= incrementFreezeWindow) revert IncrementFreezeError(); } - bool added = _userGauges[user].add(gauge); // idempotent add - if (added && _userGauges[user].length() > maxGauges && !canContractExceedMaxGauges[user]) - revert MaxGaugeError(); + EnumerableSet.AddressSet storage pUserGauges = _userGauges[user]; + + bool added = pUserGauges.add(gauge); // idempotent add + if (added && pUserGauges.length() > maxGauges && !canContractExceedMaxGauges[user]) revert MaxGaugeError(); getUserGaugeWeight[user][gauge] += weight; @@ -337,9 +338,10 @@ abstract contract ERC20Gauges is ERC20, Auth { uint112 weight, uint32 cycle ) internal { - uint112 oldWeight = getUserGaugeWeight[user][gauge]; + mapping(address => uint112) storage pUserGaugeWeight = getUserGaugeWeight[user]; + uint112 oldWeight = pUserGaugeWeight[gauge]; - getUserGaugeWeight[user][gauge] = oldWeight - weight; + pUserGaugeWeight[gauge] = oldWeight - weight; if (oldWeight == weight) { // If removing all weight, remove gauge from user list. require(_userGauges[user].remove(gauge)); @@ -561,9 +563,11 @@ abstract contract ERC20Gauges is ERC20, Auth { // Free gauges until through entire list or under weight uint256 size = gaugeList.length; + mapping(address => uint112) storage pUserGaugeWeight = getUserGaugeWeight[user]; + for (uint256 i = 0; i < size && (userFreeWeight + totalFreed) < weight; ) { address gauge = gaugeList[i]; - uint112 userGaugeWeight = getUserGaugeWeight[user][gauge]; + uint112 userGaugeWeight = pUserGaugeWeight[gauge]; if (userGaugeWeight != 0) { // If the gauge is live (not deprecated), include its weight in the total to remove if (!_deprecatedGauges.contains(gauge)) {
diff --git a/.gas-snapshot b/.gas-snapshot index ddddaf1..14733f7 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -4,17 +4,17 @@ FlywheelTest:testAccrueTwoUsersSeparately() (gas: 330979) FlywheelTest:testFailAddStrategy() (gas: 15092) FlywheelTest:testSetFlywheelBoosterUnauthorized() (gas: 13594) FlywheelTest:testSetFlywheelRewardsUnauthorized() (gas: 13615) -FlywheelGaugeRewardsTest:testGetPriorRewards() (gas: 566122) -FlywheelGaugeRewardsTest:testGetRewards() (gas: 545907) +FlywheelGaugeRewardsTest:testGetPriorRewards() (gas: 565862) +FlywheelGaugeRewardsTest:testGetRewards() (gas: 545777) FlywheelGaugeRewardsTest:testGetRewardsUninitialized() (gas: 12512) -FlywheelGaugeRewardsTest:testIncompletePagination() (gas: 972019) -FlywheelGaugeRewardsTest:testPagination() (gas: 900390) -FlywheelGaugeRewardsTest:testQueue() (gas: 524721) -FlywheelGaugeRewardsTest:testQueueFullPagination() (gas: 527800) -FlywheelGaugeRewardsTest:testQueueSkipCycle() (gas: 345700) -FlywheelGaugeRewardsTest:testQueueSkipCycleFullPagination() (gas: 348358) -FlywheelGaugeRewardsTest:testQueueTwoCycles() (gas: 560543) -FlywheelGaugeRewardsTest:testQueueTwoCyclesFullPagination() (gas: 566522) +FlywheelGaugeRewardsTest:testIncompletePagination() (gas: 971499) +FlywheelGaugeRewardsTest:testPagination() (gas: 900130) +FlywheelGaugeRewardsTest:testQueue() (gas: 524591) +FlywheelGaugeRewardsTest:testQueueFullPagination() (gas: 527670) +FlywheelGaugeRewardsTest:testQueueSkipCycle() (gas: 345635) +FlywheelGaugeRewardsTest:testQueueSkipCycleFullPagination() (gas: 348293) +FlywheelGaugeRewardsTest:testQueueTwoCycles() (gas: 560283) +FlywheelGaugeRewardsTest:testQueueTwoCyclesFullPagination() (gas: 566262) FlywheelGaugeRewardsTest:testQueueWithoutGaugesBeforeCycle() (gas: 13468) FlywheelGaugeRewardsTest:testQueueWithoutGaugesNoGauges() (gas: 69137) FlywheelStaticRewardsTest:testGetAccruedRewards() (gas: 97429) diff --git a/src/rewards/FlywheelGaugeRewards.sol b/src/rewards/FlywheelGaugeRewards.sol index 85a77c0..f099b07 100644 --- a/src/rewards/FlywheelGaugeRewards.sol +++ b/src/rewards/FlywheelGaugeRewards.sol @@ -186,7 +186,7 @@ contract FlywheelGaugeRewards is Auth, BaseFlywheelRewards { if (size == 0) revert EmptyGaugesError(); - for (uint256 i = 0; i < size; i++) { + for (uint256 i = 0; i < size; ) { ERC20 gauge = ERC20(gauges[i]); QueuedRewards memory queuedRewards = gaugeQueuedRewards[gauge]; @@ -206,6 +206,10 @@ contract FlywheelGaugeRewards is Auth, BaseFlywheelRewards { }); emit QueueRewards(address(gauge), currentCycle, nextRewards); + + unchecked { + i++; + } } }