You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Caching the length in a variable before the for loop
Bools for storage are expensive
IMPACT
Booleans are more expensive than uint256: each write operation emits an extra SLOAD to first read the slot's contents, replace the bits taken up by the boolean, and then write back.
Compared to using uint256, this costs an extra 100 gas, even one Gsset 20000 gas when changing from 'false' to 'true' - if this is not the first time setting it to true.
Replace bool with uint256.
Use uint256(1) and uint256(2) instead of true/false
Caching storage variables in local variables to save gas
IMPACT
Anytime you are reading from storage more than once, it is cheaper in gas cost to cache the variable: a SLOAD cost 100gas, while MLOAD and MSTORE cost 3 gas.
In particular, in for loops, when using the length of a storage array as the condition being checked after each loop, caching the array length can yield significant gas savings if the array length is high
PROOF OF CONCEPT
2 instances:
ETHRegistrarController.sol
scope: _consumeCommitment:
commitments- [commitment] is read twice, it can be cached with a local variable
Marking constants as private save gas upon deployment, as the compiler does not have to create getter functions for these variables. It is worth noting that a private variable can still be read using either the verified contract source code or the bytecode.
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information, as explained - here
Custom errors are defined using the error statement
Replace require and revert statements with custom errors.
For instance, in ETHRegistrarController.sol:
-require(resolver != address(0),"ETHRegistrarController: resolver is required when data is supplied")+if (resolver == address(0)) {+ revert ResolverRequired();+}
and define the custom error in the contract
+error ResolverRequired();
Functions with access control cheaper if payable
PROBLEM
A function with access control marked as payable will lbe cheaper for legitimate callers: the compiler removes checks for msg.value, saving approximately 20 gas per function call.
This saves 77,658 gas upon deployment, an average of 11,094 gas saved per revert string.
Unchecked arithmetic
IMPACT
The default "checked" behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected.
if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an unchecked block will save gas
It saves 12,518 upon deployment, and an average of 156 gas per register call - here for data.length == 15, so an average of 10 gas per byte of data.
Upgrade Solidity compiler version
IMPACT
0.8.10 removes contract existence checks if the external call has a return value - 700 gas
PROOF OF CONCEPT
All the contracts in scope have the pragma set to ^0.8.4
TOOLS USED
Manual Analysis
MITIGATION
Upgrade these contracts compiler versions.
use Assembly for simple setters
IMPACT
Where it does not affect readability, using assembly allows to save gas not only on deployment, but also on function calls.
This is the case for instance for simple admin setters.
Gas Report
Table of Contents
Array length should not be looked up in every iteration
IMPACT
It wastes gas to read an array's length in every iteration of a
for
loop, even if it is a memory or calldata array:3
gas per read.PROOF OF CONCEPT
5 instances:
DNSSECImpl.sol
RRUtils.sol
ETHRegistrarController.sol
ERC1155Fuse.sol
TOOLS USED
Manual Analysis
MITIGATION
Caching the length in a variable before the
for
loopBools for storage are expensive
IMPACT
Booleans are more expensive than uint256: each write operation emits an extra
SLOAD
to first read the slot's contents, replace the bits taken up by the boolean, and then write back.Compared to using
uint256
, this costs an extra100
gas, even one Gsset20000
gas when changing from 'false' to 'true' - if this is not the first time setting it totrue
.PROOF OF CONCEPT
2 instances:
ERC1155Fuse.sol
Controllable.sol
TOOLS USED
Manual Analysis
MITIGATION
Replace
bool
withuint256
.Use
uint256(1)
anduint256(2)
instead oftrue
/false
Caching storage variables in local variables to save gas
IMPACT
Anytime you are reading from storage more than once, it is cheaper in gas cost to cache the variable: a SLOAD cost 100gas, while MLOAD and MSTORE cost 3 gas.
In particular, in
for
loops, when using the length of a storage array as the condition being checked after each loop, caching the array length can yield significant gas savings if the array length is highPROOF OF CONCEPT
2 instances:
ETHRegistrarController.sol
scope:
_consumeCommitment
:commitments- [commitment]
is read twice, it can be cached with a local variablecommitments- [commitment] + minCommitmentAge <= block.timestamp
commitments- [commitment] + maxCommitmentAge > block.timestamp
NameWrapper.sol
scope:
setUpgradeContract
:upgradeContract
is read 3 times, while it could be cached with the function argument_upgradeAddress
:if (address(upgradeContract) != address(0))
registrar.setApprovalForAll(address(upgradeContract), true)
ens.setApprovalForAll(address(upgradeContract), true)
TOOLS USED
Manual Analysis
Constants can be private
IMPACT
Marking constants as
private
save gas upon deployment, as the compiler does not have to create getter functions for these variables. It is worth noting that aprivate
variable can still be read using either the verified contract source code or the bytecode.PROOF OF CONCEPT
2 instances:
ETHRegistrarController.sol
TOOLS USED
Manual Analysis
MITIGATION
Make the constants
private
instead ofpublic
·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 2010419 · 6.4 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·
·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 1974221 · 6.4 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·
This saves
36,198
gas upon deployment.Custom Errors
IMPACT
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information, as explained - here
Custom errors are defined using the error statement
PROOF OF CONCEPT
41 instances:
BytesUtils.sol
RRUtils.sol
ETHRegistrarController.sol
ReverseRegistrar.sol
BytesUtil.sol
ERC1155Fuse.sol
Controllable.sol
TOOLS USED
Manual Analysis
MITIGATION
Replace require and revert statements with custom errors.
For instance, in
ETHRegistrarController.sol
:and define the custom error in the contract
+error ResolverRequired();
Functions with access control cheaper if payable
PROBLEM
A function with access control marked as payable will lbe cheaper for legitimate callers: the compiler removes checks for
msg.value
, saving approximately20
gas per function call.PROOF OF CONCEPT
6 instances:
DNSSECImpl.sol
Owned.sol
ReverseRegistrar.sol
NameWrapper.sol
TOOLS USED
Manual Analysis
MITIGATION
Mark these functions as
payable
Inline functions
PROBLEM
When we define internal functions to perform computation:
When it does not affect readability, it is recommended to inline
internal
functions that are called only once.PROOF OF CONCEPT
3 instances:
DNNSECImpl.sol
ETHRegistrarController.sol
ReverseRegistrar.sol
TOOLS USED
Manual Analysis
MITIGATION
Inline these functions where they are called. Another method is to use the
viaIR
compiler optimization flag, which helps with inline optimizations.Mathematical optimizations
PROBLEM
X += Y costs
22
more gas than X = X + Y. This can mean a lot of gas wasted in a function call when the computation is repeatedn
times (loops)PROOF OF CONCEPT
9 instances include:
RRUtils.sol
TOOLS USED
Manual Analysis
MITIGATION
use
X = X + Y
instead ofX += Y
(same with-
). IfY ==1
, use++Y
or--Y
.Modifier instead of duplicate require
PROBLEM
When a
require
statement is used multiple times, it is cheaper in deployment costs to use a modifier instead.PROOF OF CONCEPT
4 instances include:
BytesUtils.sol
require(offset + len <= self.length)
ERC1155Fuse.sol
require(to != address(0), "ERC1155: transfer to the zero address")
require(to != address(0), "ERC1155: transfer to the zero address")
require(from == msg.sender || isApprovedForAll(from, msg.sender),"ERC1155: caller is not owner nor approved")
require(from == msg.sender || isApprovedForAll(from, msg.sender),"ERC1155: caller is not owner nor approved")
require(amount == 1 && oldOwner == from,"ERC1155: insufficient balance for transfer")
require(amount == 1 && oldOwner == from,"ERC1155: insufficient balance for transfer")
TOOLS USED
Manual Analysis
MITIGATION
Use modifiers for these repeated statements
Prefix increments
IMPACT
Prefix increments are cheaper than postfix increments -
6
gas. This can mean interesting savings infor
loops.PROOF OF CONCEPT
5 instances:
BytesUtils.sol
DNSSECImpl.sol
ETHRegistrarController.sol
StringUtils.sol
TOOLS USED
Manual Analysis
MITIGATION
change
variable++
to++variable
.Revert strings length
IMPACT
Revert strings cost more gas to deploy if the string is larger than 32 bytes.
PROOF OF CONCEPT
22 instances:
ETHRegistrarController.sol
ReverseRegistrar.sol
ERC1155Fuse.sol
Controllable.sol
TOOLS USED
Manual Analysis
MITIGATION
Write the error strings so that they do not exceed 32 bytes. For further gas savings, consider also using - custom errors.
For instance, changing all strings in
ETHRegistrarController.sol
to strings shorter than 32 bytes:·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 2010419 · 6.4 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·
·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 1932761 · 6.4 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·
This saves
77,658
gas upon deployment, an average of11,094
gas saved per revert string.Unchecked arithmetic
IMPACT
The default "checked" behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected.
if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an
unchecked
block will save gasPROOF OF CONCEPT
9 instances:
BytesUtils.sol
DNSSECImpl.sol
ETHRegistrarController.sol
StringUtils.sol
ERC1155Fuse.sol
TOOLS USED
Manual Analysis
MITIGATION
Place the arithmetic operations in an
unchecked
block.For instance, doing it for the 3 instances mentioned in
ETHRegistrarController
:·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Methods │
································|·····················|·············|·············|···············|···············|··············
| Contract · Method · Min · Max · Avg · # calls · eur (avg) │
································|·····················|·············|·············|···············|···············|··············
| ETHRegistrarController · register · 233372 · 377120 · 293714 · 15 · - │
································|·····················|·············|·············|···············|···············|··············
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 2010419 · 6.7 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·
·-----------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
······················································|···························|···············|······························
| Methods │
································|·····················|·············|·············|···············|···············|··············
| Contract · Method · Min · Max · Avg · # calls · eur (avg) │
································|·····················|·············|·············|···············|···············|··············
| ETHRegistrarController · register · 233244 · 376921 · 293558 · 15 · - │
································|·····················|·············|·············|···············|···············|··············
| Deployments · · % of limit · │
······················································|·············|·············|···············|···············|··············
| ETHRegistrarController · - · - · 1997901 · 6.7 % · - │
·-----------------------------------------------------|-------------|-------------|---------------|---------------|-------------·
It saves
12,518
upon deployment, and an average of156
gas perregister
call - here fordata.length == 15
, so an average of10
gas per byte of data.Upgrade Solidity compiler version
IMPACT
700
gasPROOF OF CONCEPT
All the contracts in scope have the pragma set to
^0.8.4
TOOLS USED
Manual Analysis
MITIGATION
Upgrade these contracts compiler versions.
use Assembly for simple setters
IMPACT
Where it does not affect readability, using assembly allows to save gas not only on deployment, but also on function calls.
This is the case for instance for simple admin setters.
PROOF OF CONCEPT
2 instances:
Owned.sol
NameWrapper.sol
MITIGATION
Use assembly to write the new value in storage. For instance in
NameWrapper.sol
:·---------------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
··························································|···························|···············|······························
| Methods │
································|·························|·············|·············|···············|···············|··············
| Contract · Method · Min · Max · Avg · # calls · eur (avg) │
································|·························|·············|·············|···············|···············|··············
| NameWrapper · setMetadataService · - · - · 29013 · 1 · - │
································|·························|·············|·············|···············|···············|··············
| Deployments · · % of limit · │
··························································|·············|·············|···············|···············|··············
| NameWrapper · - · - · 5250093 · 17.5 % · - │
·---------------------------------------------------------|-------------|-------------|---------------|---------------|-------------·
·---------------------------------------------------------|---------------------------|---------------|-----------------------------·
| Solc version: 0.8.13 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
··························································|···························|···············|······························
| Methods │
································|·························|·············|·············|···············|···············|··············
| Contract · Method · Min · Max · Avg · # calls · eur (avg) │
································|·························|·············|·············|···············|···············|··············
| NameWrapper · setMetadataService · - · - · 28962 · 1 · - │
································|·························|·············|·············|···············|···············|··············
| Deployments · · % of limit · │
··························································|·············|·············|···············|···············|··············
| NameWrapper · - · - · 5238900 · 17.5 % · - │
·---------------------------------------------------------|-------------|-------------|---------------|---------------|-------------·
It saves
11,193
gas upon deployment, and an average of51
gas per function call.The text was updated successfully, but these errors were encountered: