PoolTogether V5: Part Deux - Rolezn's results

A protocol for no-loss prize savings.

General Information

Platform: Code4rena

Start Date: 02/08/2023

Pot Size: $42,000 USDC

Total HM: 13

Participants: 45

Period: 5 days

Judge: hickuphh3

Total Solo HM: 5

Id: 271

League: ETH

PoolTogether

Findings Distribution

Researcher Performance

Rank: 11/45

Findings: 2

Award: $743.02

QA:
grade-a
Gas:
grade-a

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: 0xmystery

Also found by: 0xbepresent, MohammedRizwan, Rolezn, bin2chen, rvierdiiev

Labels

bug
grade-a
high quality report
QA (Quality Assurance)
sponsor confirmed
Q-05

Awards

284.0095 USDC - $284.01

External Links

QA Summary<a name="QA Summary">

Low Risk Issues

IssueContexts
LOW‑1Function can risk gas exhaustion on large receipt calls due to multiple mandatory loops1
LOW‑2Functions should be marked as payable when interacting with ETH like for transfering ETH to other contracts1
LOW‑3Large transfers may not work with some ERC20 tokens5
LOW‑4Unsafe downcast10
LOW‑5Using zero as a parameter8

Total: 25 contexts over 5 issues

Non-critical Issues

IssueContexts
NC‑1Condition will not revert when block.timestamp is == to the compared variable2
NC‑2Functions that alter state should emit events2
NC‑3Generate perfect code headers for better solidity code layout and readability2
NC‑4Initial value check is missing in Set Functions2
NC‑5Redundant Cast1
NC‑6Remove forge-std import1
NC‑7Remove unused error definition1
NC‑8Use named function calls1
NC‑9Zero as a function argument should have a descriptive meaning1

Total: 13 contexts over 9 issues

Low Risk Issues

<a href="#qa-summary">[LOW‑1]</a><a name="LOW&#x2011;1"> Function can risk gas exhaustion on large receipt calls due to multiple mandatory loops

The function contains loops over arrays that the user cannot influence. In any call for the function, users spend gas to iterate over the array. There are loops in the following functions that iterate all array values. Which could risk gas exhaustion on large array calls.

This risk is small, but could be lessened somewhat by allowing separate calls for small loops. Consider allowing partial calls via array arguments, or detecting expensive call bundles and only partially iterating over them. Since the logic already filters out calls, this would make the later loops smaller.

<ins>Proof Of Concept</ins>
File: RngRelayAuction.sol

131: function rngComplete(
    ...

    for (uint8 i = 0; i < _rewards.length; i++) {
      uint104 _reward = uint104(_rewards[i]);
      if (_reward > 0) {
        prizePool.withdrawReserve(auctionResults[i].recipient, _reward);
        emit AuctionRewardDistributed(_sequenceId, auctionResults[i].recipient, i, _reward);
      }
    }

    return bytes32(uint(drawId));
  }

https://github.com/GenerationSoftware/pt-v5-draw-auction/tree/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L131

<a href="#qa-summary">[LOW‑2]</a><a name="LOW&#x2011;2"> Functions should be marked as payable when interacting with ETH like for transfering ETH to other contracts

<ins>Proof Of Concept</ins>
File: RemoteOwner.sol

