AI Arena - y4y's results

In AI Arena you train an AI character to battle in a platform fighting game. Imagine a cross between Pokémon and Super Smash Bros, but the characters are AIs, and you can train them to learn almost any skill in preparation for battle.

General Information

Platform: Code4rena

Start Date: 09/02/2024

Pot Size: $60,500 USDC

Total HM: 17

Participants: 283

Period: 12 days

Judge:

Id: 328

League: ETH

AI Arena

Findings Distribution

Researcher Performance

Rank: 257/283

Findings: 1

Award: $0.23

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/MergingPool.sol#L139

Vulnerability details

Impact

When the roundId is too high and user is a first time reward claimer, the function call can potentially cause DoS due to out-of-gas.

Proof of Concept

In claimRewards, the function is doing a loop for the last round msg.sender has claimed reward, and for each round, another loop with length of round winner length is also applied. When the rounds start to build up and get high, this can potentially cause huge amount of gas usage. To an extreme point, it may even block users' reward claim because of out-of-gas or gas exceeds block gas limit.

        uint256 winnersLength;
        uint32 claimIndex = 0;
        uint32 lowerBound = numRoundsClaimed[msg.sender];
        for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) {
            numRoundsClaimed[msg.sender] += 1;
            winnersLength = winnerAddresses[currentRound].length;
            for (uint32 j = 0; j < winnersLength; j++) {
                if (msg.sender == winnerAddresses[currentRound][j]) {
                    _fighterFarmInstance.mintFromMergingPool(
                        msg.sender,
                        modelURIs[claimIndex],
                        modelTypes[claimIndex],
                        customAttributes[claimIndex]
                    );
                    claimIndex += 1;
                }
            }
        }

By default, the winner count per round is set to 2 in the contract, and in the test contracts, they are updated to 3. As the round progress, it's more likely for the scenario to happen. Suppose it's current round 20, and Alice is aware that she has some rewards to claim in the merging pool contract, since this is her first time to claim rewards, to it would take around 20 * 2 = 40 total loops to complete this function. 40 loops itself can already cost huge amount of gas, and with time passes, the cost can only increase, unless each player claim rewards after each round has concluded.

Tools Used

Manual review

Considering adding a map to store winning info for each round, such as:

mapping(uint256 => map(address => bool)) public winnerAddresses

when checking if an address has won anything in the round:

                if (winnerAddresses[currentRound][msg.sender]) {
                    _fighterFarmInstance.mintFromMergingPool(
                        msg.sender,
                        modelURIs[claimIndex],
                        modelTypes[claimIndex],
                        customAttributes[claimIndex]
                    );
                    claimIndex += 1;
                }

Assessed type

DoS

#0 - c4-pre-sort

2024-02-23T23:48:37Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-23T23:48:54Z

raymondfam marked the issue as duplicate of #1541

#2 - c4-judge

2024-03-11T13:00:33Z

HickupHH3 marked the issue as duplicate of #216

#3 - c4-judge

2024-03-11T13:08:46Z

HickupHH3 marked the issue as partial-50

#4 - c4-judge

2024-03-21T02:09:19Z

HickupHH3 marked the issue as satisfactory

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