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: 29/93
Findings: 2
Award: $281.56
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Kulk0
Also found by: 0x1f8b, 0xDjango, BowTiedWardens, Dinddle, broccolirob, defsec, dirk_y, hyh, rajatbeladiya, throttle, unforgiven
Currently there are no actual restrictions on the number of warriors in teamSummon, which can break up the stated supply composition.
Setting severity to medium as while the function is Owner controlled, the improper teamSummon use can break up the announced distribution mechanics, which is the main goal of the system.
There is a supply schedule announced:
https://github.com/code-423n4/2022-05-runes#supply-breakdown
Any number of NFTs up to total supply cap can be minted anytime with teamSummon:
* @notice Mint a Warrior (owner only) * @param recipient address the address of the recipient * @param count uint256 of the number of warriors you're trying to mint */ function teamSummon(address recipient, uint256 count) external onlyOwner { require(address(recipient) != address(0), 'address req'); for (uint256 i = 0; i < count; i++) { _mint(recipient); } }
If by any chance teamSummon be called for more than 710 = 16000 - 14190 - 1100
warriors, the stated supply composition will not be met.
That is permanent as there is no time limit for phases after DA.
Introduce the team cap variable and control the number of NFTs minted with teamSummon.
#0 - KenzoAgada
2022-06-06T05:38:59Z
Duplicate of #104.
🌟 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
35.025 USDC - $35.02
As the Guild-Minter link is essential to collection's economics any material change after the initial setup can break the system, and that is possible now.
As a simplest example, setting new minter after the action start when some NFTs were minted will reduce maximum issuance cap for the new minter.
/** * @dev Convenient way to initialize the contract * @param newMinter address of the minter contract */ function initialize(address newMinter) public onlyOwner { setMinter(newMinter); }
Consider making setMinter() a one time action, requiring current minter to be zero.
Besides final price and vault address all other configuration variables shouldn't be available for any change after auction phase has started as it will break the system mechanics in the various ways.
For example, setting warriors
to any other NFT contract after first mint will split the collection and increase the maximum available number of warriors (i.e. hard cap will be breached).
Now all configuration variables can be changed by owner at any time:
For example, is NFT address be reset at any point after auction start, which can break the mechanics of the system:
/** * @notice set the warriors token address */ function setWarriorsAddress( IForgottenRunesWarriorsGuild _newWarriorsAddress ) public onlyOwner { warriors = _newWarriorsAddress; }
It looks like that all the config variables besides final price and vault address are not subject to change after DA start, so consider requiring block.timestamp <= daStartTime
in the corresponding setters.
Transfer without result check is used for an arbitrary ERC20 in onlyOwner rescue function.
The function can be rerun, but the lack of execution control makes its use ambiguous.
token.transfer(msg.sender, amount);
Require transfer success or consider safeTransfer as not all the tokens revert on failure.
As different compiler versions have critical behavior specifics if the contracts get accidentally deployed using another compiler version compared to the one they were tested with, various types of undesired behavior can be introduced.
Interfaces use pragma solidity ^0.8.6
:
Both contracts use pragma solidity ^0.8.0
:
Consider fixing the version to 0.8.x
across all the codebase.
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
is used as max int representation in ForgottenRunesWarriorsMinter.
There are better ways that reduce operational error probability and improve readability.
/// @notice The start timestamp for the Dutch Auction (DA) sale and price uint256 public daStartTime = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; /// @notice The start timestamp for mintlisters /// @dev This is the end of DA phase. No more DA bids when this is hit uint256 public mintlistStartTime = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; /// @notice The start timestamp for the public sale uint256 public publicStartTime = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; /// @notice The start timestamp for the claims uint256 public claimsStartTime = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; /// @notice The start timestamp for self refunds uint256 public selfRefundsStartTime = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
For compilers after 0.6.8 it's a kind of optimal to use type(uint256).max
:
https://forum.openzeppelin.com/t/using-the-maximum-integer-in-solidity/3000/13