Platform: Code4rena
Start Date: 07/07/2023
Pot Size: $121,650 USDC
Total HM: 36
Participants: 111
Period: 7 days
Judge: Picodes
Total Solo HM: 13
Id: 258
League: ETH
Rank: 78/111
Findings: 1
Award: $24.30
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: c3phas
Also found by: 0x11singh99, 0xAnah, 0xn006e7, LeoS, Rageur, Raihan, ReyAdmirado, Rolezn, SAAJ, SAQ, SM3_SS, Udsen, alymurtazamemon, hunter_w3b, koxuan, naman1778, petrichor, ybansal2403
24.2984 USDC - $24.30
S.N. | Issues | Instances | Gas Savings |
---|---|---|---|
G-01 | Avoid caching the result of a function call when that result is being used only once | 35 | 388 + additional gas |
G-02 | Use do while loops instead of for loops | 7 | 448 |
G-03 | Avoid explicit initialization of variable in for loop | 5 | 40 |
G-04 | Don't calculate constants | 117 | - |
G-05 | Fail as early as possible | 3 | - |
G-06 | Gas savings can be achieved by changing the model for assigning value to the storage structure | 3 | 244 |
G-07 | Save a storage variable reference instead of repeatedly fetching the value in a mapping or an array | 8 | 320 |
G-08 | Use a positive conditional flow to save a NOT opcode | 2 | 6 |
G-09 | Use assembly to write address storage varibales | 7 | - |
G-10 | Emitting events can be rearranged to save gas | 3 | 48 |
G-11 | Avoid emitting storage variables when possible | 1 | 110 |
G-12 | Cache calldata/memory pointers for complex types to avoid complex offset calculations | 49 | 340 |
Caching the result of a function call can save gas only when that result is being accessed mutiple times inside a function .There is no point in caching such result when it is being used only once inside a function as it would increase gas costs due to involved memory operations.
35 instances in 7 files
Note : All the gas savings calculated in this section have been calculated using protocol's provided tests
Here, div
, ln
and maxDiv
cached in lines 107
, 108
and 109
have been used only once in lines 108
, 109
and 110
respectively
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getMaximumPriceDeltaScale | 1836 | 1836 | 1836 | 1836 | 1 |
After | getMaximumPriceDeltaScale | 1803 | 1803 | 1803 | 1803 | 1 |
File: claimer/src/libraries/LinearVRGDALib.sol 107: int256 div = wadDiv(int256(_maxFee), int256(_minFee)); 108: int256 ln = wadLn(div); 109: int256 maxDiv = wadDiv(ln, int256(_time)); 110: return ud2x18(uint64(uint256(wadExp(maxDiv / 1e18))));
recommended code :
107: return ud2x18 108: (uint64(uint256(wadExp(wadDiv(wadLn(wadDiv(int256(_maxFee), int256(_minFee))), int256(_time)) / 1e18))) 109: );
Here, reclaimed
, computedLiquidity
and remainder
cached in lines 419
, 424
and 425
have been used only once in lines 429
, 425
and 430
respectively
No test has been provided for the function used in this instance from protocol's side(using which gas saving) could be demonstrated but as it can be analayzed from other similar instances,gas savings can surely be achieved in this instance too after applying the recommended mitigation
File: prize-pool/src/abstract/TieredLiquidityDistributor.sol 419: uint reclaimed = _getTierLiquidityToReclaim( 420: _numberOfTiers, 421: _nextNumberOfTiers, 422: _currentPrizeTokenPerShare 423: ); 424: uint computedLiquidity = fromUD60x18(deltaPrizeTokensPerShare.mul(toUD60x18(totalShares))); 425: uint remainder = (_prizeTokenLiquidity - computedLiquidity); 426: 427: newReserve = uint104( 428: fromUD60x18(deltaPrizeTokensPerShare.mul(toUD60x18(reserveShares))) + // reserve portion 429: reclaimed + // reclaimed liquidity from tiers 430: remainder // remainder 431: );
recommended code :
419: newReserve = uint104( 420: fromUD60x18(deltaPrizeTokensPerShare.mul(toUD60x18(reserveShares))) + // reserve portion 421: _getTierLiquidityToReclaim(_numberOfTiers,_nextNumberOfTiers,_currentPrizeTokenPerShare) + // reclaimed liquidity from tiers 422: (_prizeTokenLiquidity - fromUD60x18(deltaPrizeTokensPerShare.mul(toUD60x18(totalShares)))) // remainder 423: );
consumeLiquidity
function as it is an internal function used by consumeLiquidity
Here, delta
cached in line 542
has been used only once in line 544
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | consumeLiquidity | 7237 | 24822 | 19588 | 52877 | 4 |
After | consumeLiquidity | 7237 | 24816 | 19582 | 52864 | 4 |
File: prize-pool/src/abstract/TieredLiquidityDistributor.sol 542: UD34x4 delta = fromUD60x18toUD34x4(toUD60x18(_liquidity).div(toUD60x18(_shares))); 543: _tierStruct.prizeTokenPerShare = UD34x4.wrap( 544: UD34x4.unwrap(_tierStruct.prizeTokenPerShare) + UD34x4.unwrap(delta) 545: );
recommended code :
542: _tierStruct.prizeTokenPerShare = UD34x4.wrap( 543: UD34x4.unwrap(_tierStruct.prizeTokenPerShare) 544: + UD34x4.unwrap(fromUD60x18toUD34x4(toUD60x18(_liquidity).div(toUD60x18(_shares)))) 545: );
getTierLiquidityToReclaim
function as it is an internal function used by getTierLiquidityToReclaim
Here, shares
and liq
cached in lines 632
and 633
have been used only once in lines 634
and 638
respectively
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getTierLiquidityToReclaim | 2862 | 4575 | 4579 | 6286 | 3 |
After | getTierLiquidityToReclaim | 2851 | 4553 | 4557 | 6253 | 3 |
File: prize-pool/src/abstract/TieredLiquidityDistributor.sol 632: uint8 shares = _computeShares(i, _numberOfTiers); 633: UD60x18 liq = _getTierRemainingLiquidity( 634: shares, 635: fromUD34x4toUD60x18(tierLiquidity.prizeTokenPerShare), 636: _prizeTokenPerShare 637: ); 638: reclaimedLiquidity = reclaimedLiquidity.add(liq);
recommended code :
632: reclaimedLiquidity = reclaimedLiquidity.add 633: (_getTierRemainingLiquidity( 634: _computeShares(i, _numberOfTiers), 635: fromUD34x4toUD60x18(tierLiquidity.prizeTokenPerShare), 636: _prizeTokenPerShare 637: ) 638: );
getTierRemainingLiquidity
function as it is an internal function used by getTierRemainingLiquidity
Here, delta
cached in line 671
has been used only once in line 672
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getTierRemainingLiquidity | 2652 | 3494 | 2667 | 6991 | 41 |
After | getTierRemainingLiquidity | 2639 | 3482 | 2654 | 6982 | 41 |
File: prize-pool/src/abstract/TieredLiquidityDistributor.sol 671: UD60x18 delta = _prizeTokenPerShare.sub(_tierPrizeTokenPerShare); 672: return delta.mul(toUD60x18(_shares));
recommended code :
671: return _prizeTokenPerShare.sub(_tierPrizeTokenPerShare).mul(toUD60x18(_shares));
Here, newestIndex
cached in line 75
has been used only once in line 76
.
Also, remainder
cached in line 88
has been used only once in line 93
And also nextIndex
cached in line 95
has been used only once in line 101
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | add | 530 | 88086 | 90649 | 110657 | 23 |
After | add | 530 | 88012 | 90568 | 110576 | 23 |
File: prize-pool/src/libraries/DrawAccumulatorLib.sol 75: uint256 newestIndex = RingBufferLib.newestIndex(ringBufferInfo.nextIndex, MAX_CARDINALITY); 76: uint16 newestDrawId_ = accumulator.drawRingBuffer[newestIndex]; ... 88: uint256 remainder = newestObservation_.available - (remainingAmount + disbursedAmount); 89: 90: accumulator.drawRingBuffer[ringBufferInfo.nextIndex] = _drawId; 91: accumulator.observations[_drawId] = Observation({ 92: available: uint96(_amount + remainingAmount), 93: disbursed: uint168(newestObservation_.disbursed + disbursedAmount + remainder) 94: }); ... 095: uint16 nextIndex = uint16(RingBufferLib.nextIndex(ringBufferInfo.nextIndex, MAX_CARDINALITY)); 096: uint16 cardinality = ringBufferInfo.cardinality; 097: if (ringBufferInfo.cardinality < MAX_CARDINALITY) { 098: cardinality += 1; 099: } 100: accumulator.ringBufferInfo = RingBufferInfo({ 101: nextIndex: nextIndex, 102: cardinality: cardinality 103: });
recommended code :
75: uint16 newestDrawId_ = accumulator.drawRingBuffer[RingBufferLib.newestIndex(ringBufferInfo.nextIndex, MAX_CARDINALITY)]; ... 89: accumulator.drawRingBuffer[ringBufferInfo.nextIndex] = _drawId; 90: accumulator.observations[_drawId] = Observation({ 91: available: uint96(_amount + remainingAmount), 92: disbursed: uint168(newestObservation_.disbursed + disbursedAmount + newestObservation_.available - (remainingAmount + disbursedAmount)) 93: }); ... 095: uint16 cardinality = ringBufferInfo.cardinality; 096: if (ringBufferInfo.cardinality < MAX_CARDINALITY) { 097: cardinality += 1; 098: } 099: accumulator.ringBufferInfo = RingBufferInfo({ 100: nextIndex: uint16(RingBufferLib.nextIndex(ringBufferInfo.nextIndex, MAX_CARDINALITY)), 101: cardinality: cardinality 102: });
getTotalRemaining
function as it is an internal function used by getTotalRemaining
Here, newestIndex
cached in line 129
has been used only once in line 130
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getTotalRemaining | 1337 | 2541 | 2765 | 3521 | 3 |
After | getTotalRemaining | 1329 | 2531 | 2765 | 3500 | 3 |
File: prize-pool/src/libraries/DrawAccumulatorLib.sol 129: uint256 newestIndex = RingBufferLib.newestIndex(ringBufferInfo.nextIndex, MAX_CARDINALITY); 130: uint16 newestDrawId_ = accumulator.drawRingBuffer[newestIndex];
recommended code :
129: uint16 newestDrawId_ = RingBufferLib.newestIndex(ringBufferInfo.nextIndex, MAX_CARDINALITY);
Here,headEndDrawId
and amount
cached in lines 278
and 280
have been used only once in lines 280
and 281
respectively
Also, amount
cached in line line 294
has been used only once in line 295
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getDisbursedBetween | 619 | 15114 | 15337 | 39999 | 14 |
After | getDisbursedBetween | 619 | 15112 | 15334 | 39982 | 14 |
File: prize-pool/src/libraries/DrawAccumulatorLib.sol 278: uint16 headEndDrawId = headStartDrawId + 279: (firstObservationDrawIdOccurringAtOrAfterStart - _startDrawId); 280: uint amount = integrate(_alpha, headStartDrawId, headEndDrawId, beforeOrAtStart.available); 281: total += amount; ... 294: uint amount = atOrBeforeEnd.disbursed - atOrAfterStart.disbursed; 295: total += amount;
recommended code :
278: total += integrate(_alpha, headStartDrawId, headStartDrawId + 279: (firstObservationDrawIdOccurringAtOrAfterStart - _startDrawId), beforeOrAtStart.available; ... 295: total += atOrBeforeEnd.disbursed - atOrAfterStart.disbursed;
getDisbursedBetween
function as it is an internal function used by getDisbursedBetween
Here, tailRangeStartDrawId
and amount
cached in lines 325
and 330
have been used only once in lines 332
and 336
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getDisbursedBetween | 619 | 15114 | 15337 | 39999 | 14 |
After | getDisbursedBetween | 619 | 15101 | 15319 | 39981 | 14 |
File: prize-pool/src/libraries/DrawAccumulatorLib.sol 325: uint16 tailRangeStartDrawId = ( 326: _startDrawId > _lastObservationDrawIdOccurringAtOrBeforeEnd 327: ? _startDrawId 328: : _lastObservationDrawIdOccurringAtOrBeforeEnd 329: ) - _lastObservationDrawIdOccurringAtOrBeforeEnd; 330: uint256 amount = integrate( 331: _alpha, 332: tailRangeStartDrawId, 333: _endDrawId - _lastObservationDrawIdOccurringAtOrBeforeEnd + 1, 334: lastObservation.available 335: ); 336: return amount;
recommended code :
326: return integrate( 327: _alpha, 328: ( 329: ( 330: _startDrawId > _lastObservationDrawIdOccurringAtOrBeforeEnd 331: ? _startDrawId 332: : _lastObservationDrawIdOccurringAtOrBeforeEnd 333: ) - _lastObservationDrawIdOccurringAtOrBeforeEnd 334: ), 335: _endDrawId - _lastObservationDrawIdOccurringAtOrBeforeEnd + 1, 336: lastObservation.available 337: );
getDisbursedBetween
function as it is an internal function used by getDisbursedBetween
Here, start
and end
cached in lines 396
and 397
respectively have been used only once in line 398
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getDisbursedBetween | 619 | 15114 | 15337 | 39999 | 14 |
After | getDisbursedBetween | 619 | 15098 | 15317 | 39959 | 14 |
File: prize-pool/src/libraries/DrawAccumulatorLib.sol 396: int start = unwrap(computeC(_alpha, _start, _k)); 397: int end = unwrap(computeC(_alpha, _end, _k)); 398: return uint256(fromSD59x18(sd(start - end)));
recommended code :
396: return uint256(fromSD59x18(sd(unwrap(computeC(_alpha, _start, _k)) - unwrap(computeC(_alpha, _end, _k)))));
estimatedClaimCount
function as it is an internal function used by estimatedClaimCount
Here, _k
cached in line 22
has been used only once in line 26
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | estimatedClaimCount | 38530 | 180531 | 180238 | 322688 | 15 |
After | estimatedClaimCount | 38504 | 180414 | 180121 | 322480 | 15 |
File: prize-pool/src/libraries/TierCalculationLib.sol 22: SD59x18 _k = sd(1).div(sd(int16(_grandPrizePeriod))).ln().div( 23: sd((-1 * int8(_numberOfTiers) + 1)) 24: ); 25: 26: return E.pow(_k.mul(sd(int8(_tier) - (int8(_numberOfTiers) - 1))));
recommended code :
22: return E.pow((sd(1).div(sd(int16(_grandPrizePeriod))).ln().div( 23: sd((-1 * int8(_numberOfTiers) + 1)) 24: )).mul(sd(int8(_tier) - (int8(_numberOfTiers) - 1))));
canaryPrizeCount
function as it is an internal function used by canaryPrizeCount
Here, _numberOfPrizes
cached in line 40
has been used only once in line 42
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | canaryPrizeCount | 2961 | 2961 | 2961 | 2961 | 14 |
After | canaryPrizeCount | 2956 | 2956 | 2956 | 2956 | 14 |
File: prize-pool/src/libraries/TierCalculationLib.sol 40: uint256 _numberOfPrizes = 4 ** _tier; 41: 42: return _numberOfPrizes;
recommended code :
40: return 4 ** _tier;
Here, numerator
and denominator
cached in lines 58
and 60
have been used only once in lines 62
respectively and also multiplier
cached in line 62
has been used only once in line 63
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | canaryPrizeCount | 2961 | 2961 | 2961 | 2961 | 14 |
After | canaryPrizeCount | 2922 | 2922 | 2922 | 2922 | 14 |
File: prize-pool/src/libraries/TierCalculationLib.sol 58: uint256 numerator = uint256(_canaryShares) * 59: ((_numberOfTiers + 1) * uint256(_tierShares) + _canaryShares + _reserveShares); 60: uint256 denominator = uint256(_tierShares) * 61: ((_numberOfTiers) * uint256(_tierShares) + _canaryShares + _reserveShares); 62: UD60x18 multiplier = toUD60x18(numerator).div(toUD60x18(denominator)); 63: return multiplier.mul(toUD60x18(prizeCount(_numberOfTiers)));
recommended code :
58: return toUD60x18( uint256(_canaryShares) * 59: ((_numberOfTiers + 1) * uint256(_tierShares) + _canaryShares + _reserveShares)).div(toUD60x18(uint256(_tierShares) * 60: ((_numberOfTiers) * uint256(_tierShares) + _canaryShares + _reserveShares))).mul(toUD60x18(prizeCount(_numberOfTiers)));
Here, constrainedRandomNumber
and winningZone
cached in lines 92
and 93
has been used only once in line 95
--gas-report
option in foundry didn't produce the gas used by the specific function involved in this isntance.That's why related test functions of the related test contracts have been used to demonstrate the gas savings as these test functions use the functions involved in these instances
test contract's test function | |
---|---|
Before | testIsWinner_HalfLiquidity() (gas: 2276605) |
After | testIsWinner_HalfLiquidity() (gas: 2257149) |
test contract's test function | |
---|---|
Before | testIsWinner_WinsAll() (gas: 2315780) |
After | testIsWinner_WinsAll() (gas: 2296324) |
File: prize-pool/src/libraries/TierCalculationLib.sol 92: uint256 constrainedRandomNumber = _userSpecificRandomNumber % (_vaultTwabTotalSupply); 93: uint256 winningZone = calculateWinningZone(_userTwab, _vaultContributionFraction, _tierOdds); 94: 95: return constrainedRandomNumber < winningZone;
recommended code :
File: prize-pool/src/libraries/TierCalculationLib.sol 94: return (_userSpecificRandomNumber % (_vaultTwabTotalSupply)) < (calculateWinningZone(_userTwab, _vaultContributionFraction, _tierOdds));
Here, xUint
cached in line 22
has been used only once in line 23
--gas-report
option in foundry didn't produce the gas used by the specific function involved in this isntance.That's why related test functions of the related test contracts have been used to demonstrate the gas savings as these test functions use the functions involved in these instances
test contract's test function | |
---|---|
Before | testIntoUD60x18() (gas: 393) |
After | testIntoUD60x18() (gas: 388) |
test contract's test function | |
---|---|
Before | testIntoUD60x18_large() (gas: 329) |
After | testIntoUD60x18_large() (gas: 324) |
File: prize-pool/src/libraries/UD34x4.sol 22: uint256 xUint = uint256(UD34x4.unwrap(x)) * uint256(1e14); 23: result = UD60x18.wrap(xUint);
recommended code :
22: result = UD60x18.wrap(uint256(UD34x4.unwrap(x)) * uint256(1e14));
Here, tierOdds
and durationInSeconds
cached in line 686
and 687
has been used only once in line 687
and 691
respectively
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | calculateTierTwabTimestamps | 2231 | 2270 | 2260 | 2957 | 33 |
After | calculateTierTwabTimestamps | 2208 | 2247 | 2237 | 2934 | 33 |
File: prize-pool/src/PrizePool.sol 686: SD59x18 tierOdds = _tierOdds(_tier, _numberOfTiers); 687: uint256 durationInSeconds = TierCalculationLib.estimatePrizeFrequencyInDraws(tierOdds) * drawPeriodSeconds; 688: 689: startTimestamp = uint64( 690: endTimestamp - 691: durationInSeconds 692: );
recommended code :
File: prize-pool/src/PrizePool.sol 687: startTimestamp = uint64( 688: endTimestamp - 689: TierCalculationLib.estimatePrizeFrequencyInDraws(_tierOdds(_tier, _numberOfTiers)) * drawPeriodSeconds 690: );
decreaseBalances
function as it is an internal function used by decreaseBalances
Here, newestObservationPeriod
cached in line 372
has been accessed only once in line 381
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | decreaseBalances | 1889 | 23370 | 28334 | 28334 | 16 |
After | decreaseBalances | 1889 | 23360 | 28321 | 28321 | 16 |
File: twab-controller/src/libraries/TwabLib.sol 372: uint32 newestObservationPeriod = _getTimestampPeriod( 373: PERIOD_LENGTH, 374: PERIOD_OFFSET, 375: newestObservation.timestamp 376: ); 377: 378: // TODO: Could skip this check for period 0 if we're sure that the PERIOD_OFFSET is in the past. 379: // Create a new Observation if the current time falls within a new period 380: // Or if the timestamp is the initial period. 381: if (currentPeriod == 0 || currentPeriod > newestObservationPeriod) {
recommended code :
373: // TODO: Could skip this check for period 0 if we're sure that the PERIOD_OFFSET is in the past. 374: // Create a new Observation if the current time falls within a new period 375: // Or if the timestamp is the initial period. 376: if (currentPeriod == 0 || currentPeriod > _getTimestampPeriod( 377: PERIOD_LENGTH, 378: PERIOD_OFFSET, 379: newestObservation.timestamp 380: )) {
isTimeSafe
function as it is an internal function used by isTimeSafe
Here, preOrAtPeriod
and postPeriod
cached in lines 718
and 723
has been used only once in line 730
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | isTimeSafe | 987 | 2153 | 2136 | 17479 | 19572 |
After | isTimeSafe | 987 | 2152 | 2136 | 17453 | 19572 |
File: twab-controller/src/libraries/TwabLib.sol 718: uint32 preOrAtPeriod = _getTimestampPeriod( 719: PERIOD_LENGTH, 720: PERIOD_OFFSET, 721: preOrAtObservation.timestamp 722: ); 723: uint32 postPeriod = _getTimestampPeriod( 724: PERIOD_LENGTH, 725: PERIOD_OFFSET, 726: nextOrNewestObservation.timestamp 727: ); 728: 729: // The observation after it falls in a new period 730: return period >= preOrAtPeriod && period < postPeriod;
recommended code :
File: twab-controller/src/libraries/TwabLib.sol 718: // The observation after it falls in a new period 719: return period >= _getTimestampPeriod( 720: PERIOD_LENGTH, 721: PERIOD_OFFSET, 722: preOrAtObservation.timestamp 723: ) && period < _getTimestampPeriod( 724: PERIOD_LENGTH, 725: PERIOD_OFFSET, 726: nextOrNewestObservation.timestamp 727: );
Here, prevOrAtObservation
cached in line 269
has been used only once in line 275
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getBalanceAt | 1219 | 2106 | 1956 | 4554 | 11 |
After | getBalanceAt | 1214 | 2101 | 1951 | 4549 | 11 |
File: twab-controller/src/libraries/TwabLib.sol 269: ObservationLib.Observation memory prevOrAtObservation = _getPreviousOrAtObservation( 270: PERIOD_OFFSET, 271: _observations, 272: _accountDetails, 273: _targetTime 274: ); 275: return prevOrAtObservation.balance;
recommended code :
File: twab-controller/src/libraries/TwabLib.sol 269: return _getPreviousOrAtObservation( 270: PERIOD_OFFSET, 271: _observations, 272: _accountDetails, 273: _targetTime 274: ).balance;
do while
loops instead of for
loopsA do while
loop costs less gas in comparison to a for
loop as the condition is not checked for the first iteration in a do while
7 instances in 4 files
Note : All the gas savings calculated in this section have been calculated using protocol's provided tests
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | claimPrizes | 9521 | 10918 | 10096 | 13962 | 4 |
After | claimPrizes | 9480 | 10874 | 10055 | 13907 | 4 |
File: claimer/src/Claimer.sol 68: for (uint i = 0; i < winners.length; i++) { 69: claimCount += prizeIndices[i].length; 70: }
recommended code :
68: uint256 i = 0; 69: do { 70: claimCount += prizeIndices[i].length; 71: i++; 72: } while (i < winners.length);
computeFeePerClaim
function as it is an internal function used by computeFeePerClaim
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | computeFeePerClaim | 5816 | 5829 | 5829 | 5842 | 2 |
After | computeFeePerClaim | 5772 | 5786 | 5786 | 5801 | 2 |
File: claimer/src/Claimer.sol 144: for (uint i = 0; i < _claimCount; i++) { 145: fee += _computeFeeForNextClaim( 146: minimumFee, 147: decayConstant, 148: perTimeUnit, 149: elapsed, 150: _claimedCount + i, 151: _maxFee 152: ); 153: }
recommended code :
143: uint256 i = 0; 144: do { 145: fee += _computeFeeForNextClaim( 146: minimumFee, 147: decayConstant, 148: perTimeUnit, 149: elapsed, 150: _claimedCount + i, 151: _maxFee 152: ); 153: i++; 154: } while (i < _claimCount);
nextDraw
function as it is an internal function used by nextDraw
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | nextDraw | 526 | 68306 | 35429 | 330583 | 19 |
After | nextDraw | 526 | 68233 | 35376 | 330362 | 19 |
File: prize-pool/src/abstract/TieredLiquidityDistributor.sol 361: for (uint8 i = start; i < end; i++) { 362: _tiers[i] = Tier({ 363: drawId: closedDrawId, 364: prizeTokenPerShare: prizeTokenPerShare, 365: prizeSize: uint96( 366: _computePrizeSize(i, _nextNumberOfTiers, _prizeTokenPerShare, newPrizeTokenPerShare) 367: ) 368: });
recommended code :
361: uint8 i = start; 362: do { 363: _tiers[i] = Tier({ 364: drawId: closedDrawId, 365: prizeTokenPerShare: prizeTokenPerShare, 366: prizeSize: uint96( 367: _computePrizeSize(i, _nextNumberOfTiers, _prizeTokenPerShare, newPrizeTokenPerShare) 368: ) 369: }); 370: i++; 371: } while (i < end);
getTierLiquidityToReclaim
function as it is an internal function used by getTierLiquidityToReclaim
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getTierLiquidityToReclaim | 2862 | 4575 | 4579 | 6286 | 3 |
After | getTierLiquidityToReclaim | 2806 | 4502 | 4506 | 6196 | 3 |
File: prize-pool/src/abstract/TieredLiquidityDistributor.sol 630: for (uint8 i = start; i < end; i++) { 631: Tier memory tierLiquidity = _tiers[i]; 632: uint8 shares = _computeShares(i, _numberOfTiers); 633: UD60x18 liq = _getTierRemainingLiquidity( 634: shares, 635: fromUD34x4toUD60x18(tierLiquidity.prizeTokenPerShare), 636: _prizeTokenPerShare 637: ); 638: reclaimedLiquidity = reclaimedLiquidity.add(liq); 639: }
recommended code :
630: uint8 i = start; 631: do { 632: Tier memory tierLiquidity = _tiers[i]; 633: uint8 shares = _computeShares(i, _numberOfTiers); 634: UD60x18 liq = _getTierRemainingLiquidity( 635: shares, 636: fromUD34x4toUD60x18(tierLiquidity.prizeTokenPerShare), 637: _prizeTokenPerShare 638: ); 639: reclaimedLiquidity = reclaimedLiquidity.add(liq); 640: i++; 641: } while (i < end);
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | estimatedClaimCount | 38530 | 180531 | 180238 | 322688 | 15 |
After | estimatedClaimCount | 38463 | 180366 | 180073 | 322425 | 15 |
File: prize-pool/src/libraries/TierCalculationLib.sol 138: for (uint8 i = 0; i < _numberOfTiers; i++) { 139: count += uint32( 140: uint256( 141: unwrap(sd(int256(prizeCount(i))).mul(getTierOdds(i, _numberOfTiers, _grandPrizePeriod))) 142: ) 143: ); 144: }
recommended code :
138: uint8 i = 0; 139: do { 140: count += uint32( 141: uint256( 142: unwrap(sd(int256(prizeCount(i))).mul(getTierOdds(i, _numberOfTiers, _grandPrizePeriod))) 143: ) 144: ); 145: i++; 146: } while (i < _numberOfTiers);
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | claimPrizes | 1447 | 4989 | 5991 | 7475 | 5 |
After | claimPrizes | 1447 | 4939 | 5909 | 7393 | 5 |
File: vault/src/Vault.sol 618: for (uint w = 0; w < _winners.length; w++) { 619: uint prizeIndicesLength = _prizeIndices[w].length; 620: for (uint p = 0; p < prizeIndicesLength; p++) { 621: totalPrizes += _claimPrize( 622: _winners[w], 623: _tier, 624: _prizeIndices[w][p], 625: _feePerClaim, 626: _feeRecipient 627: ); 628: } 629: }
recommended code :
617: 618: uint256 w = 0; 619: do { 620: uint prizeIndicesLength = _prizeIndices[w].length; 621: uint256 p = 0; 622: do { 623: totalPrizes += _claimPrize( 624: _winners[w], 625: _tier, 626: _prizeIndices[w][p], 627: _feePerClaim, 628: _feeRecipient 629: ); 630: p++; 631: } while (p < prizeIndicesLength); 632: w++; 633: } while (w < _winners.length);
for
loopIn solidity,concept of null
value for a variable doesn't exist i.e when variables are declared in solidity,they are initialized with the default value of their type rather than being set to null
.Thus,explicitly initializing variables(counter varaible for which generally i
is used) in for
loops wastes gas.
According to remix,around 8 gas per loop can be saved by avoiding the explicit initialization of counter varaible in a for loop.Here is a little POC experimented in remix to demonstrate the gas saving that can be obtained via the recommended mitigation.
// SPDX-License-Identifier: MIT pragma solidity 0.8.18; contract AvoidExplicitInitializationGasTest { // @audit : call to test in CASE 1 costs 3090 gas wheras it costs 3082 gas in CASE 2 function test(uint256 a) public pure returns(uint256) { // CASE 1 // for(uint256 i = 0; i < 10;) { // a = a + i; // unchecked { // ++i; // } // } // return a; // CASE 2 for(uint256 i; i < 10;) { a = a + i; unchecked { ++i; } } return a; } }
5 instances in 4 files
Estimated gas saving : 5 * 8 = 40
File: claimer/src/Claimer.sol 68: for (uint i = 0; i < winners.length; i++) {
File: claimer/src/Claimer.sol 144: for (uint i = 0; i < _claimCount; i++) {
File: prize-pool/src/libraries/TierCalculationLib.sol 138: for (uint8 i = 0; i < _numberOfTiers; i++) {
File: vault/src/Vault.sol 618: for (uint w = 0; w < _winners.length; w++) { 620: for (uint p = 0; p < prizeIndicesLength; p++) {
Assigning constant variables based on the result of a mathematical calculation/function call/casting wastes gas as constant variables need to be revaluated each time they are accessed.
117 instances in 2 files
File: prize-pool/src/abstract/TieredLiquidityDistributor.sol 084: SD59x18 internal constant TIER_ODDS_0_3 = SD59x18.wrap(2739726027397260); 085: SD59x18 internal constant TIER_ODDS_1_3 = SD59x18.wrap(52342392259021369); 086: SD59x18 internal constant TIER_ODDS_2_3 = SD59x18.wrap(1000000000000000000); 087: SD59x18 internal constant TIER_ODDS_0_4 = SD59x18.wrap(2739726027397260); 088: SD59x18 internal constant TIER_ODDS_1_4 = SD59x18.wrap(19579642462506911); 089: SD59x18 internal constant TIER_ODDS_2_4 = SD59x18.wrap(139927275620255366); 090: SD59x18 internal constant TIER_ODDS_3_4 = SD59x18.wrap(1000000000000000000); 091: SD59x18 internal constant TIER_ODDS_0_5 = SD59x18.wrap(2739726027397260); 092: SD59x18 internal constant TIER_ODDS_1_5 = SD59x18.wrap(11975133168707466); 093: SD59x18 internal constant TIER_ODDS_2_5 = SD59x18.wrap(52342392259021369); 094: SD59x18 internal constant TIER_ODDS_3_5 = SD59x18.wrap(228784597949733865); 095: SD59x18 internal constant TIER_ODDS_4_5 = SD59x18.wrap(1000000000000000000); 096: SD59x18 internal constant TIER_ODDS_0_6 = SD59x18.wrap(2739726027397260); 097: SD59x18 internal constant TIER_ODDS_1_6 = SD59x18.wrap(8915910667410451); 098: SD59x18 internal constant TIER_ODDS_2_6 = SD59x18.wrap(29015114005673871); 099: SD59x18 internal constant TIER_ODDS_3_6 = SD59x18.wrap(94424100034951094); 100: SD59x18 internal constant TIER_ODDS_4_6 = SD59x18.wrap(307285046878222004); 101: SD59x18 internal constant TIER_ODDS_5_6 = SD59x18.wrap(1000000000000000000); 102: SD59x18 internal constant TIER_ODDS_0_7 = SD59x18.wrap(2739726027397260); 103: SD59x18 internal constant TIER_ODDS_1_7 = SD59x18.wrap(7324128348251604); 104: SD59x18 internal constant TIER_ODDS_2_7 = SD59x18.wrap(19579642462506911); 105: SD59x18 internal constant TIER_ODDS_3_7 = SD59x18.wrap(52342392259021369); 106: SD59x18 internal constant TIER_ODDS_4_7 = SD59x18.wrap(139927275620255366); 107: SD59x18 internal constant TIER_ODDS_5_7 = SD59x18.wrap(374068544013333694); 108: SD59x18 internal constant TIER_ODDS_6_7 = SD59x18.wrap(1000000000000000000); 109: SD59x18 internal constant TIER_ODDS_0_8 = SD59x18.wrap(2739726027397260); 110: SD59x18 internal constant TIER_ODDS_1_8 = SD59x18.wrap(6364275529026907); 111: SD59x18 internal constant TIER_ODDS_2_8 = SD59x18.wrap(14783961098420314); 112: SD59x18 internal constant TIER_ODDS_3_8 = SD59x18.wrap(34342558671878193); 113: SD59x18 internal constant TIER_ODDS_4_8 = SD59x18.wrap(79776409602255901); 114: SD59x18 internal constant TIER_ODDS_5_8 = SD59x18.wrap(185317453770221528); 115: SD59x18 internal constant TIER_ODDS_6_8 = SD59x18.wrap(430485137687959592); 116: SD59x18 internal constant TIER_ODDS_7_8 = SD59x18.wrap(1000000000000000000); 117: SD59x18 internal constant TIER_ODDS_0_9 = SD59x18.wrap(2739726027397260); 118: SD59x18 internal constant TIER_ODDS_1_9 = SD59x18.wrap(5727877794074876); 119: SD59x18 internal constant TIER_ODDS_2_9 = SD59x18.wrap(11975133168707466); 120: SD59x18 internal constant TIER_ODDS_3_9 = SD59x18.wrap(25036116265717087); 121: SD59x18 internal constant TIER_ODDS_4_9 = SD59x18.wrap(52342392259021369); 122: SD59x18 internal constant TIER_ODDS_5_9 = SD59x18.wrap(109430951602859902); 123: SD59x18 internal constant TIER_ODDS_6_9 = SD59x18.wrap(228784597949733865); 124: SD59x18 internal constant TIER_ODDS_7_9 = SD59x18.wrap(478314329651259628); 125: SD59x18 internal constant TIER_ODDS_8_9 = SD59x18.wrap(1000000000000000000); 126: SD59x18 internal constant TIER_ODDS_0_10 = SD59x18.wrap(2739726027397260); 127: SD59x18 internal constant TIER_ODDS_1_10 = SD59x18.wrap(5277233889074595); 128: SD59x18 internal constant TIER_ODDS_2_10 = SD59x18.wrap(10164957094799045); 129: SD59x18 internal constant TIER_ODDS_3_10 = SD59x18.wrap(19579642462506911); 130: SD59x18 internal constant TIER_ODDS_4_10 = SD59x18.wrap(37714118749773489); 131: SD59x18 internal constant TIER_ODDS_5_10 = SD59x18.wrap(72644572330454226); 132: SD59x18 internal constant TIER_ODDS_6_10 = SD59x18.wrap(139927275620255366); 133: SD59x18 internal constant TIER_ODDS_7_10 = SD59x18.wrap(269526570731818992); 134: SD59x18 internal constant TIER_ODDS_8_10 = SD59x18.wrap(519159484871285957); 135: SD59x18 internal constant TIER_ODDS_9_10 = SD59x18.wrap(1000000000000000000); 136: SD59x18 internal constant TIER_ODDS_0_11 = SD59x18.wrap(2739726027397260); 137: SD59x18 internal constant TIER_ODDS_1_11 = SD59x18.wrap(4942383282734483); 138: SD59x18 internal constant TIER_ODDS_2_11 = SD59x18.wrap(8915910667410451); 139: SD59x18 internal constant TIER_ODDS_3_11 = SD59x18.wrap(16084034459031666); 140: SD59x18 internal constant TIER_ODDS_4_11 = SD59x18.wrap(29015114005673871); 141: SD59x18 internal constant TIER_ODDS_5_11 = SD59x18.wrap(52342392259021369); 142: SD59x18 internal constant TIER_ODDS_6_11 = SD59x18.wrap(94424100034951094); 143: SD59x18 internal constant TIER_ODDS_7_11 = SD59x18.wrap(170338234127496669); 144: SD59x18 internal constant TIER_ODDS_8_11 = SD59x18.wrap(307285046878222004); 145: SD59x18 internal constant TIER_ODDS_9_11 = SD59x18.wrap(554332974734700411); 146: SD59x18 internal constant TIER_ODDS_10_11 = SD59x18.wrap(1000000000000000000); 147: SD59x18 internal constant TIER_ODDS_0_12 = SD59x18.wrap(2739726027397260); 148: SD59x18 internal constant TIER_ODDS_1_12 = SD59x18.wrap(4684280039134314); 149: SD59x18 internal constant TIER_ODDS_2_12 = SD59x18.wrap(8009005012036743); 150: SD59x18 internal constant TIER_ODDS_3_12 = SD59x18.wrap(13693494143591795); 151: SD59x18 internal constant TIER_ODDS_4_12 = SD59x18.wrap(23412618868232833); 152: SD59x18 internal constant TIER_ODDS_5_12 = SD59x18.wrap(40030011078337707); 153: SD59x18 internal constant TIER_ODDS_6_12 = SD59x18.wrap(68441800379112721); 154: SD59x18 internal constant TIER_ODDS_7_12 = SD59x18.wrap(117019204165776974); 155: SD59x18 internal constant TIER_ODDS_8_12 = SD59x18.wrap(200075013628233217); 156: SD59x18 internal constant TIER_ODDS_9_12 = SD59x18.wrap(342080698323914461); 157: SD59x18 internal constant TIER_ODDS_10_12 = SD59x18.wrap(584876652230121477); 158: SD59x18 internal constant TIER_ODDS_11_12 = SD59x18.wrap(1000000000000000000); 159: SD59x18 internal constant TIER_ODDS_0_13 = SD59x18.wrap(2739726027397260); 160: SD59x18 internal constant TIER_ODDS_1_13 = SD59x18.wrap(4479520628784180); 161: SD59x18 internal constant TIER_ODDS_2_13 = SD59x18.wrap(7324128348251604); 162: SD59x18 internal constant TIER_ODDS_3_13 = SD59x18.wrap(11975133168707466); 163: SD59x18 internal constant TIER_ODDS_4_13 = SD59x18.wrap(19579642462506911); 164: SD59x18 internal constant TIER_ODDS_5_13 = SD59x18.wrap(32013205494981721); 165: SD59x18 internal constant TIER_ODDS_6_13 = SD59x18.wrap(52342392259021369); 166: SD59x18 internal constant TIER_ODDS_7_13 = SD59x18.wrap(85581121447732876); 167: SD59x18 internal constant TIER_ODDS_8_13 = SD59x18.wrap(139927275620255366); 168: SD59x18 internal constant TIER_ODDS_9_13 = SD59x18.wrap(228784597949733866); 169: SD59x18 internal constant TIER_ODDS_10_13 = SD59x18.wrap(374068544013333694); 170: SD59x18 internal constant TIER_ODDS_11_13 = SD59x18.wrap(611611432212751966); 171: SD59x18 internal constant TIER_ODDS_12_13 = SD59x18.wrap(1000000000000000000); 172: SD59x18 internal constant TIER_ODDS_0_14 = SD59x18.wrap(2739726027397260); 173: SD59x18 internal constant TIER_ODDS_1_14 = SD59x18.wrap(4313269422986724); 174: SD59x18 internal constant TIER_ODDS_2_14 = SD59x18.wrap(6790566987074365); 175: SD59x18 internal constant TIER_ODDS_3_14 = SD59x18.wrap(10690683906783196); 176: SD59x18 internal constant TIER_ODDS_4_14 = SD59x18.wrap(16830807002169641); 177: SD59x18 internal constant TIER_ODDS_5_14 = SD59x18.wrap(26497468900426949); 178: SD59x18 internal constant TIER_ODDS_6_14 = SD59x18.wrap(41716113674084931); 179: SD59x18 internal constant TIER_ODDS_7_14 = SD59x18.wrap(65675485708038160); 180: SD59x18 internal constant TIER_ODDS_8_14 = SD59x18.wrap(103395763485663166); 181: SD59x18 internal constant TIER_ODDS_9_14 = SD59x18.wrap(162780431564813557); 182: SD59x18 internal constant TIER_ODDS_10_14 = SD59x18.wrap(256272288217119098); 183: SD59x18 internal constant TIER_ODDS_11_14 = SD59x18.wrap(403460570024895441); 184: SD59x18 internal constant TIER_ODDS_12_14 = SD59x18.wrap(635185461125249183); 185: SD59x18 internal constant TIER_ODDS_13_14 = SD59x18.wrap(1000000000000000000); 186: SD59x18 internal constant TIER_ODDS_0_15 = SD59x18.wrap(2739726027397260); 187: SD59x18 internal constant TIER_ODDS_1_15 = SD59x18.wrap(4175688124417637); 188: SD59x18 internal constant TIER_ODDS_2_15 = SD59x18.wrap(6364275529026907); 189: SD59x18 internal constant TIER_ODDS_3_15 = SD59x18.wrap(9699958857683993); 190: SD59x18 internal constant TIER_ODDS_4_15 = SD59x18.wrap(14783961098420314); 191: SD59x18 internal constant TIER_ODDS_5_15 = SD59x18.wrap(22532621938542004); 192: SD59x18 internal constant TIER_ODDS_6_15 = SD59x18.wrap(34342558671878193); 193: SD59x18 internal constant TIER_ODDS_7_15 = SD59x18.wrap(52342392259021369); 194: SD59x18 internal constant TIER_ODDS_8_15 = SD59x18.wrap(79776409602255901); 195: SD59x18 internal constant TIER_ODDS_9_15 = SD59x18.wrap(121589313257458259); 196: SD59x18 internal constant TIER_ODDS_10_15 = SD59x18.wrap(185317453770221528); 197: SD59x18 internal constant TIER_ODDS_11_15 = SD59x18.wrap(282447180198804430); 198: SD59x18 internal constant TIER_ODDS_12_15 = SD59x18.wrap(430485137687959592); 199: SD59x18 internal constant TIER_ODDS_13_15 = SD59x18.wrap(656113662171395111); 200: SD59x18 internal constant TIER_ODDS_14_15 = SD59x18.wrap(1000000000000000000);
Mitigation : Either use hardcoded values for these constants or chnage them into immutables as immutable variables are evaluated only once i.e during contract creation time and thus can save some gas in comparison to constants.
File: twab-controller/src/TwabController.sol 24: address public constant SPONSORSHIP_ADDRESS = address(1);
Mitigation : Similar mitigation steps as shown in above can be applied here too
Move revert statement to the top of the function as it's logic is not influenced by the code above it in any way and in the current code design,users will have to pay more gas if function reverts.Thus by moving revert statements to the top,users gas can be saved if they call the function and the function reverts as early as possible
3 instances in 2 files
Lines 320-325
to the top of the constructorFile: prize-pool/src/abstract/TieredLiquidityDistributor.sol 320: if (_numberOfTiers < MINIMUM_NUMBER_OF_TIERS) { 321: revert NumberOfTiersLessThanMinimum(_numberOfTiers); 322: } 323: if (_numberOfTiers > MAXIMUM_NUMBER_OF_TIERS) { 324: revert NumberOfTiersGreaterThanMaximum(_numberOfTiers); 325: }
396
to the top of the functionFile: vault/src/Vault.sol 395: _requireVaultCollateralized(); 396: if (_shares > _yieldFeeTotalSupply) revert YieldFeeGTAvailable(_shares, _yieldFeeTotalSupply);
By changing the pattern of assigning value to the storage structure, gas savings can be achieved.In addition, this use will provide significant savings in distribution costs.
3 instances in 1 file
Note : All the gas savings calculated in this section have been calculated using protocol's provided tests
--via-ir
flag in foundry to avoid stack too deep error
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | add | 506 | 85103 | 86993 | 107583 | 23 |
After | add | 506 | 84859 | 86725 | 107315 | 23 |
File: prize-pool/src/libraries/DrawAccumulatorLib.sol 91: accumulator.observations[_drawId] = Observation({ 92: available: uint96(_amount + remainingAmount), 93: disbursed: uint168(newestObservation_.disbursed + disbursedAmount + remainder) 94: }); ... 100: accumulator.ringBufferInfo = RingBufferInfo({ 101: nextIndex: nextIndex, 102: cardinality: cardinality 103: }); ... 106: accumulator.observations[newestDrawId_] = Observation({ 107: available: uint96(newestObservation_.available + _amount), 108: disbursed: newestObservation_.disbursed 109: });
recommended code :
91: Observation storage _observation1 = accumulator.observations[_drawId]; 92: _observation1.available = uint96(_amount + remainingAmount); 93: _observation1.disbursed =uint168(newestObservation_.disbursed + disbursedAmount + remainder); ... 100: RingBufferInfo storage _ringBufferInfo = accumulator.ringBufferInfo; 101: _ringBufferInfo.nextIndex = nextIndex; 102: _ringBufferInfo.cardinality = cardinality; ... 106: Observation storage _observation2 = accumulator.observations[newestDrawId_]; 107: _observation2.available = uint96(newestObservation_.available + _amount); 108: _observation2.disbursed = newestObservation_.disbursed;
Caching the value of a particular key of a mapping or the value of a particular index of an array in a local storage variable when the value is accessed(read and written) multiple times saves ~40 gas per access due to not having to perform the same offset calculation every time.Help the Optimizer by saving a storage variable’s reference instead of repeatedly fetching it.
To help the optimizer,declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array.
As an example, instead of repeatedly calling someMap[someKey]
and SomeArray[someIndex]
, save their references like SomeType storage someVariable = someMap[someIndex]
and SomeType storage someVariable = SomeArray[someIndex]
use them.
8 instances in 3 files
accumulator.observations[newestDrawId_]
should be cached in local storage as it has been accessed twice ,once in line 82
and then in line 106
File: prize-pool/src/libraries/DrawAccumulatorLib.sol 082: Observation memory newestObservation_ = accumulator.observations[newestDrawId_]; // @audit : 1st access 083: if (_drawId != newestDrawId_) { 084: uint256 relativeDraw = _drawId - newestDrawId_; 085: 086: uint256 remainingAmount = integrateInf(_alpha, relativeDraw, newestObservation_.available); 087: uint256 disbursedAmount = integrate(_alpha, 0, relativeDraw, newestObservation_.available); 088: uint256 remainder = newestObservation_.available - (remainingAmount + disbursedAmount); 089: 090: accumulator.drawRingBuffer[ringBufferInfo.nextIndex] = _drawId; 091: accumulator.observations[_drawId] = Observation({ 092: available: uint96(_amount + remainingAmount), 093: disbursed: uint168(newestObservation_.disbursed + disbursedAmount + remainder) 094: }); 095: uint16 nextIndex = uint16(RingBufferLib.nextIndex(ringBufferInfo.nextIndex, MAX_CARDINALITY)); 096: uint16 cardinality = ringBufferInfo.cardinality; 097: if (ringBufferInfo.cardinality < MAX_CARDINALITY) { 098: cardinality += 1; 099: } 100: accumulator.ringBufferInfo = RingBufferInfo({ 101: nextIndex: nextIndex, 102: cardinality: cardinality 103: }); 104: return true; 105: } else { 106: accumulator.observations[newestDrawId_] = Observation({ // @audit : 2nd access 107: available: uint96(newestObservation_.available + _amount), 108: disbursed: newestObservation_.disbursed 109: });
claimedPrizes[msg.sender][_winner][lastClosedDrawId][_tier][_prizeIndex]
should be cached in local storage as it has been accessed twice, once in line 434
and then in line 438
File: prize-pool/src/PrizePool.sol 434: if (claimedPrizes[msg.sender][_winner][lastClosedDrawId][_tier][_prizeIndex]) { // @audit : 1st access 435: revert AlreadyClaimedPrize(msg.sender, _winner, _tier, _prizeIndex, _prizeRecipient); 436: } 437: 438: claimedPrizes[msg.sender][_winner][lastClosedDrawId][_tier][_prizeIndex] = true; // @audit : 2nd access
claimerRewards[msg.sender]
should be cached in local storage as it has been accessed twice, once in line 484
and then in line 490
File: prize-pool/src/PrizePool.sol 484: uint256 _available = claimerRewards[msg.sender]; // @audit : 1st access 485: 486: if (_amount > _available) { 487: revert InsufficientRewardsError(_amount, _available); 488: } 489: 490: claimerRewards[msg.sender] -= _amount; // @audit : 2nd access
_observations[0].timestamp
should be cached in local storage as it has been accessed twice, once in line 698
and then in line 700
File: twab-controller/src/libraries/TwabLib.sol 697: return 698: period != _getTimestampPeriod(PERIOD_LENGTH, PERIOD_OFFSET, _observations[0].timestamp) // @audit : 1st access 699: ? true 700: : _time >= _observations[0].timestamp; // @audit : 2nd access 701: }
By switiching to a positive conditional flow in if
statement, a NOT opcode(costing 3 gas) can be saved
2 instances in 2 files
File: prize-pool/src/libraries/DrawAccumulatorLib.sol 458: // If `beforeOrAtTimestamp` is greater than `_target`, then we keep searching lower. To the left of the current index. 459: if (!targetAtOrAfter) { 460: rightSide = currentIndex - 1; 461: } else { 462: // Otherwise, we keep searching higher. To the left of the current index. 463: leftSide = currentIndex + 1; 464: }
recommended code :
458: // If `beforeOrAtTimestamp` is greater than `_target`, then we keep searching lower. To the left of the current index. 459: if (!targetAtOrAfter) { 460: leftSide = currentIndex + 1; 461: } else { 462: // Otherwise, we keep searching higher. To the left of the current index. 463: rightSide = currentIndex - 1; 464: }
File: twab-controller/src/libraries/ObservationLib.sol 92: // If `beforeOrAtTimestamp` is greater than `_target`, then we keep searching lower. To the left of the current index. 93: if (!targetAfterOrAt) { 94: rightSide = currentIndex - 1; 95: } else { 96: // Otherwise, we keep searching higher. To the left of the current index. 97: leftSide = currentIndex + 1; 98: }
recommended code :
File: twab-controller/src/libraries/ObservationLib.sol 92: // If `beforeOrAtTimestamp` is greater than `_target`, then we keep searching lower. To the left of the current index. 93: if (!targetAfterOrAt) { 94: leftSide = currentIndex + 1; 95: } else { 96: // Otherwise, we keep searching higher. To the left of the current index. 97: rightSide = currentIndex - 1; 98: }
7 instances in 2 files
File: prize-pool/src/PrizePool.sol 278: drawManager = params.drawManager;
File: prize-pool/src/PrizePool.sol 303: drawManager = _drawManager;
File: vault/src/Vault.sol 270: 271: _twabController = twabController_; 272: _yieldVault = yieldVault_; 273: _prizePool = prizePool_;
File: vault/src/Vault.sol 1210: _claimer = claimer_;
File: vault/src/Vault.sol 1230: _yieldFeeRecipient = yieldFeeRecipient_;
In the following instances,storage variable is being cached in memory and that cache is being used only once as event paramter.Thus,by rearranging the order in which events are emitted,gas costs incurred due to unnecessary memory operations can be saved.With the suggested arrangement,each time the functions are triggered (~16 gas) gas savings will be achieved.
3 isnatces in 1 file
File: vault/src/Vault.sol 642: address _previousClaimer = _claimer; 643: _setClaimer(claimer_); 644: 645: emit ClaimerSet(_previousClaimer, claimer_);
recommended code :
File: vault/src/Vault.sol 642: emit ClaimerSet(_claimer, claimer_); 643: _setClaimer(claimer_);
File: vault/src/Vault.sol 692: uint256 _previousYieldFeePercentage = _yieldFeePercentage; 693: _setYieldFeePercentage(yieldFeePercentage_); 694: 695: emit YieldFeePercentageSet(_previousYieldFeePercentage, yieldFeePercentage_);
recommended code :
File: vault/src/Vault.sol 692: emit YieldFeePercentageSet(_yieldFeePercentage, yieldFeePercentage_); 693: _setYieldFeePercentage(yieldFeePercentage_);
File: vault/src/Vault.sol 705: address _previousYieldFeeRecipient = _yieldFeeRecipient; 706: _setYieldFeeRecipient(yieldFeeRecipient_); 707: 708: emit YieldFeeRecipientSet(_previousYieldFeeRecipient, yieldFeeRecipient_);
recommended code :
File: vault/src/Vault.sol 705: emit YieldFeeRecipientSet(_yieldFeeRecipient, yieldFeeRecipient_); 706: _setYieldFeeRecipient(yieldFeeRecipient_);
1 instance in 1 files
Here, _lastClosedDrawStartedAt
is being emitted in line 382
which is assigned in line 372
the value openDrawStartedAt_
cached in line 364
.To save gas, openDrawStartedAt_
can be emitted directly in line 382
File: prize-pool/src/PrizePool.sol 364: uint64 openDrawStartedAt_ = _openDrawStartedAt(); 365: 366: _nextDraw(_nextNumberOfTiers, uint96(_contributionsForDraw(lastClosedDrawId + 1))); 367: 368: _winningRandomNumber = winningRandomNumber_; 369: claimCount = 0; 370: canaryClaimCount = 0; 371: largestTierClaimed = 0; 372: _lastClosedDrawStartedAt = openDrawStartedAt_; 373: _lastClosedDrawAwardedAt = uint64(block.timestamp); 374: 375: emit DrawClosed( 376: lastClosedDrawId, 377: winningRandomNumber_, 378: _numTiers, 379: _nextNumberOfTiers, 380: _reserve, 381: prizeTokenPerShare, 382: _lastClosedDrawStartedAt 383: );
recommended code :
364: uint64 openDrawStartedAt_ = _openDrawStartedAt(); 365: 366: _nextDraw(_nextNumberOfTiers, uint96(_contributionsForDraw(lastClosedDrawId + 1))); 367: 368: _winningRandomNumber = winningRandomNumber_; 369: claimCount = 0; 370: canaryClaimCount = 0; 371: largestTierClaimed = 0; 372: _lastClosedDrawStartedAt = openDrawStartedAt_; 373: _lastClosedDrawAwardedAt = uint64(block.timestamp); 374: 375: emit DrawClosed( 376: lastClosedDrawId, 377: winningRandomNumber_, 378: _numTiers, 379: _nextNumberOfTiers, 380: _reserve, 381: prizeTokenPerShare, 382: openDrawStartedAt_ 383: );
The function parameters in the following instances are complex types and thus will result in more complex offset calculations to retrieve specific data from calldata. We can avoid peforming some of these offset calculations by instantiating calldata/memory pointers.
49 instances in 3 files
Note : All the gas savings calculated in this section have been calculated using protocol's provided tests
Note : most of the gas savings demonstrated in this section has been calculated using the --via-ir
flag in foundry to avoid stack too deep
error
ringBufferInfo.nextIndex
, ringBufferInfo.cardinality
, newestObservation_.available
and newestObservation_.disbursed
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | add | 506 | 85103 | 86993 | 107583 | 23 |
After | add | 503 | 85051 | 86935 | 107525 | 23 |
File: prize-pool/src/libraries/DrawAccumulatorLib.sol 075: uint256 newestIndex = RingBufferLib.newestIndex(ringBufferInfo.nextIndex, MAX_CARDINALITY); // @audit : 1st access 076: uint16 newestDrawId_ = accumulator.drawRingBuffer[newestIndex]; 077: 078: if (_drawId < newestDrawId_) { 079: revert DrawClosed(_drawId, newestDrawId_); 080: } 081: 082: Observation memory newestObservation_ = accumulator.observations[newestDrawId_]; 083: if (_drawId != newestDrawId_) { 084: uint256 relativeDraw = _drawId - newestDrawId_; 085: 086: uint256 remainingAmount = integrateInf(_alpha, relativeDraw, newestObservation_.available); // @audit : 1st access 087: uint256 disbursedAmount = integrate(_alpha, 0, relativeDraw, newestObservation_.available); // @audit : 2nd access 088: uint256 remainder = newestObservation_.available - (remainingAmount + disbursedAmount); // @audit : 3rd access 089: 090: accumulator.drawRingBuffer[ringBufferInfo.nextIndex] = _drawId; // @audit : 2nd access 091: accumulator.observations[_drawId] = Observation({ 092: available: uint96(_amount + remainingAmount), 093: disbursed: uint168(newestObservation_.disbursed + disbursedAmount + remainder) // @audit : 1st access 094: }); 095: uint16 nextIndex = uint16(RingBufferLib.nextIndex(ringBufferInfo.nextIndex, MAX_CARDINALITY)); // @audit : 3rd access 096: uint16 cardinality = ringBufferInfo.cardinality; // @audit : 1st access 097: if (ringBufferInfo.cardinality < MAX_CARDINALITY) { // @audit : 2nd access 098: cardinality += 1; 099: } 100: accumulator.ringBufferInfo = RingBufferInfo({ 101: nextIndex: nextIndex, 102: cardinality: cardinality 103: }); 104: return true; 105: } else { 106: accumulator.observations[newestDrawId_] = Observation({ 107: available: uint96(newestObservation_.available + _amount), // @audit : 4th access 108: disbursed: newestObservation_.disbursed // @audit : 2nd access
recommended code :
074: 075: uint16 ringBufferInfoNextIndex = ringBufferInfo.nextIndex; 076: uint16 ringBufferInfoCardinality = ringBufferInfo.cardinality; 077: 078: uint256 newestIndex = RingBufferLib.newestIndex(ringBufferInfoNextIndex, MAX_CARDINALITY); 079: uint16 newestDrawId_ = accumulator.drawRingBuffer[newestIndex]; 080: 081: if (_drawId < newestDrawId_) { 082: revert DrawClosed(_drawId, newestDrawId_); 083: } 084: 085: Observation memory newestObservation_ = accumulator.observations[newestDrawId_]; 086: 087: uint96 newestObservation_Available = newestObservation_.available; 088: uint168 newestObservation_Disbursed = newestObservation_.disbursed; 089: if (_drawId != newestDrawId_) { 090: uint256 relativeDraw = _drawId - newestDrawId_; 091: 092: uint256 remainingAmount = integrateInf(_alpha, relativeDraw, newestObservation_Available); 093: uint256 disbursedAmount = integrate(_alpha, 0, relativeDraw, newestObservation_Available); 094: uint256 remainder = newestObservation_Available - (remainingAmount + disbursedAmount); 095: 096: accumulator.drawRingBuffer[ringBufferInfoNextIndex] = _drawId; 097: accumulator.observations[_drawId] = Observation({ 098: available: uint96(_amount + remainingAmount), 099: disbursed: uint168(newestObservation_Disbursed + disbursedAmount + remainder) 100: }); 101: uint16 nextIndex = uint16(RingBufferLib.nextIndex(ringBufferInfoNextIndex, MAX_CARDINALITY)); 102: uint16 cardinality = ringBufferInfoCardinality; 103: if (ringBufferInfoCardinality < MAX_CARDINALITY) { 104: cardinality += 1; 105: } 106: accumulator.ringBufferInfo = RingBufferInfo({ 107: nextIndex: nextIndex, 108: cardinality: cardinality 109: }); 110: return true; 111: } else { 112: accumulator.observations[newestDrawId_] = Observation({ 113: available: uint96(newestObservation_Available + _amount), 114: disbursed: newestObservation_Disbursed
ringBufferInfo.cardinality
, indexes.second
, drawIds.first
and drawIds.second
Here, only the lines involving the required parameters have been shown as the function is too long.
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getDisbursedBetween | 591 | 13628 | 13387 | 36409 | 14 |
After | getDisbursedBetween | 583 | 13523 | 13248 | 36229 | 14 |
File: prize-pool/src/libraries/DrawAccumulatorLib.sol 178: if (ringBufferInfo.cardinality == 0) { // @audit : 1st access 190: if (_endDrawId < drawIds.second - 1) { // @audit : 1st access 194: if (_endDrawId < drawIds.first) { // @audit : 1st access 229: if (_endDrawId >= drawIds.second) { // @audit : 2nd access 231: lastObservationDrawIdOccurringAtOrBeforeEnd = drawIds.second; // @audit : 3rd access 235: uint16(RingBufferLib.offset(indexes.second, 1, ringBufferInfo.cardinality)) // @audit : 1st access and 2nd access 242: if (_startDrawId >= drawIds.second) { // @audit : 4th access 244: observationDrawIdBeforeOrAtStart = drawIds.second; // @audit : 5th access 245: } else if (_startDrawId <= drawIds.first) { // @audit : 2nd access 248: firstObservationDrawIdOccurringAtOrAfterStart = drawIds.first; // @audit : 3rd access 260: indexes.second, // @audit : 2nd access 261: ringBufferInfo.cardinality, // @audit : 3rd access
Mitigation : As the function is too long,recommended code has not been shown but similar mitigation steps as shown in other similar instances can be applied here too
ringBufferInfo.nextIndex
and ringBufferInfo.cardinality
This function gas saving has been calculated using getDisbursedBetween
function as it is an internal function used by getDisbursedBetween
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getDisbursedBetween | 619 | 15114 | 15337 | 39999 | 14 |
After | getDisbursedBetween | 619 | 15104 | 15326 | 39988 | 14 |
File: prize-pool/src/libraries/DrawAccumulatorLib.sol 348: RingBufferLib.oldestIndex( 349: ringBufferInfo.nextIndex, // @audit : 1st access 350: ringBufferInfo.cardinality, // @audit : 1st access 351: MAX_CARDINALITY 352: ) 353: ), 354: second: uint16( 355: RingBufferLib.newestIndex(ringBufferInfo.nextIndex, ringBufferInfo.cardinality) // @audit : 2nd access 356: )
recommended code :
345: uint16 ringBufferInfoNextIndex = ringBufferInfo.nextIndex; 346: uint16 ringBufferInfoCardinality = ringBufferInfo.cardinality; 347: return 348: Pair32({ 349: first: uint16( 350: RingBufferLib.oldestIndex( 351: ringBufferInfoNextIndex, 352: ingBufferInfoCardinality, 353: MAX_CARDINALITY 354: ) 355: ), 356: second: uint16( 357: RingBufferLib.newestIndex(ringBufferInfoNextIndex, ingBufferInfoCardinality) 358: )
params.smoothing
and params.drawManager
Here, deployement gas has been used to demonstrate gas saving as the parameters involved in this instance have been used in constructor
Before : 5073048 (deployment gas)
After : 5072945 (deployment gas)
File: prize-pool/src/PrizePool.sol 261: TieredLiquidityDistributor( 262: params.numberOfTiers, 263: params.tierShares, 264: params.canaryShares, 265: params.reserveShares 266: ) 267: { 268: if (unwrap(params.smoothing) >= unwrap(UNIT)) { // @audit : 1st access 269: revert SmoothingGTEOne(unwrap(params.smoothing)); // @audit : 2nd access 270: } 271: prizeToken = params.prizeToken; 272: twabController = params.twabController; 273: smoothing = params.smoothing; // @audit : 3rd access 274: claimExpansionThreshold = params.claimExpansionThreshold; 275: drawPeriodSeconds = params.drawPeriodSeconds; 276: _lastClosedDrawStartedAt = params.firstDrawStartsAt; 277: 278: drawManager = params.drawManager; // @audit : 1st access 279: if (params.drawManager != address(0)) { // @audit : 2nd access 280: emit DrawManagerSet(params.drawManager); // @audit : 3rd access
recommended code :
268: SD1x18 paramsSmoothing = params.smoothing; 269: 270: if (unwrap(paramsSmoothing) >= unwrap(UNIT)) { 271: revert SmoothingGTEOne(unwrap(paramsSmoothing)); 272: } 273: 274: address paramsDrawManager = params.drawManager; 275: 276: prizeToken = params.prizeToken; 277: twabController = params.twabController; 278: smoothing = paramsSmoothing; 279: claimExpansionThreshold = params.claimExpansionThreshold; 280: drawPeriodSeconds = params.drawPeriodSeconds; 281: _lastClosedDrawStartedAt = params.firstDrawStartsAt; 282: 283: drawManager = paramsDrawManager; 284: if (paramsDrawManager != address(0)) { 285: emit DrawManagerSet(paramsDrawManager); 286: }
tierLiquidity.prizeSize
Here, only the lines involving the required parameters have been shown as the function is too long.
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | claimPrize | 4840 | 127752 | 149638 | 174234 | 32 |
After | claimPrize | 4849 | 127691 | 149570 | 174166 | 32 |
File: prize-pool/src/PrizePool.sol 417: if (_fee > tierLiquidity.prizeSize) { @audit : 1st access 418: revert FeeTooLarge(_fee, tierLiquidity.prizeSize); // @audit : 2nd access 451: _consumeLiquidity(tierLiquidity, _tier, tierLiquidity.prizeSize); // @audit : 3rd access 459: uint256 amount = tierLiquidity.prizeSize - _fee; // @audit : 4th access 475: return tierLiquidity.prizeSize; // @audit : 5th access
Mitigation : As the function is too long,recommended code has not been shown but similar mitigation steps as shown in other similar instances can be applied here too
newestObservation.timestamp
This function gas saving has been calculated using decreaseBalances
function as it is an internal function used by decreaseBalances
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | decreaseBalances | 1889 | 23370 | 28334 | 28334 | 16 |
After | decreaseBalances | 1889 | 23367 | 28330 | 28330 | 16 |
File: twab-controller/src/libraries/TwabLib.sol 367: if (newestObservation.timestamp == currentTime) { // @audit : 1st access 368: return (newestIndex, newestObservation, false); 369: } 370: 371: uint32 currentPeriod = _getTimestampPeriod(PERIOD_LENGTH, PERIOD_OFFSET, currentTime); 372: uint32 newestObservationPeriod = _getTimestampPeriod( 373: PERIOD_LENGTH, 374: PERIOD_OFFSET, 375: newestObservation.timestamp // @audit : 2nd access 376: );
recommended code :
File: twab-controller/src/libraries/TwabLib.sol 366: uint32 newestObservationTimestamp = newestObservation.timestamp; 367: 368: // if we're in the same block, return 369: if (newestObservationTimestamp == currentTime) { 370: return (newestIndex, newestObservation, false); 371: } 372: 373: uint32 currentPeriod = _getTimestampPeriod(PERIOD_LENGTH, PERIOD_OFFSET, currentTime); 374: uint32 newestObservationPeriod = _getTimestampPeriod( 375: PERIOD_LENGTH, 376: PERIOD_OFFSET, 377: newestObservationTimestamp 378: );
_accountDetails.cardinality
and prevOrAtObservation.timestamp
Here, only the lines involving the required parameters have been shown as the function is too long.
This function gas saving has been calculated using getPreviousOrAtObservation
function as it is an internal function used by getPreviousOrAtObservation
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getPreviousOrAtObservation | 2226 | 6219 | 4825 | 16782 | 23 |
After | getPreviousOrAtObservation | 2235 | 6217 | 4816 | 16773 | 23 |
File: twab-controller/src/libraries/TwabLib.sol 485: if (_accountDetails.cardinality == 0) { // @audit : 1st access 492: if (_targetTime >= prevOrAtObservation.timestamp) { // @audit : 1st access 497: if (_accountDetails.cardinality == 1) { // @audit : 2nd access 498: if (_targetTime >= prevOrAtObservation.timestamp) { // @audit : 2nd access 524: _accountDetails.cardinality, // @audit : 3rd access
Mitigation : As the function is too long,recommended code has not been shown but similar mitigation steps as shown in other similar instances can be applied here too
_accountDetails.cardinality
Here, only the lines involving the required parameters have been shown as the function is too long.
This function gas saving has been calculated using getNextOrNewestObservation
function as it is an internal function used by getNextOrNewestObservation
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | getNextOrNewestObservation | 1943 | 5333 | 2820 | 16919 | 16 |
After | getNextOrNewestObservation | 1948 | 5330 | 2816 | 16906 | 16 |
File: twab-controller/src/libraries/TwabLib.sol 572: if (_accountDetails.cardinality == 0) { // @audit : 1st access 587: if (_accountDetails.cardinality == 1) { // @audit : 2nd access 608: _accountDetails.cardinality, // @audit : 3rd access
Mitigation : As the function is too long,recommended code has not been shown but similar mitigation steps as shown in other similar instances can be applied here too
_accountDetails.cardinality
Here, only the lines involving the required parameters have been shown as the function is too long.
This function gas saving has been calculated using isTimeSafe
function as it is an internal function used by isTimeSafe
Function | min | avg | med | max | calls | |
---|---|---|---|---|---|---|
Before | isTimeSafe | 987 | 2153 | 2136 | 17479 | 19572 |
After | isTimeSafe | 998 | 2152 | 2135 | 17478 | 19572 |
File: twab-controller/src/libraries/TwabLib.sol 690: if (_accountDetails.cardinality == 0) { // @audit : 1st access 696: if (_accountDetails.cardinality == 1) { // @audit : 2nd access
Mitigation : As the function is too long,recommended code has not been shown but similar mitigation steps as shown in other similar instances can be applied here too
#0 - c4-judge
2023-07-18T19:03:47Z
Picodes marked the issue as grade-b
#1 - PierrickGT
2023-09-08T22:50:35Z
G-01:
G-02, 03: We would lose in code clarity G-04: There is no calculation here G-05: Has been fixed G-06: we would lose in code clarity G-07: no access here, we assign variables G-08, 09, 10: we would lose in code clarity G-11: has been fixed G-12: these variables are accessed in memory, the gas saving is negligible