2. <array>.length should not be looked up in every loop of a for-loop
Reading array length at each iteration of the loop consumes more gas than necessary.
In the best case scenario (length read on a memory variable), caching the array length in the stack saves around 3 gas per iteration.
In the worst case scenario (external calls at each iteration), the amount of gas wasted can be massive.
Here, Consider storing the array's length in a variable before the for-loop, and use this new variable instead:
PuttyV2.sol:653: for (uint256 i = 0; i < orders.length; i++) {
PuttyV2.sol:697: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:716: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:736: for (uint256 i = 0; i < floorTokens.length; i++) {
PuttyV2.sol:750: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:763: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:781: for (uint256 i = 0; i < floorTokens.length; i++) {
PuttyV2.sol:801: for (uint256 i = 0; i < whitelist.length; i++) {
PuttyV2.sol:871: for (uint256 i = 0; i < arr.length; i++) {
PuttyV2.sol:895: for (uint256 i = 0; i < arr.length; i++) {
3. ++i costs less gas compared to i++ or i += 1 (same for --i vs i-- or i -= 1)
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)
Note that post-increments (or post-decrements) return the old value before incrementing or decrementing, hence the name post-increment:
uint i = 1;
uint j = 2;
require(j == i++, "This will be false as i is incremented after the comparison");
However, pre-increments (or pre-decrements) return the new value:
uint i = 1;
uint j = 2;
require(j == ++i, "This will be true as i is incremented before the comparison");
In the pre-increment case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2.
Affected code:
PuttyV2.sol:653: for (uint256 i = 0; i < orders.length; i++) {
PuttyV2.sol:697: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:716: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:736: for (uint256 i = 0; i < floorTokens.length; i++) {
PuttyV2.sol:750: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:763: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:781: for (uint256 i = 0; i < floorTokens.length; i++) {
PuttyV2.sol:801: for (uint256 i = 0; i < whitelist.length; i++) {
PuttyV2.sol:871: for (uint256 i = 0; i < arr.length; i++) {
PuttyV2.sol:895: for (uint256 i = 0; i < arr.length; i++) {
Consider using pre-increments and pre-decrements where they are relevant (meaning: not where post-increments/decrements logic are relevant).
4. Increments/decrements can be unchecked in for-loops
In Solidity 0.8+, there's a default overflow check on unsigned integers. It's possible to uncheck this in for-loops and save some gas at each iteration, but at the cost of some code readability, as this uncheck cannot be made inline.
PuttyV2.sol:653: for (uint256 i = 0; i < orders.length; i++) {
PuttyV2.sol:697: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:716: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:736: for (uint256 i = 0; i < floorTokens.length; i++) {
PuttyV2.sol:750: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:763: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:781: for (uint256 i = 0; i < floorTokens.length; i++) {
PuttyV2.sol:801: for (uint256 i = 0; i < whitelist.length; i++) {
PuttyV2.sol:871: for (uint256 i = 0; i < arr.length; i++) {
PuttyV2.sol:895: for (uint256 i = 0; i < arr.length; i++) {
The change would be:
- for (uint256 i; i < numIterations; i++) {
+ for (uint256 i; i < numIterations;) {
// ...
+ unchecked { ++i; }
}
The same can be applied with decrements (which should use break when i == 0).
The risk of overflow is non-existant for uint256 here.
5. Without the Optimizer: it costs more gas to initialize variables with their default value than letting the default value be applied
If the optimizer is enabled, this finding isn't true anymore
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.
As an example: for (uint256 i = 0; i < numIterations; ++i) { should be replaced with for (uint256 i; i < numIterations; ++i) {
Affected code:
PuttyV2.sol:583: uint256 feeAmount = 0;
PuttyV2.sol:653: for (uint256 i = 0; i < orders.length; i++) {
PuttyV2.sol:697: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:716: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:736: for (uint256 i = 0; i < floorTokens.length; i++) {
PuttyV2.sol:750: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:763: for (uint256 i = 0; i < assets.length; i++) {
PuttyV2.sol:781: for (uint256 i = 0; i < floorTokens.length; i++) {
PuttyV2.sol:801: for (uint256 i = 0; i < whitelist.length; i++) {
PuttyV2.sol:871: for (uint256 i = 0; i < arr.length; i++) {
PuttyV2.sol:895: for (uint256 i = 0; i < arr.length; i++) {
Consider removing explicit initializations for default values.
6. Use Custom Errors instead of Revert Strings to save Gas
Solidity 0.8.4 introduced custom errors. They are more gas efficient than revert strings, when it comes to deploy cost as well as runtime cost when the revert condition is met. Use custom errors instead of revert strings for gas savings.
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).
Consider replacing all revert strings with custom errors in the solution.
PuttyV2.sol:231: require(_weth != address(0), "Unset weth address");
PuttyV2.sol:258: require(_fee < 30, "fee must be less than 3%");
PuttyV2.sol:295: require(
PuttyV2.sol:305: require(!cancelledOrders[orderHash], "Order has been cancelled");
PuttyV2.sol:308: require(
PuttyV2.sol:315: require(order.duration < 10_000 days, "Duration too long");
PuttyV2.sol:318: require(block.timestamp < order.expiration, "Order has expired");
PuttyV2.sol:321: require(order.baseAsset.code.length > 0, "baseAsset is not contract");
PuttyV2.sol:325: ? require(
PuttyV2.sol:329: : require(
PuttyV2.sol:369: require(
PuttyV2.sol:404: require(msg.value == order.strike, "Incorrect ETH amount sent");
PuttyV2.sol:457: require(ownerOf(uint256(orderHash)) == msg.sender, "Not owner");
PuttyV2.sol:460: require(order.isLong, "Can only exercise long positions");
PuttyV2.sol:463: require(
PuttyV2.sol:470: ? require(
PuttyV2.sol:474: : require(
PuttyV2.sol:500: require(msg.value == order.strike, "Incorrect ETH amount sent");
PuttyV2.sol:552: require(!order.isLong, "Must be short position");
PuttyV2.sol:557: require(ownerOf(uint256(orderHash)) == msg.sender, "Not owner");
PuttyV2.sol:563: require(
PuttyV2.sol:621: require(msg.sender == order.maker, "Not your order");
PuttyV2.sol:645: require(orders.length == signatures.length, "Length mismatch in input");
PuttyV2.sol:646: require(
PuttyV2.sol:701: require(token.code.length > 0, "ERC20: Token is not contract");
PuttyV2.sol:702: require(tokenAmount > 0, "ERC20: Amount too small");
PuttyV2.sol:924: require(_ownerOf[id] != address(0), "URI query for NOT_MINTED token");
PuttyV2Nft.sol:13: require(to != address(0), "INVALID_RECIPIENT");
PuttyV2Nft.sol:14: require(_ownerOf[id] == address(0), "ALREADY_MINTED");
PuttyV2Nft.sol:29: require(from == _ownerOf[id], "WRONG_FROM");
PuttyV2Nft.sol:30: require(to != address(0), "INVALID_RECIPIENT");
PuttyV2Nft.sol:31: require(
PuttyV2Nft.sol:46: require(owner != address(0), "ZERO_ADDRESS");
Overview
Table of Contents:
<array>.length
should not be looked up in every loop of afor-loop
++i
costs less gas compared toi++
ori += 1
(same for--i
vsi--
ori -= 1
)1. Unchecking arithmetics operations that can't underflow/overflow
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-arithmeticConsider wrapping with an
unchecked
block here:2.
<array>.length
should not be looked up in every loop of afor-loop
Reading array length at each iteration of the loop consumes more gas than necessary.
In the best case scenario (length read on a memory variable), caching the array length in the stack saves around 3 gas per iteration. In the worst case scenario (external calls at each iteration), the amount of gas wasted can be massive.
Here, Consider storing the array's length in a variable before the for-loop, and use this new variable instead:
3.
++i
costs less gas compared toi++
ori += 1
(same for--i
vsi--
ori -= 1
)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 formi++
costs 6 gas less thani += 1
++i
costs 5 gas less thani++
(11 gas less thani += 1
)Decrement:
i -= 1
is the most expensive formi--
costs 11 gas less thani -= 1
--i
costs 5 gas less thani--
(16 gas less thani -= 1
)Note that post-increments (or post-decrements) return the old value before incrementing or decrementing, hence the name post-increment:
However, pre-increments (or pre-decrements) return the new value:
In the pre-increment case, the compiler has to create a temporary variable (when used) for returning
1
instead of2
.Affected code:
Consider using pre-increments and pre-decrements where they are relevant (meaning: not where post-increments/decrements logic are relevant).
4. Increments/decrements can be unchecked in for-loops
In Solidity 0.8+, there's a default overflow check on unsigned integers. It's possible to uncheck this in for-loops and save some gas at each iteration, but at the cost of some code readability, as this uncheck cannot be made inline.
ethereum/solidity#10695
Affected code:
The change would be:
The same can be applied with decrements (which should use
break
wheni == 0
).The risk of overflow is non-existant for
uint256
here.5. Without the Optimizer: it costs more gas to initialize variables with their default value than letting the default value be applied
If the optimizer is enabled, this finding isn't true anymore If a variable is not set/initialized, it is assumed to have the default value (
0
foruint
,false
forbool
,address(0)
for address...). Explicitly initializing it with its default value is an anti-pattern and wastes gas.As an example:
for (uint256 i = 0; i < numIterations; ++i) {
should be replaced withfor (uint256 i; i < numIterations; ++i) {
Affected code:
Consider removing explicit initializations for default values.
6. Use Custom Errors instead of Revert Strings to save Gas
Solidity 0.8.4 introduced custom errors. They are more gas efficient than revert strings, when it comes to deploy cost as well as runtime cost when the revert condition is met. Use custom errors instead of revert strings for gas savings.
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: https://blog.soliditylang.org/2021/04/21/custom-errors/:
Custom errors are defined using the
error
statement, which can be used inside and outside of contracts (including interfaces and libraries).Consider replacing all revert strings with custom errors in the solution.