63: function execute(address target, uint256 value, bytes calldata data) external returns (bytes memory) {
    ...
    (bool success, bytes memory returnData) = target.call{ value: value }(data);

https://github.com/GenerationSoftware/remote-owner/tree/285749ab51e98afc8ebb4e4049a4348d669a3e9d/src/RemoteOwner.sol#L63

<a href="#qa-summary">[LOW‑3]</a><a name="LOW&#x2011;3"> Large transfers may not work with some ERC20 tokens

Some IERC20 implementations (e.g UNI, COMP) may fail if the valued transferred is larger than uint96. Source

<ins>Proof Of Concept</ins>
<details>
File: LiquidationRouter.sol

69: IERC20(_liquidationPair.tokenIn()).safeTransferFrom(

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/tree/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationRouter.sol#L69

File: RngAuction.sol

182: IERC20(_feeToken).transferFrom(msg.sender, address(this), _requestFee);

https://github.com/GenerationSoftware/pt-v5-draw-auction/tree/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L182

File: VaultBooster.sol

173: _token.safeTransferFrom(msg.sender, address(this), _amount);

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L173

File: VaultBooster.sol

193: _token.transfer(msg.sender, _amount);

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L193

File: VaultBooster.sol

226: IERC20(_tokenOut).safeTransfer(_account, _amountOut);

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L226

</details>

<a href="#qa-summary">[LOW‑4]</a><a name="LOW&#x2011;4"> Unsafe Downcast

When a type is downcast to a smaller type, the higher order bits are truncated, effectively applying a modulo to the original value. Without any other checks, this wrapping will lead to unexpected behavior and bugs

<ins>Proof Of Concept</ins>
File: RngAuction.sol

358: return uint64(block.timestamp);

https://github.com/GenerationSoftware/pt-v5-draw-auction/tree/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L358

File: RngRelayAuction.sol

139: uint64 _auctionElapsedSeconds = uint64(block.timestamp < _rngCompletedAt ? 0 : block.timestamp - _rngCompletedAt);

https://github.com/GenerationSoftware/pt-v5-draw-auction/tree/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L139

File: VaultBooster.sol

154: lastAccruedAt: uint48(block.timestamp)
154: uint48(block.timestamp)
163: uint48(block.timestamp)
224: uint48(block.timestamp)
252: uint48(block.timestamp)
224: _boosts[IERC20(_tokenOut)].lastAccruedAt = uint48(block.timestamp);
252: _boosts[_tokenOut].lastAccruedAt = uint48(block.timestamp);
273: uint256 totalSupply = twabController.getTotalSupplyTwabBetween(address(vault), uint32(boost.lastAccruedAt), uint32(block.timestamp));

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L154

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L154

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L163

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L224

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L252

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L224

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L252

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L273

<a href="#qa-summary">[LOW‑5]</a><a name="LOW&#x2011;5"> Using zero as a parameter

Taking 0 as a valid argument in Solidity without checks can lead to severe security issues. A historical example is the infamous 0x0 address bug where numerous tokens were lost. This happens because 0 can be interpreted as an uninitialized address, leading to transfers to the 0x0 address, effectively burning tokens. Moreover, 0 as a denominator in division operations would cause a runtime exception. It's also often indicative of a logical error in the caller's code. It's important to always validate input and handle edge cases like 0 appropriately. Use require() statements to enforce conditions and provide clear error messages to facilitate debugging and safer code.

<ins>Proof Of Concept</ins>
<details>
File: LiquidationPair.sol

289: return wrap(0);

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/tree/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationPair.sol#L289

File: LiquidationPair.sol

357: _initialPrice = wrap(0);

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/tree/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationPair.sol#L357

File: ContinuousGDA.sol

31: return SD59x18.wrap(0);
62: return SD59x18.wrap(0);

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/tree/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/libraries/ContinuousGDA.sol#L31

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/tree/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/libraries/ContinuousGDA.sol#L62

File: RngAuctionRelayerRemoteOwner.sol

65: RemoteOwnerCallEncoder.encodeCalldata(address(_remoteRngAuctionRelayListener), 0, listenerCalldata)

https://github.com/GenerationSoftware/pt-v5-draw-auction/tree/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuctionRelayerRemoteOwner.sol#L65

</details>

Non Critical Issues

<a href="#qa-summary">[NC‑1]</a><a name="NC&#x2011;1"> Condition will not revert when block.timestamp is == to the compared variable

The condition does not revert when block.timestamp is equal to the compared > or < variable. For example, if there is a check for block.timestamp > _lastAuctionTime then there should be a check for cases where block.timestamp is == to _lastAuctionTime

<ins>Proof Of Concept</ins>
File: LiquidationPair.sol

288: if (block.timestamp < _lastAuctionTime) {

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/tree/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationPair.sol#L288

File: RngRelayAuction.sol

139: uint64 _auctionElapsedSeconds = uint64(block.timestamp < _rngCompletedAt ? 0 : block.timestamp - _rngCompletedAt);

https://github.com/GenerationSoftware/pt-v5-draw-auction/tree/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L139

<a href="#qa-summary">[NC‑2]</a><a name="NC&#x2011;2"> Functions that alter state should emit events

<ins>Proof Of Concept</ins>
File: RngAuction.sol

347: function setNextRngService(RNGInterface _rngService) external onlyOwner {
    _setNextRngService(_rngService);
  }

https://github.com/GenerationSoftware/pt-v5-draw-auction/tree/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L347

File: RemoteOwner.sol

96: function setOriginChainOwner(address _newOriginChainOwner) external {
    _checkSender();
    _setOriginChainOwner(_newOriginChainOwner);
  }

https://github.com/GenerationSoftware/remote-owner/tree/285749ab51e98afc8ebb4e4049a4348d669a3e9d/src/RemoteOwner.sol#L96

<a href="#qa-summary">[NC‑3]</a><a name="NC&#x2011;3"> Generate perfect code headers for better solidity code layout and readability

It is recommended to use pre-made headers for Solidity code layout and readability: https://github.com/transmissions11/headers

/*//////////////////////////////////////////////////////////////
                           TESTING 123
//////////////////////////////////////////////////////////////*/
<ins>Proof Of Concept</ins>
File: LiquidationPair.sol

5: LiquidationPair.sol

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/tree/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationPair.sol#L5

File: ContinuousGDA.sol

10: ContinuousGDA.sol

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/tree/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/libraries/ContinuousGDA.sol#L10

<a href="#qa-summary">[NC‑4]</a><a name="NC&#x2011;4"> Initial value check is missing in Set Functions

A check regarding whether the current value and the new value are the same should be added

<ins>Proof Of Concept</ins>
File: RngAuction.sol

347: function setNextRngService(RNGInterface _rngService) external onlyOwner {
    _setNextRngService(_rngService);
  }

https://github.com/GenerationSoftware/pt-v5-draw-auction/tree/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L347

File: RemoteOwner.sol

96: function setOriginChainOwner(address _newOriginChainOwner) external {
    _checkSender();
    _setOriginChainOwner(_newOriginChainOwner);
  }

https://github.com/GenerationSoftware/remote-owner/tree/285749ab51e98afc8ebb4e4049a4348d669a3e9d/src/RemoteOwner.sol#L96

<a href="#qa-summary">[NC‑5]</a><a name="NC&#x2011;5"> Redundant Cast

The type of the variable is the same as the type to which the variable is being cast

<ins>Proof Of Concept</ins>
File: VaultBooster.sol

192: IERC20(_token)

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L192

<a href="#qa-summary">[NC‑6]</a><a name="NC&#x2011;6"> Remove forge-std import

forge-std is used for logging and debugging purposes and should be removed when not used for development.

<ins>Proof Of Concept</ins>
File: VaultBooster.sol

4: import "forge-std/console2.sol";

https://github.com/GenerationSoftware/pt-v5-vault-boost/tree/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L4

<a href="#qa-summary">[NC‑7]</a><a name="NC&#x2011;7"> Remove unused error definition

Note that there may be cases where an error superficially appears to be used, but this is only because there are multiple definitions of the error in different files. In such cases, the error definition should be moved into a separate file. The instances below are the unused definitions.

<ins>Proof Of Concept</ins>
File: RngAuction.sol

error AuctionDurationGteSequencePeriod

https://github.com/GenerationSoftware/pt-v5-draw-auction/tree/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol

<a href="#qa-summary">[NC‑8]</a><a name="NC&#x2011;8"> Use named function calls

Code base has an extensive use of named function calls, but it somehow missed one instance where this would be appropriate.

It should use named function calls on function call, as such:

    library.exampleFunction{value: _data.amount.value}({
        _id: _data.id,
        _amount: _data.amount.value,
        _token: _data.token,
        _example: "",
        _metadata: _data.metadata
    });
<ins>Proof Of Concept</ins>
File: RemoteOwner.sol

67: (bool success, bytes memory returnData) = target.call{ value: value }(data);

https://github.com/GenerationSoftware/remote-owner/tree/285749ab51e98afc8ebb4e4049a4348d669a3e9d/src/RemoteOwner.sol#L67

<a href="#qa-summary">[NC‑9]</a><a name="NC&#x2011;9"> Zero as a function argument should have a descriptive meaning

Consider using descriptive constants or an enum instead of passing zero directly on function calls, as that might be error-prone, to fully describe the caller's intention.

<ins>Proof Of Concept</ins>
File: RngAuctionRelayerRemoteOwner.sol

65: RemoteOwnerCallEncoder.encodeCalldata(address(_remoteRngAuctionRelayListener), 0, listenerCalldata)

https://github.com/GenerationSoftware/pt-v5-draw-auction/tree/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuctionRelayerRemoteOwner.sol#L65

#0 - c4-pre-sort

2023-08-08T23:41:55Z

raymondfam marked the issue as high quality report

#1 - c4-sponsor

2023-08-09T19:29:40Z

asselstine marked the issue as sponsor confirmed

#2 - c4-judge

2023-08-14T10:15:53Z

HickupHH3 marked the issue as grade-a

Findings Information

🌟 Selected for report: Rolezn

Also found by: 0xhex, 0xta, JCK, K42, Rageur, Raihan, ReyAdmirado, SAQ, SY_S, dharma09, hunter_w3b, petrichor, shamsulhaq123, wahedtalash77

Labels

bug
G (Gas Optimization)
grade-a
high quality report
selected for report
sponsor confirmed
G-14

Awards

459.0115 USDC - $459.01

External Links

GAS Summary<a name="GAS Summary">

Gas Optimizations

IssueContextsEstimated Gas Saved
GAS‑1Avoid emitting event on every iteration1375
GAS‑2Counting down in for statements is more gas efficient2514
GAS‑3Use assembly to write address storage values11814
GAS‑4Multiple accesses of a mapping/array should use a local variable cache8640
GAS‑5Remove forge-std import1100
GAS‑6The result of a function call should be cached rather than re-calling the function5250
GAS‑7Use do while loops instead of for loops28
GAS‑8Use nested if and avoid multiple check combinations212
GAS‑9Using XOR (^) and AND (&) bitwise equivalents678

Total: 44 contexts over 9 issues

Gas Optimizations

<a href="#gas-summary">[GAS‑1]</a><a name="GAS&#x2011;1"> Avoid emitting event on every iteration

Expensive operations should always try to be avoided within loops. Such operations include: reading/writing to storage, heavy calculations, external calls, and emitting events. In this instance, an event is being emitted every iteration. Events have a base cost of Glog (375 gas) per emit and Glogdata (8 gas) * number of bytes in event. We can avoid incurring those costs each iteration by emitting the event outside of the loop.

<ins>Proof Of Concept</ins>
File: RngRelayAuction.sol

167: for (uint8 i = 0; i < _rewards.length; i++) {
      uint104 _reward = uint104(_rewards[i]);
      if (_reward > 0) {
        prizePool.withdrawReserve(auctionResults[i].recipient, _reward);
        emit AuctionRewardDistributed(_sequenceId, auctionResults[i].recipient, i, _reward);
      }

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/RngRelayAuction.sol#L167

<a href="#gas-summary">[GAS‑2]</a><a name="GAS&#x2011;2"> Counting down in for statements is more gas efficient

Counting down is more gas efficient than counting up because neither we are making zero variable to non-zero variable and also we will get gas refund in the last transaction when making non-zero to zero variable.

<ins>Proof Of Concept</ins>
File: RngRelayAuction.sol

167: for (uint8 i = 0; i < _rewards.length; i++) {

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/RngRelayAuction.sol#L167

File: RewardLib.sol

65: for (uint256 i; i < _auctionResultsLength; i++) {

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/libraries/RewardLib.sol#L65

Test Code
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.AddNum(); c1.AddNum(); } } contract Contract0 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=0;i<=9;i++){ _num = _num +1; } num = _num; } } contract Contract1 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=9;i>=0;i--){ _num = _num +1; } num = _num; } }
Gas Test Report
Contract0 contract
Deployment CostDeployment Size
77011311
Function Nameminavgmedianmax# calls
AddNum70407040704070401
Contract1 contract
Deployment CostDeployment Size
73811295
Function Nameminavgmedianmax# calls
AddNum38193819381938191

<a href="#gas-summary">[GAS‑3]</a><a name="GAS&#x2011;3"> Use assembly to write address storage values

<ins>Proof Of Concept</ins>
<details>
File: LiquidationPair.sol

130: _lastNonZeroAmountIn = _initialAmountIn;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-cgda-liquidator/src/LiquidationPair.sol#L130

File: LiquidationPair.sol

131: _lastNonZeroAmountOut = _initialAmountOut;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-cgda-liquidator/src/LiquidationPair.sol#L131

File: LiquidationPair.sol

334: _lastNonZeroAmountIn = _amountInForPeriod;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-cgda-liquidator/src/LiquidationPair.sol#L334

File: LiquidationPair.sol

335: _lastNonZeroAmountOut = _amountOutForPeriod;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-cgda-liquidator/src/LiquidationPair.sol#L335

File: LiquidationPair.sol

342: _emissionRate = emissionRate_;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-cgda-liquidator/src/LiquidationPair.sol#L342

File: LiquidationRouter.sol

50: _liquidationPairFactory = liquidationPairFactory_;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-cgda-liquidator/src/LiquidationRouter.sol#L50

File: RngAuction.sol

435: _nextRng = _newRng;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/RngAuction.sol#L435

File: RngRelayAuction.sol

117: _auctionDurationSeconds = auctionDurationSeconds_;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/RngRelayAuction.sol#L117

File: RngRelayAuction.sol

145: _lastSequenceId = _sequenceId;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/RngRelayAuction.sol#L145

File: RemoteOwner.sol

57: _originChainId = originChainId_;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/remote-owner/src/RemoteOwner.sol#L57

File: RemoteOwner.sol

106: _originChainOwner = _newOriginChainOwner;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/remote-owner/src/RemoteOwner.sol#L106

</details>

<a href="#gas-summary">[GAS‑4]</a><a name="GAS&#x2011;4"> Multiple accesses of a mapping/array should use a local variable cache

Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times saves ~42 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[someIndex], save its reference like this: SomeStruct storage someStruct = someMap[someIndex] and use it.

<ins>Proof Of Concept</ins>
File: RngRelayAuction.sol

170: prizePool.withdrawReserve(auctionResults[i].recipient, _reward);
171: emit AuctionRewardDistributed(_sequenceId, auctionResults[i].recipient, i, _reward);

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/RngRelayAuction.sol#L170-L171

File: AddressRemapper.sol

33: if (_destinationAddress[_addr] == address(0)) {
36: return _destinationAddress[_addr];

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/abstract/AddressRemapper.sol#L33

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/abstract/AddressRemapper.sol#L36

File: VaultBooster.sol

223: _boosts[IERC20(_tokenOut)].available = amountAvailable.toUint144();
224: _boosts[IERC20(_tokenOut)].lastAccruedAt = uint48(block.timestamp);

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-vault-boost/src/VaultBooster.sol#L223-L224

File: VaultBooster.sol

251: _boosts[_tokenOut].available = available.toUint144();
252: _boosts[_tokenOut].lastAccruedAt = uint48(block.timestamp);

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-vault-boost/src/VaultBooster.sol#L251-L252

<a href="#gas-summary">[GAS‑5]</a><a name="GAS&#x2011;5"> Remove import forge-std

It's used to print the values of variables while running tests to help debug and see what's happening inside your contracts but since it's a development tool, it serves no purpose on mainnet. Also, the remember to remove the usage of calls that use forge-std when removing of the import of forge-std.

<ins>Proof Of Concept</ins>
File: VaultBooster.sol

4: import "forge-std/console2.sol";

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-vault-boost/src/VaultBooster.sol#L4

<a href="#gas-summary">[GAS‑6]</a><a name="GAS&#x2011;6"> The result of a function call should be cached rather than re-calling the function

External calls are expensive. Results of external function calls should be cached rather than call them multiple times. Consider caching the following:

<ins>Proof Of Concept</ins>
File: RngAuction.sol

370: uint64 currentTime = _currentTime();
382: uint64 currentTime = _currentTime();
386: return (_currentTime() - sequenceOffset) % sequencePeriod;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/RngAuction.sol#L370

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/RngAuction.sol#L382

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/RngAuction.sol#L386

File: RemoteOwner.sol

119: if (_fromChainId() != _originChainId) revert OriginChainIdUnsupported(_fromChainId());
120: if (_msgSender() != address(_originChainOwner)) revert OriginSenderNotOwner(_msgSender());

https://github.com/code-423n4/2023-08-pooltogether/tree/main/remote-owner/src/RemoteOwner.sol#L119

https://github.com/code-423n4/2023-08-pooltogether/tree/main/remote-owner/src/RemoteOwner.sol#L120

<a href="#gas-summary">[GAS‑7]</a><a name="GAS&#x2011;7"> Use do while loops instead of for loops

A do while loop will cost less gas since the condition is not being checked for the first iteration.

<ins>Proof Of Concept</ins>
File: RngRelayAuction.sol

131: function rngComplete

for (uint8 i = 0; i < _rewards.length; i++) {
      uint104 _reward = uint104(_rewards[i]);
      if (_reward > 0) {
        prizePool.withdrawReserve(auctionResults[i].recipient, _reward);
        emit AuctionRewardDistributed(_sequenceId, auctionResults[i].recipient, i, _reward);
      }

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/RngRelayAuction.sol#L132

File: RewardLib.sol

58: function rewards

for (uint256 i; i < _auctionResultsLength; i++) {
      _rewards[i] = reward(_auctionResults[i], remainingReserve);
      remainingReserve = remainingReserve - _rewards[i];
    }

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/libraries/RewardLib.sol#L59

<a href="#gas-summary">[GAS‑8]</a><a name="GAS&#x2011;8"> Use nested if and avoid multiple check combinations

Using nested if, is cheaper than using && multiple check combinations. There are more advantages, such as easier to read code and better coverage reports.

<ins>Proof Of Concept</ins>
File: LiquidationPair.sol

332: if (_amountInForPeriod > 0 && _amountOutForPeriod > 0) {

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-cgda-liquidator/src/LiquidationPair.sol#L332

File: RngAuction.sol

179: if (_feeToken != address(0) && _requestFee > 0) {

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/RngAuction.sol#L179

Test Code
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.checkAge(19); c1.checkAgeOptimized(19); } } contract Contract0 { function checkAge(uint8 _age) public returns(string memory){ if(_age>18 && _age<22){ return "Eligible"; } } } contract Contract1 { function checkAgeOptimized(uint8 _age) public returns(string memory){ if(_age>18){ if(_age<22){ return "Eligible"; } } } }
Gas Test Report
Contract0 contract
Deployment CostDeployment Size
76923416
Function Nameminavgmedianmax# calls
checkAge6516516516511
Contract1 contract
Deployment CostDeployment Size
76323413
Function Nameminavgmedianmax# calls
checkAgeOptimized6456456456451

<a href="#gas-summary">[GAS‑9]</a><a name="GAS&#x2011;9"> Using XOR (^) and AND (&) bitwise equivalents

Given 4 variables a, b, c and d represented as such:

0 0 0 0 0 1 1 0 <- a 0 1 1 0 0 1 1 0 <- b 0 0 0 0 0 0 0 0 <- c 1 1 1 1 1 1 1 1 <- d

To have a == b means that every 0 and 1 match on both variables. Meaning that a XOR (operator ^) would evaluate to 0 ((a ^ b) == 0), as it excludes by definition any equalities.Now, if a != b, this means that there’s at least somewhere a 1 and a 0 not matching between a and b, making (a ^ b) != 0.Both formulas are logically equivalent and using the XOR bitwise operator costs actually the same amount of gas.However, it is much cheaper to use the bitwise OR operator (|) than comparing the truthy or falsy values.These are logically equivalent too, as the OR bitwise operator (|) would result in a 1 somewhere if any value is not 0 between the XOR (^) statements, meaning if any XOR (^) statement verifies that its arguments are different.

<ins>Proof Of Concept</ins>
File: LiquidationPair.sol

298: if (_amountOut == 0) {
314: if (purchasePrice == 0) {

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-cgda-liquidator/src/LiquidationPair.sol#L298

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-cgda-liquidator/src/LiquidationPair.sol#L314

File: RewardLib.sol

83: if (_auctionResult.recipient == address(0)) return 0;
84: if (_reserve == 0) return 0;

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-draw-auction/src/libraries/RewardLib.sol#L83-L84

File: VaultBooster.sol

266: if (deltaTime == 0) {

https://github.com/code-423n4/2023-08-pooltogether/tree/main/pt-v5-vault-boost/src/VaultBooster.sol#L266

File: RemoteOwner.sol

104: if (_newOriginChainOwner == address(0)) revert OriginChainOwnerZeroAddress();

https://github.com/code-423n4/2023-08-pooltogether/tree/main/remote-owner/src/RemoteOwner.sol#L104

Test Code
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.not_optimized(1,2); c1.optimized(1,2); } } contract Contract0 { function not_optimized(uint8 a,uint8 b) public returns(bool){ return ((a==1) || (b==1)); } } contract Contract1 { function optimized(uint8 a,uint8 b) public returns(bool){ return ((a ^ 1) & (b ^ 1)) == 0; } }
Gas Test Report
Contract0 contract
Deployment CostDeployment Size
46099261
Function Nameminavgmedianmax# calls
not_optimized4564564564561
Contract1 contract
Deployment CostDeployment Size
42493243
Function Nameminavgmedianmax# calls
optimized4304304304301

#0 - c4-pre-sort

2023-08-09T00:01:25Z

raymondfam marked the issue as high quality report

#1 - c4-sponsor

2023-08-09T19:30:24Z

asselstine marked the issue as sponsor confirmed

#2 - c4-judge

2023-08-14T11:07:41Z

HickupHH3 marked the issue as selected for report

#3 - c4-judge

2023-08-14T11:07:45Z

HickupHH3 marked the issue as grade-a

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