AI Arena - emrekocak'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: 145/283

Findings: 2

Award: $13.86

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L299-L305 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L149-L163

Vulnerability details

Impact

After a certain point, newly registered players and players who have stopped playing for a long time cannot claim the rewards.

Proof of Concept

After a few years roundId will reach very high numbers. Since newly registered player's numRoundsClaimed[player] starts with 0, all newly registered players cannot execute RankedBattle::claimNRN and MergingPool::claimRewards functions because of high gas consumption. It'll be higher than block gas limit. https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L299-L305 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L149-L163

Tools Used

Manual Review

Newly registered player's numRoundsClaimed value should start current roundId. If numRoundsClaimed[msg.sender] == 0, it's unnecessary to make remaining calculations inside the loop like below. This change will reduce spent gas.

diff --git a/src/RankedBattle.sol#L299-L305
        uint32 lowerBound = numRoundsClaimed[msg.sender];
+       uint256 accumulatedPoints;
        for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) {
+           accumulatedPoints = accumulatedPointsPerAddress[msg.sender][currentRound];
            if (accumulatedPoints == 0){
                lowerBound += 1;
            }
+           else{
                nrnDistribution = getNrnDistribution(currentRound);
                claimableNRN += (
-                   accumulatedPointsPerAddress[msg.sender][currentRound] * nrnDistribution
+                   accumulatedPoints * nrnDistribution  
                ) / totalAccumulatedPoints[currentRound];
-               numRoundsClaimed[msg.sender] += 1;
+               lowerBound += 1;
+           }
        }
+       numRoundsClaimed[msg.sender] = lowerBound;

Assessed type

DoS

#0 - c4-pre-sort

2024-02-25T02:22:58Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-25T02:23:10Z

raymondfam marked the issue as duplicate of #1541

#2 - c4-judge

2024-03-11T13:00:36Z

HickupHH3 marked the issue as duplicate of #216

#3 - c4-judge

2024-03-11T13:08:05Z

HickupHH3 changed the severity to 2 (Med Risk)

#4 - c4-judge

2024-03-12T02:43:52Z

HickupHH3 marked the issue as partial-50

#5 - c4-judge

2024-03-21T02:10:29Z

HickupHH3 marked the issue as satisfactory

Awards

13.6293 USDC - $13.63

Labels

bug
G (Gas Optimization)
grade-b
sufficient quality report
G-28

External Links

G-1) If numRoundsClaimed[msg.sender] == 0 it's unnecessary to calculate claimableNRN

If a player is newly registered or has stopped playing for a long time, there will be lots of 0 value in numRoundsClaimed. For each 0 value there is unnecessary claimableNRN calculation. Similar issue also can be found here

Also numRoundsClaimed[msg.sender] should be cached. (This is mentioned in bot races but I wrote it in recommendation to make it clear)

diff --git a/src/RankedBattle.sol#L299-L305
        uint32 lowerBound = numRoundsClaimed[msg.sender];
+       uint256 accumulatedPoints;
        for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) {
+           accumulatedPoints = accumulatedPointsPerAddress[msg.sender][currentRound];
            if (accumulatedPoints == 0){
                lowerBound += 1;
            }
+           else{
                nrnDistribution = getNrnDistribution(currentRound);
                claimableNRN += (
-                   accumulatedPointsPerAddress[msg.sender][currentRound] * nrnDistribution
+                   accumulatedPoints * nrnDistribution  
                ) / totalAccumulatedPoints[currentRound];
-               numRoundsClaimed[msg.sender] += 1;
+               lowerBound += 1;
+           }
        }
+       numRoundsClaimed[msg.sender] = lowerBound;

G-2) Same thing done twice in constructor

This line run already in addAttributeProbabilities function.

diff --git a/src/AiArenaHelper.sol#L44-L51
        // Initialize the probabilities for each attribute
        addAttributeProbabilities(0, probabilities);

        uint256 attributesLength = attributes.length;
        for (uint8 i = 0; i < attributesLength; i++) {
-           attributeProbabilities[0][attributes[i]] = probabilities[i];
            attributeToDnaDivisor[attributes[i]] = defaultAttributeDivisor[i];
        }

#0 - raymondfam

2024-02-25T22:18:44Z

G1 to #1541

#1 - c4-pre-sort

2024-02-25T22:18:51Z

raymondfam marked the issue as sufficient quality report

#2 - c4-judge

2024-03-12T02:47:44Z

HickupHH3 marked the issue as grade-b

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