Gas savings are estimated using the gas report of existing yarn compile && REPORT_GAS=true yarn test tests (the sum of all deployment costs and the sum of the costs of calling all methods) and may vary depending on the implementation of the fix.
Some optimizations (mostly logical) cannot be scored with a exact gas quantity.
Gas Optimizations
Issue
Instances
Estimated gas(deployments)
Estimated gas(method call)
1
Use custom errors rather than revert()/require() strings to save gas
26
282 389
49
2
Using private rather than public for constants, saves gas
8
99 638
45
3
Duplicated require()/revert() checks should be refactored to a modifier or function
7
91 132
-169
4
Increment in loop can be unchecked
5
37 105
589
5
require()/revert() strings longer than 32 bytes cost extra gas
2
24 842
0
6
Using calldata instead of memory for read-only arguments in external functions saves gas
2
19 891
0
7
Using bools for storage incurs overhead
6
17 186
777
8
Unnecessary if statement
3
16 191
4
9
<x> = <x> + 1 even more efficient than increment.
5
9 654
-50
10
private functions only called once can be inlined to save gas
1
4 071
0
11
Unchecking arithmetics operations that can't underflow/overflow
2
3 894
65
12
Do not emit storage variables, when stack available
5
2 988
144
13
++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)
4
2 136
40
14
State variables should be cached in stack variables rather than re-reading them from storage
1
1 734
0
↓↓↓
General optimizations*
↓↓↓
↓↓↓
↓↓↓
1
<array>.length should not be looked up in every loop of a for-loop
4
2
It costs more gas to initialize non-constant/non-immutable variables to zero than to let the default of zero be applied
5
3
Don't compare boolean expressions to boolean literals
5
Overall gas saved
91
519 061
1 422
Total: 91 instances over 17 issues
*General optimizations is a "best practices", that i can't estimate, because optimization is:
highly depends on user input; OR
depends on optimizer settings; OR
logical.
1. Use custom errors rather than revert()/require() strings to save gas (26 instances)
2. Using private rather than public for constants, saves gas (8 instances)
Deployements. Gas Saved: 99 638
Method Calls. Gas Saved: 45
If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table
diff --git a/contracts/BlurExchange.sol b/contracts/BlurExchange.sol
index 6fdc510..0d253ac 100644
--- a/contracts/BlurExchange.sol
+++ b/contracts/BlurExchange.sol
@@ -479,7 +479,7 @@ contract BlurExchange is IBlurExchange, ReentrancyGuarded, EIP712, OwnableUpgrad
479, 479: totalFee += fee;
480, 480: }
481, 481:
- 482 :- require(totalFee <= price, "Total amount of fees are more than the price");
+ 482:+ require(totalFee <= price, "fees are more than the price");
483, 483:
484, 484: /* Amount that will be received by seller. */
485, 485: uint256 receiveAmount = price - totalFee;
diff --git a/contracts/ExecutionDelegate.sol b/contracts/ExecutionDelegate.sol
index bb5e5ca..bf7d39e 100644
--- a/contracts/ExecutionDelegate.sol
+++ b/contracts/ExecutionDelegate.sol
@@ -19,7 +19,7 @@ contract ExecutionDelegate is IExecutionDelegate, Ownable {
19, 19: mapping(address => bool) public revokedApproval;
20, 20:
21, 21: modifier approvedContract() {
- 22 :- require(contracts[msg.sender], "Contract is not approved to make transfers");
+ 22:+ require(contracts[msg.sender], "Contract is not approved");
23, 23: _;
24, 24: }
25, 25:
diff --git a/tests/execution.test.ts b/tests/execution.test.ts
index 7b115b7..cc752e9 100644
--- a/tests/execution.test.ts
+++ b/tests/execution.test.ts
@@ -144,7 +144,7 @@ export function runExecuteTests(setupTest: any) {
144, 144:
145, 145: await expect(
146, 146: exchange.connect(bob).execute(sellInput, buyInput),
- 147 :- ).to.be.revertedWith('Contract is not approved to make transfers');
+ 147:+ ).to.be.revertedWith('Contract is not approved');
148, 148: await executionDelegate.approveContract(exchange.address);
149, 149: });
150, 150: it('should succeed is approval is given', async () => {
6. Using calldata instead of memory for read-only arguments in external functions saves gas (2 instances)
Deployements. Gas Saved: 19 891
Method Calls. Gas Saved: 0
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. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * .length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution.
If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one
7. Using bools for storage incurs overhead (4 instances)
Deployements. Gas Saved: 17 186
Method Calls. Gas Saved: 777
// 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.
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
diff --git a/contracts/BlurExchange.sol b/contracts/BlurExchange.sol
index 6fdc510..fbedbc9 100644
--- a/contracts/BlurExchange.sol
+++ b/contracts/BlurExchange.sol
@@ -205,8 +205,8 @@ contract BlurExchange is IBlurExchange, ReentrancyGuarded, EIP712, OwnableUpgrad
205, 205: * @dev Cancel all current orders for a user, preventing them from being matched. Must be called by the trader of the order
206, 206: */
207, 207: function incrementNonce() external {
- 208 :- nonces[msg.sender] += 1;
- 209 :- emit NonceIncremented(msg.sender, nonces[msg.sender]);
+ 208:+ uint256 _nonce = nonces[msg.sender] += 1;
+ 209:+ emit NonceIncremented(msg.sender, _nonce);
210, 210: }
211, 211:
212, 212:
@@ -218,7 +218,7 @@ contract BlurExchange is IBlurExchange, ReentrancyGuarded, EIP712, OwnableUpgrad
218, 218: {
219, 219: require(address(_executionDelegate) != address(0), "Address cannot be zero");
220, 220: executionDelegate = _executionDelegate;
- 221 :- emit NewExecutionDelegate(executionDelegate);
+ 221:+ emit NewExecutionDelegate(_executionDelegate);
222, 222: }
223, 223:
224, 224: function setPolicyManager(IPolicyManager _policyManager)
@@ -227,7 +227,7 @@ contract BlurExchange is IBlurExchange, ReentrancyGuarded, EIP712, OwnableUpgrad
227, 227: {
228, 228: require(address(_policyManager) != address(0), "Address cannot be zero");
229, 229: policyManager = _policyManager;
- 230 :- emit NewPolicyManager(policyManager);
+ 230:+ emit NewPolicyManager(_policyManager);
231, 231: }
232, 232:
233, 233: function setOracle(address _oracle)
@@ -236,7 +236,7 @@ contract BlurExchange is IBlurExchange, ReentrancyGuarded, EIP712, OwnableUpgrad
236, 236: {
237, 237: require(_oracle != address(0), "Address cannot be zero");
238, 238: oracle = _oracle;
- 239 :- emit NewOracle(oracle);
+ 239:+ emit NewOracle(_oracle);
240, 240: }
241, 241:
242, 242: function setBlockRange(uint256 _blockRange)
@@ -244,7 +244,7 @@ contract BlurExchange is IBlurExchange, ReentrancyGuarded, EIP712, OwnableUpgrad
244, 244: onlyOwner
245, 245: {
246, 246: blockRange = _blockRange;
- 247 :- emit NewBlockRange(blockRange);
+ 247:+ emit NewBlockRange(_blockRange);
248, 248: }
249, 249:
250, 250:
13. ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too) (4 instances)
Deployements. Gas Saved: 2 136
Method Calls. Gas Saved: 40
Saves 6 gas per loop
Pre-increments and pre-decrements are cheaper.
For a uint256 i variable, the following is true with the Optimizer enabled at 10k:
Increment:
i += 1 is the most expensive form
i++ costs 6 gas less than i += 1
++i costs 5 gas less than i++ (11 gas less than i += 1)
Decrement:
i -= 1 is the most expensive form
i-- costs 11 gas less than i -= 1
--i costs 5 gas less than i-- (16 gas less than i -= 1)
Summary
Gas savings are estimated using the gas report of existing
yarn compile && REPORT_GAS=true yarn test
tests (the sum of all deployment costs and the sum of the costs of calling all methods) and may vary depending on the implementation of the fix. Some optimizations (mostly logical) cannot be scored with a exact gas quantity.private
rather thanpublic
for constants, saves gasrequire()
/revert()
strings longer than 32 bytes cost extra gas<x> = <x> + 1
even more efficient than increment.private
functions only called once can be inlined to save gas++i
costs less gas thani++
, especially when it's used in for-loops (--i
/i--
too)<array>.length
should not be looked up in every loop of a for-loopTotal: 91 instances over 17 issues
*General optimizations is a "best practices", that i can't estimate, because optimization is:
logical.
1. Use custom errors rather than revert()/require() strings to save gas (26 instances)
Deployements. Gas Saved: 282 389
Method Calls. Gas Saved: 49
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
- contracts/lib/ReentrancyGuarded.sol:14
- contracts/BlurExchange.sol:36, 134, 139, 140, 142, 143, 183, 219, 228, 237, 318, 407, 424, 428, 431, 452, 482, 534
- contracts/ExecutionDelegate.sol:22, 77, 92, 108, 124
- contracts/PolicyManager.sol:26, 37
2. Using
private
rather thanpublic
for constants, saves gas (8 instances)Deployements. Gas Saved: 99 638
Method Calls. Gas Saved: 45
If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table
- contracts/BlurExchange.sol:57, 58, 59
- contracts/lib/EIP712.sol:20, 23, 26, 29, 33
3. Duplicated require()/revert() checks should be refactored to a modifier or function (7 instances)
Deployements. Gas Saved: 91 132
Method Calls. Gas Saved: -169
- contracts/BlurExchange.sol:219, 228, 237
- contracts/ExecutionDelegate.sol:77, 92, 108, 124
4. Increment in loop can be unchecked (5 instances)
Deployements. Gas Saved: 37 105
Method Calls. Gas Saved: 589
- contracts/BlurExchange.sol:199, 476
- contracts/PolicyManager.sol:77
- contracts/lib/EIP712.sol:77
-contracts/lib/MerkleVerifier.sol:38
5.
require()
/revert()
strings longer than 32 bytes cost extra gas (2 instances)Deployements. Gas Saved: 24 842
Method Calls. Gas Saved: 0
Each extra chunk of bytes past the original 32 incurs an MSTORE which costs 3 gas
- contracts/BlurExchange.sol:482
- contracts/ExecutionDelegate.sol:22
6. Using calldata instead of memory for read-only arguments in external functions saves gas (2 instances)
Deployements. Gas Saved: 19 891
Method Calls. Gas Saved: 0
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. Each iteration of this for-loop costs at least 60 gas (i.e. 60 *.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution.
If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one
- contracts/lib/MerkleVerifier.sol:20, 35
7. Using bools for storage incurs overhead (4 instances)
Deployements. Gas Saved: 17 186
Method Calls. Gas Saved: 777
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
- contracts/BlurExchange.sol:71
- contracts/ExecutionDelegate.sol:18, 19
- contracts/lib/ReentrancyGuarded.sol:10
8. Unnecessary if statement (3 instances)
Deployements. Gas Saved: 16 191
Method Calls. Gas Saved: 4
Note: Enum variable can take only defined values
- contracts/BlurExchange.sol:357, 386, 539
9.
<x> = <x> + 1
even more efficient than increment. (5 instances)Deployements. Gas Saved: 9 654
Method Calls. Gas Saved: -50
- contracts/BlurExchange.sol:199, 476
- contracts/PolicyManager.sol:77
- contracts/lib/EIP712.sol:77
-contracts/lib/MerkleVerifier.sol:38
10.
private
functions only called once can be inlined to save gas (1 instance)Deployements. Gas Saved: 4 071
Method Calls. Gas Saved: 0
Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.
- contracts/lib/MerkleVerifier.sol:45-48
11. Unchecking arithmetics operations that can't underflow/overflow (2 instances)
Deployements. Gas Saved: 3 894
Method Calls. Gas Saved: 65
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: https://docs.soliditylang.org/en/v0.8.10/control-structures.html#checked-or-unchecked-arithmetic
- contracts/BlurExchange.sol:479
NOTE: Cannot overflow because
fee = X / 10000
and loop limited byuint8.MAX
- contracts/PolicyManager.sol:72
12. Do not emit storage variables, when stack available (5 instances)
Deployements. Gas Saved: 2 988
Method Calls. Gas Saved: 144
- contracts/BlurExchange.sol:208-209, 221, 230, 239, 247
13.
++i
costs less gas thani++
, especially when it's used in for-loops (--i
/i--
too) (4 instances)Deployements. Gas Saved: 2 136
Method Calls. Gas Saved: 40
... See the rest this report here