2- Multiple address mappings can be combined into a single mapping of an address to a struct :
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
There are 10 instances of this issue :
File: contracts/JBTiered721DelegateStore.sol
119 mapping(address => uint256) public override maxTierIdOf;
129 mapping(address => mapping(address => mapping(uint256 => uint256))) public override tierBalanceOf;
138 mapping(address => mapping(uint256 => uint256)) public override numberOfReservesMintedFor;
147 mapping(address => mapping(uint256 => uint256)) public override numberOfBurnedFor;
155 mapping(address => address) public override defaultReservedTokenBeneficiaryOf;
164 mapping(address => mapping(uint256 => address)) public override firstOwnerOf;
172 mapping(address => string) public override baseUriOf;
180 mapping(address => IJBTokenUriResolver) public override tokenUriResolverOf;
188 mapping(address => string) public override contractUriOf;
197 mapping(address => mapping(uint256 => bytes32)) public override encodedIPFSUriOf;
These mappings could be refactored into the following struct and mapping for example :
Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn’t possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block.
There is 1 instance of this issue:
File: contracts/JBTiered721DelegateStore.sol Line 1077
5- Use calldata instead of memory for function parameters type :
If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
There are 12 instances of this issue:
File: contracts/JB721TieredGovernance.sol Line 147
function setTierDelegates(JBTiered721SetTierDelegatesData[] memory _setTierDelegatesData)
Gas Optimizations
Findings
immutable
unchecked
blocks to save gasx += y
/x -= y
costs more gas thanx = x + y
/x = x - y
for state variablescalldata
instead ofmemory
for function parameters type++i/i++
should beunchecked{++i}
/unchecked{i++}
when it is not possible for them to overflow, as in the case when used in for & while loopsFindings
1- State variables only set in the constructor should be declared
immutable
:Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmacces (100 gas) with a PUSH32 (3 gas).
There are 4 instances of this issue:
File: contracts/JBTiered721Delegate.sol Line 48
2- Multiple address mappings can be combined into a single mapping of an address to a struct :
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
There are 10 instances of this issue :
These mappings could be refactored into the following struct and mapping for example :
3- Use
unchecked
blocks to save gas :Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn’t possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block.
There is 1 instance of this issue:
File: contracts/JBTiered721DelegateStore.sol Line 1077
The above operation cannot underflow due to the check :
if (_storedTier.contributionFloor > leftoverAmount) revert INSUFFICIENT_AMOUNT();
and should be modified as follows :
4-
x += y/x -= y
costs more gas thanx = x + y/x = x - y
for state variables :Using the addition operator instead of plus-equals saves 113 gas as explained here
There is 1 instance of this issue:
File: contracts/JBTiered721DelegateStore.sol Line 827
5- Use
calldata
instead ofmemory
for function parameters type :If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
There are 12 instances of this issue:
File: contracts/JB721TieredGovernance.sol Line 147
File: contracts/JBTiered721Delegate.sol Line 264
File: contracts/JBTiered721Delegate.sol Line 290
File: contracts/JBTiered721Delegate.sol Line 490
File: contracts/JBTiered721Delegate.sol Line 598
File: contracts/JBTiered721Delegate.sol Line 650-654
File: contracts/JBTiered721Delegate.sol Line 695
File: contracts/JBTiered721DelegateStore.sol Line 628
File: contracts/JBTiered721DelegateStore.sol Line 1091
File: contracts/libraries/JBIpfsDecoder.sol Line 628
File: contracts/libraries/JBIpfsDecoder.sol Line 74
File: contracts/libraries/JBIpfsDecoder.sol Line 82
6-
++i/i++
should beunchecked{++i}/unchecked{i++}
when it is not possible for them to overflow, as in the case when used in for & while loops :There are 5 instances of this issue:
File: contracts/libraries/JBIpfsDecoder.sol Line 49
File: contracts/libraries/JBIpfsDecoder.sol Line 51
File: contracts/libraries/JBIpfsDecoder.sol Line 68
File: contracts/libraries/JBIpfsDecoder.sol Line 76
File: contracts/libraries/JBIpfsDecoder.sol Line 84