3. != 0 is cheaper than > 0 especially in state variables
Description
Replace all > 0 for != 0. In cases where we used a variable from state assigned to a local variable it's the same gas save.
Proof of concept
contract TestA {
uint256 _paramA;
function Test () public returns (bool) {
return _paramA > 0;
}
}
contract TestB {
uint256 _paramA;
function Test () public returns (bool) {
return _paramA != 0;
}
}
contract TestC {
uint256 _paramA;
function Test () public returns (bool) {
uint256 aux = _paramA;
return aux > 0;
}
}
contract TestD {
uint256 _paramA;
function Test () public returns (bool) {
uint256 aux = _paramA;
return aux != 0;
}
}
Transaction cost of TestA/C is 23491/23504 gas
Transaction cost of TestB/D is 23494/23507 gas
After the test it's possible to save 3 gas per ocurrence with this optimization.
5. Use custom errors rather than REVERT()/REQUIRE() strings to save deployment gas
Description
Custom errors are available from solidity version 0.8.4.
Custom errors save ~50 gas each time they're hitby avoiding having to allocate and store the revert string.
Not defining the strings also save deployment gas.
6. Don't compare boolean expressions to boolean literals
Description
The following conditions could be represented in a way that save gas.
Example,
IF condition == true could be IF condition
IF contition == false could be IF !condition
Proof of concept
contract TestA {
function Test (bool paramA) public {
require(paramA == true, "Test bool");
}
}
contract TestB {
function Test (bool paramA) public {
require(paramA, "Test bool");
}
}
Transaction cost of TestA is 21629 gas
Transaction cost of TestB is 21611 gas
After the test it's possible to save 18 gas per ocurrence with this optimization.
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from 'false' to 'true', after having been 'true' in the past
8. ABI.ENCODE() is less efficient than ABI.ENCODEPACKED()
Description
abi.encode will apply ABI encoding rules. Therefore all elementary types are padded to 32 bytes and dynamic arrays include their length.
Therefore it is possible to also decode this data again (with abi.decode) when the type are known.
abi.encodePacked will only use the only use the minimal required memory to encode the data. E.g. an address will only use 20 bytes and for dynamic arrays only the elements will be stored without length.
For more info see the Solidity docs for packed mode
For the input of the keccak method it is important that you can ensure that the resulting bytes of the encoding are unique. So if you always encode the same types and arrays
always have the same length then there is no problem. But if you switch the parameters that you encode or encode multiple dynamic arrays you might have conflicts.
For example:
abi.encodePacked(address(0x0000000000000000000000000000000000000001), uint(0)) encodes to the same as abi.encodePacked(uint(0x0000000000000000000000000000000000000001000000000000000000000000), address(0))
and
abi.encodePacked(uint, uint) encodes to the same as abi.encodePacked(uint, uint)
Therefore these examples will also generate the same hashes even so they are different inputs.
On the other hand you require less memory and therefore in most cases abi.encodePacked uses less gas than abi.encode.
Use a solidity version of at least 0.8.0 to get overflow protection without SafeMath
Use a solidity version of at least 0.8.2 to get compiler automatic inlining
Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads
Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value.
Index
Details
1. Call to KECCAK256 should use IMMUTABLE rather than constant
Description
Expressions for constant values such as a call to KECCAK256 should use IMMUTABLE rather than constant
Lines in the code
GraphTokenUpgradeable.sol#L38 GraphTokenUpgradeable.sol#L39
2. Require instead of &&
Description
Split of conditions of an require sentence in different requires sentences can save gas
Lines in the code
L1GraphTokenGateway.sol#L142
3. != 0 is cheaper than > 0 especially in state variables
Description
Replace all > 0 for != 0. In cases where we used a variable from state assigned to a local variable it's the same gas save.
Proof of concept
Lines in the code
L2GraphTokenGateway.sol#L146 L2GraphTokenGateway.sol#L238 L1GraphTokenGateway.sol#L201 L1GraphTokenGateway.sol#L217
4. Require / Revert strings longer than 32 bytes cost extra gas
Description
Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas
Lines in the code
GraphUpgradeable.sol#L32 GraphProxy.sol#L105 GraphProxy.sol#L141 GraphProxy.sol#L142 Managed.sol#L53 GraphTokenGateway.sol#L19
5. Use custom errors rather than REVERT()/REQUIRE() strings to save deployment gas
Description
Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hitby avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas.
Lines in the code
GraphUpgradeable.sol#L24 GraphUpgradeable.sol#L32 Governed.sol#L24 Governed.sol#L41 Governed.sol#L54 L2GraphToken.sol#L36 L2GraphToken.sol#L49 L2GraphToken.sol#L60 L2GraphToken.sol#L70 GraphProxyAdmin.sol#L34 GraphProxyAdmin.sol#L47 GraphProxyAdmin.sol#L59 GraphProxyStorage.sol#L62 GraphProxy.sol#L105 GraphProxy.sol#L133 GraphProxy.sol#L141 GraphProxy.sol#L142 GraphProxy.sol#L157 Managed.sol#L44 Managed.sol#L45 Managed.sol#L49 Managed.sol#L53 Managed.sol#L57 Managed.sol#L104 GraphTokenUpgradeable.sol#L60 GraphTokenUpgradeable.sol#L94 GraphTokenUpgradeable.sol#L95 GraphTokenUpgradeable.sol#L106 GraphTokenUpgradeable.sol#L115 GraphTokenUpgradeable.sol#L123 L2GraphTokenGateway.sol#L69 L2GraphTokenGateway.sol#L98 L2GraphTokenGateway.sol#L108 L2GraphTokenGateway.sol#L118 L2GraphTokenGateway.sol#L126 L2GraphTokenGateway.sol#L145 L2GraphTokenGateway.sol#L146 L2GraphTokenGateway.sol#L147 L2GraphTokenGateway.sol#L148 L2GraphTokenGateway.sol#L153 L2GraphTokenGateway.sol#L233 L2GraphTokenGateway.sol#L234 L1GraphTokenGateway.sol#L74 L1GraphTokenGateway.sol#L78 L1GraphTokenGateway.sol#L82 L1GraphTokenGateway.sol#L110 L1GraphTokenGateway.sol#L111 L1GraphTokenGateway.sol#L122 L1GraphTokenGateway.sol#L132 L1GraphTokenGateway.sol#L142 L1GraphTokenGateway.sol#L153 L1GraphTokenGateway.sol#L154 L1GraphTokenGateway.sol#L165 L1GraphTokenGateway.sol#L166 L1GraphTokenGateway.sol#L200 L1GraphTokenGateway.sol#L201 L1GraphTokenGateway.sol#L202 L1GraphTokenGateway.sol#L213 L1GraphTokenGateway.sol#L217 L1GraphTokenGateway.sol#L224 L1GraphTokenGateway.sol#L271 L1GraphTokenGateway.sol#L275 GraphTokenGateway.sol#L19 GraphTokenGateway.sol#L31 GraphTokenGateway.sol#L40
6. Don't compare boolean expressions to boolean literals
Description
The following conditions could be represented in a way that save gas.
Example,
Proof of concept
Lines in the code
L1GraphTokenGateway.sol#L214
7. Using bools for storage incurs overhead
Description
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from 'false' to 'true', after having been 'true' in the past
Lines in the code
Pausable.sol#L8 Pausable.sol#L10 GraphProxyAdmin.sol#L33 GraphProxyAdmin.sol#L46 GraphProxyAdmin.sol#L58 GraphProxy.sol#L132 GraphTokenUpgradeable.sol#L51 L1GraphTokenGateway.sol#L35
8. ABI.ENCODE() is less efficient than ABI.ENCODEPACKED()
Description
abi.encode will apply ABI encoding rules. Therefore all elementary types are padded to 32 bytes and dynamic arrays include their length. Therefore it is possible to also decode this data again (with abi.decode) when the type are known.
abi.encodePacked will only use the only use the minimal required memory to encode the data. E.g. an address will only use 20 bytes and for dynamic arrays only the elements will be stored without length. For more info see the Solidity docs for packed mode
For the input of the keccak method it is important that you can ensure that the resulting bytes of the encoding are unique. So if you always encode the same types and arrays always have the same length then there is no problem. But if you switch the parameters that you encode or encode multiple dynamic arrays you might have conflicts.
For example:
abi.encodePacked(address(0x0000000000000000000000000000000000000001), uint(0)) encodes to the same as abi.encodePacked(uint(0x0000000000000000000000000000000000000001000000000000000000000000), address(0))
and
abi.encodePacked(uint, uint) encodes to the same as abi.encodePacked(uint, uint)
Therefore these examples will also generate the same hashes even so they are different inputs.
On the other hand you require less memory and therefore in most cases abi.encodePacked uses less gas than abi.encode.
Lines in the code
GraphTokenUpgradeable.sol#L88 GraphTokenUpgradeable.sol#L162 L2GraphTokenGateway.sol#L174 L2GraphTokenGateway.sol#L275 L1GraphTokenGateway.sol#L249 L1GraphTokenGateway.sol#L342
9. Use a more recent version of solidity
Description
Use a solidity version of at least 0.8.0 to get overflow protection without SafeMath Use a solidity version of at least 0.8.2 to get compiler automatic inlining Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value.
Lines in the code
BridgeEscrow.sol#L3 GraphUpgradeable.sol#L3 Governed.sol#L3 Pausable.sol#L3 L2GraphToken.sol#L3 GraphProxyAdmin.sol#L3 GraphProxyStorage.sol#L3 GraphProxy.sol#L3 Managed.sol#L3 GraphTokenUpgradeable.sol#L3 L2GraphTokenGateway.sol#L3 L1GraphTokenGateway.sol#L3 GraphTokenGateway.sol#L3 IGraphCurationToken.sol#L3 ICallhookReceiver.sol#L9 IGraphProxy.sol#L3 IEpochManager.sol#L3 IController.sol#L3 IGraphToken.sol#L3 IRewardsManager.sol#L3 IStakingData.sol#L3 ICuration.sol#L3 IStaking.sol#L3
10. Cache storage variables in local variables
Description
To avoid access to the storage to save gas is better to store the value in a local variable and use it.
Governed.acceptOwnership
Lines in the code
Governed.sol#L53-L67
11. Internal functions used once could be inlined
Managed._notPartialPaused
Managed.sol#L43
Managed._onlyGovernor
Managed.sol#L52
Managed._onlyController
Managed.sol#L56