Forgotten Runes Warrior Guild contest - horsefacts's results

16,000 Warrior NFTs sold in a phased Dutch Auction.

General Information

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

Forgotten Runes

Findings Distribution

Researcher Performance

Rank: 33/93

Findings: 3

Award: $134.87

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

48.5872 USDC - $48.59

Labels

bug
duplicate
2 (Med Risk)
upgraded by judge

External Links

Judge has assessed an item in Issue #270 as Medium risk. The relevant finding follows:

Gas stipend for payable.send may be too low for contract wallets

ETH withdrawals in both the minter and token contracts use payable(address).send to transfer ether to the vault address. If the configured vault is a smart contract wallet like a Gnosis Safe multisig, the gas stipend from .send may be too low to complete the transfer and these methods may revert with an out of gas error. There are workarounds to this issue, but it could be an unexpected interruption.

If your vault address is already known, consider simulating transfers out of these contracts using a fork test or transaction simulator to ensure they will succeed.

Alternatively, consider reusing the existing _safeTransferETH function. Using .call can introduce reentrancy, but these are permissioned calls to a trusted address that change no state variables.

ForgottenRunesWarriorsMinter.sol#L604-L619

    /**
     * @notice Withdraw funds to the vault
     * @param _amount uint256 the amount to withdraw
     */
    function withdraw(uint256 _amount) public onlyOwner {
        require(address(vault) != address(0), 'no vault');
        require(payable(vault).send(_amount));
    }

    /**
     * @notice Withdraw all funds to the vault
     */
    function withdrawAll() public payable onlyOwner {
        require(address(vault) != address(0), 'no vault');
        require(payable(vault).send(address(this).balance));
    }

ForgottenRunesWarriorsGuild.sol#L159-L165

    /**
     * @dev ETH should not be sent to this contract, but in the case that it is
     * sent by accident, this function allows the owner to withdraw it.
     */
    function withdrawAll() public payable onlyOwner {
        require(payable(msg.sender).send(address(this).balance));
    }

#0 - gzeoneth

2022-06-18T19:19:17Z

Duplicate of #254

Thanks for the detailed explanations of your design decisions and reasoning and the walkthrough video you created for this contest!

Low

Gas stipend for payable.send may be too low for contract wallets

ETH withdrawals in both the minter and token contracts use payable(address).send to transfer ether to the vault address. If the configured vault is a smart contract wallet like a Gnosis Safe multisig, the gas stipend from .send may be too low to complete the transfer and these methods may revert with an out of gas error. There are workarounds to this issue, but it could be an unexpected interruption.

If your vault address is already known, consider simulating transfers out of these contracts using a fork test or transaction simulator to ensure they will succeed.

Alternatively, consider reusing the existing _safeTransferETH function. Using .call can introduce reentrancy, but these are permissioned calls to a trusted address that change no state variables.

ForgottenRunesWarriorsMinter.sol#L604-L619

    /**
     * @notice Withdraw funds to the vault
     * @param _amount uint256 the amount to withdraw
     */
    function withdraw(uint256 _amount) public onlyOwner {
        require(address(vault) != address(0), 'no vault');
        require(payable(vault).send(_amount));
    }

    /**
     * @notice Withdraw all funds to the vault
     */
    function withdrawAll() public payable onlyOwner {
        require(address(vault) != address(0), 'no vault');
        require(payable(vault).send(address(this).balance));
    }

ForgottenRunesWarriorsGuild.sol#L159-L165

    /**
     * @dev ETH should not be sent to this contract, but in the case that it is
     * sent by accident, this function allows the owner to withdraw it.
     */
    function withdrawAll() public payable onlyOwner {
        require(payable(msg.sender).send(address(this).balance));
    }

Missing validations for setLowestPrice

The contract owner can set lowestPrice to a value greater than startPrice. This will cause price calculations to revert with a checked arithmetic error.

This would interrupt bidding, but does not prevent the owner from setting parameters back to valid values.

The owner can also set lowestPrice to zero, which would allow users to mint for free if the auction price decreases low enough.

Consider validating these parameters.

ForgottenRunesWarriorsMinter.sol#L554-L559

    /**
     * @notice set the dutch auction lowest price
     */
    function setLowestPrice(uint256 _newPrice) public onlyOwner {
        require(_newPrice > 0 && _newPrice < startPrice, "Invalid lowest price");
        lowestPrice = _newPrice;
    }

Missing validations for setDaDropInterval

The contract owner can set daDropInterval to a value greater than daPriceCurveLength, causing step calculations to round down to zero. This will cause auction price calculations to revert with a division by zero error.

This would interrupt bidding, but does not prevent the owner from setting parameters back to valid values.

Consider validating these parameters.

ForgottenRunesWarriorsMinter.sol#L568-L574

    /**
     * @notice set how long it takes for the dutch auction to step down in price
     */
    function setDaDropInterval(uint256 _newTime) public onlyOwner {
        require(daDropInterval > 0 && daDropInterval < daPriceCurveLength, "Invalid drop interval");
        daDropInterval = _newTime;
    }

QA/Noncritical

Prefer safeTransfer for IERC20

The forwardERC20s function calls IERC20.transfer without checking the return value. The transfer may fail silently without reverting. Consider using OpenZeppelin SafeERC20 and safeTransfer.

