AI Arena - Greed'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: 35/283

Findings: 6

Award: $178.47

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L291-L303

Vulnerability details

Impact

There are some tokens representing GameItems that must not be transfered. The issue allows a user to bypass this restriction and be able to transfer these tokens.

Proof of concept

In the GameItems.sol contract, in order to create a new item, an admin can call the createGameItem() function.

The transferability of these tokens can be set using the transferable parameter and is enforced by the safeTransferFrom() function that has been overridden.

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L208-L216

function createGameItem(
    string memory name_,
    string memory tokenURI,
    bool finiteSupply,
    bool transferable,
    uint256 itemsRemaining,
    uint256 itemPrice,
    uint16 dailyAllowance
) 

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L291-L303

function safeTransferFrom(
    address from, 
    address to, 
    uint256 tokenId,
    uint256 amount,
    bytes memory data
) 
    public 
    override(ERC1155)
{
    require(allGameItemAttributes[tokenId].transferable);
    super.safeTransferFrom(from, to, tokenId, amount, data);
}

However this restriction can be bypassed using the safeBatchTransferFrom from ERC1155, giving the ability to transfer all GameItems (even those that should not be)

Tools used

Manual analysis

Override the safeBatchTransferFrom() function and make sure all the tokens given in the array parameter are not transferable

Assessed type

Token-Transfer

#0 - c4-pre-sort

2024-02-22T04:32:27Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-22T04:32:33Z

raymondfam marked the issue as duplicate of #18

#2 - c4-pre-sort

2024-02-26T00:29:36Z

raymondfam marked the issue as duplicate of #575

#3 - c4-judge

2024-03-05T04:47:38Z

HickupHH3 changed the severity to 3 (High Risk)

#4 - c4-judge

2024-03-05T04:58:01Z

HickupHH3 marked the issue as satisfactory

Awards

111.676 USDC - $111.68

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
:robot:_49_group
duplicate-68

External Links

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L370-L391

Vulnerability details

Impact

If a user has minted a fighter after the 255th, he won't ever be able to reRoll() it.

This completely breaks the dedicated functionality of the protocol

Proof of concept

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L370-L391

The FighterFarm::reRoll() function allows a user to reroll his fighter to change his attributes.

Here is the function declaration :

function reRoll(uint8 tokenId, uint8 fighterType) public

The tokenId represents the fighter to reroll.

As you can see, this parameter is a uint8 meaning it can only go up to 255.

If a user owns the NFT number 260 for example, he won't be able to reroll it and utilize the functionality.

Tools used

Manual analysis

Change the type of the tokenId parameter of the reRoll() function from uint8 to uint256 like such :

function reRoll(uint256 tokenId, uint8 fighterType) public

Assessed type

Error

#0 - c4-pre-sort

2024-02-22T02:35:57Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-22T02:36:04Z

raymondfam marked the issue as duplicate of #68

#2 - c4-judge

2024-03-05T02:00:51Z

HickupHH3 marked the issue as satisfactory

Awards

7.2869 USDC - $7.29

Labels

bug
2 (Med Risk)
downgraded by judge
insufficient quality report
partial-25
:robot:_48_group
duplicate-1507

External Links

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/Neuron.sol#L93-L96 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/Neuron.sol#L101-L104 https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/Neuron.sol#L109-L112

Vulnerability details

Impact

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/Neuron.sol#L93-L96

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/Neuron.sol#L101-L104

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/Neuron.sol#L109-L112

Once a role is given to an address, it will persist forever with no way to revoke it.

If either RankedBattle.sol, GameItems.sol, FighterFarm.sol is set to a new address (after an upgrade for example), their old implementation will still possess the roles meaning they are still able to perform privileged actions.

These privileged actions include minting, approving, staking NRN tokens

In some scenarios, these roles could also be temporarly given to an address but there would be no way to remove their privileges

Proof of concept

In order to revoke a role given to an address, the admin of this specific role has to call the revokeRole(bytes32 role, address account) function from Openzeppelin AccessControl.sol

function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
    _revokeRole(role, account);
}

This function checks that the msg.sender has the required privileges to manage the role (that he is admin of this role) using the modifier onlyRole(getRoleAdmin(role))

function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
    return _roles[role].adminRole;
}

