PoolTogether - 0xn006e7's results

A protocol for no-loss prize savings

General Information

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

PoolTogether

Findings Distribution

Researcher Performance

Rank: 78/111

Findings: 1

Award: $24.30

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Awards

24.2984 USDC - $24.30

Labels

bug
G (Gas Optimization)
grade-b
G-17

External Links

Table of contents

S.N.IssuesInstancesGas Savings
G-01Avoid caching the result of a function call when that result is being used only once35388 + additional gas
G-02Use do while loops instead of for loops7448
G-03Avoid explicit initialization of variable in for loop540
G-04Don't calculate constants117-
G-05Fail as early as possible3-
G-06Gas savings can be achieved by changing the model for assigning value to the storage structure3244
G-07Save a storage variable reference instead of repeatedly fetching the value in a mapping or an array8320
G-08Use a positive conditional flow to save a NOT opcode26
G-09Use assembly to write address storage varibales7-
G-10Emitting events can be rearranged to save gas348
G-11Avoid emitting storage variables when possible1110
G-12Cache calldata/memory pointers for complex types to avoid complex offset calculations49340

[G-01] : Avoid caching the result of a function call when that result is being used only once

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

1) claimer/src/libraries/LinearVRGDALib.sol :: getMaximumPriceDeltaScale()

Here, div, ln and maxDiv cached in lines 107, 108 and 109 have been used only once in lines 108, 109 and 110 respectively

avg. gas saving obtained : 33
Functionminavgmedmaxcalls
BeforegetMaximumPriceDeltaScale18361836183618361
AftergetMaximumPriceDeltaScale18031803180318031
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: );

https://github.com/GenerationSoftware/pt-v5-claimer/blob/57a381aef690a27c9198f4340747155a71cae753/src/libraries/LinearVRGDALib.sol#L107-L110

2) prize-pool/src/abstract/TieredLiquidityDistributor.sol :: _computeNewDistributions()

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:     );

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/abstract/TieredLiquidityDistributor.sol#L419-L431

3) prize-pool/src/abstract/TieredLiquidityDistributor.sol :: _consumeLiquidity(), this function gas saving has been calculated using 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

avg. gas saving : 6
Functionminavgmedmaxcalls
BeforeconsumeLiquidity72372482219588528774
AfterconsumeLiquidity72372481619582528644
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:       );

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/abstract/TieredLiquidityDistributor.sol#L542-L545

4) prize-pool/src/abstract/TieredLiquidityDistributor.sol :: _getTierLiquidityToReclaim(), this function gas saving has been calculated using 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

avg. gas saving : 22
Functionminavgmedmaxcalls
BeforegetTierLiquidityToReclaim28624575457962863
AftergetTierLiquidityToReclaim28514553455762533
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:       );

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/abstract/TieredLiquidityDistributor.sol#L632-L638

5) prize-pool/src/abstract/TieredLiquidityDistributor.sol :: _getTierRemainingLiquidity(), this function gas saving has been calculated using 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

avg. gas saving : 12
Functionminavgmedmaxcalls
BeforegetTierRemainingLiquidity265234942667699141
AftergetTierRemainingLiquidity263934822654698241
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));

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/abstract/TieredLiquidityDistributor.sol#L671-L672

6) prize-pool/src/libraries/DrawAccumulatorLib.sol :: add()

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

avg. gas saving : 74
Functionminavgmedmaxcalls
Beforeadd530880869064911065723
Afteradd530880129056811057623
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:       });

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L75-L76

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L88-L94

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L95-L103

7) prize-pool/src/libraries/DrawAccumulatorLib.sol :: getTotalRemaining(), this function gas saving has been calculated using 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

avg. gas saving : 10
Functionminavgmedmaxcalls
BeforegetTotalRemaining13372541276535213
AftergetTotalRemaining13292531276535003
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);

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L129-L130