ForgottenRunesWarriorsMinter.sol#forwardERC20s

    /**
     * @dev ERC20s should not be sent to this contract, but if someone
     * does, it's nice to be able to recover them
     * @param token IERC20 the token address
     * @param amount uint256 the amount to send
     */
    function forwardERC20s(IERC20 token, uint256 amount) public onlyOwner {
        require(address(msg.sender) != address(0));
        token.transfer(msg.sender, amount);
    }

Missing events for permissioned parameter changes

Consider adding and emitting events when the contract owner changes sensitive parameters, like the merkle roots, phase times, prices and vault address in ForgottenRunesWarriorsMinter.sol and the base URI, minter address, and provenance hash in ForgottenRunesWarriorsGuild.sol. This enables you to monitor off chain for suspicious activity, and allows end users to observe and trust changes to these parameters.

Avoid long numeric literals

It's possible to use type(uint256).max rather than a numeric literal to access the maximum value of uint256. This is more concise, and is a safeguard against accidentally deleting a stray f:

ForgottenRunesWarriorsMinter.sol#L16-L18

    /// @notice The start timestamp for the Dutch Auction (DA) sale and price
    uint256 public daStartTime =
        0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;

Suggested:

    /// @notice The start timestamp for the Dutch Auction (DA) sale and price
    uint256 public daStartTime = type(uint256).max;

Incorrect address validation

The forwardERC20s function in both contracts checks that msg.sender is not the zero address. The intent here is not clear, but this is very unlikely to be the case. Perhaps this was meant to zero check address(token) instead:

ForgottenRunesWarriorsMinter.sol#forwardERC20s

    /**
     * @dev ERC20s should not be sent to this contract, but if someone
     * does, it's nice to be able to recover them
     * @param token IERC20 the token address
     * @param amount uint256 the amount to send
     */
    function forwardERC20s(IERC20 token, uint256 amount) public onlyOwner {
        require(address(msg.sender) != address(0));
        token.transfer(msg.sender, amount);
    }

Unlocked version pragmas

ForgottenRunesWarriorsMinter.sol and ForgottenRunesWarriorsGuild.sol have floating version pragmas. To prevent compiling with an unexpected Solidity version, set these explicitly.

Missing license identifiers

ForgottenRunesWarriorsMinter.sol and ForgottenRunesWarriorsGuild.sol are missing license identifiers.

Two big ideas and a few minor ones. If you're willing to use smaller types for counting bids per user and prices, using a packed struct to store auction bid data can reduce bid and refund gas costs. Even if not, increasing the number of optimizer runs will reduce runtime gas costs for bids, mints, and refunds.

In the long and less impactful tail, you can optimize a few loops, change a few increments, and inline a couple variables.

Use packed struct for bid data

Using a packed struct to store a user's auction bid data saves significant gas by reducing storage reads in bidSummon, issueRefunds, and selfRefund.

We need to store three values related to a user's auction bid:

  • daNumMinted, the total number of tokens minted in the auction phase by address
  • daAmountPaid, the total amount paid by address
  • daAmountRefunded, the total amount refunded by address

Currently, we use three (address => uint256) mappings to store these values, so each one uses a full storage slot:

ForgottenRunesWarriorsMinter.sol#L75

    /// @notice Tracks the total amount paid by a given address in the DA
    mapping(address => uint256) public daAmountPaid;

    /// @notice Tracks the total amount refunded to a given address for the DA
    mapping(address => uint256) public daAmountRefunded;

    /// @notice Tracks the total count of NFTs minted by a given address in the DA
    mapping(address => uint256) public daNumMinted;

However, this data can comfortably fit into smaller types, so we can store all three values in a struct that will use a single storage slot. We want to find integer types for these values that can pack together into 256 bits.

There is a known upper bound on daNumMinted: even if a single address claims all possible tokens, it won't exceed the maxDaSupply of 8000. Even if the auction supply increases, it can't exceed the fixed total supply of 16000. This means we can safely fit each user's daNumMinted into a uint16 (max value 65535). We can't go lower, as it's possible for one address to mint more than type(uint8).max (255) tokens.

This leaves us to find a packable type for the two prices: daAmountPaid and daAmountRefunded. We have 240 bits remaining (256 - 16), and could use a uint120 for these two values. However, since we're going to be casting integer values, it's a good idea to use OpenZeppelin's SafeCast library, which checks for overflow when we convert a type. The largest type this library supports that will pack with daNumMinted in a single slot is uint96.

Is this big enough for daAmountPaid and daAmountRefunded? The uint96 type stores a max value equivalent to around 79 billion ether:

» type(uint96).max 79228162514264337593543950335 » type(uint96).max / (1 ether) 79228162514

However, we need enough room to multiply number minted by final price and fit the result in a uint96:

ForgottenRunesWarriorsMinter.sol#388

function refundOwed(address minter) public view returns (uint256) { uint256 totalCostOfMints = finalPrice * daNumMinted[minter]; uint256 refundsPaidAlready = daAmountRefunded[minter]; return daAmountPaid[minter] - totalCostOfMints - refundsPaidAlready; }

Let's take the more generous upper bound on daNumMinted: even if the mint supply parameters change, nobody can possibly mint more than 16000. So what is the max finalPrice value that fits? We want to solve for uint96 totalCostOfMints = finalPrice * 16000, so we can take type(uint96).max / 16000 (and convert it to ether):

» type(uint96).max / (16000 ether) 4951760

As long as we keep final price constrained to a value less than 4,951,760 ETH, there should be room to calculate prices using our packed data. Since it's likely to actually be between 2.5 and 0.6 ETH, we're all good.

