1. 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).
I suggest replacing revert strings with custom errors.
2. !=0 instead of >0 for UINT
0 is less efficient than != 0 for unsigned integers (with proof)
!= 0 costs less gas compared to > 0 for unsigned integers in require statements with the optimizer enabled (6 gas) Proof: While it may seem that > 0 is cheaper than !=, this is only true without the optimizer enabled and outside a require statement. If you enable the optimizer at 10k AND you’re in a require statement, this will save gas. You can see this tweet for more proofs: https://twitter.com/gzeon/status/1485428085885640706
contracts/VotingEscrow.sol:412: require(_value > 0, "Only non zero amount");
contracts/VotingEscrow.sol:448: require(_value > 0, "Only non zero amount");
contracts/VotingEscrow.sol:449: require(locked_.amount > 0, "No lock");
contracts/VotingEscrow.sol:469: require(locked_.amount > 0, "Delegatee has no lock");
contracts/VotingEscrow.sol:502: require(locked_.amount > 0, "No lock");
contracts/VotingEscrow.sol:529: require(locked_.amount > 0, "No lock");
contracts/VotingEscrow.sol:564: require(locked_.amount > 0, "No lock");
contracts/VotingEscrow.sol:587: require(toLocked.amount > 0, "Delegatee has no lock");
contracts/VotingEscrow.sol:635: require(locked_.amount > 0, "No lock");
I suggest changing > 0 with != 0. Also, please enable the Optimizer.
3. 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; or bool abc; instead of bool abc = false
4. ++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
contracts/VotingEscrow.sol:309: for (uint256 i = 0; i < 255; i++) {
contracts/VotingEscrow.sol:717: for (uint256 i = 0; i < 128; i++) {
contracts/VotingEscrow.sol:739: for (uint256 i = 0; i < 128; i++) {
contracts/VotingEscrow.sol:834: for (uint256 i = 0; i < 255; i++) {
A division by 2 can be calculated by shifting one to the right.
While the DIV opcode uses 5 gas, the SHR opcode only uses 3 gas. Furthermore, Solidity’s division operation also includes a division-by-0 prevention which is bypassed using shifting.
1. 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:
https://github.com/code-423n4/2022-08-fiatdao/blob/main/contracts/VotingEscrow.sol
https://github.com/code-423n4/2022-08-fiatdao/blob/main/contracts/features/Blocklist.sol
References:
https://blog.soliditylang.org/2021/04/21/custom-errors/
Remediation:
I suggest replacing revert strings with custom errors.
2. !=0 instead of >0 for UINT
Instances
https://github.com/code-423n4/2022-08-fiatdao/blob/main/contracts/VotingEscrow.sol
Reference:
https://twitter.com/gzeon/status/1485428085885640706
Remediation:
I suggest changing > 0 with != 0. Also, please enable the Optimizer.
3. 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;
orbool abc;
instead ofbool abc = false
Instances:
https://github.com/code-423n4/2022-08-fiatdao/blob/main/contracts/VotingEscrow.sol
4. ++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:
https://github.com/code-423n4/2022-08-fiatdao/blob/main/contracts/VotingEscrow.sol
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
5. 10 ** 18 can be changed to 1e18 and save some gas Submitted by WatchPug, also found by robee
Instances:
https://github.com/code-423n4/2022-08-fiatdao/blob/main/contracts/VotingEscrow.sol
Recommendations:
10 ** 18 can be changed to 1e18 to avoid unnecessary arithmetic operation and save gas.s
References:
https://github.com/code-423n4/2021-12-yetifinance-findings/issues/274
6. Shift Right instead of Dividing by 2
A division by 2 can be calculated by shifting one to the right.
While the DIV opcode uses 5 gas, the SHR opcode only uses 3 gas. Furthermore, Solidity’s division operation also includes a division-by-0 prevention which is bypassed using shifting.
I suggest replacing
/ 2
with>> 1
here:Instances:
Github Links: https://github.com/code-423n4/2022-08-fiatdao/blob/main/contracts/VotingEscrow.sol
References:
https://code4rena.com/reports/2022-04-badger-citadel/#g-32-shift-right-instead-of-dividing-by-2