xanoutas/solidity-security-findings huggingface dataset:
Minting fighters will fail after generation 0
Audit: 2024-02-ai-arena-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

`FighterFarm` contract allows minting fighters through three functions: `claimFighters`, `redeemMintPass` and `mintFromMergingPool`.

Both [`claimFighters`](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L219) and [`redeemMintPass`](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L259) pass the value 100 to the `customAttributes` parameter of the internal function `_createFighter`.

This makes the code [execute the `_createFighterBase` function](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L499-L501).

In the `_createFighterBase` function, the [`elements` variable is calculated](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L470) as the module of the `dna` and the number of elements for the current generation of the `fighterType`.

The problem is that the `numElements` is only [set in constructor](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L110) for generation 0, but cannot be set for other generations. So after the generation is increased for a fighter type via the `incrementGeneration` function, `numElements[generation[fighterType]]` will be 0 and the execution will revert due to a modulo by zero error.

This means that fighters of generation 1 or higher will only be mintable from the merging pool. Note that fighters minted from the merging pool [can only be of type champion](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L327), so dendroids will not be mintable at all.

Another thing that is worth mentioning is that the `numElements` is associated with a generation, but not with a fighter type. So the protocol lacks the flexibility to have different values for different fighter types in the same generation.

## Impact

For fighter generations higher than 0 champions will only be mintable from the merging pool and dendroids will not be mintable at all.

## Proof of Concept

Add the following code to the contract `FighterFarm.t.sol` and run `forge test --mt testFighterCreationBroken`.

