sherlock-audit / 2023-10-looksrare-judging

6 stars 6 forks source link

0xWSeeC - `MAX_MINT_PER_ADDRESS` invariant can be broke #112

Closed sherlock-admin closed 1 year ago

sherlock-admin commented 1 year ago

0xWSeeC

high

MAX_MINT_PER_ADDRESS invariant can be broke

Summary

The premint function in the Infiltration.sol contract lacks necessary checks for MAX_MINT_PER_ADDRESS, potentially violating the minting limitations established for fair gameplay. Additionally, the function can be invoked at any time before the game starts, which is inconsistent with the intended functionality as documented.

Vulnerability Detail

The premint function does not enforce the MAX_MINT_PER_ADDRESS limit which exists in the regular mint function. This oversight allows for an unlimited amount of tokens to be preminted for an address, which can break the game's fairness as the invariant of MAX_MINT_PER_ADDRESS is not respected. Furthermore, the function lacks a timestamp check to ensure it is called only after the minting period ends, thus allowing premature execution that can disrupt the game start.

Impact

If exploited, this vulnerability could lead to an unfair advantage for certain players who could bypass the minting limitations, undermining the integrity of the game's economy and the trust of the player base. This may also have financial repercussions if the game involves trading or value associated with the minted items.

Code Snippet

https://github.com/sherlock-audit/2023-10-looksrare/blob/881e75651d6592892f10a99f57d2862cf0df65f5/contracts-infiltration/contracts/Infiltration.sol#L445-L463

/*
* @inheritdoc IInfiltration
* @notice As long as the game has not started (after mint end), the owner can still mint.
*/
function premint(address to, uint256 quantity) external payable onlyOwner {
    if (quantity * PRICE != msg.value) {
        revert InsufficientNativeTokensSupplied();
    }

    if (totalSupply() + quantity > MAX_SUPPLY) {
        revert ExceededTotalSupply();
    }

    if (gameInfo.currentRoundId != 0) {
        revert GameAlreadyBegun();
    }

    _mintERC2309(to, quantity);
}

https://github.com/sherlock-audit/2023-10-looksrare/blob/881e75651d6592892f10a99f57d2862cf0df65f5/contracts-infiltration/contracts/Infiltration.sol#L465-L492

/**
* @inheritdoc IInfiltration
*/
function mint(uint256 quantity) external payable nonReentrant {
    if (block.timestamp < mintStart || block.timestamp > mintEnd) {
        revert NotInMintPeriod();
    }

    if (gameInfo.currentRoundId != 0) {
        revert GameAlreadyBegun();
    }

    uint256 amountMinted = amountMintedPerAddress[msg.sender] + quantity;
    if (amountMinted > MAX_MINT_PER_ADDRESS) {
        revert TooManyMinted();
    }

    if (quantity * PRICE != msg.value) {
        revert InsufficientNativeTokensSupplied();
    }

    if (totalSupply() + quantity > MAX_SUPPLY) {
        revert ExceededTotalSupply();
    }

    amountMintedPerAddress[msg.sender] = amountMinted;
    _mintERC2309(msg.sender, quantity);
}

Tool used

Manual Review

Recommendation

It is recommended to add a check within the premint function to enforce the MAX_MINT_PER_ADDRESS limit. This check should mimic the one present in the mint function to maintain consistency and ensure fairness. Moreover, a timestamp condition should be introduced to confirm that premint can only be called after mintEnd. Implementing these checks will align the function's behavior with its intended use as specified in the documentation.

Duplicate of #24