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: 38/93
Findings: 3
Award: $94.43
π Selected for report: 0
π Solo Findings: 0
48.5872 USDC - $48.59
Judge has assessed an item in Issue #143 as Medium risk. The relevant finding follows:
_safeTransferETH()
should perform simple ETH transfers and donβt forward 30k gasBeing a simple funds transfer, having a fallback of a WETH deposit, there should be no extra gas involved when potentially transferring the control to a smart contract. We suggest replacing the call
and its 30_000 gas limit with a send()
or transfer()
call, to prevent possible reentrancy by malicious actors
#0 - gzeoneth
2022-06-18T19:19:24Z
Duplicate of #254
π 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
30.3871 USDC - $30.39
mintlistSummon()
can be called after mintlist phase has endedCurrently, phase 2 is enforced only with this check:
require(mintlistStarted(), 'Mintlist phase not started');
However, there is no check that ensures that the next phase is not started. We suggest adding the following check:
require(!publicStarted(), 'Mintlist phase over');
publicSummon()
can be called after public phase has endedCurrently, phase 33 is enforced only with this check:
require(publicStarted(), 'Public sale not started');
However, there is no check that ensures that the next phase is not started. We suggest adding the following check:
require(!claimsStarted(), 'Public sale over');
selfRefund()
should not be called when contract is pausedAll the functions regarding the NFT sale through all the phases contain the whenNotPaused
modifier, so if any issue should arise, the contract owner can safely pause the core functions of the contract. For the same reasons, since refunds should happen between phase 1 and 2 and can be performed by users autonomously, we suggest adding whenNotPaused
here as well
_safeTransferETH()
should perform simple ETH transfers and donβt forward 30k gasBeing a simple funds transfer, having a fallback of a WETH deposit, there should be no extra gas involved when potentially transferring the control to a smart contract. We suggest replacing the call
and its 30_000 gas limit with a send()
or transfer()
call, to prevent possible reentrancy by malicious actors
initialize()
functionLink: https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L52
contracts/ForgottenRunesWarriorsGuild.sol
contains an initialize
function which is not useful at all because it is just a proxy for setMinter
and it is deceiving because it does not have a modifier that prevents it from being called again. We suggest removing it and just call setMinter
manually after contract deploy
withdrawAll()
has a useless payable
modifierThe function does not deal with msg.value
because itβs not supposed to receive any ETH. We suggest removing the payable
modifier, and possibly replace its implementation with a simpler one:
function withdrawAll() public payable onlyOwner { withdraw(address(this).balance); }
π 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.4505 USDC - $15.45
contracts/ForgottenRunesWarriorsGuild.sol
function forwardERC20s(IERC20 token, uint256 amount) public onlyOwner { // @audit unnecessary check require(address(msg.sender) != address(0)); token.transfer(msg.sender, amount); }
msg.sender
will never be the zero address, so this is probably the result of some copy-paste and can be safely removed
contracts/ForgottenRunesWarriorsMinter.sol
Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas if/when the revert condition is met. Example of long revert strings: You can summon no more than 20 Warriors at a time
We suggest using shorter errors or switch to solidity custom errors
In bidSummon()
the variables numSold
and maxDaSupply
are each read from storage 3 times. The code can be optimized by minimizing the number of SLOADs
: SLOADs
are expensive (100 gas) compared to MLOADs
(3 gas).
issueRefunds()
and refundAddress()
These two functions can only be called by a privileged account since they got the onlyOwner
. In order to save has during refunds phase we suggest removing this check, given it performs three reads from storage
function forwardERC20s(IERC20 token, uint256 amount) public onlyOwner { // @audit unnecessary check require(address(msg.sender) != address(0)); token.transfer(msg.sender, amount); }
msg.sender
will never be the zero address, so this is probably the result of some copy-paste and can be safely removed