|
| 1 | +# Gas Optimizations |
| 2 | + |
| 3 | +## G001 - Don't Initialize Variables with Default Value |
| 4 | + |
| 5 | +### Description |
| 6 | + |
| 7 | +Uninitialized variables are assigned with the types default value. |
| 8 | + |
| 9 | +Explicitly initializing a variable with it's default value costs unnecesary gas. |
| 10 | + |
| 11 | +### Example |
| 12 | + |
| 13 | +🤦 Bad: |
| 14 | +```solidity |
| 15 | +uint256 x = 0; |
| 16 | +bool y = false; |
| 17 | +``` |
| 18 | + |
| 19 | +🚀 Good: |
| 20 | +```solidity |
| 21 | +uint256 x; |
| 22 | +bool y; |
| 23 | +``` |
| 24 | + |
| 25 | +### Background Information |
| 26 | + |
| 27 | +- [Mudit Gupta's Blog](https://mudit.blog/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size/) |
| 28 | + |
| 29 | + |
| 30 | +## G002 - Cache Array Length Outside of Loop |
| 31 | + |
| 32 | +### Description |
| 33 | + |
| 34 | +Caching the array length outside a loop saves reading it on each iteration, as |
| 35 | +long as the array's length is not changed during the loop. |
| 36 | + |
| 37 | +### Example |
| 38 | + |
| 39 | +🤦 Bad: |
| 40 | +```solidity |
| 41 | +for (uint256 i = 0; i < array.length; i++) { |
| 42 | + // invariant: array's length is not changed |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +🚀 Good: |
| 47 | +```solidity |
| 48 | +uint256 len = array.length |
| 49 | +for (uint256 i = 0; i < len; i++) { |
| 50 | + // invariant: array's length is not changed |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +### Background Information |
| 55 | + |
| 56 | +- [Issue submitted by WatchPug](https://github.com/code-423n4/2021-11-badgerzaps-findings/issues/36) |
| 57 | + |
| 58 | + |
| 59 | +## G003 - Use `!= 0` instead of `> 0` for Unsigned Integer Comparison |
| 60 | + |
| 61 | +### Description |
| 62 | + |
| 63 | +When dealing with unsigned integer types, comparisons with `!= 0` are cheaper |
| 64 | +then with `> 0`. |
| 65 | + |
| 66 | +### Example |
| 67 | + |
| 68 | +🤦 Bad: |
| 69 | +```solidity |
| 70 | +// `a` being of type unsigned integer |
| 71 | +require(a > 0, "!a > 0"); |
| 72 | +``` |
| 73 | + |
| 74 | +🚀 Good: |
| 75 | +```solidity |
| 76 | +// `a` being of type unsigned integer |
| 77 | +require(a != 0, "!a > 0"); |
| 78 | +``` |
| 79 | + |
| 80 | +### Background Information |
| 81 | + |
| 82 | +TODO |
| 83 | + |
| 84 | + |
| 85 | +## G004 - Remove Unused Variables |
| 86 | + |
| 87 | +### Description |
| 88 | + |
| 89 | +Removing unused variables saves gas, especially for state variables, i.e. |
| 90 | +variables saved in storage. |
| 91 | + |
| 92 | + |
| 93 | +## G005 - Make Variable `constant`/`immutable` |
| 94 | + |
| 95 | +### Description |
| 96 | + |
| 97 | +Making variables constant/immutable, if possible, saves gas as all variables |
| 98 | +get replaced by the values assigned to the them. |
| 99 | + |
| 100 | +### Background Information |
| 101 | + |
| 102 | +- [Solidity docs](https://docs.soliditylang.org/en/latest/contracts.html?highlight=constant#constant) |
| 103 | +- [RariCapitals solcurity](https://github.com/Rari-Capital/solcurity#variables) |
| 104 | + |
| 105 | + |
| 106 | +## G006 - Use `immutable` for OpenZeppelin `AccessControl`'s Roles Declarations |
| 107 | + |
| 108 | +### Description |
| 109 | + |
| 110 | +Access roles marked as `constant` results in computing the `keccak256` operation |
| 111 | +each time the variable is used because assigned opeartions for `constant` |
| 112 | +variables are re-evaluated every time. |
| 113 | + |
| 114 | +Changing the variables to `immutable` results in computing the hash only once |
| 115 | +on deployment, leading to gas savings. |
| 116 | + |
| 117 | +### Example |
| 118 | + |
| 119 | +🤦 Bad: |
| 120 | +```solidity |
| 121 | +bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE"); |
| 122 | +``` |
| 123 | + |
| 124 | +🚀 Good: |
| 125 | +```solidity |
| 126 | +bytes32 public immutable GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE"); |
| 127 | +``` |
| 128 | + |
| 129 | +### Background Information |
| 130 | + |
| 131 | +- [Solidity issue](https://github.com/ethereum/solidity/issues/9232) |
| 132 | + |
| 133 | + |
| 134 | +## G007 - Long Revert Strings |
| 135 | + |
| 136 | +### Description |
| 137 | + |
| 138 | +Shortening revert strings to fit in 32 bytes will decrease gas costs for |
| 139 | +deployment and gas costs when the revert condition has been met. |
| 140 | + |
| 141 | +If the contract(s) in scope allow using Solidity `>= 0.8.4`, consider using |
| 142 | +[Custom Errors](https://blog.soliditylang.org/2021/04/21/custom-errors/) as |
| 143 | +they are more gas efficient while allowing developers to describe the error |
| 144 | +in detail using [NatSpec](https://docs.soliditylang.org/en/latest/natspec-format.html). |
| 145 | + |
| 146 | +### Example |
| 147 | + |
| 148 | +🤦 Bad: |
| 149 | +```solidity |
| 150 | +require(condition, "UniswapV3: The reentrancy guard. A transaction cannot re-enter the pool mid-swap"); |
| 151 | +``` |
| 152 | + |
| 153 | +🚀 Good (with shorter string): |
| 154 | +```solidity |
| 155 | +// TODO: Provide link to a reference of error codes |
| 156 | +require(condition, "LOK"); |
| 157 | +``` |
| 158 | + |
| 159 | +🚀 Good (with custom errors): |
| 160 | +```solidity |
| 161 | +/// @notice A transaction cannot re-enter the pool mid-swap. |
| 162 | +error NoReentrancy(); |
| 163 | +
|
| 164 | +// ... |
| 165 | +
|
| 166 | +if (!condition) { |
| 167 | + revert NoReentrancy(); |
| 168 | +} |
| 169 | +``` |
| 170 | + |
| 171 | +### Background Information |
| 172 | + |
| 173 | +- [C4 Issue with a deeper explanation](https://github.com/code-423n4/2021-09-sushimiso-findings/issues/134) |
| 174 | +- [Uniswap V3 Error Code](https://docs.uniswap.org/protocol/reference/error-codes) |
| 175 | + |
| 176 | + |
| 177 | +## G008 - Shift Right instead of Dividing by 2 |
| 178 | + |
| 179 | +### Description |
| 180 | + |
| 181 | +A division by 2 can be calculated by shifting one to the right. |
| 182 | + |
| 183 | +While the `DIV` opcode uses 5 gas, the `SHR` opcode only uses 3 gas. |
| 184 | +Furthermore, Solidity's division operation also includes a division-by-0 |
| 185 | +prevention which is bypassed using shifting. |
| 186 | + |
| 187 | +### Example |
| 188 | + |
| 189 | +🤦 Bad: |
| 190 | +```solidity |
| 191 | +uint256 b = a / 2; |
| 192 | +``` |
| 193 | + |
| 194 | +🚀 Good: |
| 195 | +```solidity |
| 196 | +uint256 b = a >> 1; |
| 197 | +``` |
| 198 | + |
| 199 | +### Background Information |
| 200 | + |
| 201 | +- [EVM Opcodes](https://www.evm.codes/) |
| 202 | + |
| 203 | + |
| 204 | +## G009 - Make Function `external` instead of `public` |
| 205 | + |
| 206 | +### Description |
| 207 | + |
| 208 | +While `external` functions can read directly from calldata, for `public` |
| 209 | +functions Solidity copies the arguments to memory. |
| 210 | + |
| 211 | +Furthermore, as `public` grants more access rights to a function than |
| 212 | +`external`, consider using `external` as default and only use `public` if there |
| 213 | +are explicit reasons. |
| 214 | + |
| 215 | +### Background Information |
| 216 | + |
| 217 | +- [Gustavo (Gus) Guimaraes post](https://gus-tavo-guim.medium.com/public-vs-external-functions-in-solidity-b46bcf0ba3ac) |
| 218 | +- [StackOverflow answer](https://ethereum.stackexchange.com/questions/19380/external-vs-public-best-practices?answertab=active#tab-top) |
| 219 | + |
| 220 | + |
| 221 | +## G010 - Make Function `payable` |
| 222 | + |
| 223 | +### Description |
| 224 | + |
| 225 | +Functions marked as `paybale` are slightly cheaper than non-`payable` ones, |
| 226 | +because the Solidity compiler inserts a check into non-`payable` functions |
| 227 | +requiring `msg.value` to be zero. |
| 228 | + |
| 229 | +However, keep in mind that this optimization opens the door for a whole set of |
| 230 | +security considerations involving Ether held in contracts. |
| 231 | + |
| 232 | +### Background Information |
| 233 | + |
| 234 | +- [Solidity Compiler Discussion](https://github.com/ethereum/solidity/issues/12539) |
0 commit comments