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
.solfile and copy the contents - Go to Remix, create a new
.solfile, 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
.solfiles 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.
uint8costs more to deploy and execute) - declaring
uint256is 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 auint256is 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
bytesare cheaper to deploy and use over strings ( e.g.bytes32vs. string ) (note: you are limited to the size you define) - Similar to
uintdeclarations, 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
mappings 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, uint128thanuint128, uint128, uint256, however the setting is still cheaper than (a lot)uint128, uint256, uint128as 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
constantis cheapest if you don't intend it to changeImmutableis cheaper than normal if you only set it on the constructor and never again
- Loading from storage to memory
- Try use
memorywhere possible over storage - Try use
calldataif anexternalfunction 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")withrequiremarginally more expensive to deploy
- Function accessors
- declaring
externalis cheaper than declaringpublicby a tiny amount - using
calldataforexternalis cheaper thanmemoryforexternalor 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
autoDeployLiboff
- 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