Assessment reports>Yeet>Medium findings>NFT boosting can be applied multiple times with one NFT
Category: Coding Mistakes

NFT boosting can be applied multiple times with one NFT

Medium Severity
Medium Impact
High Likelihood

Description

When a user invokes the yeet function, they can optionally provide the tokenIds parameter, which indicates the list of the NFTs of the user. If a user has NFTs, they can receive the boost for their reward. The multiplier of the boost increases, depending on the number of NFTs. The following code implements this feature:

/// @notice this function is used to yeet with tokenIds (NFTs)
function yeet(uint256[] memory tokenIds) external payable {
    _yeet(tokenIds);
}

/// @notice yeet is the main function of the game, users yeet the native token in the pot
/// @param tokenIds the tokenIds of the NFTs the user owns, used to calculate the boost
function _yeet(uint256[] memory tokenIds) internal {
    // (...)

    uint256 boostedValue = getBoostedValue(msg.sender, valueToPot, tokenIds);
    rewardsContract.addYeetVolume(msg.sender, boostedValue);
    stakingContract.depositReward{value: valueToStakers}();
}

// (...)

/// @notice getBoostedValue is a function that returns the boosted value of a yeet based on how many NFTs the user owns
/// @param sender the user that yeeted
/// @param value the value of the yeet
/// @param tokenIds the tokenIds of the NFTs the user owns
/// @return uint256 the boosted value
function getBoostedValue(address sender, uint256 value, uint256[] memory tokenIds) public view returns (uint256) {
    uint256 nftBoost = getNFTBoost(sender, tokenIds);
    return value + (value * nftBoost) / 10000;
}

// (...)

/// @notice getNFTBoost is a function that returns the NFT boost of a user based on how many NFTs the user owns
function getNFTBoost(address owner, uint256[] memory tokenIds) public view returns (uint256) {
    if (yeetardsNFTsAddress == address(0)) {
        return 0;
    }
    INFTContract nftContract = INFTContract(yeetardsNFTsAddress);

    uint256 balance = tokenIds.length;
    for (uint i = 0; i < balance; i++) {
        // make sure the user is the owner of all the tokensIds
        uint256 tokenId = tokenIds[i];
        if (nftContract.ownerOf(tokenId) != owner) {
            revert UserDoesNotOwnNFTs(owner, tokenId);
        }
        if (!nftContract.isEligibleForBoost(tokenId)) {
            revert NFTNotEligibleForBoost(tokenId);
        }
    }

    if (balance > nftBoostLookup.length - 1) {
        return nftBoostLookup[nftBoostLookup.length - 1];
    }

    return nftBoostLookup[balance];
}

However, it is not checked that an NFT appears only once in the given parameter tokenIds.

Impact

A malicious user can provide the tokenIds parameter, which includes one NFT the user owns, multiple times, in order to maximize their boost without having several NFTs.

Recommendations

Consider checking if all elements of the given tokenIds parameter are unique.

Remediation

This issue has been acknowledged by Sanguine Labs LTD, and a fix was implemented in commit 7e5e936d.

Zellic © 2024Back to top ↑