Open rajatbeladiya opened 1 year ago
Please provide a summary of total gas savings with the proposed changes in terms of contract calls and deployment costs. Please provide relevant sources to substantiate submissions wherever possible.
Hi @LodestarFinance, I can give you an exact number of gas savings but I am not able to setup and run tests in your repo and getting some errors. If you can help me out with tests setup, I can provide you with total gas savings numbers. Thanks.
In meantime I will provide gas savings here.
Hi @LodestarFinance , here is the full report with gas savings. Thanks.
total gas savings:
278585
Deployment gas savings:
130647
I created repo at https://github.com/rajatbeladiya/gas-checker to substantiate my submission with hardhat-gas-reporter
where needed.
here I created two Files called NormalCodeGas.sol
and OprimizedCodeGas.sol
NormalCodeGas.sol
= this file includes code before the optimization
OprimizedCodeGas.sol
= this file includes code after optimization
compared gas with both files.
it saves 15 gas per comparison ( checkout below image )
total gas:
5 * 11 instances => 165 gas
Source Commit:
https://github.com/rajatbeladiya/gas-checker/commit/fd78ff0f75ce79bf47c9269e3bbccb6d888cf827
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past.
total gas:
100 * 22 instances = 2200 gas
bytes32 uses less gas because it fits in a single word of the EVM, and string is a dynamically sized-type which has current limitations in Solidity (such as can't be returned from a function to a contract).
Source:
https://ethereum.stackexchange.com/a/3796/66961
for the storage array ( extra sload operation ) => 100 additional extra gas for each iteration except for the first for the memory array ( extra mload operation ) => 3 additional gas for each iteration except for the first
Note:
total gas will depend on how big the array is.
here contracts has 7 storage iterating arrays and 9 memory iterating arrays
Suppose every elements is atleast 5 elements, then
total gas:
100 7 4 + 3 9 5 = 2935 gas
source:
https://gist.github.com/grGred/9bab8b9bad0cd42fc23d4e31e7347144#for-loops-improvement
cached state variables in stack variable saves 100 gas per instance rather than re-reading them from storage
total gas:
100 * 1 instance = 100
Source:
https://dev.to/jamiescript/gas-saving-techniques-in-solidity-324c
Instead of using error strings, to reduce deployment and runtime cost, you should use Custom Errors. This would save both deployment and runtime cost.
there is 169 instances => changes will reduce significant gas cost of deployment and runtime cost.
Source:
https://blog.soliditylang.org/2021/04/21/custom-errors/
here initialization to default value costs extra 3959 deployment gas, checkout Deployments
in below image
total gas:
3959 * 33 instances => 130647 gas
Source Commit:
https://github.com/rajatbeladiya/gas-checker/commit/fd78ff0f75ce79bf47c9269e3bbccb6d888cf827
using short revert string over longer revert strings will reduce deployment cost and runtime cost also.
there is 117 instances of long strings => changes will reduce significant gas cost of deployment and runtime cost.
pre increment saves 5 gas per iteration compared to post increment.
Note:
total gas will depend on how big the iterating value is. calculation is only for 1 time iteration.
total gas:
5 * 29 instances = 145 gas
Source: https://yos.io/2021/05/17/gas-efficient-solidity/#tip-12-i-costs-less-gas-compared-to-i-or-i--1
Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table
total gas:
3406 * 60 instances = 204360
it saves 2 gas because DIV / MUL opcode uses 5 gas, the SHR / SHL opcode only uses 3 gas
total gas:
2 * 5 instance = 10 gas
Source:
https://forum.openzeppelin.com/t/a-collection-of-gas-optimisation-tricks/19966/8
Incrementing with a smaller type than uint256 incurs overhead
Source:
https://yos.io/2021/05/17/gas-efficient-solidity/#tip-15-usage-of-uint8-may-increase-gas-cost
Splitting require() statements that use &&
saves 3 gas.
total gas:
3 * 16 instance = 48 gas
With EIP-1884 SLOAD now costs 800 gas. Use storage to save the location in storage and have cheaper reads
total gas:
800 * 81 instance = 64800 gas
source: https://ethereum.stackexchange.com/a/66413/66961
It saves 55 gas per unchecked increment.
Note
: calculation is only for 1 iteration.
total gas
= 55 * 28 instances = 1540 gas
Source: https://github.com/ethereum/solidity/issues/11721
saves 6 gas to Use != 0 instead of > 0 for unsigned integer comparison. The reason != 0 is better is the absence of DUP1(0X80) and GT(0X11)
total gas:
6 * 26 instance = 156 gas
Source:
https://github.com/code-423n4/2022-01-yield-findings/issues/57
here below image shows it saves 13 gas per operation to use
total gas
: 13 * 2 instance = 26 gas
Source Commit:
https://github.com/rajatbeladiya/gas-checker/commit/387d914e1ebdfde5a0cde9af1d968d479cf7a5d8
Using immutable on variables that are only set in the constructor and never after will replace the expensive storage-reading operations ( around 2100 gas per reading )
total gas
= 2100 * 1 instance = 2100
Source:
https://dev.to/jamiescript/gas-saving-techniques-in-solidity-324c
Report
Gas Optimizations
true
orfalse
private
rather thanpublic
for constants, saves gasuint256
incurs overheadstorage
instead ofmemory
for structs/arraysunchecked
in for-loops<x> += <y>
costs more gas than<x> = <x> + <y>
for state variables[GAS-1] Comparing booleans to
true
orfalse
true
andfalse
are constants and it is more expensive comparing a boolean against them than directly checking the returned boolean valueInstances (11):
[GAS-2] Using bools for storage incurs overhead
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past. See source.
Instances (22):
[GAS-3] Bytes constants are more efficient than string constants
Instances (4):
[GAS-4] Cache array length outside of loop
If not cached, the solidity compiler will always read the length of the array during each iteration. That is, if it is a storage array, this is an extra sload operation (100 additional extra gas for each iteration except for the first) and if it is a memory array, this is an extra mload operation (3 additional gas for each iteration except for the first).
Instances (16):
[GAS-5] State variables should be cached in stack variables rather than re-reading them from storage
The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.
Saves 100 gas per instance
Instances (1):
[GAS-6] Use Custom Errors
Source Instead of using error strings, to reduce deployment and runtime cost, you should use Custom Errors. This would save both deployment and runtime cost.
Instances (169):
[GAS-7] Don't initialize variables with default value
Instances (33):
[GAS-8] Long revert strings
Instances (117):
[GAS-9] Pre-increments and pre-decrements are cheaper than post-increments and post-decrements
Saves 5 gas per iteration
Instances (29):
[GAS-10] Using
private
rather thanpublic
for constants, saves gasIf needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table
Instances (60):
[GAS-11] Use shift Right/Left instead of division/multiplication if possible
Shifting left by N is like multiplying by 2^N and shifting right by N is like dividing by 2^N
Instances (5):
[GAS-12] Incrementing with a smaller type than
uint256
incurs overheadInstances (1):
[GAS-13] Splitting require() statements that use && saves gas
Instances (16):
[GAS-14] Use
storage
instead ofmemory
for structs/arraysUsing
memory
copies the struct or array in memory. Usestorage
to save the location in storage and have cheaper reads:Instances (81):
[GAS-15] Increments can be
unchecked
in for-loopsInstances (28):
[GAS-16] Use != 0 instead of > 0 for unsigned integer comparison
Instances (26):
[GAS-17]
<x> += <y>
costs more gas than<x> = <x> + <y>
for state variablesInstances (2):
[GAS-18] Using immutable on variables that are only set in the constructor and never after (2.1k gas per var)
Use immutable if you want to assign a permanent value at construction. Use constants if you already know the permanent value. Both get directly embedded in bytecode, saving SLOAD. Variables only set in the constructor and never edited afterwards should be marked as immutable, as it would avoid the expensive storage-writing operation in the constructor (around 20 000 gas per variable) and replace the expensive storage-reading operations (around 2100 gas per reading) to a less expensive value reading (3 gas)
Instances (1):