We can replace the three mappings above with a single "bids" mapping and packed structs:

    struct Bid {
        uint16 daNumMinted;
        uint96 daAmountPaid;
        uint96 daAmountRefunded;
    }
    mapping(address => Bid) public daBids;

Since these were previously public state variables with default getters, we can introduce view functions to read these values if necessary:

    function daNumMinted(address bidder) external view returns (uint256) {
        return daBids[bidder].daNumMinted;
    }

    function daAmountPaid(address bidder) external view returns (uint256) {
        return daBids[bidder].daAmountPaid;
    }

    function daAmountRefunded(address bidder) external view returns (uint256) {
        return daBids[bidder].daAmountRefunded;
    }

The current code in ForgottenRunesWarriorsMinter#bidSummon will perform two SLOADs and two SSTOREs to update the bid data by user: one read from storage, and one write to storage of the updated value per mapping.

        daMinters.push(msg.sender);
        daAmountPaid[msg.sender] += msg.value;
        daNumMinted[msg.sender] += numWarriors;
        numSold += numWarriors;

We can save an SSTORE by updating the bid in memory and writing it once:

        daMinters.push(msg.sender);

        Bid memory bid = daBids[msg.sender];
        bid.daAmountPaid += SafeCast.toUint96(msg.value);
        bid.daNumMinted += SafeCast.toUint16(numWarriors);
        daBids[msg.sender] = bid;

        numSold += numWarriors;

Remember to import the SafeCast library:

import "@openzeppelin/contracts/utils/math/SafeCast.sol";

SafeCast will revert with an error in this function if msg.value or numWarriors cannot be safely downcast. We know numWarriors is less than 20 since we check in a require statement. msg.value could theoretically be too big, but it would require sending 79 billion ETH. (Congratulations on your mint!)

We'll also need to update refunds to read from the new struct.

Here's the existing code:

ForgottenRunesWarriorsMinter.sol#L376

    function _refundAddress(address minter) private {
        uint256 owed = refundOwed(minter);
        if (owed > 0) {
            daAmountRefunded[minter] += owed;
            _safeTransferETHWithFallback(minter, owed);
        }
    }

    /**
     * @notice returns the amount owed the address
     * @param minter address the address of the account that wants a refund
     */
    function refundOwed(address minter) public view returns (uint256) {
        uint256 totalCostOfMints = finalPrice * daNumMinted[minter];
        uint256 refundsPaidAlready = daAmountRefunded[minter];
        return daAmountPaid[minter] - totalCostOfMints - refundsPaidAlready;
    }

The refundOwed function uses all three bid values: daNumMinted, daAmountPaid, and daAmountRefunded. Instead of four SLOADs, we can load the bid with just two SLOADs (one for the whole bid struct, and one for finalPrice). This has a significant impact when iterating over many refunds:

    /**
     * @notice returns the amount owed the address
     * @param minter address the address of the account that wants a refund
     */
    function refundOwed(address minter) public view returns (uint256) {
        Bid memory bid = daBids[minter];
        return bid.daAmountPaid - (finalPrice * bid.daNumMinted) - bid.daAmountRefunded;
    }

We won't save gas in _refundAddress (this still requires one SLOAD to read and one SSTORE to set), but we do need to update the code to cast the calculated refund amount to uint96:

    function _refundAddress(address minter) private {
        uint256 owed = refundOwed(minter);
        if (owed > 0) {
            daBids[minter].daAmountRefunded += SafeCast.toUint96(owed);
            _safeTransferETHWithFallback(minter, owed);
        }
    }

This is a big change, but it has a significant impact, especially on the gas cost of refunds.

Gas report before:

·------------------------------------------------------------|---------------------------|-------------|-----------------------------· | Solc version: 0.8.6 · Optimizer enabled: true · Runs: 200 · Block limit: 30000000 gas │ ·····························································|···························|·············|······························ | Methods │ ·································|···························|·············|·············|·············|···············|·············· | Contract · Method · Min · Max · Avg · # calls · usd (avg) │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · burn · - · - · 31294 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · initialize · 46261 · 46273 · 46272 · 90 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · mint · 61624 · 95824 · 82999 · 4 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · setBaseURI · - · - · 31993 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · setMinter · - · - · 46157 · 3 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · setProvenanceHash · - · - · 46855 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · uploadAttributes · - · - · 23987 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · uploadImage · - · - · 23975 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · withdrawAll · - · - · 23593 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · bidSummon · 158591 · 755809 · 285939 · 18 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · claimSummon · - · - · 158179 · 6 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · issueRefunds · 69745 · 143756 · 115139 · 5 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · mintlistSummon · 126115 · 160315 · 151765 · 8 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · pause · - · - · 27732 · 2 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · publicSummon · 110840 · 654549 · 490754 · 20 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · selfRefund · - · - · 62257 · 2 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setClaimlistMerkleRoot · - · - · 46109 · 5 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setClaimsStartTime · - · - · 28703 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setDaDropInterval · - · - · 28637 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setDaPriceCurveLength · - · - · 28660 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setDaStartTime · - · - · 28657 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setFinalPrice · 28723 · 28783 · 28779 · 15 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMaxDaSupply · - · - · 28701 · 3 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMaxForClaim · - · - · 28679 · 2 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMaxForSale · - · - · 28636 · 3 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMintlist1MerkleRoot · - · - · 46195 · 9 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMintlist2MerkleRoot · - · - · 46174 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMintlistStartTime · - · - · 28680 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setPhaseTimes · 36598 · 44998 · 43987 · 50 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setPublicStartTime · - · - · 28679 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setSelfRefundsStartTime · 28702 · 28738 · 28720 · 2 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setStartPrice · - · - · 28637 · 2 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setVaultAddress · 26270 · 29070 · 28510 · 5 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setWarriorsAddress · - · - · 29007 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setWethAddress · - · - · 29004 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · teamSummon · 103853 · 623637 · 363745 · 4 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · unpause · - · - · 27748 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · withdraw · - · - · 35364 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · withdrawAll · - · - · 35074 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | Deployments · · % of limit · │ ·····························································|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · - · - · 2023631 · 6.7 % · - │ ·····························································|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · 2738151 · 2738175 · 2738173 · 9.1 % · - │ ·------------------------------------------------------------|-------------|-------------|-------------|---------------|-------------·