8) prize-pool/src/libraries/DrawAccumulatorLib.sol :: getDisbursedBetween()

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

avg. gas saving : 2
Functionminavgmedmaxcalls
BeforegetDisbursedBetween61915114153373999914
AftergetDisbursedBetween61915112153343998214
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;

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L278-L281

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L294-L295

9) prize-pool/src/libraries/DrawAccumulatorLib.sol :: _computeTail(), this function gas saving has been calculated using 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

avg. gas saving : 13
Functionminavgmedmaxcalls
BeforegetDisbursedBetween61915114153373999914
AftergetDisbursedBetween61915101153193998114
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:     );

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L325-L336

10) prize-pool/src/libraries/DrawAccumulatorLib.sol :: integrate(), this function gas saving has been calculated using 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

avg. gas saving : 16
Functionminavgmedmaxcalls
BeforegetDisbursedBetween61915114153373999914
AftergetDisbursedBetween61915098153173995914
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)))));

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L396-L398

11) prize-pool/src/libraries/TierCalculationLib.sol :: getTierOdds(), this function gas saving has been calculated using 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

avg. gas saving : 117
Functionminavgmedmaxcalls
BeforeestimatedClaimCount3853018053118023832268815
AfterestimatedClaimCount3850418041418012132248015
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))));

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculationLib.sol#L22-L26

12) prize-pool/src/libraries/TierCalculationLib.sol :: prizeCount(), this function gas saving has been calculated using 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

avg. gas saving : 5
Functionminavgmedmaxcalls
BeforecanaryPrizeCount296129612961296114
AftercanaryPrizeCount295629562956295614
File: prize-pool/src/libraries/TierCalculationLib.sol
40:     uint256 _numberOfPrizes = 4 ** _tier;
41: 
42:     return _numberOfPrizes;

recommended code :

40:     return 4 ** _tier;

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculationLib.sol#L40-L42

13) prize-pool/src/libraries/TierCalculationLib.sol :: canaryPrizeCount()

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

avg. gas saving : 39
Functionminavgmedmaxcalls
BeforecanaryPrizeCount296129612961296114
AftercanaryPrizeCount292229222922292214
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)));

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculationLib.sol#L58-L63

14) prize-pool/src/libraries/TierCalculationLib.sol :: isWinner()

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
BeforetestIsWinner_HalfLiquidity() (gas: 2276605)
AftertestIsWinner_HalfLiquidity() (gas: 2257149)
test contract's test function
BeforetestIsWinner_WinsAll() (gas: 2315780)
AftertestIsWinner_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));

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculationLib.sol#L92-L95

15) prize-pool/src/libraries/UD34x4.sol :: intoUD60x18()

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
BeforetestIntoUD60x18() (gas: 393)
AftertestIntoUD60x18() (gas: 388)
test contract's test function
BeforetestIntoUD60x18_large() (gas: 329)
AftertestIntoUD60x18_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));

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/UD34x4.sol#L22-L23

16) prize-pool/src/PrizePool.sol :: calculateTierTwabTimestamps()

Here, tierOdds and durationInSeconds cached in line 686 and 687 has been used only once in line 687 and 691 respectively

avg. gas saving : 23
Functionminavgmedmaxcalls
BeforecalculateTierTwabTimestamps223122702260295733
AftercalculateTierTwabTimestamps220822472237293433
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:     );

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L686-L692

17) twab-controller/src/libraries/TwabLib.sol :: _getNextObservationIndex(), this function gas saving has been calculated using 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

avg. gas saving : 10
Functionminavgmedmaxcalls
BeforedecreaseBalances188923370283342833416
AfterdecreaseBalances188923360283212832116
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:     )) {

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L372-L381

18) twab-controller/src/libraries/TwabLib.sol :: _isTimeSafe(), this function gas saving has been calculated using 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

avg. gas saving : 1
Functionminavgmedmaxcalls
BeforeisTimeSafe987215321361747919572
AfterisTimeSafe987215221361745319572
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:     );

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L718-L730