```solidity
function testFighterCreationBroken() public {
    uint8[2] memory numToMint = [1, 0];
    bytes memory claimSignature = abi.encodePacked(
        hex"407c44926b6805cf9755a88022102a9cb21cde80a210bc3ad1db2880f6ea16fa4e1363e7817d5d87e4e64ba29d59aedfb64524620e2180f41ff82ca9edf942d01c"
    );
    string[] memory claimModelHashes = new string[](1);
    claimModelHashes[0] = "ipfs://bafybeia
The `_ableToTransfer` check can be bypassed for fighter NFTs
Audit: 2024-02-ai-arena-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-ai-arena/blob/1d18d1298729e443e14fea08149c77182a65da32/src/FighterFarm.sol#L355-L365


# Vulnerability details

Fighter NFTs should only be transferable if they are not staked and the recipient of the transfer has not reached the maximum number of fighters allowed.

The `FighterFarm` contract ensures this is the case by overriding the [`transferFrom(address from, address to, uint256 tokenId)`](https://github.com/code-423n4/2024-02-ai-arena/blob/1d18d1298729e443e14fea08149c77182a65da32/src/FighterFarm.sol#L338-L348) and [`safeTransferFrom(address from, address to, uint256 tokenId)`](https://github.com/code-423n4/2024-02-ai-arena/blob/1d18d1298729e443e14fea08149c77182a65da32/src/FighterFarm.sol#L355-L365) functions from the `ERC721` contract, adding [the necessary checks](https://github.com/code-423n4/2024-02-ai-arena/blob/1d18d1298729e443e14fea08149c77182a65da32/src/FighterFarm.sol#L539-L545).

However, the inherited `ERC721` contract also [exposes a `safeTransferFrom` function with the additional parameter `data`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC721/ERC721.sol#L175-L183), and this function is not overridden in the `FighterFarm` contract. This makes it possible to bypass the `_ableToTransfer` check by calling `safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)`.

This can cause many unwanted side effects. For one, the maximum number of fighters allowed can be exceeded. Another issue is that a fighter owner could transfer the fighter to another address to avoid losing points by making the `updateBattleRecord` function revert [due to an underflow error](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L486) (see PoC).

## Impact

Fighter owners can bypass the `_ableToTransfer` check and transfer fighters to other addresses, potentially causing unwanted side effects like exceeding the maximum number of fighters allowed or avoiding losing points.

## Proof of Concept

Add the following code to the contract `RankedBattle.t.sol` and run `forge test --mt testTransferFighterStaked`:

```solidity
function testTransferFighterStaked() public {
    address alice = makeAddr("alice");
    address alice2 = makeAddr("alice2");

    _fundUserWith4kNeuronByTreasury(alice);
    _mintFromMergingPool(alice);
    uint8 tokenId = 0;
    
    // Alice stakes NRN for fighter
    vm.prank(alice);
    _rankedBattleContract.stakeNRN(3_000e18, tokenId);

    // Fighter wins battle and points are earned
    vm.prank(address(_GAME_SERVER_ADDRESS));
    _rankedBattleContract.updateBattleRecord(tokenId, 50, 0, 1500, true);
    
    // Alice transfers fighter to another address
    vm.prank(alice);
    _fighterFarmContract.safeTransferFrom(alice, alice2, 0, "0x");
    assertEq(_fighterFarmContract.ownerOf(0), alice2);

    // Fighter loses battle an
Non-transferable game items can be transferred
Audit: 2024-02-ai-arena-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

Game items [can be non transferable](https://github.com/code-423n4/2024-02-ai-arena/blob/1d18d1298729e443e14fea08149c77182a65da32/src/GameItems.sol#L38) and this is [handled](https://github.com/code-423n4/2024-02-ai-arena/blob/1d18d1298729e443e14fea08149c77182a65da32/src/GameItems.sol#L301) in the `safeTransferFrom` override function. However, the inherited `ERC1155` contract also [exposes the `safeBatchTransferFrom` function](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/token/ERC1155/ERC1155.sol#L134-L146), and this function is not overridden in the `GameItems` contract. This means that non-transferable tokens can be transferred using the `safeBatchTransferFrom` function.

## Impact

Users can transfer non-transferable `GameItems` tokens. The final impact for the protocol will depend on the expected use of the non-transferable tokens created.

According to the documentation and the deployment script, at launch, the protocol is meant to use the "battery" item, that will be used to initiate battles. The units of this item are refilled every 24 hours and users can buy additional units, but they are not transferable. This issue opens the possibility of users selling their unspent "battery" tokens, increasing the circulating supply, which will reduce the value of the token and the number of sales of the token by the protocol. It will also discourage some users from using the "battery" token to initiate battles, as they can sell them instead. This will in turn concentrate the use of the token in fewer users, reducing the number of battles and the overall activity of the protocol.

## Proof of Concept

Add the following code to the contract `GameItems.t.sol` and run `forge test --mt testTransferNonTransferableToken -vv`.

```solidity
function testTransferNonTransferableToken() public {
    _fundUserWith4kNeuronByTreasury(_ownerAddress);
    _gameItemsContract.mint(0, 1);
    _gameItemsContract.adjustTransferability(0, false);

    uint256[] memory ids = new uint256[](1);
    ids[0] = 0;
    uint256[] memory amounts = new uint256[](1);
    amounts[0] = 1;
    _gameItemsContract.safeBatchTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, ids, amounts, "");

    assertEq(_gameItemsContract.balanceOf(_DELEGATED_ADDRESS, 0), 1);
    assertEq(_gameItemsContract.balanceOf(_ownerAddress, 0), 0);
}
```

## Tools Used

Manual inspection.

## Recommended Mitigation Steps

```diff
-    /// @notice Safely transfers an NFT from one address to another.
-    /// @dev Added a check to see if the game item is transferable.
-    function safeTransferFrom(
-        address from, 
-        address to, 
-        uint256 tokenId,
-        uint256 amount,
-        bytes memory data
-    ) 
-        public 
-        override(ERC1155)
-    {
-        req
Fighters can engage in battles with barely no downside risk
Audit: 2024-02-ai-arena-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

When a fighter has NRN staked and loses a battle, there are two possible scenarios:

1) If the user [has accumulated points](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L479-L490) from previous battles, the points are decreased.

2) If the user [has no points](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L491-L498), some of the staked NRN is set at risk.

Given that the requirement for the first scenario is just having accumulated points higher than 0, in case the user has very few points but a lot of NRN staked, the user can engage in battles with barely any downside risk, but a lot of upside potential.

A possible strategy would be to stake a very low amount of NRN and engage in battles with fighters that can be easily defeated and, after having accumulated at least one point, increase the stake to a very high amount and then engage in battles knowing that if the battle is lost, the user will only lose a few points, but if the battle is won, the user will win a lot of them.

## Impact

Fighter owners can engage in battles with almost no downside risk, but a lot of upside potential.

## Tools Used

Manual inspection.

## Recommended Mitigation Steps

Adjust the `_addResultPoints` function to add stake at risk if the fighter does not have enough points to cover the `curStakeAtRisk` amount.


## Assessed type

Other
Upgraded Q -> 2 from #1606 [1711008404566]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #1606 as 2 risk. The relevant finding follows:

 [L-04] Asking for user input or a choice before setting the value in the mapping
Asking the user for a choice provides transparency and allows users to have control over certain aspects of the contract. Users may prefer to have a say in whether a particular address is allowed to perform burning operations.

File : src/GameItems.sol

185:    function setAllowedBurningAddresses(address newBurningAddress) public {
186:        require(isAdmin[msg.sender]);
187:        allowedBurningAddresses[newBurningAddress] = true;
188:    }
src/GameItems.sol#L185C1-L188C6


// Ask for user choice (you might implement this based on your specific use case)
bool userChoice = getUserChoice(); // You need to define and implement the getUserChoice function

// Set the mapping based on user choice
allowedBurningAddresses[newBurningAddress] = userChoice;

In this modification, you would need to implement a function (getUserChoice in this example) that interacts with the user or a user interface to obtain their choice (whether to set the value to true or false). This approach provides more flexibility and user control over the setting of burning addresses.
Upgraded Q -> 2 from #2018 [1710992700545]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #2018 as 2 risk. The relevant finding follows:

 L-03 GameItems allowedBurningAddresses cannot be unset
Instances

GameItems.sol
Description

Function setAllowedBurningAddresses can only grant and never revoke this permission to given addresses since there is a hardcoded true.

Recommended Mitigation Steps

Remove the hardcoded boolean and, similarly to adjustAllowedVoltageSpenders, add a second parameter bool allowed to setAllowedBurningAddresses, to indicate whether the given address should or should not be allowed to burn game items.

L-04 FighterFarm hasStakerRole cannot be unset
Instances

FighterFarm.sol
Description

Function addStaker can only grant and never revoke this permission to given addresses since there is a hardcoded true.

Recommended Mitigation Steps

Remove the hardcoded boolean and, similarly to adjustAllowedVoltageSpenders, add a second parameter bool allowed to addStaker, to indicate whether the given address should or should not be allowed to stake.
Upgraded Q -> 2 from #1951 [1710990638360]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #1951 as 2 risk. The relevant finding follows:

 [L-03] DEFAULT_ADMIN_ROLE not assigned
In Neuron ERC20 contract DEFAULT_ADMIN_ROLE is not assigned to any user. Due to this the role once assigned cannot be revoked.

So assign DEFAULT_ADMIN_ROLE role to _ownerAddress.
Upgraded Q -> 2 from #1023 [1710981433051]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #1023 as 2 risk. The relevant finding follows:

 AccessControl is being used incorrectly
Links to affected code
https://github.com/code-423n4/2024-02-ai-arena/blob/1d18d1298729e443e14fea08149c77182a65da32/src/Neuron.sol#L95

https://github.com/code-423n4/2024-02-ai-arena/blob/1d18d1298729e443e14fea08149c77182a65da32/src/Neuron.sol#L103

https://github.com/code-423n4/2024-02-ai-arena/blob/1d18d1298729e443e14fea08149c77182a65da32/src/Neuron.sol#L111

Impact
Using _setupRole for the purpose of grantRole is deprecated and _setupRole should only be used at initialization. Because there is no DEFAULT_ADMIN_ROLE, there is no way to delete the role of the previously registered address.

Proof of Concept
When using AccessControl, you have to set the DEFAULT_ADMIN_ROLE or each role’s admin and use the grantRole function to set roles. Using _setupRole for the purpose of grantRole is deprecated and _setupRole should only be used at initialization.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ecd2ca2cd7cac116f7a37d0e474bbb3d7d5e1c4d/contracts/access/AccessControl.sol#L194-L203

/**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * May emit a {RoleGranted} event.
     *
@>   * [WARNING]
     * ====
@>   * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
@>   * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     *
@>   * NOTE: This function is deprecated in favor of {_grantRole}.
     */
In addition, because there is no DEFAULT_ADMIN_ROLE, there is no way to delete the role of the previously registered address when it is changed to a new Minter/Staker/Spender in the future.

Tools Used
Manual Review

Recommended Mitigation Steps
Set the DEFAULT_ADMIN_ROLE in the constructor. Don’t use _setupRole in the addMinter, addStaker, addSpender functions, use grantRole instead.
Upgraded Q -> 2 from #2024 [1710925973347]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #2024 as 2 risk. The relevant finding follows:

 [N-07] Consider passing bool as value to function setAllowedBurningAddresses()
Passing bool as value would allow the admins to remove the burning addresses in case of any problem.

File: GameItems.sol
189:     function setAllowedBurningAddresses(address newBurningAddress) public {
190:         require(isAdmin[msg.sender]);
191:         allowedBurningAddresses[newBurningAddress] = true;
192:     }
Upgraded Q -> 2 from #2024 [1710925717910]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #2024 as 2 risk. The relevant finding follows:

 [L-10] Function claimRewards() does not check if the custom attribute values for weight and element fall in range [65,95] and [0,2] respectively
Upgraded Q -> 2 from #1355 [1710923257289]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #1355 as 2 risk. The relevant finding follows:

 [I-09] No way to revoke MINTER_ROLE, STAKER_ROLE or SPENDER_ROLE in Neuron.sol should it be necessary.
Upgraded Q -> 2 from #1931 [1710921564849]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #1931 as 2 risk. The relevant finding follows:

 [L-04] Unstaked transferred fighter is unusable in the same roundId
When a user unstakes their NRNs and then decides to sell/transfer their fighter to another user, they must wait until the next round to restake and enter the ranked battle.

POC:

Alice has fighter 1 and stakes 100 NRNs.
Alice unstakes 100 NRNs.
Alice sells fighter 1 to Bob on OpenSea.
Bob cannot stake NRNs, thus cannot enter the ranked battle in the same round.
unstakeNRN sets hasUnstaked to true.

stakeNRN check only if tokenId and roundId have been unstaked.

    require(hasUnstaked[tokenId][roundId] == false, "Cannot add stake after unstaking this round");
Recommendation
Consider revising the logic to allow newly transferred fighters to participate in the same round.
Upgraded Q -> 2 from #1964 [1710738038251]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #1964 as 2 risk. The relevant finding follows:

 claimFighters() function allows users to claim a pre-determined number of fighters. It does so by minting NFTs via a loop. The length of the loop is determined by the total amount of NFTs to mint. Relevant code:

 uint16 totalToMint = uint16(numToMint[0] + numToMint[1]);
The potential overflow exists in this line of code. As both numToMint variables are of type uint8 if summation overflows type(uint8).max, the transaction will revert.
Upgraded Q -> 2 from #1504 [1710737062373]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #1504 as 2 risk. The relevant finding follows:

 In FighterFarm::_createFighterBase, a new set of weight, element and dna is created. According to the protocol, weight should lie in the range 65-95 with jumps of 10 between the 3 weight classes (Striker, Scraper, Slugger), such that the first class is 65-74, second one 75-84, and third one should be 85-94. But the code defines up to 95 as can be seen here:

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

uint256 weight = dna % 31 + 65;
Upgraded Q -> 2 from #1794 [1710212111218]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #1794 as 2 risk. The relevant finding follows:

 [L-4] New users to the protocol joining a significant amount of time after project launch will incur huge gas fees when calling RankedBattle.claimNRN()
Upgraded Q -> 2 from #1929 [1710212057623]
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

Judge has assessed an item in Issue #1929 as 2 risk. The relevant finding follows:

 Increasing to 7 winners per round and with 6000 roundIds previously played, approximately when a transaction reaches the 30M limit for new users in the protocol, considering that each roundId is 14 days, we would not have problems with the current implementation for new users starting at elevated roundIds.
Loss of Precision in `claimableNRN` Calculations in `RankedBattle.sol`
Audit: 2024-02-ai-arena-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Loss of Precision in `claimableNRN` Calculations in `RankedBattle.sol`

## Impact

The `RankedBattle.sol` contract within the AI Arena protocol is designed to manage the staking of NRN tokens, track battle outcomes, and distribute rewards based on performance. A critical aspect of this contract is the calculation of `claimableNRN`, the amount of NRN tokens a player can claim as rewards.

A significant issue has been identified in the calculation of `claimableNRN` within the `RankedBattle.sol` contract, leading to a potential loss of precision that can adversely affect players' reward claims. This issue arises under conditions where the `nrnDistribution` (the amount of NRN distributed for a particular round) is relatively low, a scenario that is especially likely at the beginning of rounds.

- **Code References**:
  - [RankedBattle.sol#L301-L303](https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol/#L301-L303)
  - [RankedBattle.sol#L392-L394](https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol/#L392-L394)

The issue stems from the division operation in Solidity, which does not support fractional results. When calculating `claimableNRN`, the product of `accumulatedPointsPerAddress` for a claimer and `nrnDistribution` is divided by `totalAccumulatedPoints` for the current `roundId`. If the numerator of this division (the product of points and distribution) is smaller than the denominator (`totalAccumulatedPoints`), the result of the division defaults to zero due to Solidity's inability to handle fractions. This results in players receiving no rewards for their participation, despite having accumulated points.

- **Undermined Fairness**: Players who have actively participated and accumulated points in early rounds may find their efforts unrewarded due to the precision loss in reward calculations.
- **Disincentive for Participation**: Knowing that early efforts might not be rewarded adequately could disincentivize player participation in the initial stages of competition rounds.

## Proof of Concept

Consider a scenario where a player, Alice, participates in the first round of a ranked battle and accumulates a significant number of points. However, due to the low `nrnDistribution` set for this early round, the calculation of Alice's `claimableNRN` results in zero, despite her having a substantial point accumulation. This outcome is a direct consequence of the division operation yielding zero when the numerator is smaller than the denominator.

## Recommended Mitigation Steps

1. **Minimum Threshold for Numerator**: Implement a minimum threshold for the numerator in the division operation to ensure it is always larger than the denominator, thereby avoiding a zero result.
2. **Fractional Handling Mechanism**: Explore mechanisms to handle fractional results more accurately withi