AI Arena - deadrxsezzz'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: 245/283

Findings: 3

Award: $0.33

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L539 https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC721/ERC721.sol#L175

Vulnerability details

Impact

Users can bypass _ableToTransfer restriction within FighterFarm, due to not overwritten ERC721 method

Proof of Concept

Before every transfer, there are certain checks which are supposed to be made - that the receiver does not exceed the MAX_FIGHTERS_ALLOWED and that the ERC721 transferred is not currently staked.

    function _ableToTransfer(uint256 tokenId, address to) private view returns(bool) {
        return (
          _isApprovedOrOwner(msg.sender, tokenId) &&
          balanceOf(to) < MAX_FIGHTERS_ALLOWED &&
          !fighterStaked[tokenId]
        );
    }
}

In order to enforce these restrictions, the transferFrom(address,address,uint256) and safeTransferFrom(address,address,uint256) ERC721 methods are overwritten.

    function transferFrom(
        address from, 
        address to, 
        uint256 tokenId
    ) 
        public 
        override(ERC721, IERC721)
    {
        require(_ableToTransfer(tokenId, to));
        _transfer(from, to, tokenId);
    }
    function safeTransferFrom(
        address from, 
        address to, 
        uint256 tokenId
    ) 
        public 
        override(ERC721, IERC721)
    {
        require(_ableToTransfer(tokenId, to));
        _safeTransfer(from, to, tokenId, "");
    }

However, the inherited ERC721 OZ contract also has a third method for transferring tokens, which is not overwritten - safeTransferFrom(address,address,uint256,bytes)

Because the method is not overwritten, any user can use it and bypass all restrictions, allowing them to send staked tokens and bypass max NFT limit

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) public virtual override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");
        _safeTransfer(from, to, tokenId, data);
    }

Tools Used

Manual review

overwrite the safeTransferFrom(address,address,uint256,bytes) method

Assessed type

ERC721

#0 - c4-pre-sort

2024-02-23T05:09:01Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-23T05:09:11Z

raymondfam marked the issue as duplicate of #739

#2 - c4-judge

2024-03-11T02:44:32Z

HickupHH3 marked the issue as satisfactory

Lines of code

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC1155/ERC1155.sol#L134

Vulnerability details

Impact

Users can transfer game items marked as non-transferable

Proof of Concept

The GameItems contract inherits ERC1155. Since some items, can be marked as non-transferable, the safeTransferFrom method is overwritten, so that it first performs a check whether the item has transferable set to false.

    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,ERC1155 also has another method which allows transferring assets - safeBatchTransferFrom. This method is not overwritten and does not perform the necessary transferable check, allowing any user to send non-transferable assets freely.

Tools Used

Manual review

Overwrite the safeBatchTransferFrom

PoC

Add to GameItems.t.sol

  function testAdjustTransferabilityFromOwner() public {
        _fundUserWith4kNeuronByTreasury(_ownerAddress);
        _gameItemsContract.mint(0, 2); //paying 2 $NRN for 2 batteries
        _gameItemsContract.adjustTransferability(0, false);
        (,, bool transferable,,,) = _gameItemsContract.allGameItemAttributes(0);
        assertEq(transferable, false);

        uint256[] memory ids = new uint256[](1);
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = 2;

        address randomAddr = address(2);
        assertEq(2, _gameItemsContract.balanceOf(_ownerAddress, 0));
        assertEq(0, _gameItemsContract.balanceOf(randomAddr, 0));
        _gameItemsContract.safeBatchTransferFrom(_ownerAddress, randomAddr, ids, amounts, "");
        assertEq(0, _gameItemsContract.balanceOf(_ownerAddress, 0));
        assertEq(2, _gameItemsContract.balanceOf(randomAddr, 0));

        (,, transferable,,,) = _gameItemsContract.allGameItemAttributes(0);
        assertEq(transferable, false);

    }

Assessed type

Error

#0 - c4-pre-sort

2024-02-22T04:01:02Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-22T04:01:12Z

raymondfam marked the issue as duplicate of #18

#2 - c4-pre-sort

2024-02-26T00:28:18Z

raymondfam marked the issue as duplicate of #575

#3 - c4-judge

2024-03-05T04:54:24Z

HickupHH3 marked the issue as satisfactory

Lines of code

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

Vulnerability details

Impact

Users won't be able to claim their rewards.

Proof of Concept

In order to claim their rewards, users have to call claimNRN. What it does is it loops over all rounds (since 0) and calculates the user's rewards. The problem is that it even goes through the rounds when the user has not had a stake whatsoever.

This could be problematic if a user deposits after a lot of rounds have gone by (e.g. there's been 10,000 rounds prior). In order to claim any reward, the function would have to loop through all 10,000 rounds, leading to OOG issues.

    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);
        }
    }

Tools Used

Manual review

When a user stakes for the first time, set numRoundsClaimed[user] value to the current round

Assessed type

Other

#0 - c4-pre-sort

2024-02-25T02:25:47Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-25T02:25:56Z

raymondfam marked the issue as duplicate of #1541

#2 - c4-judge

2024-03-11T13:01:02Z

HickupHH3 marked the issue as duplicate of #216

#3 - c4-judge

2024-03-12T02:44:21Z

HickupHH3 marked the issue as partial-50

#4 - c4-judge

2024-03-21T02:11:48Z

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