AI Arena - ladboy233'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: 105/283

Findings: 4

Award: $59.56

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Lines of code

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

Vulnerability details

H staked figher nft transferrable restriction can be bypassed

Proof of concept

Line of code

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

when the figher's nft is staked and fighterStaked[tokenId] flag is false,

the nft should not be transferrable

Line of code

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

    /// @notice Safely transfers an NFT from one address to another.
    /// @dev Add a custom check for an ability to transfer the fighter.
    /// @param from Address of the current owner.
    /// @param to Address of the new owner.
    /// @param tokenId ID of the fighter being transferred.
    function safeTransferFrom(
        address from, 
        address to, 
        uint256 tokenId
    ) 
        public 
        override(ERC721, IERC721)
    {
        require(_ableToTransfer(tokenId, to));
        _safeTransfer(from, to, tokenId, "");
    }

the contract actually override in two function to make sure that this checks correctly

Impact

However, the ERC721 openzepplin version used is 4.7.3

Line of code

contract FighterFarm is ERC721, ERC721Enumerable {

ERC721 contract also defines another version of the safeTransferFrom function

that has another overide

then the code does not override the method in FighterFarm.sol

Line of code

/**
     * @dev See {IERC721-safeTransferFrom}.
     */
    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);
    }

Thus, the user can always call the method above to bypass the staked fighter nft restriction.

Recommendation

should also override the safeTransferFrom function in FighterFarm contract.

This ensures that all transfers, regardless of which safeTransferFrom method is called,

will be subject to your custom restrictions.

Assessed type

Token-Transfer

#0 - c4-pre-sort

2024-02-23T05:21:41Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-23T05:21:56Z

raymondfam marked the issue as duplicate of #739

#2 - c4-judge

2024-03-11T02:47:15Z

HickupHH3 marked the issue as satisfactory

Lines of code

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

Vulnerability details

M MAX_FIGHTERS_ALLOWED per address balance restriction can be bypassed

Proof of concept

Line of code

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

when the a single address's nft balance exceed MAX_FIGHTERS_ALLOWED

the nft should not be transferrable

Line of code

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

    /// @notice Safely transfers an NFT from one address to another.
    /// @dev Add a custom check for an ability to transfer the fighter.
    /// @param from Address of the current owner.
    /// @param to Address of the new owner.
    /// @param tokenId ID of the fighter being transferred.
    function safeTransferFrom(
        address from, 
        address to, 
        uint256 tokenId
    ) 
        public 
        override(ERC721, IERC721)
    {
        require(_ableToTransfer(tokenId, to));
        _safeTransfer(from, to, tokenId, "");
    }

the contract actually override in two function to make sure that this checks correctly

Impact

However, the ERC721 openzepplin version used is 4.7.3

Line of code

contract FighterFarm is ERC721, ERC721Enumerable {

ERC721 contract also defines another version of the safeTransferFrom function

that has another overide

then the code does not override the method in FighterFarm.sol

Line of code

/**
     * @dev See {IERC721-safeTransferFrom}.
     */
    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");
        _s

Thus, the user can always call the method above to bypass the MAX_FIGHTERS_ALLOWED per address restriction.

Recommendation

should also override the safeTransferFrom function in FighterFarm contract.

This ensures that all transfers, regardless of which safeTransferFrom method is called,

will be subject to your custom restrictions.

Assessed type

Invalid Validation

#0 - c4-pre-sort

2024-02-23T05:22:35Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-23T05:22:44Z

raymondfam marked the issue as duplicate of #739

#2 - c4-judge

2024-03-11T02:09:27Z

HickupHH3 changed the severity to 3 (High Risk)

#3 - c4-judge

2024-03-11T02:47:22Z

HickupHH3 marked the issue as satisfactory

Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/GameItems.sol#L212 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/GameItems.sol#L301

Vulnerability details

H - Game item transferrable flag setting can be bypassed

Proof of concept

Line of code

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

the admin of game item can create game item for user to mint

if the admin sets the transferable flag to false,

that means the item should not be transferrable after the nft is minted.

Line of code

{
        require(allGameItemAttributes[tokenId].transferable);
        super.safeTransferFrom(from, to, tokenId, amount, data);
}

Impact

However, this restriction can be bypassed, user can just call

Line of code

function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory values,
        bytes memory data
    ) public virtual {
        address sender = _msgSender();
        if (from != sender && !isApprovedForAll(from, sender)) {
            revert ERC1155MissingApprovalForAll(sender, from);
        }
        _safeBatchTransferFrom(from, to, ids, values, data);
    }

