Forgotten Runes Warrior Guild contest - throttle'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: 16/93

Findings: 4

Award: $611.56

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: throttle

Also found by: 0xDjango, BowTiedWardens, WatchPug, defsec, dipp, fatherOfBlocks, gzeon, hake, reassor, shung, unforgiven

Labels

bug
2 (Med Risk)

Awards

296.7571 USDC - $296.76

External Links

Lines of code

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L275-L297

Vulnerability details

Impact

Unbounded and under-constrained variables.

Proof of Concept

  1. dsStartTime | daPriceCurveLength | daDropInterval

The team can change the above variables during sale. It will either increase or decrease the price of an NFT. Or it can make currentDaPrice() revert.

uint256 dropPerStep = (startPrice - lowestPrice) / (daPriceCurveLength / daDropInterval);

uint256 elapsed = block.timestamp - daStartTime;
uint256 steps = elapsed / daDropInterval;
uint256 stepDeduction = steps * dropPerStep;

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

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L275-L297

  1. dsStartTime | mintlistStartTime | publicStartTime | claimsStartTime selfRefundsStartTime

The team can change the above variables. It can result in the wrong sale phases order. For example, the public sale can end up being before every other phase due to accidentally setting it to 0.

Tools Used

Manual review

Possible mitigation:

  1. Bound and constrain variables. For example, daDropInterval should be less than daPriceCurveLength Another example: The total sum of each supply phase should not be bigger than MAX_SUPPLY in the NFT smart contract.

#0 - wagmiwiz

2022-05-05T09:58:10Z

This is true but is a low operational risk and can be undone.

#1 - gzeoneth

2022-06-18T18:37:58Z

Decided to consolidate all issues regarding missing validation of the listed variables here.

Findings Information

🌟 Selected for report: Kulk0

Also found by: 0x1f8b, 0xDjango, BowTiedWardens, Dinddle, broccolirob, defsec, dirk_y, hyh, rajatbeladiya, throttle, unforgiven

Labels

bug
duplicate
2 (Med Risk)
sponsor acknowledged

Awards

246.5367 USDC - $246.54

External Links

Lines of code

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L257-L262

Vulnerability details

Impact

Team minting supply is unbounded

Proof of Concept

  1. Team mints its supply well before the auction starts.
  2. They can sell their NFTs in the public market before others
function teamSummon(address recipient, uint256 count) external onlyOwner {
    require(address(recipient) != address(0), 'address req');
    for (uint256 i = 0; i < count; i++) {
        _mint(recipient);
    }
}

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L257-L262

Tools Used

Manual review

Possible mitigation:

  1. Bound team minting time schedule
  2. Create a lock variable that will enable selling NFT only after the sale ends.

#0 - wagmiwiz

2022-05-05T09:41:41Z

This is true, but by design. It's a risk for minters, but it would be obvious, so we're economically disincentivized to do this. Acknowledged, but not changing it.

#1 - KenzoAgada

2022-06-06T05:43:10Z

Duplicate of #104.

[L-01] Lack of input sane value validation

Description

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L502-602

Check if address/numbers are not 0

Add input validation

-----------------------------------------------------------------

[L-02] Burn function doesn't update numMinted variable

Description

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L108-L119

numMinted is never decreased in burn function.

Decrease numMinted

-----------------------------------------------------------------

[L-03] Unnecessary function

Description

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L108-L119

The above function might be harmful if called accidentally (very unlikely).

Remove function

-----------------------------------------------------------------

[N-01] Use type(uint256).max for max uint value

Description

It's more readable to use type(uint256).max for max uint value

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L16-L35

Consider type(uint256).max

-----------------------------------------------------------------

[N-02] Unnecessary require statement

Description

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L136

Consider removing that line

-----------------------------------------------------------------

[N-03] Divergence from documentation

Description

According to documentation teamSummon starts at the same time as bidSummon:

/*
Timeline:

bidSummon       : |------------|
mintlistSummon  :              |------------|-------------------------------------|
publicSummon    :                           |------------|------------------------|
claimSummon     :                                        |------------|-----------|
teamSummon      : |---------------------------------------------------------------|

But in fact it is time-unbounded:

function teamSummon(address recipient, uint256 count) external onlyOwner {
    require(address(recipient) != address(0), 'address req');
    for (uint256 i = 0; i < count; i++) {
        _mint(recipient);
    }
}

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L257-L262

Consider fixing either code or documentation

-----------------------------------------------------------------

[N-04] More readable for loop check

Description

This:

for (uint256 i = startIdx; i < endIdx + 1; i++) {
    _refundAddress(daMinters[i]);
}

can be simplified to

for (uint256 i = startIdx; i <= endIdx; i++) {
    _refundAddress(daMinters[i]);
}

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L257-L262

This one is just a personal style preference.

-----------------------------------------------------------------

[N-05] Bump packages

Description

4.6.0 OpenZeppelin version is available

Bump packages accordingly

-----------------------------------------------------------------

[N-06] Unnecessary function

Description

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L48-L54

The above function is not needed. First of all, initialize name is misleading because it can be called multiple times. Additionally, setMinter() is public function

Remove function

-----------------------------------------------------------------

[N-07] Unnecessary function

Description

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L108-L119

The above function is not needed. It doesn't make sense to have a burn function.

Remove function

-----------------------------------------------------------------

[G-01] Use gas efficient ERC721 implementation

Description

In the following code there is a nft minting in a for loop. This is very costly. https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L162-L164

Use gas efficient ERC721 implementation from here: https://www.erc721a.org/

Minting multiple NFTs to the same wallet is nearly the same price as minting a single NFT. This would save money for every multi-mint.

Consider using optimized ERC721

-----------------------------------------------------------------

[G-02] Unnecessary require statement

Description

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L136

Consider removing that line

-----------------------------------------------------------------

[G-03] Cheaper for loop

Description

This:

for (uint256 i = startIdx; i < endIdx + 1; i++) {
    _refundAddress(daMinters[i]);
}

can be optimized to

for (uint256 i = startIdx; i <= endIdx;) {
    _refundAddress(daMinters[i]);
    unchecked { ++i };
}

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L257-L262

Consider optimizing all for loop this way

-----------------------------------------------------------------

[G-04] Unnecessary function

Description

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L48-L54

Above function is not needed. First of all initialize name is misleading because it can be called multiple times. Additionally, setMinter() is public function. Removing can save some deployment gas and EVM function selection gas.

Remove function

-----------------------------------------------------------------

[G-05] Unnecessary function

Description

https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L108-L119

Above function is not needed. It doesn't make sense to have a burn function. Removing can save some deployment gas and EVM function selection gas.

Remove function

-----------------------------------------------------------------

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