The Graph L2 Bridge Contest Gas Optimization Report
Summary
The following gas optimization issues were found during the code review:
Use calldata instead of memory (3 instances)
Long require()/revert() string (4 instances)
Using bools for storage incurs overhead (2 instances)
Use != 0 instead of > 0 when comparing uint (4 instances)
Split require(xxx && yyy) to require(xxx) and require(yyy) (1 instance)
Use abi.encodePacked() instead of abi.encode() (6 instances)
Use custom errors instead of revert()/require() strings (55 instances)
Total 75 instances of 7 issues.
1. Use calldata instead of memory (3 instances)
When a function with a memory array is called externally, the abi.decode() step has to use a for loop to copy each index of the calldata to the memory index. This overhead can be optimized by using calldata directly.
contracts/governance/Managed.sol::173 => function _syncContract(string memory _name) internal {
contracts/l2/gateway/L2GraphTokenGateway.sol::286 => function parseOutboundData(bytes memory _data) private view returns (address, bytes memory) {
contracts/gateway/L1GraphTokenGateway.sol::290 => function parseOutboundData(bytes memory _data)
2. Long require()/revert() string (4 instances)
Shortening revert strings to fit in 32 bytes will decrease gas costs for deployment and gas costs when the revert condition has been met.
contracts/upgrades/GraphUpgradeable.sol::32 => require(msg.sender == _implementation(), "Caller must be the implementation");
contracts/upgrades/GraphProxy.sol::105 => require(_newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
contracts/upgrades/GraphProxy.sol::141 => require(Address.isContract(_pendingImplementation), "Implementation must be a contract");
contracts/governance/Managed.sol::53 => require(msg.sender == controller.getGovernor(), "Caller must be Controller governor");
3. Using bools for storage incurs overhead (2 instances)
Use uint256(1) and uint256(2) for true/false. Booleans are more expensive than uint256 or any type that takes up a full word because 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. This is the compiler's defense against contract upgrades and pointer aliasing, and it cannot be disabled.
6. Use abi.encodePacked() instead of abi.encode() (6 instances)
abi.encodePacked() is more efficient than abi.encode().
contracts/l2/token/GraphTokenUpgradeable.sol::88 => abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, nonces[_owner], _deadline)
contracts/l2/token/GraphTokenUpgradeable.sol::162 => abi.encode(
contracts/l2/gateway/L2GraphTokenGateway.sol::174 => return abi.encode(id);
contracts/l2/gateway/L2GraphTokenGateway.sol::275 => abi.encode(0, _data) // we don't need to track exitNums (b/c we have no fast exits) so we always use 0
contracts/gateway/L1GraphTokenGateway.sol::249 => return abi.encode(seqNum);
contracts/gateway/L1GraphTokenGateway.sol::342 => abi.encode(emptyBytes, _data)
7. Use custom errors instead of revert()/require() strings (55 instances)
Using require()/revert() strings is expensive. Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors.
Custom errors decrease both deploy and runtime gas costs. Note that runtime gas cost is only relevant when the revert condition is met.
The Graph L2 Bridge Contest Gas Optimization Report
Summary
The following gas optimization issues were found during the code review:
calldata
instead ofmemory
(3 instances)require()
/revert()
string (4 instances)bool
s for storage incurs overhead (2 instances)!= 0
instead of> 0
when comparing uint (4 instances)require(xxx && yyy)
torequire(xxx)
andrequire(yyy)
(1 instance)abi.encodePacked()
instead ofabi.encode()
(6 instances)revert()
/require()
strings (55 instances)Total 75 instances of 7 issues.
1. Use
calldata
instead ofmemory
(3 instances)When a function with a
memory
array is called externally, theabi.decode()
step has to use a for loop to copy each index of thecalldata
to thememory
index. This overhead can be optimized by usingcalldata
directly.2. Long
require()
/revert()
string (4 instances)Shortening revert strings to fit in 32 bytes will decrease gas costs for deployment and gas costs when the revert condition has been met.
3. Using
bool
s for storage incurs overhead (2 instances)Use
uint256(1)
anduint256(2)
for true/false. Booleans are more expensive than uint256 or any type that takes up a full word because 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. This is the compiler's defense against contract upgrades and pointer aliasing, and it cannot be disabled.4. Use
!= 0
instead of> 0
when comparing uint (4 instances)When dealing with unsigned integer types, comparisons with
!= 0
are cheaper then with> 0
.5. Split
require(xxx && yyy)
torequire(xxx)
andrequire(yyy)
(1 instance)Instead of using operator && on single require check, using double
require()
checks can save more gas.6. Use
abi.encodePacked()
instead ofabi.encode()
(6 instances)abi.encodePacked()
is more efficient thanabi.encode()
.7. Use custom errors instead of
revert()
/require()
strings (55 instances)Using
require()
/revert()
strings is expensive. Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors.Custom errors decrease both deploy and runtime gas costs. Note that runtime gas cost is only relevant when the revert condition is met.