to transfer not-transferrable item

because the game item is ERC1155 NFT

Recommendation

If any item in the batch is found to be non-transferable,

the function should revert the transaction,

preventing the transfer of non-transferable items.

Assessed type

Token-Transfer

#0 - c4-pre-sort

2024-02-22T04:03:09Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-22T04:03:17Z

raymondfam marked the issue as duplicate of #18

#2 - c4-pre-sort

2024-02-26T00:28:21Z

raymondfam marked the issue as duplicate of #575

#3 - c4-judge

2024-03-05T04:54:30Z

HickupHH3 marked the issue as satisfactory

Lines of code

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

Vulnerability details

first time when user claim the reward, he has to pay a lot of gas and this can even DoS user claim and make transaction run out of gas

Proof of concept

Line of code

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

In the first time user call it numRoundsClaimed starts from 0

but roundId will keep incrementing.

Line of code

function setNewRound() external {
        require(isAdmin[msg.sender]);
        require(totalAccumulatedPoints[roundId] > 0);
        roundId += 1;
        _stakeAtRiskInstance.setNewRound(roundId);
        rankedNrnDistribution[roundId] = rankedNrnDistribution[roundId - 1];
    }

Impact

this leads to problem

suppose user participate in round 100, he does not claim the reward,

now the round id is 20000

even he does not participate in most of round between round id 100 to round 20000

the for loop has to interate 20000 times, and he has to pay for the gas fee for round that he does not participate.

this can eventually leads to out of gas and block user claim if the round id number is too large.

Recommendation

records the last round a user participated in,

instead of iterating from the user's last claim round to the current round.

The contract only iterates through the rounds in which the user actually has claimable rewards,

reducing the number of iterations and, consequently, the gas cost.

Assessed type

DoS

#0 - c4-pre-sort

2024-02-23T23:59:50Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-02-24T00:00:09Z

raymondfam marked the issue as duplicate of #1541

#2 - c4-judge

2024-03-11T13:01:19Z

HickupHH3 marked the issue as duplicate of #216

#3 - c4-judge

2024-03-12T02:36:04Z

HickupHH3 marked the issue as partial-50

#4 - c4-judge

2024-03-21T02:57:41Z

HickupHH3 marked the issue as satisfactory

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/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/GameItems.sol#L166

Vulnerability details

M dailyAllowanceReplenishTime per address can by bypassed

Proof of concept

Line of code

 bool success = _neuronInstance.transferFrom(msg.sender, treasuryAddress, price);
        if (success) {
            if (dailyAllowanceReplenishTime[msg.sender][tokenId] <= block.timestamp) {
                _replenishDailyAllowance(tokenId);
            }
            allowanceRemaining[msg.sender][tokenId] -= quantity;
            if (allGameItemAttributes[tokenId].finiteSupply) {
                allGameItemAttributes[tokenId].itemsRemaining -= quantity;
            }
            _mint(msg.sender, tokenId, quantity, bytes("random"));
            emit BoughtItem(msg.sender, tokenId, quantity);
        }

Impact

this limit is track per address using msg.sender address

However, this restriction can be bypassed

A player uses their account (Account A) to mint items until they reach their daily limit.

The player then creates a new account (Account B), buys some NRN token, and uses this new account to mint more items.

If the items are allowed to be transferred between players,

the player can simply move the items from Account B back to their main Account A,

effectively bypassing the daily mint limit.

Recommendation

N/A

Assessed type

Token-Transfer

#0 - c4-pre-sort

2024-02-22T18:03:00Z

raymondfam marked the issue as insufficient quality report

#1 - c4-pre-sort

2024-02-22T18:03:16Z

raymondfam marked the issue as duplicate of #43

#2 - c4-judge

2024-03-07T04:16:50Z

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