// ...

modifier onlyRole(bytes32 role) {
    _checkRole(role);
    _;
}

// ...

function _checkRole(bytes32 role) internal view virtual {
    _checkRole(role, _msgSender());
}

// ...

function _checkRole(bytes32 role, address account) internal view virtual {
    if (!hasRole(role, account)) {
        revert(
            string(
                abi.encodePacked(
                    "AccessControl: account ",
                    Strings.toHexString(uint160(account), 20),
                    " is missing role ",
                    Strings.toHexString(uint256(role), 32)
                )
            )
        );
    }
}

If this condition is not met, the transaction will revert which, in the case of the Neuron.sol contract, always will because neither the admin role nor the admin are set.

Tools used

Manual analysis

First, add the admin roles in the storage

bytes32 public constant MINTER_ADMIN_ROLE = keccak256("MINTER_ADMIN_ROLE");
bytes32 public constant SPENDER_ADMIN_ROLE = keccak256("SPENDER_ADMIN_ROLE");
bytes32 public constant STAKER_ADMIN_ROLE = keccak256("STAKER_ADMIN_ROLE");

Second, set role admin in the constructor

_setRoleAdmin(MINTER_ROLE, MINTER_ADMIN_ROLE);
_setRoleAdmin(SPENDER_ROLE, SPENDER_ADMIN_ROLE);
_setRoleAdmin(STAKER_ROLE, STAKER_ADMIN_ROLE);

Finally, implement functions to set the role admin

function assignMinterAdmin(address minterAdmin) external {
    require(msg.sender == _ownerAddress);
    _setupRole(MINTER_ADMIN_ROLE, minterAdmin);
}

function assignSpenderAdmin(address spenderAdmin) external {
    require(msg.sender == _ownerAddress);
    _setupRole(SPENDER_ADMIN_ROLE, spenderAdmin);
}

function assignStakerAdmin(address stakerAdmin) external {
    require(msg.sender == _ownerAddress);
    _setupRole(STAKER_ADMIN_ROLE, stakerAdmin);
}

Now the role admin can use the following functions

revokeRole(MINTER_ROLE, oldMinter);
revokeRole(SPENDER_ROLE, oldSpender);
revokeRole(STAKER_ROLE, oldStaker);

Assessed type

Access Control

#0 - c4-pre-sort

2024-02-22T05:14:02Z

raymondfam marked the issue as insufficient quality report

#1 - c4-pre-sort

2024-02-22T05:14:09Z

raymondfam marked the issue as duplicate of #20

#2 - c4-judge

2024-03-05T07:31:04Z

HickupHH3 changed the severity to 2 (Med Risk)

#3 - c4-judge

2024-03-05T09:58:35Z

HickupHH3 marked the issue as not a duplicate

#4 - c4-judge

2024-03-05T10:00:08Z

HickupHH3 marked the issue as duplicate of #1507

#5 - c4-judge

2024-03-05T10:00:13Z

HickupHH3 marked the issue as partial-25

Lines of code

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

Vulnerability details

Impact

Users can lose the NRN tokens they earned as rewards

Proof of concept

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

When users win a match, they may earn points which are stored in the accumulatedPointsPerAddress mapping.

These points can later on be used to claim NRN tokens using the RankedBattle::claimNRN() function.

This function goes through every round that the caller didn't claim rewards, in a for loop.

If the user didn't claim his rewards for a very long time and kept accumulating them, there is a risk that the loop consumes all the gas available and makes the transaction revert.

As a result, the user won't be able to claim his rewards ever.

Tools used

Manual analysis

Add a new function to allow users to claim their rewards for a particular round.

Assessed type

DoS

#0 - c4-pre-sort

2024-02-25T02:29:12Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-25T02:29:23Z

raymondfam marked the issue as duplicate of #1541

#2 - c4-judge

2024-03-11T13:01:35Z

HickupHH3 marked the issue as duplicate of #216

#3 - c4-judge

2024-03-12T02:45:21Z

HickupHH3 marked the issue as partial-50

#4 - c4-judge

2024-03-21T02:59:50Z

HickupHH3 marked the issue as satisfactory

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L370-L391

Vulnerability details

Impact

User can predict the attributes of their fighters and get an unfair advantage compared to other players.

