Number | Details | Instances |
---|---|---|
[G-01] | Functions guaranteed to revert when called by normal users can be marked payable | 3 |
[G-02] | A modifier used only once and not being inherited should be inlined to save gas | 3 |
[G-03] | Use hardcode address instead address(this) | 5 |
[G-04] | Using unchecked blocks to save gas | 1 |
[G-05] | Use functions instead of modifiers | 1 |
[G-06] | Use assembly to emit events | 4 |
[G-07] | If statements that use && can be refactored into nested if statements | 1 |
[G-08] | Constructors can be marked payable | 1 |
[G-09] | Use != 0 instead of > 0 for unsigned integer comparison | 3 |
[G-10] | use assembly for address zero to save gas | 4 |
[G-11] | Amounts should be checked for 0 before calling a transfer | 5 |
[G-12] | abi.encode() is less efficient than abi.encodePacked() | 1 |
[G-13] | Use solidity version 0.8.19 to gain some gas boost | 4 |
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/blob/main/src/Escrow.sol#L94
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/Escrow.sol#L58
Instead of using address(this), it is more gas-efficient to pre-calculate and use the hardcoded address. Foundry’s script.sol and solmate’s LibRlp.sol contracts can help achieve this.
References:
https://book.getfoundry.sh/reference/forge-std/compute-create-address
https://twitter.com/transmissions11/status/1518507047943245824
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/Escrow.sol#L44
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/EscrowFactory.sol#L30
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://github.com/Cyfrin/2023-07-escrow/blob/main/src/Escrow.sol#L111
Modifiers:
Modifiers are used to add pre or post-conditions to functions. They are typically applied using the modifier keyword and can be used to enforce access control, check conditions, or perform other checks before or after a function's execution. Modifiers are often used to reduce code duplication and enforce certain rules consistently across multiple functions.
However, using modifiers can lead to increased gas costs, especially when multiple function are applied to a single modifier. Each modifier adds overhead in terms of gas consumption because its code is effectively inserted into the function at the point of the modifier keyword. This results in additional gas usage for each modifier, which can accumulate if multiple modifiers are involved.
Functions:
Functions, on the other hand, are more efficient in terms of gas consumption. When you define a function, its code is written only once and can be called from multiple places within the contract. This reduces gas costs compared to using modifiers since the function's code is not duplicated every time it's called.
By using functions instead of modifiers, you can avoid the gas overhead associated with multiple modifiers and create more gas-efficient smart contracts. If you find that certain logic needs to be applied to multiple functions, you can write a separate function to encapsulate that logic and call it from the functions that require it. This way, you achieve code reusability without incurring unnecessary gas costs.
Let's illustrate the difference between using modifiers and functions with a simple example.
Using Modifiers:
Using Functions:
In the first contract (BankAccountUsingModifiers), we used a modifier called onlyOwner to check if the caller is the owner of the bank account. The onlyOwner modifier is applied to both the withdraw and transfer functions to ensure that only the owner can perform these actions. However, using the modifier will result in additional gas costs since the modifier's code is duplicated in both functions.
In the second contract (BankAccountUsingFunctions), we replaced the modifier with a private function called onlyOwner, which checks if the caller is the owner. We then call this function at the beginning of both the withdraw and transfer functions. By doing this, we avoid the gas overhead associated with using modifiers, making the contract more gas-efficient.
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/Escrow.sol#L82
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.
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/Escrow.sol#L96
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/EscrowFactory.sol#L51
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/Escrow.sol#L67
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/Escrow.sol#L32-L51
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/Escrow.sol#L112
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/Escrow.sol#L40
Checking non-zero transfer values can avoid an expensive external call and save gas.
While this is done at some places, it’s not consistently done in the solution.
I suggest adding a non-zero-value check here:
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/Escrow.sol#L98
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/EscrowFactory.sol#L39
Consider changing it if possible.
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/EscrowFactory.sol#L77
Upgrade to the latest solidity version 0.8.19 to get additional gas savings.
See latest release for reference:
https://blog.soliditylang.org/2023/02/22/solidity-0.8.19-release-announcement/
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/Escrow.sol#L2
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/EscrowFactory.sol#L2
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/IEscrow.sol#L2
https://github.com/Cyfrin/2023-07-escrow/blob/main/src/IEscrowFactory.sol#L2
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.