Gas report after:

·------------------------------------------------------------|---------------------------|-------------|-----------------------------· | Solc version: 0.8.6 · Optimizer enabled: true · Runs: 200 · Block limit: 30000000 gas │ ·····························································|···························|·············|······························ | Methods │ ·································|···························|·············|·············|·············|···············|·············· | Contract · Method · Min · Max · Avg · # calls · usd (avg) │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · burn · - · - · 31294 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · initialize · 46261 · 46273 · 46272 · 90 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · mint · 61624 · 95824 · 82999 · 4 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · setBaseURI · - · - · 31993 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · setMinter · - · - · 46157 · 3 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · setProvenanceHash · - · - · 46855 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · uploadAttributes · - · - · 23987 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · uploadImage · - · - · 23975 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · withdrawAll · - · - · 23593 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · bidSummon · 154292 · 734410 · 268340 · 18 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · claimSummon · - · - · 158201 · 6 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · issueRefunds · 48980 · 122991 · 86068 · 5 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · mintlistSummon · 126048 · 160248 · 151698 · 8 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · pause · - · - · 27754 · 2 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · publicSummon · 110862 · 654571 · 490776 · 20 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · selfRefund · - · - · 41492 · 2 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setClaimlistMerkleRoot · - · - · 46109 · 5 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setClaimsStartTime · - · - · 28703 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setDaDropInterval · - · - · 28659 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setDaPriceCurveLength · - · - · 28660 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setDaStartTime · - · - · 28679 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setFinalPrice · 28636 · 28696 · 28692 · 15 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMaxDaSupply · - · - · 28723 · 3 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMaxForClaim · - · - · 28701 · 2 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMaxForSale · - · - · 28636 · 3 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMintlist1MerkleRoot · - · - · 46109 · 9 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMintlist2MerkleRoot · - · - · 46196 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setMintlistStartTime · - · - · 28702 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setPhaseTimes · 36598 · 44998 · 43987 · 50 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setPublicStartTime · - · - · 28701 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setSelfRefundsStartTime · 28635 · 28671 · 28653 · 2 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setStartPrice · - · - · 28637 · 2 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setVaultAddress · 26292 · 29092 · 28532 · 5 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setWarriorsAddress · - · - · 29007 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · setWethAddress · - · - · 29026 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · teamSummon · 103875 · 623659 · 363767 · 4 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · unpause · - · - · 27748 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · withdraw · - · - · 35364 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · withdrawAll · - · - · 35096 · 1 · - │ ·································|···························|·············|·············|·············|···············|·············· | Deployments · · % of limit · │ ·····························································|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsGuild · - · - · 2023631 · 6.7 % · - │ ·····························································|·············|·············|·············|···············|·············· | ForgottenRunesWarriorsMinter · 2924973 · 2924997 · 2924995 · 9.7 % · - │ ·------------------------------------------------------------|-------------|-------------|-------------|---------------|-------------·

Increase optimizer runs

Consider increasing the number of configured optimizer runs above 200. This value represents an optimize-for-contract-size/optimize-for-runtime-gas trade-off, and selecting a larger value will reduce gas usage of mint and refund functions while increasing the one time gas cost of contract deployment.

Use unchecked counter increments in loops

This is a minor optimization, but it can save a bit of gas in the hottest loops (like minting batches, and especially issuing refunds).

Loop counter increments can safely use unchecked addition in most cases. For example, ForgottenRunesWarriorsMinter.sol#bidSummon

        for (uint256 i = 0; i < numWarriors; i++) {
            _mint(msg.sender);
        }

Can be rewritten as:

        for (uint256 i = 0; i < numWarriors;) {
            _mint(msg.sender);
            unchecked { ++i; }
        }

This saves around 100 gas/loop.

Potential optimizations:

Inline unused locals

The node local variable in claimSummon#L242 is unused and can be inlined:

        require(
            MerkleProof.verify(_merkleProof, claimlistMerkleRoot, keccak256(abi.encodePacked(msg.sender))),
            'Invalid proof'
        );

The elapsed and steps variables in currentDaPrice#L289 can similarly be inlined:

        uint256 stepDeduction = ((block.timestamp - daStartTime) / daDropInterval) * dropPerStep;

These have a very minor impact and you may prefer readability.

Use preincrement operators

The preincrement operator (++value) is the cheapest way to increment a value. value += 1 or value++ can be rewritten as ++value. This has a very minor impact (11 gas), but it's an easy optimization.

Potential optimizations:

Don't initialize empty storage variables

