code-423n4 / 2024-02-ai-arena-findings

4 stars 3 forks source link

Users may not be able to claim their NRN rewards, because MAX_SUPPLY is reached #1942

Closed c4-bot-9 closed 8 months ago

c4-bot-9 commented 8 months ago

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L294-L311

Vulnerability details

Impact

The Neuron.sol contract has a max supply set to 10**18 * 10**9. And the mint function has the following check

    function mint(address to, uint256 amount) public virtual {
        require(totalSupply() + amount < MAX_SUPPLY, "Trying to mint more than the max supply");
        require(hasRole(MINTER_ROLE, msg.sender), "ERC20: must have minter role to mint");
        _mint(to, amount);
    }

In the RankedBattle.sol contract the NRN rewards for a round are set in the following way:

    function setRankedNrnDistribution(uint256 newDistribution) external {
        require(isAdmin[msg.sender]);
        rankedNrnDistribution[roundId] = newDistribution * 10**18;
    }

There are no checks if max supply is reached, and tokens are not directly minted to the contract. There is an initial mint to the treasury for 10**18 * 10**8 * 2 and an initial contributor mint for 10**18 * 10**8 * 5. There is also the possibility that there are airdrops which will increase the supply of NRN tokens. After the initial mints the contract will be left with 10**9 - ((10**8 * 2) +(10**8 * 5)) = 300_000_000. The problem will arise after many rounds, also depends on the amount of NRN tokens allocated for rewards in each round. The claimNRN() function is implemented in the following way

    function claimNRN() external {
        require(numRoundsClaimed[msg.sender] < roundId, "Already claimed NRNs for this period");
        uint256 claimableNRN = 0;
        uint256 nrnDistribution;
        uint32 lowerBound = numRoundsClaimed[msg.sender];
        for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) {
            nrnDistribution = getNrnDistribution(currentRound);
            claimableNRN += (
                accumulatedPointsPerAddress[msg.sender][currentRound] * nrnDistribution 
            ) / totalAccumulatedPoints[currentRound];
            numRoundsClaimed[msg.sender] += 1;
        }
        if (claimableNRN > 0) {
            amountClaimed[msg.sender] += claimableNRN;
            _neuronInstance.mint(msg.sender, claimableNRN);
            emit Claimed(msg.sender, claimableNRN);
        }
    }

There are scenarios where a user may not claim his rewards for a couple of rounds and when he decides to do so, the contract can't mint him any rewards, even if he has to receive 100 e18 NRN tokens for round n, and 300 e18 NRN tokens for round n+1. If the contract has already minted all tokens but 300 e18 NRN the claimNRN() function will revert. Also the contract can keep on starting new rounds, when the MAX_SUPPLY is already reached, in this case users will spend gas fees, and risk their NRN tokens to try an win additional NRN tokens, but when they try and claim them it won't be possible as the mint call will revert.

Proof of Concept

Tools Used

Manual review

Recommended Mitigation Steps

When setting up the rewards for each round, consider first having them minted to the RankedBattle.sol contract and then transfer them from the contract to the user when they decide to claim them by calling the claimNRN() function.

Assessed type

Invalid Validation

c4-pre-sort commented 8 months ago

raymondfam marked the issue as insufficient quality report

c4-pre-sort commented 8 months ago

raymondfam marked the issue as duplicate of #7

c4-judge commented 8 months ago

HickupHH3 changed the severity to QA (Quality Assurance)