Platform: Code4rena
Start Date: 03/05/2022
Pot Size: $30,000 USDC
Total HM: 6
Participants: 93
Period: 3 days
Judge: gzeon
Id: 118
League: ETH
Rank: 21/93
Findings: 3
Award: $365.02
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: throttle
Also found by: 0xDjango, BowTiedWardens, WatchPug, defsec, dipp, fatherOfBlocks, gzeon, hake, reassor, shung, unforgiven
296.7571 USDC - $296.76
ForgottenRunesWarriorsMinter.sol#L480
If publicStartTime
or mintlistStartTime
happen to overlap with daStartTime
all users will be able to buy tokens concurrently.
In case of high demand leaving a big overlap between mintlistStartTime
, publicStartTime
and daStartTime
may generate high gas prices and sell out before some members in the mint list have a chance to purchase their mint and defeating the puporse of having different phases.
The functions to set StartTime#L441-L464 have no checks that prevent them to all run or start at the same time.
And the checks in setPhaseTimes
also allow total overlap.
require( newPublicStartTime >= newMintlistStartTime, 'Set public after mintlist'); require( newClaimsStartTime >= newPublicStartTime, 'Set claims after public');
newPublicStartTime
can be set equal to newMintlistStartTime
, and this may lead to a sell out before intended in case of high demand.
A worse scenario would be mistakenly starting publicStartTime
or/and mintlistStartTime
at the same time as daStartTime
.
Manual Review
I recommend setting a minimum time window between daStartime
and mintlistStartTime
so mistakenly setting mintlistStartTime
to a time too early is not a possibility.
Same goes for mintlistStartTime
in regards for publicStartTime
and so on.
Is always better to prevent mistakes from happening than having to deal with them on the spot.
#0 - cryppadotta
2022-05-05T02:02:14Z
I appreciate the attention to detail here but this would require the mintlist to both:
while you're not technically wrong, this is quite the edge case.
It's not a terrible idea to add this though. We wouldn't want confusion because we leave off a zero
#1 - gzeoneth
2022-06-18T18:32:53Z
🌟 Selected for report: defsec
Also found by: 0v3rf10w, 0x1f8b, 0x4non, 0x52, 0xDjango, 0xf15ers, 0xkatana, 0xliumin, AuditsAreUS, BowTiedWardens, CertoraInc, Cr4ckM3, Funen, GimelSec, Hawkeye, IllIllI, Kulk0, M0ndoHEHE, MaratCerby, Picodes, Ruhum, TerrierLover, TrungOre, VAD37, WatchPug, berndartmueller, broccolirob, catchup, cccz, cryptphi, csanuragjain, delfin454000, dirk_y, eccentricexit, ellahi, fatherOfBlocks, gzeon, hake, hansfriese, hickuphh3, horsefacts, hubble, hyh, ilan, joestakey, kebabsec, kenta, kenzo, leastwood, m9800, marximimus, minhquanym, oyc_109, p4st13r4, pauliax, pedroais, peritoflores, plotchy, rajatbeladiya, reassor, rfa, robee, rotcivegaf, samruna, shenwilly, shung, simon135, sorrynotsorry, sseefried, teddav, throttle, tintin, unforgiven, z3s
52.771 USDC - $52.77
Implementing zero address checks could prevent from having to spend extra gas to call these functions again.
initialize
is redundant and can be called any number of timesForgottenRunesWarriorsGuild.sol#L52
Function name initialize
suggests it should only be able to be called once, which is not the case. It is also redundant since all it does is call setMinter
.
forwardERC20s
could be exploited by ERC777 or malicious ERC20ForgottenRunesWarriorsGuild.sol#L173
Attacker could deposit malicious ERC20 or ERC777 token that can take control of the execution flow and potentially do something harmful.
refundAddress
could lead to spending gas for nothing.ForgottenRunesWarriorsMinter.sol#364
If owner accidentally calls refundAddress
with address zero no funds will be burned but gas will be spent for no reason.
I suggest implement a zero address check to avoid wasting gas.
Example:
function refundAddress(address minter) public onlyOwner nonReentrant { require(minter != address(0)); _refundAddress(minter); }
Using a floating pragma (^) might result in the contract being deployed with a version it was not tested with and might result in bugs that affect the contract system negatively. In addition, older compilers might be susceptible to some bugs. A list of known compiler bugs and their severity can be found here: https://etherscan.io/solcbuginfo
Locking the pragma (deleting the ^) helps to ensure that contracts do not accidentally get deployed using an outdated compiler version or a version it was not tested with. I recommend changing the solidity version pragma to the latest version to enforce the use of an up-to-date compiler.
The following has been copied from slither:
ForgottenRunesWarriorsGuild.initialize(address) (contracts/ForgottenRunesWarriorsGuild.sol#52-54) exists(uint256) should be declared external:
🌟 Selected for report: BowTiedWardens
Also found by: 0v3rf10w, 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xProf, 0xc0ffEE, 0xf15ers, 0xkatana, 0xliumin, ACai, AlleyCat, CertoraInc, Cityscape, Cr4ckM3, DavidGialdi, Dinddle, FSchmoede, Funen, GimelSec, Hawkeye, IllIllI, Kulk0, M0ndoHEHE, MaratCerby, MiloTruck, Picodes, RoiEvenHaim, Tadashi, TerrierLover, TrungOre, VAD37, WatchPug, antonttc, catchup, defsec, delfin454000, dirk_y, eccentricexit, ellahi, fatherOfBlocks, gzeon, hake, hansfriese, hickuphh3, horsefacts, ilan, joestakey, kebabsec, kenta, kenzo, marximimus, minhquanym, noobie, oyc_109, p4st13r4, pauliax, rajatbeladiya, reassor, rfa, robee, rotcivegaf, saian, samruna, shenwilly, shung, simon135, slywaters, sorrynotsorry, throttle, unforgiven, z3s
15.491 USDC - $15.49
GAS
for
loop gas optimizationForgottenRunesWarriorsMinter.sol#L162 ForgottenRunesWarriorsMinter.sol#L220 ForgottenRunesWarriorsMinter.sol#L259 ForgottenRunesWarriorsMinter.sol#L355
Unnecessary to update uint to default value of zero. Prefix instead of postfix increments and unchecked increments can save gas.
Taking all changes into account I recommend change a loop like this:
for (uint256 i = 0; i < numWarriors; i++) { _mint(msg.sender); }
To this:
for (uint256 i; i < numWarriors;) { _mint(msg.sender); unchecked { ++i; } }
ForgottenRunesWarriorsMinter.sol#L163
Parameters daMinters
, daAmountPaid
, daNumMinted
and numSold
are not necessary to _mint
. Therefore gas could be saved in case _mint
reverts if _mint
was set before such parameters. Check example below.
for (uint256 i = 0; i < numWarriors; i++) { _mint(msg.sender); } daMinters.push(msg.sender); daAmountPaid[msg.sender] += msg.value; daNumMinted[msg.sender] += numWarriors; numSold += numWarriors; if (numSold == maxDaSupply) { // optimistic: save gas by not setting on every mint, but will // require manual `setFinalPrice` before refunds if da falls short finalPrice = currentPrice; }
_msgSender
ForgottenRunesWarriorsGuild.sol#L101
The use of _msgSender
when there is no implementation of a meta transaction mechanism that uses it, such as EIP-2771, very slightly increases gas consumption.
I suggest replacing _msgSender
with msg.sender
if there is no mechanism to support meta-transactions like EIP-2771 implemented.
ForgottenRunesWarriorsMinter.sol#L182
Gas could be saved by changing:
require(mintlistMinted[msg.sender] == false, 'Already minted');
To:
require(!mintlistMinted[msg.sender], 'Already minted');
R
contains very large stringForgottenRunesWarriorsGuild.sol#L33
Having in mind that this might be unnegotiable I suggest shortening Lore R
to less than 32 bytes to save gas.