This is to illustrate different ways to optimise gas - what to do and what not to do
- Data Types
- Functions and contract execution
- Contract design
The solution is comprised of multiple solidity .sol
files. Each of these files will contain more than one contract in them with usage permutations to illustrate how much gas is used for Deployment and sometimes how much gas is used for executing a transaction. Some of the contracts pertain to storage costs.
- Open the
.sol
file and copy the contents - Go to Remix, create a new
.sol
file, and paste the contents - It is generally best to clear out the log output and previously deployed contracts for ease of use
- For each of these contracts (note: each contract should only differ by the implementation, and care has been taken to avoid misleading costs - please submit a PR if you find something that makes the output comparison inaccurate)
- Turn the optimiser off
- Deploy the contract (with the JavaScript VM - the object is for gas cost comparison)
- View the gas costs and take note of this cost (some
.sol
files will have some numbers at the top prepopulated in the format ofdeploy cost - execution cost
) - If there are some functions, execute the function(s) there
- Take note of the gas costs for the function(s)
- Compare the gas costs against each contract implementation to gain insight into how they differ (each line reconstructs the contract, so deduct the construction cost as seen in the first test output line)
- Take the difference and see the
$
value - by inputting it at https://www.cryps.info/en/Gwei_to_USD - Turn the optimiser on and repeat steps 2-7 to optimise deploy or execution
An alternate approach is to run the tests pointing a chain instance (ganache UI etc.) and view the test outputs and the gas report and then examine what the tests are doing. Note: this is a work in progress. Additionally, each test line constructs the contract anew, so be sure to check the gas report to see actual execution costs.
Try go in order, as some of the understanding is layered
- Variable naming
- Names make no difference in gas costs
- Function naming
- Names make no difference in gas costs
- Uints
- declaring as a lower uint (e.g.
uint8
costs more to deploy and execute) - declaring
uint256
is not cheaper than declaring asuint
- they are the same - as with point 1, it is cheaper to construct a contract with a
uint256
, but interestingly auint256
is a lot more expensive to set (it has to do with the variable packing going on - see packing section)
- declaring as a lower uint (e.g.
- Strings vs. bytes
- Fixed
bytes
are cheaper to deploy and use over strings ( e.g.bytes32
vs. string ) (note: you are limited to the size you define) - Similar to
uint
declarations, it is cheaper to use smaller sizedbytes
, but if packed to fill a 256 bit slot it is more expensive to declare - Structs
- If doing calculations or using a temporary struct, a tuple may be better
- Passing structs is cheaper than passing multiple variables
- Arrays and mappings
- Try use
mapping
s for conditional checks - looping is expensive - Arrays can be used for purely listing objects/collections (also consider off chain hashed files)
- Try use
- Storage variable packing layout
- Try combine variables to use 256bit storage spaces
- Sometimes the order makes little difference on deploy, but setting values it does.
- It oddly is more expensive (a little) to have
uint256, uint128, uint128
thanuint128, uint128, uint256
, however the setting is still cheaper than (a lot)uint128, uint256, uint128
as that uses two vs. three slots.
- It oddly is more expensive (a little) to have
- Bools can be broken down into single bits vs. char if really required
- Immutable and constant keywords
constant
is cheapest if you don't intend it to changeImmutable
is cheaper than normal if you only set it on the constructor and never again
- Loading from storage to memory
- Try use
memory
where possible over storage - Try use
calldata
if anexternal
function overmemory
- Try use
- Math
- Incrementing values is cheapest by doing
variable++
; - Doubling with Binary shifts are cheapest
a = a<<2;
vs. using exponent math - Halving with Binary shifts are cheapest
a = a>>10;
vs using exponent math
- Incrementing values is cheapest by doing
- Error strings, Conditionals and short-circuiting, and error throwers
- Use simple and more likely scenario checks first
- Using
revert("message here")
is the same costs asrequire(condition,"message here")
withrequire
marginally more expensive to deploy
- Function accessors
- declaring
external
is cheaper than declaringpublic
by a tiny amount - using
calldata
forexternal
is cheaper thanmemory
forexternal
or forpublic
- declaring
- Modifiers vs. Requires
- Stack is limited with modifiers
- Modifiers save gas for contract deployment
- Deleting
- Clearing up items you no longer use will return gas to you
- Assembly code // TBC
- Inheritance and Overrides
- If you are pressed to save gas, you may be able to skim some from your inherited contracts
- Note this does make your code messier and less reusable - perhaps a library may be better suited for some functions
- Libraries
- When included in the same metadata and compiled together, then makes little difference and is more expensive
- When deployed independently it works out cheaper if reused many times
- Note: you will need to turn deploy the library separately, set the address in the metadata and then turn off the
autoDeployLib
off
- Compiler optimisation
- The number of runs is to try optimise the number of times you expect the functions to be called
- The smaller the number, the cheaper the deploy
- The higher the number, in some cases, the cheaper the execute
- Factory patterns
- Saves having to redeploy a new contract each time
- EIP-1167 (Minimal proxy)
- Saves a lot of gas by cloning code 1
- Note it is restricted to delegated calls
(Another GitHub)[https://github.com/iskdrews/awesome-solidity-gas-optimization#medium--articles]
- EVM ByteCode and Assembly optimisations