Issue | Contexts | Estimated Gas Saved | |
---|---|---|---|
GAS‑1 | abi.encode() is less efficient than abi.encodepacked() |
1 | 53 |
GAS‑2 | Consider activating via-ir for deploying |
1 | 250 |
GAS‑3 | Use assembly to emit events | 4 | 152 |
GAS‑4 | Setting the constructor to payable |
1 | 13 |
GAS‑5 | Functions guaranteed to revert when called by normal users can be marked payable |
3 | 63 |
GAS‑6 | Optimize names to save gas | 2 | 44 |
GAS‑7 | Use solidity version 0.8.20 to gain some gas boost | 4 | 352 |
GAS‑8 | Using XOR (^) and AND (&) bitwise equivalents | 1 | 13 |
Total: 17 contexts over 8 issues
abi.encode()
is less efficient than abi.encodepacked()
See for more information: https://github.com/ConnorBlockchain/Solidity-Encode-Gas-Comparison
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/EscrowFactory.sol#L77
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
}
function testGas() public {
c0.not_optimized();
c1.optimized();
}
}
contract Contract0 {
string a = "Code4rena";
function not_optimized() public returns(bytes32){
return keccak256(abi.encode(a));
}
}
contract Contract1 {
string a = "Code4rena";
function optimized() public returns(bytes32){
return keccak256(abi.encodePacked(a));
}
}
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
101871 | 683 | ||||
Function Name | min | avg | median | max | # calls |
not_optimized | 2661 | 2661 | 2661 | 2661 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
99465 | 671 | ||||
Function Name | min | avg | median | max | # calls |
optimized | 2608 | 2608 | 2608 | 2608 | 1 |
via-ir
for deployingThe IR-based code generator was introduced with an aim to not only allow code generation to be more transparent and auditable but also to enable more powerful optimization passes that span across functions.
You can enable it on the command line using --via-ir
or with the option {"viaIR": true}
.
This will take longer to compile, but you can just simple test it before deploying and if you got a better benchmark then you can add --via-ir to your deploy command
More on: https://docs.soliditylang.org/en/v0.8.17/ir-breaking-changes.html
We can use assembly to emit events efficiently by utilizing scratch space
and the free memory pointer
. This will allow us to potentially avoid memory expansion costs.
Note: In order to do this optimization safely, we will need to cache and restore the free memory pointer.
For example, for a generic emit
event for eventSentAmountExample
:
We can use the following assembly emit events:
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/Escrow.sol#L96
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/Escrow.sol#L105
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/Escrow.sol#L117
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/EscrowFactory.sol#L51
constructor
to payable
Saves ~13 gas per instance
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/Escrow.sol#L32
payable
If a function modifier or require such as onlyOwner/onlyX is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2), DUP1(3), ISZERO(3), PUSH2(3), JUMPI(10), PUSH1(3), DUP1(3), REVERT(0), JUMPDEST(1), POP(2) which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/Escrow.sol#L94
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/Escrow.sol#L102
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/Escrow.sol#L109
Functions guaranteed to revert when called by normal users can be marked payable.
Contracts most called functions could simply save gas by function ordering via Method ID. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions.
See more here
All in-scope contracts
Find a lower method ID name for the most called functions for example Call() vs. Call1() is cheaper by 22 gas
For example, the function IDs in the Gauge.sol contract will be the most used; A lower method ID may be given.
Upgrade to the latest solidity version 0.8.20 to get additional gas savings.
See latest release for reference: https://blog.soliditylang.org/2023/05/10/solidity-0.8.20-release-announcement/
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/Escrow.sol#L2
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/EscrowFactory.sol#L2
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/IEscrow.sol#L2
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/IEscrowFactory.sol#L2
Given 4 variables a, b, c and d represented as such:
0 0 0 0 0 1 1 0 <- a
0 1 1 0 0 1 1 0 <- b
0 0 0 0 0 0 0 0 <- c
1 1 1 1 1 1 1 1 <- d
To have a == b means that every 0 and 1 match on both variables. Meaning that a XOR (operator ^) would evaluate to 0 ((a ^ b) == 0), as it excludes by definition any equalities.Now, if a != b, this means that there’s at least somewhere a 1 and a 0 not matching between a and b, making (a ^ b) != 0.Both formulas are logically equivalent and using the XOR bitwise operator costs actually the same amount of gas.However, it is much cheaper to use the bitwise OR operator (|) than comparing the truthy or falsy values.These are logically equivalent too, as the OR bitwise operator (|) would result in a 1 somewhere if any value is not 0 between the XOR (^) statements, meaning if any XOR (^) statement verifies that its arguments are different.
https://github.com/Cyfrin/2023-07-escrow/tree/main/src/Escrow.sol#L103
contract GasTest is DSTest {
Contract0 c0;
Contract1 c1;
function setUp() public {
c0 = new Contract0();
c1 = new Contract1();
}
function testGas() public {
c0.not_optimized(1,2);
c1.optimized(1,2);
}
}
contract Contract0 {
function not_optimized(uint8 a,uint8 b) public returns(bool){
return ((a==1) || (b==1));
}
}
contract Contract1 {
function optimized(uint8 a,uint8 b) public returns(bool){
return ((a ^ 1) & (b ^ 1)) == 0;
}
}
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
46099 | 261 | ||||
Function Name | min | avg | median | max | # calls |
not_optimized | 456 | 456 | 456 | 456 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
42493 | 243 | ||||
Function Name | min | avg | median | max | # calls |
optimized | 430 | 430 | 430 | 430 | 1 |
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.