Setting the empty storage variable numMinted to 0 and METADATA_PROVENANCE_HASH to '' in ForgottenRunesWarriorsGuild.sol can be omitted.

This does not save runtime gas: it only reduces the gas cost at deployment time.

All optimizations

Here is a revised version of ForgottenRunesWarriorsMinter.sol with all the above optimizations.

pragma solidity ^0.8.0;

import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/security/ReentrancyGuard.sol';
import '@openzeppelin/contracts/security/Pausable.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/cryptography/MerkleProof.sol';
import './interfaces/IWETH.sol';
import './interfaces/IForgottenRunesWarriorsGuild.sol';

import "@openzeppelin/contracts/utils/math/SafeCast.sol";

/**
 * @dev This implements the minter of the Forgotten Runes Warriors Guild. They are {ERC721} tokens.
 */
contract ForgottenRunesWarriorsMinter is Ownable, Pausable, ReentrancyGuard {
    /// @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;

    /// @notice The main Merkle root
    bytes32 public mintlist1MerkleRoot;

    /// @notice The secondary Merkle root
    /// @dev Having a backup merkle root lets us atomically update the merkletree without downtime on the frontend
    bytes32 public mintlist2MerkleRoot;

    /// @notice The claimslist Merkle root
    bytes32 public claimlistMerkleRoot;

    /// @notice The address of the Warriors contract
    IForgottenRunesWarriorsGuild public warriors;

    /// @notice The address of the vault
    address public vault;

    /// @notice The address of the WETH contract
    address public weth;

    /// @notice The start price of the DA
    uint256 public startPrice = 2.5 ether;

    /// @notice The lowest price of the DA
    uint256 public lowestPrice = 0.6 ether;

    /// @notice The length of time for the price curve in the DA
    uint256 public daPriceCurveLength = 380 minutes;

    /// @notice The interval of time in which the price steps down
    uint256 public daDropInterval = 10 minutes;

    /// @notice The final price of the DA. Will be updated when DA is over and then used for subsequent phases
    uint256 public finalPrice = 2.5 ether;

    /// @notice An array of the addresses of the DA minters
    /// @dev An entry is created for every da minting tx, so the same minter address is quite likely to appear more than once
    address[] public daMinters;

    struct Bid {
        uint16 daNumMinted;
        uint96 daAmountPaid;
        uint96 daAmountRefunded;
    }
    mapping(address => Bid) public daBids;

    /// @notice Tracks if a given address minted in the mintlist
    mapping(address => bool) public mintlistMinted;

    /// @notice Tracks the total count of NFTs claimed by a given address
    mapping(address => bool) public claimlistMinted;

    /// @notice The total number of tokens reserved for the DA phase
    uint256 public maxDaSupply = 8000;

    /// @notice Tracks the total count of NFTs sold (vs. freebies)
    uint256 public numSold;

    /// @notice Tracks the total count of NFTs for sale
    uint256 public maxForSale = 14190;

    /// @notice Tracks the total count of NFTs claimed for free
    uint256 public numClaimed;

    /// @notice Tracks the total count of NFTs that can be claimed
    /// @dev While we will have a merkle root set for this group, putting a hard cap helps limit the damage of any problems with an overly-generous merkle tree
    uint256 public maxForClaim = 1100;

    /**
     * @dev Create the contract and set the initial baseURI
     * @param _warriors address the initial warriors contract address
     */
    constructor(IForgottenRunesWarriorsGuild _warriors, address _weth) {
        setWarriorsAddress(_warriors);
        setWethAddress(_weth);
        setVaultAddress(msg.sender);
    }

    /*
     * Timeline:
     *
     * bidSummon       : |------------|
     * mintlistSummon  :              |------------|-------------------------------------|
     * publicSummon    :                           |------------|------------------------|
     * claimSummon     :                                        |------------|-----------|
     * teamSummon      : |---------------------------------------------------------------|
     */

    /**
     * @notice Mint a Warrior in the Dutch Auction phase
     * @param numWarriors uint256 of the number of warriors you're trying to mint
     */
    function bidSummon(uint256 numWarriors)
        external
        payable
        nonReentrant
        whenNotPaused
    {
        require(numSold < maxDaSupply, 'Auction sold out');
        require(numSold + numWarriors <= maxDaSupply, 'Not enough remaining');
        require(daStarted(), 'Auction not started');
        require(!mintlistStarted(), 'Auction phase over');
        require(
            numWarriors > 0 && numWarriors <= 20,
            'You can summon no more than 20 Warriors at a time'
        );

        uint256 currentPrice = currentDaPrice();
        require(
            msg.value >= (currentPrice * numWarriors),
            'Ether value sent is not sufficient'
        );

        daMinters.push(msg.sender);

        Bid memory bid = daBids[msg.sender];
        bid.daAmountPaid += SafeCast.toUint96(msg.value);
        bid.daNumMinted += SafeCast.toUint16(numWarriors);
        daBids[msg.sender] = bid;

        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;
        }

        for (uint256 i = 0; i < numWarriors;) {
            _mint(msg.sender);
            unchecked { ++i; }
        }
    }

    /**
     * @notice Mint a Warrior in the mintlist phase (paid)
     * @param _merkleProof bytes32[] your proof of being able to mint
     */
    function mintlistSummon(bytes32[] calldata _merkleProof)
        external
        payable
        nonReentrant
        whenNotPaused
    {
        require(numSold < maxForSale, 'Sold out');
        require(mintlistStarted(), 'Mintlist phase not started');
        require(msg.value == finalPrice, 'Ether value incorrect');

        // verify didn't already mint
        require(mintlistMinted[msg.sender] == false, 'Already minted');
        mintlistMinted[msg.sender] = true;

        // verify mintlist merkle
        bytes32 node = keccak256(abi.encodePacked(msg.sender));
        require(
            MerkleProof.verify(_merkleProof, mintlist1MerkleRoot, node) ||
                MerkleProof.verify(_merkleProof, mintlist2MerkleRoot, node),
            'Invalid proof'
        );

        numSold += 1;
        _mint(msg.sender);
    }

    /**
     * @notice Mint a Warrior in the Public phase (paid)
     * @param numWarriors uint256 of the number of warriors you're trying to mint
     */
    function publicSummon(uint256 numWarriors)
        external
        payable
        nonReentrant
        whenNotPaused
    {
        require(numSold < maxForSale, 'Sold out');
        require(numSold + numWarriors <= maxForSale, 'Not enough remaining');
        require(publicStarted(), 'Public sale not started');
        require(
            numWarriors > 0 && numWarriors <= 20,
            'You can summon no more than 20 Warriors at a time'
        );
        require(
            msg.value == (finalPrice * numWarriors),
            'Ether value sent is incorrect'
        );

        numSold += numWarriors;
        for (uint256 i = 0; i < numWarriors;) {
            _mint(msg.sender);
            unchecked { ++i; }
        }
    }

    /**
     * @dev claim a warrior for free if you're in the claimlist
     * @param _merkleProof bytes32[] the proof that you're eligible to mint here
     */
    function claimSummon(bytes32[] calldata _merkleProof)
        external
        nonReentrant
        whenNotPaused
    {
        require(numClaimed < maxForClaim, 'No more claims');
        require(claimsStarted(), 'Claim phase not started');

        // verify didn't already claim
        require(claimlistMinted[msg.sender] == false, 'Already claimed');
        claimlistMinted[msg.sender] = true;

        require(
            MerkleProof.verify(_merkleProof, claimlistMerkleRoot, keccak256(abi.encodePacked(msg.sender))),
            'Invalid proof'
        );

        ++numClaimed;
        _mint(msg.sender);
    }

    /**
     * @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;) {
            _mint(recipient);
            unchecked { ++i; }
        }
    }

    function _mint(address recipient) private {
        warriors.mint(recipient);
    }

    /*
     * View utilities
     */

    /**
     * @notice returns the current dutch auction price
     */
    function currentDaPrice() public view returns (uint256) {
        if (!daStarted()) {
            return startPrice;
        }
        if (block.timestamp >= daStartTime + daPriceCurveLength) {
            // end of the curve
            return lowestPrice;
        }

        uint256 dropPerStep = (startPrice - lowestPrice) /
            (daPriceCurveLength / daDropInterval);
        uint256 stepDeduction = ((block.timestamp - daStartTime) / daDropInterval) * dropPerStep;

        // don't go negative in the next step
        if (stepDeduction > startPrice) {
            return lowestPrice;
        }
        uint256 currentPrice = startPrice - stepDeduction;
        return currentPrice > lowestPrice ? currentPrice : lowestPrice;
    }

    /**
     * @notice returns whether the dutch auction has started
     */
    function daStarted() public view returns (bool) {
        return block.timestamp > daStartTime;
    }

    /**
     * @notice returns whether the mintlist has started
     */
    function mintlistStarted() public view returns (bool) {
        return block.timestamp > mintlistStartTime;
    }

    /**
     * @notice returns whether the public mint has started
     */
    function publicStarted() public view returns (bool) {
        return block.timestamp > publicStartTime;
    }

    /**
     * @notice returns whether the claims phase has started
     */
    function claimsStarted() public view returns (bool) {
        return block.timestamp > claimsStartTime;
    }

    /**
     * @notice returns whether self refunds phase has started
     */
    function selfRefundsStarted() public view returns (bool) {
        return block.timestamp > selfRefundsStartTime;
    }

    /**
     * @notice returns the number of minter addresses in the DA phase (includes duplicates)
     */
    function numDaMinters() external view returns (uint256) {
        return daMinters.length;
    }

    function daNumMinted(address bidder) external view returns (uint256) {
        return daBids[bidder].daNumMinted;
    }

    function daAmountPaid(address bidder) external view returns (uint256) {
        return daBids[bidder].daAmountPaid;
    }

    function daAmountRefunded(address bidder) external view returns (uint256) {
        return daBids[bidder].daAmountRefunded;
    }

    /*
     * Refund logic
     */

    /**
     * @notice issues refunds for the accounts in minters between startIdx and endIdx inclusive
     * @param startIdx uint256 the starting index of daMinters
     * @param endIdx uint256 the ending index of daMinters, inclusive
     */
    function issueRefunds(uint256 startIdx, uint256 endIdx)
        public
        onlyOwner
        nonReentrant
    {
        for (uint256 i = startIdx; i < endIdx + 1;) {
            _refundAddress(daMinters[i]);
            unchecked { ++i; }
        }
    }

    /**
     * @notice issues a refund for the address
     * @param minter address the address to refund
     */
    function refundAddress(address minter) public onlyOwner nonReentrant {
        _refundAddress(minter);
    }

    /**
     * @notice refunds msg.sender what they're owed
     */
    function selfRefund() public nonReentrant {
        require(selfRefundsStarted(), 'Self refund period not started');
        _refundAddress(msg.sender);
    }

    function _refundAddress(address minter) private {
        uint256 owed = refundOwed(minter);
        if (owed > 0) {
            daBids[minter].daAmountRefunded += SafeCast.toUint96(owed);
            _safeTransferETHWithFallback(minter, owed);
        }
    }

    /**
     * @notice returns the amount owed the address
     * @param minter address the address of the account that wants a refund
     */
    function refundOwed(address minter) public view returns (uint256) {
        Bid memory bid = daBids[minter];
        return bid.daAmountPaid - (finalPrice * bid.daNumMinted) - bid.daAmountRefunded;
    }

    /**
     * @notice Transfer ETH. If the ETH transfer fails, wrap the ETH and try send it as WETH.
     * @param to account who to send the ETH or WETH to
     * @param amount uint256 how much ETH or WETH to send
     */
    function _safeTransferETHWithFallback(address to, uint256 amount) internal {
        if (!_safeTransferETH(to, amount)) {
            IWETH(weth).deposit{value: amount}();
            IERC20(weth).transfer(to, amount);
        }
    }

    /**
     * @notice Transfer ETH and return the success status.
     * @dev This function only forwards 30,000 gas to the callee.
     * @param to account who to send the ETH to
     * @param value uint256 how much ETH to send
     */
    function _safeTransferETH(address to, uint256 value)
        internal
        returns (bool)
    {
        (bool success, ) = to.call{value: value, gas: 30_000}(new bytes(0));
        return success;
    }

    /*
     * Only the owner can do these things
     */

    /**
     * @notice pause the contract
     */
    function pause() public onlyOwner {
        _pause();
    }

    /**
     * @notice unpause the contract
     */
    function unpause() public onlyOwner {
        _unpause();
    }

    /**
     * @notice set the dutch auction start timestamp
     */
    function setDaStartTime(uint256 _newTime) public onlyOwner {
        daStartTime = _newTime;
    }

    /**
     * @notice set the mintlist start timestamp
     */
    function setMintlistStartTime(uint256 _newTime) public onlyOwner {
        mintlistStartTime = _newTime;
    }

    /**
     * @notice set the public sale start timestamp
     */
    function setPublicStartTime(uint256 _newTime) public onlyOwner {
        publicStartTime = _newTime;
    }

    /**
     * @notice set the claims phase start timestamp
     */
    function setClaimsStartTime(uint256 _newTime) public onlyOwner {
        claimsStartTime = _newTime;
    }

    /**
     * @notice set the self refund phase start timestamp
     */
    function setSelfRefundsStartTime(uint256 _newTime) public onlyOwner {
        selfRefundsStartTime = _newTime;
    }

    /**
     * @notice A convenient way to set all phase times at once
     * @param newDaStartTime uint256 the dutch auction start time
     * @param newMintlistStartTime uint256 the mintlst phase start time
     * @param newPublicStartTime uint256 the public phase start time
     * @param newClaimsStartTime uint256 the claims phase start time
     */
    function setPhaseTimes(
        uint256 newDaStartTime,
        uint256 newMintlistStartTime,
        uint256 newPublicStartTime,
        uint256 newClaimsStartTime
    ) public onlyOwner {
        // we put these checks here instead of in the setters themselves
        // because they're just guardrails of the typical case
        require(
            newPublicStartTime >= newMintlistStartTime,
            'Set public after mintlist'
        );
        require(
            newClaimsStartTime >= newPublicStartTime,
            'Set claims after public'
        );
        setDaStartTime(newDaStartTime);
        setMintlistStartTime(newMintlistStartTime);
        setPublicStartTime(newPublicStartTime);
        setClaimsStartTime(newClaimsStartTime);
    }

    /**
     * @notice set the merkle root for the mintlist phase
     */
    function setMintlist1MerkleRoot(bytes32 newMerkleRoot) public onlyOwner {
        mintlist1MerkleRoot = newMerkleRoot;
    }

    /**
     * @notice set the alternate merkle root for the mintlist phase
     * @dev we have two because it lets us idempotently update the website without downtime
     */
    function setMintlist2MerkleRoot(bytes32 newMerkleRoot) public onlyOwner {
        mintlist2MerkleRoot = newMerkleRoot;
    }

    /**
     * @notice set the merkle root for the claimslist phase
     */
    function setClaimlistMerkleRoot(bytes32 newMerkleRoot) public onlyOwner {
        claimlistMerkleRoot = newMerkleRoot;
    }

    /**
     * @notice set the vault address where the funds are withdrawn
     */
    function setVaultAddress(address _newVaultAddress) public onlyOwner {
        vault = _newVaultAddress;
    }

    /**
     * @notice set the warriors token address
     */
    function setWarriorsAddress(
        IForgottenRunesWarriorsGuild _newWarriorsAddress
    ) public onlyOwner {
        warriors = _newWarriorsAddress;
    }

    /**
     * @notice set the weth token address
     */
    function setWethAddress(address _newWethAddress) public onlyOwner {
        weth = _newWethAddress;
    }

    /**
     * @notice set the dutch auction start price
     */
    function setStartPrice(uint256 _newPrice) public onlyOwner {
        startPrice = _newPrice;
    }

    /**
     * @notice set the dutch auction lowest price
     */
    function setLowestPrice(uint256 _newPrice) public onlyOwner {
        lowestPrice = _newPrice;
    }

    /**
     * @notice set the length of time the dutch auction price should change
     */
    function setDaPriceCurveLength(uint256 _newTime) public onlyOwner {
        daPriceCurveLength = _newTime;
    }

    /**
     * @notice set how long it takes for the dutch auction to step down in price
     */
    function setDaDropInterval(uint256 _newTime) public onlyOwner {
        daDropInterval = _newTime;
    }

    /**
     * @notice set "final" price of the dutch auction
     * @dev this is set automatically if the dutch-auction sells out, but needs to be set manually if the DA fails to sell out
     */
    function setFinalPrice(uint256 _newPrice) public onlyOwner {
        finalPrice = _newPrice;
    }

    /**
     * @notice the max supply available in the dutch auction
     */
    function setMaxDaSupply(uint256 _newSupply) public onlyOwner {
        maxDaSupply = _newSupply;
    }

    /**
     * @notice the total max supply available for sale in any phase
     */
    function setMaxForSale(uint256 _newSupply) public onlyOwner {
        maxForSale = _newSupply;
    }

    /**
     * @notice the max supply available in the claimlist
     */
    function setMaxForClaim(uint256 _newSupply) public onlyOwner {
        maxForClaim = _newSupply;
    }

    /**
     * @notice Withdraw funds to the vault
     * @param _amount uint256 the amount to withdraw
     */
    function withdraw(uint256 _amount) public onlyOwner {
        require(address(vault) != address(0), 'no vault');
        require(payable(vault).send(_amount));
    }

    /**
     * @notice Withdraw all funds to the vault
     */
    function withdrawAll() public payable onlyOwner {
        require(address(vault) != address(0), 'no vault');
        require(payable(vault).send(address(this).balance));
    }

    /**
     * @dev ERC20s should not be sent to this contract, but if someone
     * does, it's nice to be able to recover them
     * @param token IERC20 the token address
     * @param amount uint256 the amount to send
     */
    function forwardERC20s(IERC20 token, uint256 amount) public onlyOwner {
        require(address(msg.sender) != address(0));
        token.transfer(msg.sender, amount);
    }
}