19 twab-controller/src/libraries/TwabLib.sol :: getBalanceAt()

Here, prevOrAtObservation cached in line 269 has been used only once in line 275

avg. gas saving : 5
Functionminavgmedmaxcalls
BeforegetBalanceAt121921061956455411
AftergetBalanceAt121421011951454911
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;

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L269-L275

[G-02] : Use do while loops instead of for loops

A 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

1) claimer/src/Claimer.sol :: claimPrizes()

avg. gas saving : 44
Functionminavgmedmaxcalls
BeforeclaimPrizes95211091810096139624
AfterclaimPrizes94801087410055139074
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);

https://github.com/GenerationSoftware/pt-v5-claimer/blob/57a381aef690a27c9198f4340747155a71cae753/src/Claimer.sol#L68-L70

2) claimer/src/Claimer.sol :: _computeFeePerClaim(), this function gas saving has been calculated using computeFeePerClaim function as it is an internal function used by computeFeePerClaim

avg. gas saving : 43
Functionminavgmedmaxcalls
BeforecomputeFeePerClaim58165829582958422
AftercomputeFeePerClaim57725786578658012
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);

https://github.com/GenerationSoftware/pt-v5-claimer/blob/57a381aef690a27c9198f4340747155a71cae753/src/Claimer.sol#L144-L153

3) prize-pool/src/abstract/TieredLiquidityDistributor.sol :: _nextDraw(), this function gas saving has been calculated using nextDraw function as it is an internal function used by nextDraw

avg. gas saving : 73
Functionminavgmedmaxcalls
BeforenextDraw526683063542933058319
AfternextDraw526682333537633036219
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);

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/abstract/TieredLiquidityDistributor.sol#L361-L369

4) prize-pool/src/abstract/TieredLiquidityDistributor.sol :: _getTierLiquidityToReclaim(), this function gas saving has been calculated using getTierLiquidityToReclaim function as it is an internal function used by getTierLiquidityToReclaim

avg. gas saving : 73
Functionminavgmedmaxcalls
BeforegetTierLiquidityToReclaim28624575457962863
AftergetTierLiquidityToReclaim28064502450661963
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);

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/abstract/TieredLiquidityDistributor.sol#L630-L639

5) prize-pool/src/libraries/TierCalculationLib.sol :: estimatedClaimCount()

avg. gas saving : 165
Functionminavgmedmaxcalls
BeforeestimatedClaimCount3853018053118023832268815
AfterestimatedClaimCount3846318036618007332242515
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);

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculationLib.sol#L138-L144

6) vault/src/Vault.sol :: claimPrizes()

avg. gas saving : 50
Functionminavgmedmaxcalls
BeforeclaimPrizes14474989599174755
AfterclaimPrizes14474939590973935
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);

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L618-L629

[G-03] : Avoid explicit initialization of variable in for loop

In 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

1) claimer/src/Claimer.sol :: claimPrizes()

File: claimer/src/Claimer.sol
68:     for (uint i = 0; i < winners.length; i++) {

https://github.com/GenerationSoftware/pt-v5-claimer/blob/57a381aef690a27c9198f4340747155a71cae753/src/Claimer.sol#L68

2) claimer/src/Claimer.sol :: _computeFeePerClaim()

File: claimer/src/Claimer.sol
144:     for (uint i = 0; i < _claimCount; i++) {

https://github.com/GenerationSoftware/pt-v5-claimer/blob/57a381aef690a27c9198f4340747155a71cae753/src/Claimer.sol#L144

3) prize-pool/src/libraries/TierCalculationLib.sol :: _getTierLiquidityToReclaim()

File: prize-pool/src/libraries/TierCalculationLib.sol
138:     for (uint8 i = 0; i < _numberOfTiers; i++) {

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/TierCalculationLib.sol#L138-

4) vault/src/Vault.sol :: claimPrizes()

