Open code423n4 opened 2 years ago
Most are valid, except
ForgottenRunesWarriorsMinter.currentDaPrice(): > should be >=
strict is cheaper since there is no opcode for non-strict comparison in evm
No need to explicitly initialize variables with default values
yes but I don't think it save gas in for loop with optimizer enabled
Table of Contents:
initialize()
functionForgottenRunesWarriorsGuild.forwardERC20s()
andForgottenRunesWarriorsMinter.forwardERC20s()
: Unnecessary require statementsForgottenRunesWarriorsMinter
:bidSummon()
andpublicSummon()
: Unnecessary require statement> 0
is less efficient than!= 0
for unsigned integers (with proof)ForgottenRunesWarriorsMinter.currentDaPrice()
:>
should be>=
require()
statements that use&&
saves gas++i
costs less gas compared toi++
ori += 1
msg.sender
instead of OpenZeppelin's_msgSender()
when meta-transactions capabilities aren't usedCaching storage values in memory
The code can be optimized by minimising the number of SLOADs. SLOADs are expensive (100 gas) compared to MLOADs/MSTOREs (3 gas). Here, storage values should get cached in memory (see the
@audit
tags for further details):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-arithmeticI suggest wrapping L295 with an
unchecked
block (see@audit
):Unnecessary
initialize()
functionThe
initialize()
function isn't an initializer. It just callssetMinter()
, which has the same visibility and authorization level asinitialize()
:It could even be called repeatedly.
As the
initialize()
function is not needed, I suggest deleting it and directly callingsetMinter()
to "Conveniently initialize the contract".ForgottenRunesWarriorsGuild.forwardERC20s()
andForgottenRunesWarriorsMinter.forwardERC20s()
: Unnecessary require statementsHere, as the
onlyOwner
modifier is applied, theaddress(0)
checks are not needed here:I suggest removing these checks.
ForgottenRunesWarriorsMinter
:bidSummon()
andpublicSummon()
: Unnecessary require statementThe code is as such:
Logically speaking,
numSold + numWarriors <= maxForSale
could only reach the edge-case ifnumWarriors == 0
, but that's prevented with the condition that follows in both functions:numWarriors > 0 && numWarriors <= 20
. Meaning that, withnumSold + numWarriors <= maxForSale
andnumWarriors > 0
, we don't need to check ifnumSold < maxForSale
as it just can't happen.I suggest removing the 2
require(numSold < maxDaSupply)
checks L136 and L207.Furthermore, notice that
'Not enough remaining'
and'Sold out'
kinda mean the same thing, so the additionnal require statement might not be justified.Boolean comparisons
Comparing to a constant (
true
orfalse
) is a bit more expensive than directly checking the returned boolean value. I suggest usingif(!directValue)
instead ofif(directValue == false)
here:> 0
is less efficient than!= 0
for unsigned integers (with proof)!= 0
costs less gas compared to> 0
for unsigned integers inrequire
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 arequire
statement, this will save gas. You can see this tweet for more proofs: https://twitter.com/gzeon/status/1485428085885640706I suggest changing
> 0
with!= 0
here:Also, please enable the Optimizer.
ForgottenRunesWarriorsMinter.currentDaPrice()
:>
should be>=
The return statement is as follows:
Strict inequalities (
>
) are more expensive than non-strict ones (>=
). This is due to some supplementary checks (ISZERO, 3 gas)Furthermore,
lowestPrice
is read from storage whilecurrentPrice
is read from memory.Therefore, it's possible to always save 3 gas and sometimes further save 1 SLOAD (when
currentPrice == lowestPrice
) by replacing the code to:Splitting
require()
statements that use&&
saves gasIf you're using the Optimizer at 200, instead of using the
&&
operator in a single require statement to check multiple conditions, I suggest using multiple require statements with 1 condition per require statement:++i
costs less gas compared toi++
ori += 1
++i
costs less gas compared toi++
ori += 1
for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.i++
incrementsi
and returns the initial value ofi
. Which means:But
++i
returns the actual incremented value:In the first case, the compiler has to create a temporary variable (when used) for returning
1
instead of2
Instances include:
I suggest using
++i
instead ofi++
to increment the value of an uint variable.Increments can be unchecked
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
Instances include:
The code would go from:
to:
The risk of overflow is inexistant for a
uint256
here.Public functions to external
The following functions could be set external to save gas and improve code quality. External call cost is less expensive than of public functions.
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
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) {
Instances include:
I suggest removing explicit initializations for default values.
Upgrade pragma to at least 0.8.4
Using newer compiler versions and the optimizer give gas optimizations. Also, additional safety checks are available for free.
The advantages here are:
Consider upgrading pragma to at least 0.8.4:
Use
msg.sender
instead of OpenZeppelin's_msgSender()
when meta-transactions capabilities aren't usedmsg.sender
costs 2 gas (CALLER opcode)._msgSender()
represents the following:When no meta-transactions capabilities are used:
msg.sender
is enough.See https://docs.openzeppelin.com/contracts/2.x/gsn for more information about GSN capabilities.
Consider replacing
_msgSender()
withmsg.sender
here:In the solution,
msg.sender
is used everywhere else:Use 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)
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).Instances include:
I suggest replacing revert strings with custom errors.