He could reroll them so they all have a diamond attribute, which is particularly rare, or get the best element and weight.

Proof of concept

The reRoll() function is used to modify the attributes of a particular fighter.

These attributes are determined using the internal _createFighterBase() function which takes the dna parameter to generate attributes.

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L370-L391

function reRoll(uint8 tokenId, uint8 fighterType) public {
    require(msg.sender == ownerOf(tokenId));
    require(numRerolls[tokenId] < maxRerollsAllowed[fighterType]);
    require(_neuronInstance.balanceOf(msg.sender) >= rerollCost, "Not enough NRN for reroll");

    _neuronInstance.approveSpender(msg.sender, rerollCost);
    bool success = _neuronInstance.transferFrom(msg.sender, treasuryAddress, rerollCost);
    if (success) {
        numRerolls[tokenId] += 1;
        uint256 dna = uint256(keccak256(abi.encode(msg.sender, tokenId, numRerolls[tokenId])));
        (uint256 element, uint256 weight, uint256 newDna) = _createFighterBase(dna, fighterType);
        fighters[tokenId].element = element;
        fighters[tokenId].weight = weight;
        fighters[tokenId].physicalAttributes = _aiArenaHelperInstance.createPhysicalAttributes(
            newDna,
            generation[fighterType],
            fighters[tokenId].iconsType,
            fighters[tokenId].dendroidBool
        );
        _tokenURIs[tokenId] = "";
    }
} 

Since the dna parameter is computed based upon the value of msg.sender, in case it changes, the returned value will be completely different.

A user can simulate the reRoll() function and if the outcome is not the one he expected, he can transfer his fighter to another address he controls and repeat the process until the rerolled fighter is the one he wants (a fighter with diamond attribute for example).

Tools used

Manual analysis

When performing RNG, a smart contract should rely on an external party to make the number generated unpredictable.

Use an oracle to generate the dna number that is used to derivate the fighter's attributes

Assessed type

ERC721

#0 - c4-pre-sort

2024-02-24T01:57:09Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-24T01:57:21Z

raymondfam marked the issue as duplicate of #53

#2 - c4-judge

2024-03-06T03:52:30Z

HickupHH3 marked the issue as satisfactory

#3 - c4-judge

2024-03-15T02:10:54Z

HickupHH3 changed the severity to 2 (Med Risk)

#4 - c4-judge

2024-03-22T04:21:53Z

HickupHH3 marked the issue as duplicate of #376

Awards

59.2337 USDC - $59.23

Labels

bug
2 (Med Risk)
insufficient quality report
satisfactory
:robot:_50_group
duplicate-43

External Links

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L147-L177

Vulnerability details

Impact

The restrictions regarding the mint() function in the GameItems.sol contract can by bypassed, allowing a user to own more tokens than the allowanceRemaining and the dailyAllowanceReplenishTime should enforce.

Proof of concept

The mint() function makes sure the caller can only claim a defined amount of tokens daily using the dailyAllowanceReplenishTime and the allowanceRemaining mappings

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L147-L177

require(
    dailyAllowanceReplenishTime[msg.sender][tokenId] <= block.timestamp || 
    quantity <= allowanceRemaining[msg.sender][tokenId]
);

However, this restriction can be bypassed, allowing a user to obtain more tokens than the daily amount allowed by mint() using 1 wallet and transfering the token to another wallet.

In order to exploit the issue, a user can do the following (infinitely) :

Assume the user has a main wallet

  1. Create a new wallet
  2. Using the new wallet, call the mint() function with the maximum of quantity allowed by allGameItemAttributes
  3. Use the safeTransferFrom() function to transfer all the tokens minted to the main wallet
  4. Repeat as much as wanted

Tools used

Manual analysis

In the safeTransferFrom() overridden function, add more checks regarding the receiver's allowanceRemaining and dailyAllowanceReplenishTime mappings

Assessed type

Invalid Validation

#0 - c4-pre-sort

2024-02-22T18:12:58Z

raymondfam marked the issue as insufficient quality report

#1 - c4-pre-sort

2024-02-22T18:13:06Z

raymondfam marked the issue as duplicate of #43

#2 - c4-judge

2024-03-07T04:21:55Z

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