File: vault/src/Vault.sol
618:     for (uint w = 0; w < _winners.length; w++) {
620:       for (uint p = 0; p < prizeIndicesLength; p++) {

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L618

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L620

[G-04] : Don't calculate constants

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

1) prize-pool/src/abstract/TieredLiquidityDistributor.sol :: Line 84-200

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.

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/abstract/TieredLiquidityDistributor.sol#L84-L200

2) twab-controller/src/TwabController.sol :: Line 24

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

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/TwabController.sol#L24

[G-05] : Fail as early as possible

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

1) prize-pool/src/abstract/TieredLiquidityDistributor.sol :: constructor(), move Lines 320-325 to the top of the constructor

File: 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:     }

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/abstract/TieredLiquidityDistributor.sol#L320-L325

2) vault/src/Vault.sol :: mintYieldFee(), move line 396 to the top of the function

File: vault/src/Vault.sol
395:     _requireVaultCollateralized();
396:     if (_shares > _yieldFeeTotalSupply) revert YieldFeeGTAvailable(_shares, _yieldFeeTotalSupply);

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L395-L396

[G-06] : Gas savings can be achieved by changing the model for assigning value to the storage structure

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

1) prize-pool/src/libraries/DrawAccumulatorLib.sol :: add(),this function gas saving has been tested using the --via-ir flag in foundry to avoid stack too deep error

avg. gas saving : 244
Functionminavgmedmaxcalls
Beforeadd506851038699310758323
Afteradd506848598672510731523
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;

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L91-L94

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L100-L103

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L106-L109

[G-07] : Save a storage variable reference instead of repeatedly fetching the value in a mapping or an array

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

Estimated gas savings : 8 * 40 = 320

1) prize-pool/src/libraries/DrawAccumulatorLib.sol :: add(), 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:       });

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L82-L109

2) prize-pool/src/PrizePool.sol :: claimPrize(), 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

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L434-L438

3) prize-pool/src/PrizePool.sol :: withdrawClaimRewards(), 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

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L484-L490

4) twab-controller/src/libraries/TwabLib.sol :: _isTimeSafe(), _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: }

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L698-L701

[G-08] : Use a positive conditional flow to save a NOT opcode

By switiching to a positive conditional flow in if statement, a NOT opcode(costing 3 gas) can be saved

2 instances in 2 files

Estimated gas savings : Minimum (3 * 2 = 6)

1) prize-pool/src/libraries/DrawAccumulatorLib.sol :: binarySearch()

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:       }

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L459-L464

2) twab-controller/src/libraries/ObservationLib.sol :: binarySearch()

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:       }

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/ObservationLib.sol#L93-L98

[G-09] : Use assembly to write address storage varibales

7 instances in 2 files

1) prize-pool/src/PrizePool.sol :: constructor()

File: prize-pool/src/PrizePool.sol
278:     drawManager = params.drawManager;

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L278

2) prize-pool/src/PrizePool.sol :: setDrawManager()

File: prize-pool/src/PrizePool.sol
303:     drawManager = _drawManager;

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L303

3) vault/src/Vault.sol :: constructor()

File: vault/src/Vault.sol
270: 
271:     _twabController = twabController_;
272:     _yieldVault = yieldVault_;
273:     _prizePool = prizePool_;

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L271-L273

4) vault/src/Vault.sol :: _setClaimer()

File: vault/src/Vault.sol
1210:     _claimer = claimer_;

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L1210

5) vault/src/Vault.sol :: _setYieldFeeRecipient()

File: vault/src/Vault.sol
1230:     _yieldFeeRecipient = yieldFeeRecipient_;

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L1230

[G-10] : Emitting events can be rearranged to save gas

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

Estimated gas saving : 3 * 16 = 48

1) vault/src/Vault.sol :: setClaimer()

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_);

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L642-L645

2) vault/src/Vault.sol :: setYieldFeePercentage()

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_);

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L692-L695