Gas report after. (Note that optimizer runs are increased to 200000):

·------------------------------------------------------------|---------------------------|----------------|-----------------------------· | Solc version: 0.8.6 · Optimizer enabled: true · Runs: 200000 · Block limit: 30000000 gas │ ·····························································|···························|················|······························ | Methods │ ·································|···························|·············|·············|················|···············|·············· | Contract · Method · Min · Max · Avg · # calls · usd (avg) │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsGuild · burn · - · - · 31174 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsGuild · initialize · 46198 · 46210 · 46209 · 90 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsGuild · mint · 61513 · 95713 · 82888 · 4 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsGuild · setBaseURI · - · - · 31978 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsGuild · setMinter · - · - · 46084 · 3 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsGuild · setProvenanceHash · - · - · 46840 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsGuild · uploadAttributes · - · - · 23975 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsGuild · uploadImage · - · - · 23963 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsGuild · withdrawAll · - · - · 23581 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · bidSummon · 153755 · 730219 · 267431 · 18 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · claimSummon · - · - · 158012 · 6 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · issueRefunds · 48595 · 122392 · 85448 · 5 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · mintlistSummon · 125927 · 160127 · 151577 · 8 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · pause · - · - · 27687 · 2 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · publicSummon · 110447 · 650502 · 487884 · 20 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · selfRefund · - · - · 41205 · 2 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setClaimlistMerkleRoot · - · - · 46097 · 5 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setClaimsStartTime · - · - · 28669 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setDaDropInterval · - · - · 28647 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setDaPriceCurveLength · - · - · 28648 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setDaStartTime · - · - · 28645 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setFinalPrice · 28624 · 28684 · 28680 · 15 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setMaxDaSupply · - · - · 28689 · 3 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setMaxForClaim · - · - · 28667 · 2 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setMaxForSale · - · - · 28647 · 3 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setMintlist1MerkleRoot · - · - · 46097 · 9 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setMintlist2MerkleRoot · - · - · 46162 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setMintlistStartTime · - · - · 28668 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setPhaseTimes · 36538 · 44938 · 43927 · 50 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setPublicStartTime · - · - · 28667 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setSelfRefundsStartTime · 28646 · 28682 · 28664 · 2 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setStartPrice · - · - · 28648 · 2 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setVaultAddress · 26219 · 29019 · 28459 · 5 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setWarriorsAddress · - · - · 28956 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · setWethAddress · - · - · 28998 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · teamSummon · 103636 · 619563 · 361600 · 4 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · unpause · - · - · 27687 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · withdraw · - · - · 35306 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · withdrawAll · - · - · 35038 · 1 · - │ ·································|···························|·············|·············|················|···············|·············· | Deployments · · % of limit · │ ·····························································|·············|·············|················|···············|·············· | ForgottenRunesWarriorsGuild · - · - · 2690226 · 9 % · - │ ·····························································|·············|·············|················|···············|·············· | ForgottenRunesWarriorsMinter · 4119642 · 4119666 · 4119664 · 13.7 % · - │ ·------------------------------------------------------------|-------------|-------------|----------------|---------------|-------------·

#0 - liveactionllama

2022-05-06T03:59:07Z

Warden created this issue as a placeholder, because their submission was too large for the contest form. They then emailed their md file to our team at 21:21 (UTC) on 05/05/2022. I've updated this issue with their md file content.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter