Platform: Code4rena
Start Date: 01/12/2022
Pot Size: $26,900 USDC
Total HM: 3
Participants: 19
Period: 4 days
Judge: GalloDaSballo
Id: 188
League: ETH
Rank: 14/19
Findings: 1
Award: $338.92
🌟 Selected for report: 0
🚀 Solo Findings: 0
338.9167 USDC - $338.92
Per the sponsor's requirements, each optimization's actual gas report will be included. All gas findings are tested independently of each other, without taking other findings into account.
Initial gas report:
Final gas report (after applying all listed findings):
Number | Details | Instances |
---|---|---|
[G-01] | nonce can be uint96 to optimize storage read/writes | 2 |
[G-02] | Increments can be unchecked (and made postfix) | 4 |
nonce
can be uint96
to optimize storage read/writesTwo applicable instances:
File: src/ethereum-optimism/EthereumToOptimismRelayer.sol 29: uint256 internal nonce;
File: src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol 40: uint256 internal nonce;
The current storage layout for CrossChainRelayerArbitrum
and CrossChainRelayerOptimism
are as follow:
ICrossChainExecutor public executor; // storage slot 1 uint256 internal nonce; // storage slot 2
Interfaces are stored as an address in the storage, taking 160 bits. Therefore we can change nonce
data type to uint96
to fit in one storage slot.
ICrossChainExecutor public executor; // storage slot 1 uint96 internal nonce; // still storage slot 1
The change brings the following optimizations to relayCalls()
:
CrossChainRelayerOptimism
, saves a further ~$2000$ gas by replacing a Gcoldsload with Gwarmaccess, for both executor and nonce are accessed.This also should not bring any security issues, given the maximum value for uint96
is approx. $10^{27}$, which is more than enough.
Gas report for G-01: https://gist.github.com/midori-fuse/df2a7b756502d159b254c64a212c39b3
This finding builds upon [GAS-4] of C4 automated tool.
Four applicable instances (same with [GAS-4]):
File: src/ethereum-arbitrum/EthereumToArbitrumRelayer.sol 78: nonce++;
File: src/ethereum-optimism/EthereumToOptimismRelayer.sol 60: nonce++;
File: src/ethereum-polygon/EthereumToPolygonRelayer.sol 56: nonce++;
File: src/libraries/CallLib.sol 61: for (uint256 _callIndex; _callIndex < _callsLength; _callIndex++) {
nonce
instance, simply replaces with unchecked{++nonce;}
.CallLib
loop, change to:for (uint256 _callIndex; _callIndex < _callsLength;) { // for loop content unchecked{++_callsLength;} }
Saves about ~$40$ gas per unchecked operations. If call data's length is long, the gas saved will be significant.
Gas report for G-02 (not counting G-01): https://gist.github.com/midori-fuse/996c6e5f56b2678ed9acdc6ff01b1b80
#0 - GalloDaSballo
2022-12-15T00:38:48Z
Sweet find, awarding 5k for a non-zero write as that's what it will save in practice
100
5.1k
#1 - c4-judge
2022-12-26T23:14:07Z
GalloDaSballo marked the issue as grade-a