You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The following sections detail the gas optimizations found throughout the codebase. Each optimization is documented with the setup, an explainer for the optimization, a gas report and line identifiers for each optimization across the codebase. For each section's gas report, the optimizer was turned on and set to 10000 runs. You can replicate any tests/gas reports by heading to 0xKitsune/gas-lab and cloning the repo. Then, simply copy/paste the contract examples from any section and run forge test --gas-report. You can also easily update the optimizer runs in the foundry.toml.
Solidity >= 0.8.0 checks for overflow/underflow by default. Using Safemath when using version >= 0.8.0 is redundant and will incur additional gas costs. Instead of safemath, you can simply use Solidity's built in arithmetic. For further gas savings, you can also use assembly and check for overflow/underflow as seen below.
unchecked{++i} instead of i++ (or use assembly when applicable)
Use ++i instead of i++. This is especially useful in for loops but this optimization can be used anywhere in your code. You can also use unchecked{++i;} for even more gas savings but this will not check to see if i overflows. For extra safety if you are worried about this, you can add a require statement after the loop checking if i is equal to the final incremented value. For best gas savings, use inline assembly, however this limits the functionality you can achieve. For example you cant use Solidity syntax to internally call your own contract within an assembly block and external calls must be done with the call() or delegatecall() instruction. However when applicable, inline assembly will save much more gas.
contractGasTestisDSTest{Contract0c0;Contract1c1;Contract2c2;Contract3c3;Contract4c4;functionsetUp()public{c0=newContract0();c1=newContract1();c2=newContract2();c3=newContract3();c4=newContract4();}functiontestGas()public{c0.iPlusPlus();c1.plusPlusI();c2.uncheckedPlusPlusI();c3.safeUncheckedPlusPlusI();c4.inlineAssemblyLoop();}}contractContract0{//loop with i++functioniPlusPlus()publicpure{uint256j=0;for(uint256i;i<10;i++){j++;}}}contractContract1{//loop with ++ifunctionplusPlusI()publicpure{uint256j=0;for(uint256i;i<10;++i){j++;}}}contractContract2{//loop with unchecked{++i}functionuncheckedPlusPlusI()publicpure{uint256j=0;for(uint256i;i<10;){j++;unchecked{++i;}}}}contractContract3{//loop with unchecked{++i} with additional overflow checkfunctionsafeUncheckedPlusPlusI()publicpure{uint256j=0;uint256i=0;for(i;i<10;){j++;unchecked{++i;}}//check for overflowassembly{iflt(i,10){mstore(0x00,"loop overflow")revert(0x00,0x20)}}}}contractContract4{//loop with inline assemblyfunctioninlineAssemblyLoop()publicpure{assembly{letj :=0for{leti :=0}lt(i,10){i :=add(i,0x01)}{j :=add(j,0x01)}}}}
When creating structs, make sure that the variables are listed in ascending order by data type. The compiler will pack the variables that can fit into one 32 byte slot. If the variables are not listed in ascending order, the compiler may not pack the data into one slot, causing additional sload and sstore instructions when reading/storing the struct into the contract's storage.
Use assembly for math instead of Solidity. You can check for overflow/underflow in assembly to ensure safety. If using Solidity versions < 0.8.0 and you are using Safemath, you can gain significant gas savings by using assembly to calculate values and checking for overflow/underflow.
contractGasTestisDSTest{Contract0c0;Contract1c1;Contract2c2;Contract3c3;Contract4c4;Contract5c5;Contract6c6;Contract7c7;functionsetUp()public{c0=newContract0();c1=newContract1();c2=newContract2();c3=newContract3();c4=newContract4();c5=newContract5();c6=newContract6();c7=newContract7();}functiontestGas()public{c0.addTest(34598345,100);c1.addAssemblyTest(34598345,100);c2.subTest(34598345,100);c3.subAssemblyTest(34598345,100);c4.mulTest(34598345,100);c5.mulAssemblyTest(34598345,100);c6.divTest(34598345,100);c7.divAssemblyTest(34598345,100);}}contractContract0{//addition in SolidityfunctionaddTest(uint256a,uint256b)publicpure{uint256c=a+b;}}contractContract1{//addition in assemblyfunctionaddAssemblyTest(uint256a,uint256b)publicpure{assembly{letc :=add(a,b)iflt(c,a){mstore(0x00,"overflow")revert(0x00,0x20)}}}}contractContract2{//subtraction in SolidityfunctionsubTest(uint256a,uint256b)publicpure{uint256c=a-b;}}contractContract3{//subtraction in assemblyfunctionsubAssemblyTest(uint256a,uint256b)publicpure{assembly{letc :=sub(a,b)ifgt(c,a){mstore(0x00,"underflow")revert(0x00,0x20)}}}}contractContract4{//multiplication in SolidityfunctionmulTest(uint256a,uint256b)publicpure{uint256c=a*b;}}contractContract5{//multiplication in assemblyfunctionmulAssemblyTest(uint256a,uint256b)publicpure{assembly{letc :=mul(a,b)iflt(c,a){mstore(0x00,"overflow")revert(0x00,0x20)}}}}contractContract6{//division in SolidityfunctiondivTest(uint256a,uint256b)publicpure{uint256c=a*b;}}contractContract7{//division in assemblyfunctiondivAssemblyTest(uint256a,uint256b)publicpure{assembly{letc :=div(a,b)ifgt(c,a){mstore(0x00,"underflow")revert(0x00,0x20)}}}}
Use assembly when getting a contract's balance of ETH.
You can use selfbalance() instead of address(this).balance when getting your contract's balance of ETH to save gas. Additionally, you can use balance(address) instead of address.balance() when getting an external contract's balance of ETH.
Mark storage variables as constant if they never change.
State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile-time, while for immutable, it can still be assigned at construction time.
The compiler does not reserve a storage slot for these variables, and every occurrence is inlined by the respective value.
Compared to regular state variables, the gas costs of constant and immutable variables are much lower. For a constant variable, the expression assigned to it is copied to all the places where it is accessed and also re-evaluated each time. This allows for local optimizations. Immutable variables are evaluated once at construction time and their value is copied to all the places in the code where they are accessed. For these values, 32 bytes are reserved, even if they would fit in fewer bytes. Due to this, constant values can sometimes be cheaper than immutable values.
Mark storage variables as immutable if they never change after contract initialization.
State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile-time, while for immutable, it can still be assigned at construction time.
The compiler does not reserve a storage slot for these variables, and every occurrence is inlined by the respective value.
Compared to regular state variables, the gas costs of constant and immutable variables are much lower. For a constant variable, the expression assigned to it is copied to all the places where it is accessed and also re-evaluated each time. This allows for local optimizations. Immutable variables are evaluated once at construction time and their value is copied to all the places in the code where they are accessed. For these values, 32 bytes are reserved, even if they would fit in fewer bytes. Due to this, constant values can sometimes be cheaper than immutable values.
Gas Optimizations
The following sections detail the gas optimizations found throughout the codebase. Each optimization is documented with the setup, an explainer for the optimization, a gas report and line identifiers for each optimization across the codebase. For each section's gas report, the optimizer was turned on and set to 10000 runs. You can replicate any tests/gas reports by heading to 0xKitsune/gas-lab and cloning the repo. Then, simply copy/paste the contract examples from any section and run
forge test --gas-report
. You can also easily update the optimizer runs in thefoundry.toml
.Use assembly to hash instead of Solidity
Gas Report
Lines
BridgeFacet.sol:661
BridgeFacet.sol:736
BridgeFacet.sol:853
LibDiamond.sol:90
LibDiamond.sol:107
LibDiamond.sol:126
ConnextMessage.sol:178
Don't use SafeMath when using solidity >= 0.8.0
Solidity >= 0.8.0 checks for overflow/underflow by default. Using Safemath when using version >= 0.8.0 is redundant and will incur additional gas costs. Instead of safemath, you can simply use Solidity's built in arithmetic. For further gas savings, you can also use assembly and check for overflow/underflow as seen below.
Gas report
Lines
AmplificationUtils.sol:33
AmplificationUtils.sol:61
AmplificationUtils.sol:64
AmplificationUtils.sol:84
AmplificationUtils.sol:85
AmplificationUtils.sol:89
AmplificationUtils.sol:92
AmplificationUtils.sol:94
ConnextPriceOracle.sol:104
ConnextPriceOracle.sol:107
ConnextPriceOracle.sol:109
ConnextPriceOracle.sol:134
SwapUtils.sol:161
SwapUtils.sol:196
SwapUtils.sol:210
SwapUtils.sol:211
SwapUtils.sol:217
SwapUtils.sol:218
SwapUtils.sol:252
SwapUtils.sol:256
SwapUtils.sol:257
SwapUtils.sol:263
SwapUtils.sol:265
SwapUtils.sol:270
SwapUtils.sol:290
SwapUtils.sol:298
SwapUtils.sol:303
SwapUtils.sol:309
SwapUtils.sol:310
SwapUtils.sol:345
SwapUtils.sol:370
SwapUtils.sol:402
SwapUtils.sol:413
SwapUtils.sol:414
SwapUtils.sol:419
SwapUtils.sol:420
SwapUtils.sol:427
SwapUtils.sol:494
SwapUtils.sol:496
SwapUtils.sol:497
SwapUtils.sol:498
SwapUtils.sol:529
SwapUtils.sol:531
SwapUtils.sol:532
SwapUtils.sol:533
SwapUtils.sol:559
SwapUtils.sol:593
SwapUtils.sol:595
SwapUtils.sol:602
SwapUtils.sol:604
SwapUtils.sol:626
SwapUtils.sol:655
SwapUtils.sol:664
SwapUtils.sol:668
SwapUtils.sol:669
SwapUtils.sol:671
SwapUtils.sol:705
SwapUtils.sol:709
SwapUtils.sol:710
SwapUtils.sol:712
SwapUtils.sol:723
SwapUtils.sol:758
SwapUtils.sol:762
SwapUtils.sol:763
SwapUtils.sol:766
SwapUtils.sol:792
SwapUtils.sol:796
SwapUtils.sol:797
SwapUtils.sol:800
SwapUtils.sol:853
SwapUtils.sol:856
SwapUtils.sol:870
SwapUtils.sol:871
SwapUtils.sol:872
SwapUtils.sol:873
SwapUtils.sol:874
SwapUtils.sol:875
SwapUtils.sol:887
SwapUtils.sol:895
SwapUtils.sol:926
SwapUtils.sol:932
SwapUtils.sol:963
SwapUtils.sol:964
SwapUtils.sol:966
SwapUtils.sol:1015
SwapUtils.sol:1020
SwapUtils.sol:1022
SwapUtils.sol:1023
SwapUtils.sol:1024
SwapUtils.sol:1025
SwapUtils.sol:1026
SwapUtils.sol:1031
SwapUtils.sol:1033
SwapUtils.sol:1043
Use assembly to write storage values
Gas Report
Lines
ProposedOwnableUpgradeable.sol:292
ProposedOwnableUpgradeable.sol:297
ProposedOwnableUpgradeable.sol:298
ProposedOwnableUpgradeable.sol:303
ProposedOwnableUpgradeable.sol:308
ProposedOwnableUpgradeable.sol:309
ProposedOwnableUpgradeable.sol:315
ProposedOwnableUpgradeable.sol:316
ProposedOwnableUpgradeable.sol:321
ProposedOwnableUpgradeable.sol:322
ConnextPriceOracle.sol:77
ConnextPriceOracle.sol:78
ConnextPriceOracle.sol:165
ConnextPriceOracle.sol:170
SponsorVault.sol:161
SponsorVault.sol:309
Executor.sol:48
Executor.sol:150
Executor.sol:153
Executor.sol:169
Executor.sol:172
Use custom errors instead of string error messages
Gas Report
Lines
AmplificationUtils.sol:84
AmplificationUtils.sol:85
AmplificationUtils.sol:86
AmplificationUtils.sol:92
AmplificationUtils.sol:94
AmplificationUtils.sol:111
LibDiamond.sol:72
LibDiamond.sol:128
LibDiamond.sol:165
LibDiamond.sol:170
LibDiamond.sol:190
LibDiamond.sol:203
LibDiamond.sol:208
LibDiamond.sol:228
LibDiamond.sol:242
LibDiamond.sol:248
LibDiamond.sol:298
LibDiamond.sol:303
LibDiamond.sol:353
LibDiamond.sol:358
ConnextPriceOracle.sol:72
ConnextPriceOracle.sol:150
LPToken.sol:35
LPToken.sol:50
ConnextMessage.sol:116
StableSwap.sol:75
StableSwap.sol:76
StableSwap.sol:77
StableSwap.sol:86
StableSwap.sol:89
StableSwap.sol:90
StableSwap.sol:96
StableSwap.sol:97
StableSwap.sol:98
StableSwap.sol:102
StableSwap.sol:125
StableSwap.sol:155
StableSwap.sol:167
StableSwap.sol:177
Executor.sol:57
SwapUtils.sol:191
SwapUtils.sol:198
SwapUtils.sol:248
SwapUtils.sol:342
SwapUtils.sol:396
SwapUtils.sol:397
SwapUtils.sol:493
SwapUtils.sol:524
SwapUtils.sol:554
SwapUtils.sol:615
SwapUtils.sol:649
SwapUtils.sol:662
SwapUtils.sol:697
SwapUtils.sol:703
SwapUtils.sol:717
SwapUtils.sol:723
SwapUtils.sol:750
SwapUtils.sol:756
SwapUtils.sol:784
SwapUtils.sol:790
SwapUtils.sol:823
SwapUtils.sol:845
SwapUtils.sol:861
SwapUtils.sol:890
SwapUtils.sol:916
SwapUtils.sol:917
SwapUtils.sol:925
SwapUtils.sol:954
SwapUtils.sol:955
SwapUtils.sol:961
SwapUtils.sol:1005
SwapUtils.sol:1007
SwapUtils.sol:1032
SwapUtils.sol:1035
SwapUtils.sol:1071
SwapUtils.sol:1084
Use assembly to check for address(0)
Gas Report
Lines
BridgeFacet.sol:288
BridgeFacet.sol:293
BridgeFacet.sol:298
BridgeFacet.sol:311
BridgeFacet.sol:695
BridgeFacet.sol:784
BridgeFacet.sol:827
BridgeFacet.sol:874
StableSwapFacet.sol:421
PromiseRouter.sol:178
PortalFacet.sol:132
LibDiamond.sol:169
LibDiamond.sol:189
LibDiamond.sol:207
LibDiamond.sol:247
LibDiamond.sol:297
LibDiamond.sol:350
ProposedOwnableUpgradeable.sol:148
ProposedOwnableUpgradeable.sol:190
ProposedOwnableUpgradeable.sol:232
ProposedOwnableUpgradeable.sol:241
ProposedOwnableUpgradeable.sol:262
RoutersFacet.sol:158
RoutersFacet.sol:169
RoutersFacet.sol:171
RoutersFacet.sol:214
RoutersFacet.sol:265
RoutersFacet.sol:277
RoutersFacet.sol:283
RoutersFacet.sol:295
RoutersFacet.sol:308
RoutersFacet.sol:316
RoutersFacet.sol:444
RoutersFacet.sol:534
RoutersFacet.sol:540
RoutersFacet.sol:577
RoutersFacet.sol:580
RoutersFacet.sol:586
ConnextPriceOracle.sol:83
ConnextPriceOracle.sol:93
ConnextPriceOracle.sol:124
StableSwap.sol:89
RelayerFeeRouter.sol:108
AssetLogic.sol:74
AssetLogic.sol:136
SponsorVault.sol:179
SponsorVault.sol:203
SponsorVault.sol:243
SponsorVault.sol:272
SponsorVault.sol:276
SponsorVault.sol:291
SponsorVault.sol:305
ProposedOwnableFacet.sol:196
ProposedOwnableFacet.sol:205
ProposedOwnableFacet.sol:226
Executor.sol:118
unchecked{++i}
instead ofi++
(or use assembly when applicable)Use
++i
instead ofi++
. This is especially useful in for loops but this optimization can be used anywhere in your code. You can also useunchecked{++i;}
for even more gas savings but this will not check to see ifi
overflows. For extra safety if you are worried about this, you can add a require statement after the loop checking ifi
is equal to the final incremented value. For best gas savings, use inline assembly, however this limits the functionality you can achieve. For example you cant use Solidity syntax to internally call your own contract within an assembly block and external calls must be done with thecall()
ordelegatecall()
instruction. However when applicable, inline assembly will save much more gas.Gas Report
Lines
StableSwapFacet.sol:415
LibDiamond.sol:133
LibDiamond.sol:182
LibDiamond.sol:193
LibDiamond.sol:220
LibDiamond.sol:232
LibDiamond.sol:253
DiamondLoupeFacet.sol:31
ConnextPriceOracle.sol:176
StableSwap.sol:81
SwapUtils.sol:205
SwapUtils.sol:254
SwapUtils.sol:268
SwapUtils.sol:289
SwapUtils.sol:300
SwapUtils.sol:302
SwapUtils.sol:344
SwapUtils.sol:405
SwapUtils.sol:425
SwapUtils.sol:558
SwapUtils.sol:591
SwapUtils.sol:844
SwapUtils.sol:869
SwapUtils.sol:924
SwapUtils.sol:1014
SwapUtils.sol:1019
SwapUtils.sol:1039
SwapUtils.sol:1055
Use multiple
require()
statments insted ofrequire(expression && expression && ...)
Gas Report
Lines
AmplificationUtils.sol:86
StableSwap.sol:85
SwapUtils.sol:397
SwapUtils.sol:493
SwapUtils.sol:524
SwapUtils.sol:1007
Pack structs
When creating structs, make sure that the variables are listed in ascending order by data type. The compiler will pack the variables that can fit into one 32 byte slot. If the variables are not listed in ascending order, the compiler may not pack the data into one slot, causing additional
sload
andsstore
instructions when reading/storing the struct into the contract's storage.Gas Report
Lines
LibConnextStorage.sol:38
LibConnextStorage.sol:109
Use assembly for math (add, sub, mul, div)
Use assembly for math instead of Solidity. You can check for overflow/underflow in assembly to ensure safety. If using Solidity versions < 0.8.0 and you are using Safemath, you can gain significant gas savings by using assembly to calculate values and checking for overflow/underflow.
Gas Report
Lines
MathUtils.sol:30
MathUtils.sol:32
BridgeFacet.sol:342
BridgeFacet.sol:371
BridgeFacet.sol:596
BridgeFacet.sol:609
BridgeFacet.sol:750
BridgeFacet.sol:793
BridgeFacet.sol:839
BridgeFacet.sol:843
BridgeFacet.sol:903
BridgeFacet.sol:1061
BridgeFacet.sol:1089
BridgeFacet.sol:1101
BridgeFacet.sol:1113
StableSwapFacet.sol:426
StableSwapFacet.sol:442
StableSwapFacet.sol:443
PortalFacet.sol:87
PortalFacet.sol:140
PortalFacet.sol:147
PortalFacet.sol:185
LibDiamond.sol:88
LibDiamond.sol:308
LibDiamond.sol:327
ProposedOwnableUpgradeable.sol:178
ProposedOwnableUpgradeable.sol:220
ProposedOwnableUpgradeable.sol:258
ProposedOwnableUpgradeable.sol:282
RoutersFacet.sol:349
RoutersFacet.sol:434
RoutersFacet.sol:596
ConnextPriceOracle.sol:103
ConnextPriceOracle.sol:106
ConnextPriceOracle.sol:134
PromiseMessage.sol:142
ConnextMessage.sol:90
ConnextMessage.sol:219
StableSwap.sol:91
StableSwap.sol:110
StableSwap.sol:111
AssetLogic.sol:75
AssetLogic.sol:113
AssetLogic.sol:272
SponsorVault.sol:246
SponsorVault.sol:253
ProposedOwnableFacet.sol:151
ProposedOwnableFacet.sol:184
ProposedOwnableFacet.sol:222
ProposedOwnableFacet.sol:246
RelayerFeeMessage.sol:82
RelayerFeeMessage.sol:99
Executor.sol:156
Use assembly when getting a contract's balance of ETH.
You can use
selfbalance()
instead ofaddress(this).balance
when getting your contract's balance of ETH to save gas. Additionally, you can usebalance(address)
instead ofaddress.balance()
when getting an external contract's balance of ETH.Gas Report
Lines
SponsorVault.sol:204
SponsorVault.sol:258
SponsorVault.sol:292
Mark storage variables as
constant
if they never change.State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile-time, while for immutable, it can still be assigned at construction time.
The compiler does not reserve a storage slot for these variables, and every occurrence is inlined by the respective value.
Compared to regular state variables, the gas costs of constant and immutable variables are much lower. For a constant variable, the expression assigned to it is copied to all the places where it is accessed and also re-evaluated each time. This allows for local optimizations. Immutable variables are evaluated once at construction time and their value is copied to all the places in the code where they are accessed. For these values, 32 bytes are reserved, even if they would fit in fewer bytes. Due to this, constant values can sometimes be cheaper than immutable values.
Gas Report
Lines
VersionFacet.sol:16
Executor.sol:38
Executor.sol:43
Mark storage variables as
immutable
if they never change after contract initialization.State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile-time, while for immutable, it can still be assigned at construction time.
The compiler does not reserve a storage slot for these variables, and every occurrence is inlined by the respective value.
Compared to regular state variables, the gas costs of constant and immutable variables are much lower. For a constant variable, the expression assigned to it is copied to all the places where it is accessed and also re-evaluated each time. This allows for local optimizations. Immutable variables are evaluated once at construction time and their value is copied to all the places in the code where they are accessed. For these values, 32 bytes are reserved, even if they would fit in fewer bytes. Due to this, constant values can sometimes be cheaper than immutable values.
Gas Report
Lines
The text was updated successfully, but these errors were encountered: