Repository: kadenzipfel/gas-optimizations Branch: main Commit: ddf6ef83f041 Files: 22 Total size: 17.3 KB Directory structure: gitextract_cj3l_ucq/ ├── README.md ├── gas-costly-patterns/ │ ├── comparison-with-unilateral-outcome-in-a-loop.md │ ├── constant-outcome-of-a-loop.md │ ├── dead-code.md │ ├── expensive-operations-in-a-loop.md │ ├── loop-fusion.md │ ├── opaque-predicate.md │ ├── repeated-computations-in-a-loop.md │ ├── storage-variable-as-loop-length.md │ └── unnecessary-libraries.md ├── gas-golfing/ │ ├── function-ordering.md │ ├── optimal-comparison-operator.md │ ├── optimal-increment-and-decrement-operators.md │ ├── payable-functions.md │ ├── short-revert-strings.md │ ├── unchecked-arithmetic.md │ └── using-mulmod-over-mul&div.md └── gas-saving-patterns/ ├── constants-and-immutables.md ├── explicit-function-visibility.md ├── proper-data-types.md ├── short-circuiting.md └── struct-bit-packing.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # Gas Optimizations in Solidity ## Gas Costly Patterns - [Comparison with Unilateral Outcome in a Loop](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-costly-patterns/comparison-with-unilateral-outcome-in-a-loop.md) - [Constant Outcome of a Loop](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-costly-patterns/constant-outcome-of-a-loop.md) - [Dead Code](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-costly-patterns/dead-code.md) - [Expensive Operations in a Loop](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-costly-patterns/expensive-operations-in-a-loop.md) - [Loop Fusion](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-costly-patterns/loop-fusion.md) - [Opaque Predicate](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-costly-patterns/opaque-predicate.md) - [Repeated Computations in a Loop](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-costly-patterns/repeated-computations-in-a-loop.md) - [Storage Variable as Loop Length](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-costly-patterns/storage-variable-as-loop-length.md) - [Unnecessary Libraries](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-costly-patterns/unnecessary-libraries.md) ## Gas Saving Patterns - [Proper Data Types](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-saving-patterns/proper-data-types.md) - [Explicit Function Visibility](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-saving-patterns/explicit-function-visibility.md) - [Short Circuiting](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-saving-patterns/short-circuiting.md) - [Constants and Immutables](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-saving-patterns/constants-and-immutables.md) - [Struct Bit Packing](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-saving-patterns/struct-bit-packing.md) ## Gas Golfing - [Short Revert Strings](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-golfing/short-revert-strings.md) - [Unchecked Arithmetic](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-golfing/unchecked-arithmetic.md) - [Optimal Comparison Operator](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-golfing/optimal-comparison-operator.md) - [Payable Functions](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-golfing/payable-functions.md) - [Function Ordering](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-golfing/function-ordering.md) - [Using Mulmod Over Mul & Div](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-golfing/using-mulmod-over-mul&div.md) - [Optimal Increment and Decrement Operators](https://github.com/KadenZipfel/gas-optimizations/blob/main/gas-golfing/optimal-increment-and-decrement-operators.md) ## Sources/References - https://medium.com/coinmonks/optimizing-your-solidity-contracts-gas-usage-9d65334db6c7 - https://ethereum.stackexchange.com/questions/28813/how-to-write-an-optimized-gas-cost-smart-contract - https://arxiv.org/pdf/1703.03994.pdf - https://ethereum.stackexchange.com/questions/3067/why-does-uint8-cost-more-gas-than-uint256 - https://www.youtube.com/watch?v=qwBkeJ84d2g&feature=youtu.be&t=68 - https://ethereum.stackexchange.com/questions/11556/use-string-type-or-bytes32 - https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc - https://github.com/ZeroEkkusu/re-golf-course ================================================ FILE: gas-costly-patterns/comparison-with-unilateral-outcome-in-a-loop.md ================================================ ## Comparison with Unilateral Outcome in a Loop When a comparison is executed in each iteration of a loop but the result is the same each time, it should be removed from the loop. For example, the following: ``` function unilateralOutcome(uint x) public returns(uint) { uint sum = 0; for(uint i = 0; i <= 100; i++) { if(x > 1) { sum += 1; } } return sum; } ``` Should be re-written as: ``` function noUnilateralOutcome(uint x) public returns(uint) { uint sum = 0; bool increment = x > 1; for(uint i = 0; i <= 100; i++) { if(increment) { sum += 1; } } return sum; } ``` ================================================ FILE: gas-costly-patterns/constant-outcome-of-a-loop.md ================================================ ## Constant Outcome of a Loop If the outcome of a loop is a constant that can be inferred during compilation, it should not be used. ``` function constantOutcome() public pure returns(uint) { uint num = 0; for(uint i = 0; i < 100; i++) { num += 1; } return num; } ``` ================================================ FILE: gas-costly-patterns/dead-code.md ================================================ ## Dead Code Dead code is code that will never run because it’s evaluation is predicated on a condition that will always return false. ``` function deadCode(uint x) public pure { if(x < 1) { if(x > 2) { // this will never run return x; } } } ``` ================================================ FILE: gas-costly-patterns/expensive-operations-in-a-loop.md ================================================ ## Expensive Operations in a Loop We should aim to keep logic in loops as cheap as possible. For example, due to the expensive SLOAD and SSTORE opcodes, managing a variable in storage is much more expensive than managing variables in memory. For this reason, storage variables should be avoided in loops. ``` uint num = 0; function expensiveLoop(uint x) public { for(uint i = 0; i < x; i++) { num += 1; } } ``` The fix for this pattern would be to create a memory variable to represent the state variable and once the loop is complete, reassign the value of the temporary variable to the global variable. ``` uint num = 0; function lessExpensiveLoop(uint x) public { uint temp = num; for(uint i = 0; i < x; i++) { temp += 1; } num = temp; } ``` ================================================ FILE: gas-costly-patterns/loop-fusion.md ================================================ ## Loop Fusion Occasionally in smart contracts, you may find that there are two loops with the same parameters. In the case that the loop parameters are the same, they should be combined if possible. ``` function loopFusion(uint x, uint y) public pure returns(uint) { for(uint i = 0; i < 100; i++) { x += 1; } for(uint i = 0; i < 100; i++) { y += 1; } return x + y; } ``` Should be re-written as: ``` function lessExpensiveLoopFusion(uint x, uint y) public pure returns(uint) { for(uint i = 0; i < 100; i++) { x += 1; y += 1; } return x + y; } ``` ================================================ FILE: gas-costly-patterns/opaque-predicate.md ================================================ ## Opaque Predicate The outcome of some conditions can be known without being executed and thus do not need to be evaluated. ``` function opaquePredicate(uint x) public pure { if(x > 1) { // redundant if(x > 0) { return x; } } } ``` ================================================ FILE: gas-costly-patterns/repeated-computations-in-a-loop.md ================================================ ## Repeated Computations in a Loop If an expression in a loop produces the same outcome in each iteration, it can be moved out of the loop. This is especially important when the variables(s) used in the expression are stored in storage. ``` uint a = 4; uint b = 5; function repeatedComputations(uint x) public returns(uint) { uint sum = 0; for(uint i = 0; i <= x; i++) { sum = a + b; } return sum; } ``` ================================================ FILE: gas-costly-patterns/storage-variable-as-loop-length.md ================================================ ## Storage Variable as Loop Length Avoid using storage variables to set the length of a for loop. ``` function expensiveLoopLength { for (uint i = 0; i < array.length; i++) {} } ``` Instead assign it to a variable in memory to avoid unnecessary SLOADs. ``` function cheapLoopLength { uint length = array.length; for (uint i = 0; i < length; i++) {} } ``` ================================================ FILE: gas-costly-patterns/unnecessary-libraries.md ================================================ ## Unnecessary Libraries Libraries are often only imported for a small number of uses, meaning that they can contain a significant amount of code that is redundant to your contract. If you can safely and effectively implement the functionality imported from a library within your contract, it is optimal to do so. ``` import './SafeMath.sol' as SafeMath; // redundant library contract SafeAddition { function safeAdd(uint a, uint b) public pure returns(uint) { return SafeMath.add(a, b); } } ``` ``` // w/o library contract SafeAddition { function safeAdd(uint a, uint b) public pure returns(uint) { uint c = a + b; require(c >= a, "Addition overflow"); return c; } } ``` ================================================ FILE: gas-golfing/function-ordering.md ================================================ ## Function Ordering When a function is called, the EVM compares the method ID of the transaction to the method ID's present in the contract, with each comparison costing an additional 22 gas. These comparisons are performed in order from least to greatest byte value of method ID's (alphanumerically based on the method ID). This is done using binary search. For this reason: ### 1. Have method name with IDs that have more 0s. As with calldata, having a method name whose function selector has more 0s helps. ``` // method ID: 0x6a761202 execTransaction() // method ID: 0x00000081 execTransaction32785586() ``` This saves 192 bytes of data. ### 2. Reorder methods based on call frequency there is some value in renaming functions to be ordered based on the frequency you expect them to be used, saving average overall gas cost of interacting with your contract. ``` // method ID: 0x1249c58b mint() // method ID: 0x0000b696 mint_Aci() ``` Useful tool: https://emn178.github.io/solidity-optimize-name/ ================================================ FILE: gas-golfing/optimal-comparison-operator.md ================================================ ## Optimal Comparison Operator When comparing uints, you can save gas costs by strategically picking the optimal operator. For example, it is cheaper to use strict < or > operators over <= or >= operators. This is due to the additional EQ opcode which must be performed. In the case of a conditional statement, it is further optimal to use != when possible. ``` // pragma solidity ^0.8.0; // 149 gas function a() external pure returns(bool) { return 1 > 0; } // 193 gas function b() external pure returns(bool) { return 1 >= 0; } // 237 gas function c() external pure returns(bool) { return 1 != 0; } // 164 gas function d() external pure { require(1 > 0); } // 208 gas function e() external pure { require(1 >= 0); } // 120 gas function f() external pure { require(1 != 0); } ``` ================================================ FILE: gas-golfing/optimal-increment-and-decrement-operators.md ================================================ ## Optimal Increment and Decrement Operators Increment and decrement operators are used to either increment or decrement their operand by one. These incrementers/decrementers can either be presented as e.g. `i++` or `++i`, with the only difference being the order of operations being executed. `i++` will return `i`, then increment the value of `i`, whereas `++i` will increment before returning the value. As a result of the variance in order of operations, `++i` will save two opcodes from being performed. Consider the following example: ``` pragma solidity ^0.8.0; contract IPlusPlus { // 1996 gas function loop() external pure { for (uint256 i; i < 10; i++) { // do something } } } contract PlusPlusI { // 1946 gas function loop() external pure { for (uint256 i; i < 10; ++i) { // do something } } } ``` Using `++i` above shaves off 5 gas per loop. This is because `i++` must handle the value of `i` both before and after the increment, in this case by executing a `DUPN` opcode (3 gas) and later cleaning up the stack by performing a `POP` (2 gas). ================================================ FILE: gas-golfing/payable-functions.md ================================================ ## Payable Functions Functions that are not explicitly marked as payable require an initial check that msg.value == 0, which costs 21 gas. In the case that this check does not provide any benefit, it may be optimal to mark your function as payable. **Note**: Do not use this pattern unless you understand the security trade-off being made. ================================================ FILE: gas-golfing/short-revert-strings.md ================================================ ## Short Revert Strings Keeping revert strings under 32-bytes prevents the string from being stored in more than one memory slot. ``` function expensiveRevertStrings() { require(a < b; "long revert string over 32 bytes"); } ``` Alternatively you can write comments to map short strings to longer ones in your contract, e.g.: ``` // a: long revert string over 32 bytes function cheapRevertStrings() { require(a < b; "a"); } ``` Ideally, if you are using solidity >= 0.8.4, it is even better to use custom errors to further save on gas. ``` // pragma solidity ^0.8.0; error CustomError(); contract CustomErrors { if (a < b) { revert CustomError(); } } ``` ================================================ FILE: gas-golfing/unchecked-arithmetic.md ================================================ ## Unchecked Arithmetic If using solidity >= 0.8.0, safe arithmetic is built-in. In situations that overflow/underflow checks are redundant, code should be wrapped in an unchecked block. For example, the for loop increment can be unchecked as follows: ``` // pragma solidity ^0.8.0; for (uint i = 0; i < length; i = unchecked_inc(i)) { // do something that doesn't change the value of i } function unchecked_inc(uint i) returns (uint) { unchecked { return i + 1; } } ``` ================================================ FILE: gas-golfing/using-mulmod-over-mul&div.md ================================================ ## Using Mulmod Over Mul & Div If you have to check whether a value obtained by dividing it by a number is exact(i.e. significant digits have not truncated during division), use `mulmod` instead of mul & div as it costs 8 gas. In contrast, `mul` and `div` cost 5 gas each. - Gas inefficient method ``` contract UsingMul&Div { error InExactDivision(); function exactDivide( uint256 numerator, uint256 denominator, uint256 value ) public pure returns (uint256 newValue) { newValue = (value * numerator)/denominator; if(value != (newValue * denominator)/ numerator) { revert InExactDivision(); } } } ``` - Gas efficient method ``` contract UsingMulMod { error InExactDivision(); function exactDivide( uint256 numerator, uint256 denominator, uint256 value ) public pure returns (uint256 newValue) { newValue = (value * numerator)/denominator; if(mulmod(value, numerator, denominator) != 0) { revert InExactDivision(); } } } ``` - Gas efficient method(With Assembly) ``` contract UsingMulModWithAssembly { error InExactDivision(); function exactDivide( uint256 numerator, uint256 denominator, uint256 value ) public pure returns (uint256 newValue) { bool inexact; assembly { newValue := div(mul(value, numerator, denominator)); inexact := iszero(iszero(mulmod(value, numerator, denominator))); } if(inexact) { revert InExactDivision(); } } } ``` ================================================ FILE: gas-saving-patterns/constants-and-immutables.md ================================================ ## Constants and Immutables If a storage variable must be assigned at deployment and doesn't need to be reassigned, it should be marked as immutable. Better yet, if it doesn't need to be assigned at deployment then it can be set as a constant. ``` contract ExpensiveStorageVar { uint256 MAX_SUPPLY = 10000; // 520 gas function getMaxSupply() external view returns (uint256) { return MAX_SUPPLY; } } ``` ``` // pragma solidity ^0.8.0; contract CheapImmutable { uint256 immutable maxSupply; constructor(uint256 _maxSupply) { maxSupply = _maxSupply; } // 379 gas function getMaxSupply() external view returns (uint256) { return maxSupply; } } ``` ``` // pragma solidity ^0.8.0; contract ExtraCheapConstant { uint256 constant MAX_SUPPLY = 10000; // 320 gas function getMaxSupply() external pure returns (uint256) { return MAX_SUPPLY; } } ``` ================================================ FILE: gas-saving-patterns/explicit-function-visibility.md ================================================ ## Explicit Function Visibility Explicit function visibility can often provide benefits in terms of smart contract security as well as gas optimization. For example, explicitly labeling external functions forces the function parameter storage location to be set as calldata, which saves gas each time the function is executed. ================================================ FILE: gas-saving-patterns/proper-data-types.md ================================================ ## Proper Data Types In Solidity, some data types are more expensive than others. It’s valuable to know the most efficient type that can be used. Here are a few rules about data types. - Type uint or bytes32 should be used in place of type string whenever possible. - Type uint256 takes less gas to store than other uint types ([see why](https://ethereum.stackexchange.com/questions/3067/why-does-uint8-cost-more-gas-than-uint256)). - Type bytes should be used over byte[]. - If the length of bytes can be limited, use the lowest amount possible from bytes1 to bytes32. ================================================ FILE: gas-saving-patterns/short-circuiting.md ================================================ ## Short Circuiting Short-circuiting is a strategy we can make use of when an operation makes use of either || or &&. This pattern works by ordering the lower-cost operation first so that the higher-cost operation may be skipped (short-circuited) if the first operation evaluates to true. ``` // f(x) is low cost // g(y) is expensive // Ordering should go as follows f(x) || g(y) f(x) && g(y) ``` ================================================ FILE: gas-saving-patterns/struct-bit-packing.md ================================================ ## Struct Bit Packing Solidity's [storage layout](https://docs.soliditylang.org/en/v0.8.15/internals/layout_in_storage.html?highlight=storage%20layout) consists of 2\*\*256 32 byte slots, with reads and writes requiring a fixed cost per slot. As such, to reduce gas costs, we should aim to use as few slots as necessary. When variables are stored, they are done so in a compact way such that multiple values sometimes use the same slot. Structs may contain multiple data types, of which will also be stored as compact as the data types allow. For example, the following struct will consume 3 slots: ``` struct ThreeSlots { uint256 firstSlot; // takes an entire slot because uint256 is 256 bits == 32 bytes uint256 secondSlot; // takes an entire slot because uint256 is 256 bits == 32 bytes address thirdSlot; // although addresses are 20 bytes, it will take the third slot since the first two are full } ``` If however, we know for a fact our uints will have a low enough upper bound, we can reduce the size to pack variables into fewer slots. For example: ``` struct OneSlot { uint48 firstValue; // if the value will never exceed 2**48 we can do this to use just 48 bits == 6 bytes uint48 secondValue; // if the value will never exceed 2**48 we can do this to use just 48 bits == 6 bytes address thirdValue; // since we've only consumed 12 bytes we can fit our 20 byte address for a total of 32 bytes } ```