sherlock-audit / 2024-04-titles-judging

10 stars 7 forks source link

gesha17 - mintBatch() for a set of receivers collects fee only once #321

Closed sherlock-admin4 closed 5 months ago

sherlock-admin4 commented 5 months ago

gesha17

high

mintBatch() for a set of receivers collects fee only once

Summary

Minting functions in Edition.sol collect a fee for each amount of tokens that is minted. If a user mints a batch of tokens, they are expected to pay a fee for each amount that is minted. However, the mintBatch() function for minting to a set of receivers collects the fee only once, no matter how many receivers will have tokens minted. This means that a user that would normally call mint() with any amount could instead call mintBatch() with 2 or more addresses and get more tokens(in total) for the same fee.

Vulnerability Detail

The functions mint(), mintWithComment() and the ERC1155 mintBatch() all collect a fee once for each amount of tokens minted. The ERC1155 mintBatch() collects a fee inside the for loop, for each tokenId. However, if we look at it's overloaded variant: mintBatch() that mints an amount for a number of receivers(see code snipped below), we see that it is only collecting the fee once and then issuing tokens to every receiver. This means that any user that would normally call mint() with an amount=1e18 could instead call mintBatch() and pas in an array with his own address any number of times. This way the user would mint himself more tokens that he paid fees for.

Impact

Any user can mint tokens at a discounted price by minting to a large number of receivers using the overloaded mintBatch().

Code Snippet

The vulnerable function: https://github.com/sherlock-audit/2024-04-titles/blob/main/wallflower-contract-v2/src/editions/Edition.sol#L304

    function mintBatch(
        address[] calldata receivers_,
        uint256 tokenId_,
        uint256 amount_,
        bytes calldata data_
    ) external payable {
        // wake-disable-next-line reentrancy
        FEE_MANAGER.collectMintFee{value: msg.value}(
            this, tokenId_, amount_, msg.sender, address(0), works[tokenId_].strategy
        );

        for (uint256 i = 0; i < receivers_.length; i++) {
            _issue(receivers_[i], tokenId_, amount_, data_);
        }

        _refundExcess();
    }

Tool used

Manual Review

Recommendation

Pass the amount to the fee collector by multiplying amount * receivers_.length like this:

        FEE_MANAGER.collectMintFee{value: msg.value}(
            this, tokenId_,  amount * receivers_.length , msg.sender, address(0), works[tokenId_].strategy
        );

Duplicate of #264