3) vault/src/Vault.sol :: setYieldFeeRecipient()

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_);

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L705-L708

[G-11] : Avoid emitting storage variables when possible

1 instance in 1 files

1) prize-pool/src/PrizePool.sol :: closeDraw()

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

Estimated gas saving : 1 SLOAD(Gwarm access : 100 gas)
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:     );

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L364-L383

[G-12] : Cache calldata/memory pointers for complex types to avoid complex offset calculations

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

1) prize-pool/src/libraries/DrawAccumulatorLib.sol :: add(), cache ringBufferInfo.nextIndex, ringBufferInfo.cardinality, newestObservation_.available and newestObservation_.disbursed

avg. gas saving : 52
Functionminavgmedmaxcalls
Beforeadd506851038699310758323
Afteradd503850518693510752523
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

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L75-L108

2) prize-pool/src/libraries/DrawAccumulatorLib.sol :: getDisbursedBetween(), cache 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.

avg. gas saving : 105
Functionminavgmedmaxcalls
BeforegetDisbursedBetween59113628133873640914
AftergetDisbursedBetween58313523132483622914
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

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L178

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L190

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L194

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L229

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L231

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L235

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L242

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L244

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L245

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L248

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L260

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L261

3) prize-pool/src/libraries/DrawAccumulatorLib.sol :: computeIndices(), cache ringBufferInfo.nextIndex and ringBufferInfo.cardinality

This function gas saving has been calculated using getDisbursedBetween function as it is an internal function used by getDisbursedBetween

avg. gas saving : 10
Functionminavgmedmaxcalls
BeforegetDisbursedBetween61915114153373999914
AftergetDisbursedBetween61915104153263998814
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:         )

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/libraries/DrawAccumulatorLib.sol#L348-L356

4) prize-pool/src/PrizePool.sol :: constructor(), cache 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

Estimated gas saving : 103

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:     }

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L268-L290

5) prize-pool/src/PrizePool.sol :: claimPrize(), cache tierLiquidity.prizeSize

Here, only the lines involving the required parameters have been shown as the function is too long.

avg. gas saving : 61
Functionminavgmedmaxcalls
BeforeclaimPrize484012775214963817423432
AfterclaimPrize484912769114957017416632
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

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L417-L418

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L451

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L459

https://github.com/GenerationSoftware/pt-v5-prize-pool/blob/4bc8a12b857856828c018510b5500d722b79ca3a/src/PrizePool.sol#L475

6) twab-controller/src/libraries/TwabLib.sol :: _getNextObservationIndex(), cache newestObservation.timestamp

This function gas saving has been calculated using decreaseBalances function as it is an internal function used by decreaseBalances

avg. gas saving : 3
Functionminavgmedmaxcalls
BeforedecreaseBalances188923370283342833416
AfterdecreaseBalances188923367283302833016
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:     );

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L367-L376

7) twab-controller/src/libraries/TwabLib.sol :: _getPreviousOrAtObservation(), cache _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

avg. gas saving : 2
Functionminavgmedmaxcalls
BeforegetPreviousOrAtObservation2226621948251678223
AftergetPreviousOrAtObservation2235621748161677323
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

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L485

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L492

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L497

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L498

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L524

8) twab-controller/src/libraries/TwabLib.sol :: _getNextOrNewestObservation(), cache _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

avg. gas saving : 3
Functionminavgmedmaxcalls
BeforegetNextOrNewestObservation1943533328201691916
AftergetNextOrNewestObservation1948533028161690616
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

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L572

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L587

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L608

9) twab-controller/src/libraries/TwabLib.sol :: _isTimeSafe(), cache _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

avg. gas saving : 1
Functionminavgmedmaxcalls
BeforeisTimeSafe987215321361747919572
AfterisTimeSafe998215221351747819572
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

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L690

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/libraries/TwabLib.sol#L696

#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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter