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.
Left shift instead of multiplying by two
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
}
function testGas() public view {
c0.mul2();
c1.shl2();
}
}
contract Contract0 {
function mul2() public view {
uint256 val = 10;
uint256 valMulTwo = val * 2;
valMulTwo++;
}
}
contract Contract1 {
function shl2() public view {
uint256 val = 10;
uint256 valMulTwo = val << 1;
valMulTwo++;
}
}
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.
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
Contract2 c2;
Contract3 c3;
Contract4 c4;
Contract5 c5;
Contract6 c6;
Contract7 c7;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
c2 = new Contract2();
c3 = new Contract3();
c4 = new Contract4();
c5 = new Contract5();
c6 = new Contract6();
c7 = new Contract7();
}
function testGas() 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);
}
}
contract Contract0 {
//addition in Solidity
function addTest(uint256 a, uint256 b) public pure {
uint256 c = a + b;
}
}
contract Contract1 {
//addition in assembly
function addAssemblyTest(uint256 a, uint256 b) public pure {
assembly {
let c := add(a, b)
if lt(c, a) {
mstore(0x00, "overflow")
revert(0x00, 0x20)
}
}
}
}
contract Contract2 {
//subtraction in Solidity
function subTest(uint256 a, uint256 b) public pure {
uint256 c = a - b;
}
}
contract Contract3 {
//subtraction in assembly
function subAssemblyTest(uint256 a, uint256 b) public pure {
assembly {
let c := sub(a, b)
if gt(c, a) {
mstore(0x00, "underflow")
revert(0x00, 0x20)
}
}
}
}
contract Contract4 {
//multiplication in Solidity
function mulTest(uint256 a, uint256 b) public pure {
uint256 c = a * b;
}
}
contract Contract5 {
//multiplication in assembly
function mulAssemblyTest(uint256 a, uint256 b) public pure {
assembly {
let c := mul(a, b)
if lt(c, a) {
mstore(0x00, "overflow")
revert(0x00, 0x20)
}
}
}
}
contract Contract6 {
//division in Solidity
function divTest(uint256 a, uint256 b) public pure {
uint256 c = a * b;
}
}
contract Contract7 {
//division in assembly
function divAssemblyTest(uint256 a, uint256 b) public pure {
assembly {
let c := div(a, b)
if gt(c, a) {
mstore(0x00, "underflow")
revert(0x00, 0x20)
}
}
}
}
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
}
function testGas() public view {
c0.solidityHash(2309349, 2304923409);
c1.assemblyHash(2309349, 2304923409);
}
}
contract Contract0 {
function solidityHash(uint256 a, uint256 b) public view {
//unoptimized
keccak256(abi.encodePacked(a, b));
}
}
contract Contract1 {
function assemblyHash(uint256 a, uint256 b) public view {
//optimized
assembly {
mstore(0x00, a)
mstore(0x20, b)
let hashedVal := keccak256(0x00, 0x40)
}
}
}
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.
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
Contract2 c2;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
c2 = new Contract2();
}
function testGas() public view {
c0.addValue();
c1.addImmutableValue();
c2.addConstantValue();
}
}
contract Contract0 {
uint256 val;
constructor() {
val = 10000;
}
function addValue() public view {
uint256 newVal = val + 1000;
}
}
contract Contract1 {
uint256 immutable val;
constructor() {
val = 10000;
}
function addImmutableValue() public view {
uint256 newVal = val + 1000;
}
}
contract Contract2 {
uint256 constant val = 10;
function addConstantValue() public view {
uint256 newVal = val + 1000;
}
}
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
}
function testGas() public view {
c0.ownerNotZero(address(this));
c1.assemblyOwnerNotZero(address(this));
}
}
contract Contract0 {
function ownerNotZero(address _addr) public pure {
require(_addr != address(0), "zero address)");
}
}
contract Contract1 {
function assemblyOwnerNotZero(address _addr) public pure {
assembly {
if iszero(_addr) {
mstore(0x00, "zero address")
revert(0x00, 0x20)
}
}
}
}
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
.Left shift instead of multiplying by two
Gas Report
Lines
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
InfinityToken.sol:63
InfinityToken.sol:67
InfinityToken.sol:71
InfinityToken.sol:73
InfinityToken.sol:83
InfinityStaker.sol:122
InfinityStaker.sol:156
InfinityStaker.sol:172
InfinityStaker.sol:191
InfinityStaker.sol:192
InfinityStaker.sol:195
InfinityStaker.sol:196
InfinityStaker.sol:197
InfinityStaker.sol:198
InfinityStaker.sol:200
InfinityStaker.sol:234
InfinityStaker.sol:235
InfinityStaker.sol:236
InfinityStaker.sol:237
InfinityStaker.sol:268
InfinityStaker.sol:301
InfinityStaker.sol:305
InfinityStaker.sol:309
InfinityOrderBookComplication.sol:332
InfinityOrderBookComplication.sol:333
InfinityOrderBookComplication.sol:337
InfinityOrderBookComplication.sol:339
InfinityOrderBookComplication.sol:340
InfinityOrderBookComplication.sol:341
InfinityExchange.sol:149
InfinityExchange.sol:202
InfinityExchange.sol:231
InfinityExchange.sol:237
InfinityExchange.sol:273
InfinityExchange.sol:381
InfinityExchange.sol:725
InfinityExchange.sol:726
InfinityExchange.sol:739
InfinityExchange.sol:743
InfinityExchange.sol:775
InfinityExchange.sol:776
InfinityExchange.sol:787
InfinityExchange.sol:791
InfinityExchange.sol:819
InfinityExchange.sol:820
InfinityExchange.sol:873
InfinityExchange.sol:874
InfinityExchange.sol:893
InfinityExchange.sol:897
InfinityExchange.sol:1135
InfinityExchange.sol:1136
InfinityExchange.sol:1155
InfinityExchange.sol:1156
InfinityExchange.sol:1160
InfinityExchange.sol:1162
InfinityExchange.sol:1163
InfinityExchange.sol:1164
Use assembly to hash instead of Solidity
Gas Report
Lines
InfinityExchange.sol:106
InfinityExchange.sol:108
InfinityExchange.sol:109
InfinityExchange.sol:110
InfinityExchange.sol:1172
InfinityExchange.sol:1177
InfinityExchange.sol:1179
InfinityExchange.sol:1180
InfinityExchange.sol:1191
InfinityExchange.sol:1197
InfinityExchange.sol:1207
InfinityExchange.sol:1213
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
InfinityToken.sol:31
InfinityStaker.sol:25
Use multiple
require()
statments insted ofrequire(expression && expression && ...)
Gas Report
Lines
InfinityExchange.sol:264
InfinityExchange.sol:949
Use assembly to check for address(0)
Gas Report
Lines
InfinityExchange.sol:306
InfinityExchange.sol:325
InfinityExchange.sol:347
InfinityExchange.sol:361
InfinityExchange.sol:443
InfinityExchange.sol:467
InfinityExchange.sol:498
InfinityExchange.sol:523
InfinityExchange.sol:1138
Use custom errors instead of string error messages
Gas Report
Lines
InfinityToken.sol:61
InfinityToken.sol:64
InfinityToken.sol:68
InfinityStaker.sol:68
InfinityStaker.sol:69
InfinityStaker.sol:91
InfinityStaker.sol:94
InfinityStaker.sol:96
InfinityStaker.sol:117
InfinityStaker.sol:123
InfinityStaker.sol:193
InfinityStaker.sol:347
InfinityOrderBookComplication.sol:255
InfinityExchange.sol:138
InfinityExchange.sol:139
InfinityExchange.sol:150
InfinityExchange.sol:155
InfinityExchange.sol:183
InfinityExchange.sol:184
InfinityExchange.sol:187
InfinityExchange.sol:190
InfinityExchange.sol:263
InfinityExchange.sol:264
InfinityExchange.sol:279
InfinityExchange.sol:306
InfinityExchange.sol:310
InfinityExchange.sol:313
InfinityExchange.sol:314
InfinityExchange.sol:315
InfinityExchange.sol:326
InfinityExchange.sol:342
InfinityExchange.sol:347
InfinityExchange.sol:350
InfinityExchange.sol:351
InfinityExchange.sol:362
InfinityExchange.sol:380
InfinityExchange.sol:381
InfinityExchange.sol:392
InfinityExchange.sol:394
InfinityExchange.sol:395
InfinityExchange.sol:587
InfinityExchange.sol:621
InfinityExchange.sol:649
InfinityExchange.sol:684
InfinityExchange.sol:949
InfinityExchange.sol:1141
InfinityExchange.sol:1231
Use assembly to write storage values
Gas Report
Lines
InfinityToken.sol:51
InfinityToken.sol:52
InfinityToken.sol:80
InfinityStaker.sol:50
InfinityStaker.sol:51
InfinityStaker.sol:353
InfinityStaker.sol:355
InfinityStaker.sol:357
InfinityStaker.sol:359
InfinityStaker.sol:369
InfinityStaker.sol:370
InfinityStaker.sol:371
InfinityStaker.sol:376
InfinityExchange.sol:106
InfinityExchange.sol:115
InfinityExchange.sol:116
InfinityExchange.sol:1256
InfinityExchange.sol:1261
InfinityExchange.sol:1267