2. ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops
The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas PER LOOP
3. An array's length should be cached to save gas in for-loops
Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack.
Caching the array length in the stack saves around 3 gas per iteration.
contracts/BlurExchange.sol:476: for (uint8 i = 0; i < fees.length; i++) {
Remediation:
Here, I suggest storing the array's length in a variable before the for-loop, and use it instead.
4. Custom Errors instead of Revert Strings to save Gas
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met). 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. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");),but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.
Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).
contracts/ExecutionDelegate.sol:124: require(revokedApproval[from] == false, "User has revoked approval");
Remediation:
I suggest replacing revert strings with custom errors.
5. Usage of assert() instead of require()
Between solc 0.4.10 and 0.8.0, require() used REVERT (0xfd) opcode which refunded the remaining gas on failure while assert() used INVALID (0xfe) opcode which consumed all the supplied gas. (see https://docs.soliditylang.org/en/v0.8.1/control-structures.html#error-handling-assert-require-revert-and-exceptions).
require() should be used for checking error conditions on inputs and return values while assert() should be used for invariant checking (properly functioning code should never reach a failing assert statement, unless there is a bug in your contract you should fix).
From the current usage of assert, my guess is that they can be replaced with require unless a Panic really is intended.
Comparing to a constant (true or false) is a bit more expensive than directly checking the returned boolean value. I suggest using if(directValue) instead of if(directValue == true) and if(!directValue) instead of if(directValue == false) here
7. Strict inequalities (>) are more expensive than non-strict ones (>=)
Strict inequalities (>) are more expensive than non-strict ones (>=). This is due to some supplementary checks (ISZERO, 3 gas. I suggest using >= instead of > to avoid some opcodes here:
10. Variables: No need to explicitly initialize variables with default values
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
We can use uint number; instead of uint number = 0;
I suggest removing explicit initializations for default values.
11. Expressions for constant values such as a call to keccak256(), should use immutable rather than constant
This results in the keccak operation being performed whenever the variable is used, increasing gas costs relative to just storing the output hash. Changing to immutable will only perform hashing on contract deployment which will save gas.
Instances:
PLEASE ADD LINK TO FILES INSTEAD OF CODE FOR THIS ONE.. EIP712.sol:L20
contracts/lib/EIP712.sol:20: bytes32 constant public FEE_TYPEHASH = keccak256(
// 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.
Refer Here Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past
14. Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
Gas Optimization
Total instances found in this contest: 77 | Among all files in scope
1. Pre-increment costs less gas as compared to Post-increment :
++i costs less gas as compared to i++ for unsigned integer, as per-increment is cheaper(its about 5 gas per iteration cheaper)
i++ increments i and returns initial value of i. Which means
But ++i returns the actual incremented value:
In the first case, the compiler has create a temporary variable (when used) for returning 1 instead of 2.
Instances:
PolicyManager.sol:L77
EIP712.sol:L77
MerkleVerifier.sol:L38
BlurExchange.sol:L199
BlurExchange.sol:L476
BlurExchange.sol:L208
Recommendations:
Change post-increment to pre-increment.
2. ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops
The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas PER LOOP
Instances:
PolicyManager.sol:L77
EIP712.sol:L77
MerkleVerifier.sol:L38
BlurExchange.sol:L199
BlurExchange.sol:L476
Reference:
https://code4rena.com/reports/2022-05-cally#g-11-ii-should-be-uncheckediuncheckedi-when-it-is-not-possible-for-them-to-overflow-as-is-the-case-when-used-in-for--and-while-loops
3. An array's length should be cached to save gas in for-loops
Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack. Caching the array length in the stack saves around 3 gas per iteration.
Instances:
EIP712.sol:L77
MerkleVerifier.sol:L38
BlurExchange.sol:L199
BlurExchange.sol:L476
Remediation:
Here, I suggest storing the array's length in a variable before the for-loop, and use it instead.
4. Custom Errors instead of Revert Strings to save Gas
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met). 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. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");),but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them. Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).
Instances
PolicyManager.sol:L26
PolicyManager.sol:L37
ReentrancyGuarded.sol:L14
BlurExchange.sol:L36
BlurExchange.sol:L139
BlurExchange.sol:L140
BlurExchange.sol:L142
BlurExchange.sol:L143
BlurExchange.sol:L219
BlurExchange.sol:L228
BlurExchange.sol:L237
BlurExchange.sol:L318
BlurExchange.sol:L407
BlurExchange.sol:L424
BlurExchange.sol:L428
BlurExchange.sol:L431
BlurExchange.sol:L482
BlurExchange.sol:L534
ExecutionDelegate.sol:L22
ExecutionDelegate.sol:L77
ExecutionDelegate.sol:L92
ExecutionDelegate.sol:L108
ExecutionDelegate.sol:L124
Remediation:
I suggest replacing revert strings with custom errors.
5. Usage of assert() instead of require()
Between solc 0.4.10 and 0.8.0, require() used REVERT (0xfd) opcode which refunded the remaining gas on failure while assert() used INVALID (0xfe) opcode which consumed all the supplied gas. (see https://docs.soliditylang.org/en/v0.8.1/control-structures.html#error-handling-assert-require-revert-and-exceptions). require() should be used for checking error conditions on inputs and return values while assert() should be used for invariant checking (properly functioning code should never reach a failing assert statement, unless there is a bug in your contract you should fix). From the current usage of assert, my guess is that they can be replaced with require unless a Panic really is intended.
Instances:
ERC1967Proxy.sol:L22
References:
https://code4rena.com/reports/2022-05-bunker/#g-08-usage-of-assert-instead-of-require
6. Boolean Comparision
Comparing to a constant (true or false) is a bit more expensive than directly checking the returned boolean value. I suggest using if(directValue) instead of if(directValue == true) and if(!directValue) instead of if(directValue == false) here
Instances:
ExecutionDelegate.sol:L77
ExecutionDelegate.sol:L92
ExecutionDelegate.sol:L108
ExecutionDelegate.sol:L124
References:
https://code4rena.com/reports/2022-04-badger-citadel/#g-04-fundingonlywhenpricenotflagged-boolean-comparison-147
7. Strict inequalities (>) are more expensive than non-strict ones (>=)
Strict inequalities (>) are more expensive than non-strict ones (>=). This is due to some supplementary checks (ISZERO, 3 gas. I suggest using >= instead of > to avoid some opcodes here:
Instances:
BlurExchange.sol:L169
BlurExchange.sol:L557
References:
https://code4rena.com/reports/2022-04-badger-citadel/#g-31--is-cheaper-than
8. x += y costs more gas than x = x + y for state variables
Instances:
BlurExchange.sol:L208
BlurExchange.sol:L479
References:
https://github.com/code-423n4/2022-05-backd-findings/issues/108
9. abi.encode() is less efficient than abi.encodepacked()
Use abi.encodepacked() instead of abi.encode()
Instances:
PLEASE ADD LINK TO FILES INSTEAD OF CODE FOR THIS ONE.. EIP712.sol:L45
EIP712.sol:L61
EIP712.sol:L91
EIP712.sol:L107
EIP712.sol:L132
EIP712.sol:L147
Reference:
https://code4rena.com/reports/2022-06-notional-coop#15-abiencode-is-less-efficient-than-abiencodepacked
10. Variables: No need to explicitly initialize variables with default values
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
We can use
uint number;
instead ofuint number = 0;
Instances:
BlurExchange.sol:L475
ReentrancyGuarded.sol:L10
Recommendation:
I suggest removing explicit initializations for default values.
11. Expressions for constant values such as a call to keccak256(), should use immutable rather than constant
This results in the keccak operation being performed whenever the variable is used, increasing gas costs relative to just storing the output hash. Changing to immutable will only perform hashing on contract deployment which will save gas.
Instances:
PLEASE ADD LINK TO FILES INSTEAD OF CODE FOR THIS ONE.. EIP712.sol:L20
EIP712.sol:L23
EIP712.sol:L26
EIP712.sol:L29
EIP712.sol:L33
References:
https://github.com/code-423n4/2021-10-slingshot-findings/issues/3
12. Reduce the size of error messages (Long revert Strings)
Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met.
Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
Instances:
BlurExchange.sol:L318
BlurExchange.sol:L482
ExecutionDelegate.sol:L22
Recommendations:
I suggest shortening the revert strings to fit in 32 bytes, or using custom errors as described next.
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)
Source Custom Errors in Solidity:
13. Using bools for storage incurs overhead
Refer Here Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past
Instances:
ReentrancyGuarded.sol:L10
BlurExchange.sol:L71
BlurExchange.sol:L421
ExecutionDelegate.sol:L18
ExecutionDelegate.sol:L19
References:
https://code4rena.com/reports/2022-06-notional-coop#8-using-bools-for-storage-incurs-overhead
14. Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
Instances:
OrderStructs.sol:L9
OrderStructs.sol:L32
BlurExchange.sol:L347
BlurExchange.sol:L383
BlurExchange.sol:L403
References:
https://code4rena.com/reports/2022-04-phuture#g-14-usage-of-uintsints-smaller-than-32-bytes-256-bits-incurs-overhead
15. Use assembly to check for address(0)
Saves 6 gas per instance if using assembly to check for address(0)
e.g.
Instances
BlurExchange.sol:L219
BlurExchange.sol:L228
BlurExchange.sol:L237
BlurExchange.sol:L265