xanoutas/solidity-security-findings huggingface dataset:
Funds could stuck in `Pools.sol` contract
Audit: 2024-01-salty-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L219


# Vulnerability details


## Impact

Approximately, the dust amount could become stuck in the contract for every depositor

## Proof of Concept

https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L219

The `withdraw()` function is used for withdrawing the `amount` of the deposited token, requiring `amount > PoolUtils.DUST` to be true. This means if a user's balance falls below `PoolUtils.DUST`, they cannot withdraw all their deposits.

In contrast, other functions like `removeLiquidity()` allow users to remove all their deposits from a pool.
## Tools Used
Manual
## Recommended Mitigation Steps

Add a check to ensure the caller doesn't end up in a position where `_userDeposits[msg.sender][token] < PoolUtils.DUST`, preventing the caller from being unable to exit completely.


## Assessed type

Other
The liquidity provider can fail to withdraw his liquidity if he gets blacklisted
Audit: 2024-01-salty-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/Liquidity.sol#L131-L132


# Vulnerability details

When a liquidity provider wants to withdraw his liquidity, the reclaimed tokens are transferred to the msg.sender. 

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/Liquidity.sol#L131-L132

```solidity
tokenA.safeTransfer( msg.sender, reclaimedA );
tokenB.safeTransfer( msg.sender, reclaimedB );

```

The liquidity provider isnt able to specify on what address he wants to receive the tokens so this can be a problem if he gets for example blacklisted while having the tokens in the pools. The transaction will then revert and liquidity providers can fail to withdraw their assets that they deposited. 


## Impact
The LP will not be able to specify where he wants to receive his tokens so if he gets blacklisted then he will not be able to withdraw liquidity because the transfer would revert. 


## Proof of Concept

```solidity
tokenA.safeTransfer( msg.sender, reclaimedA );
tokenB.safeTransfer( msg.sender, reclaimedB );

```
As you can see in `_withdrawLiquidityAndClaim()`, the reclaimed tokens are transferred to the msg.sender and there is no way to specify an address where to receive tokens


## Tools Used
Manual Review

## Recommended Mitigation Steps
Allow the LP to specify an address where they want to receive their reclaimed assets. 


## Assessed type

Token-Transfer
Creation of a confirmation proposal can be blocked
Audit: 2024-01-salty-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#L122


# Vulnerability details

## Impact
The creation of a confirmation proposal can be blocked because of a front-running attack. The setting of the contract address and updating of the website can be blocked forever.

Attack scenario:

1. A proposal for setting the contract address is created:` ballotName = setContract:priceFeed1` where `contractName = priceFeed1`
2. Users vote to approve the proposal and the `ballotMinimumEndTime` is passed.
3. Someone sends a transaction to the `DAO` contract to try to call the `finalizeBallot` function.
4. A malicious user front-runs the transaction and creates a new proposal with the name `ballotName = setContract:priceFeed1_confirm` where `contractName = priceFeed1_confirm`. Here,` openBallotsByName[ballotName]` is equal to the new `ballotID`.
5. After that, the correct transaction is executed and the `createConfirmationProposal` function is reached where the `_possiblyCreateProposal` function is called. The require statement for `openBallotsByName[ballotName] == 0` will not be passed.

```solidity
// Make sure that a proposal of the same name is not already open for the ballot
require(
     openBallotsByName[ballotName] == 0,
     "Cannot create a proposal similar to a ballot that is still open"
);
```

The `ballotName` here would be equal to `setContract:priceFeed1_confirm` because the name of confirmation proposal is equal to `string.concat(ballot.ballotName, "_confirm")`

```solidity
 else if (ballot.ballotType == BallotType.SET_CONTRACT)
            proposals.createConfirmationProposal(
                string.concat(ballot.ballotName, "_confirm"), // confirmation proposal name
                BallotType.CONFIRM_SET_CONTRACT,
                ballot.address1,
                "",
                ballot.description
            );
```
As a result, the transaction will revert and can not create a confirmation proposal.


The malicious user can do that endlessly and block the creation of confirmation proposals forever. The only requirement is to have enough staked `XSalt` for creating a normal proposal.



## Tools Used
Manual Review

## Recommended Mitigation Steps
Reserve` openBallotsByName[ballotName] == 1`, where `ballotName = string.concat(ballot.ballotName, "_confirm")`, during the creation of the proposal.


## Assessed type

DoS
Upgraded Q -> 3 from #1898 [1710822808795]
Audit: 2024-02-ai-arena-findings
Severity: HIGH

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

 [L-07] User can still earn points without staking NRNs.
Document notes:

If a player does not stake NRNs, their NFT will not accrue Points or NRN rewards (i.e. Staking Factor would be 0 and the product of the equation is 0).

It is possible that a player accrue Points without staking any NRNs. This issue assumes that stake NRNs means the process of staking NRNs only, and not having NRNs that are at risk.

    function _getStakingFactor(
        uint256 tokenId, 
        uint256 stakeAtRisk
    ) 
        private 
        view 
        returns (uint256) 
    {
      uint256 stakingFactor_ = FixedPointMathLib.sqrt(
>         (amountStaked[tokenId] + stakeAtRisk) / 10**18
      );
>     if (stakingFactor_ == 0) {
>       stakingFactor_ = 1;
      }
      return stakingFactor_;
    }    
Since _getStakingFactor() takes stakeAtRisk as well, and rounds up the stakingFactor to 1 should it be zero, users can simply get a fighter with some stakeAtRisk tokens, and withdraw all their current amountStaked tokens.

This way, the user will not need to stake anything but will be able to earn points from their fighter.

Make sure _addResultPoints() is called only if amountStaked[tokenId] > 0, and not amountStaked[tokenId] + stakeAtRisk > 0
Check on transferability can be bypassed with safeBatchTransferFrom
Audit: 2024-02-ai-arena-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
`GameItems` uses the `transferable` parameter to disable the ability to transfer a token, however it is only checked in the overriden implementation of `safeTransferFrom`. The existing `safeBatchTransferFrom` method can still be used to bypass this check.
<https://github.com/code-423n4/2024-02-ai-arena/blob/5b2ab9f9fadd0b91268ff6f22b4ae0fd5b79ec09/src/GameItems.sol#L301-L302>

\## Proof of Concept
Add the following to https://github.com/code-423n4/2024-02-ai-arena/blob/5b2ab9f9fadd0b91268ff6f22b4ae0fd5b79ec09/test/GameItems.t.sol

```solidity
function testBypassTransferability() public {
        _fundUserWith4kNeuronByTreasury(_ownerAddress);
        _gameItemsContract.mint(0, 2);
        _gameItemsContract.adjustTransferability(0, false);
        uint256[] memory ids = new uint256[](1);
        uint256[] memory amounts = new uint256[](1);
        ids[0] = 0;
        amounts[0] = 2;
        _gameItemsContract.safeBatchTransferFrom(_ownerAddress, address(1), ids, amounts, "");
    }
```
\## Tools Used

Manual Review

\## Recommended Mitigation Steps
Override `safeBatchTransferFrom` and check the `transferable` of every id in the batch.


## Assessed type

Access Control
unrestricted fighter NFT transfer
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#L355-L365
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L338-L348


# Vulnerability details

## Vulnerability details:
### Details:

The contract `FighterFarm` manages fighter NFTs, imposing restrictions on their transfer via the `_ableToTransfer` function, which is called within overridden transfer functions. These functions check that the destination address doesn't possess more than 10 NFTs and that the fighter is not staked. However, the function `safeTransferFrom(from, to , tokenId, data)` lacks this override and check implementation, enabling a bypass of these restrictions.

### Impact:

This vulnerability allows a user to hold more than 10 fighters simultaneously and transfer a staked fighter, violating key system invariants.

### Proof of Concept:
- consider that Alice has 1 Fighter with tokenId = 15 which she staked with in rankedBattle contract.
- and Bob has 10 fighters
- Alice calls the function `safeTransferFrom(Alice, Bob , 15, "")`
- the function `_ableToTransfer` is not called, so there is no check and Bob ends with 11 fighters , and a fighter that is currently at stake .

### Tools Used:

vscode

### Recommended Mitigation Steps:

override the function `safeTransferFrom(from, to , tokenId, data)` in the same way you do for the other functions.


## Assessed type

ERC721
`safeTransfer()` with `data` not overridden
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#L533-L535


# Vulnerability details



## Impact
`safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)` is not overridden to check `_ableToTransfer()` in FighterFarm transfers.
Therefore it is possible to hold more than `MAX_FIGHTERS_ALLOWED` fighters and transfer staked fighters.

This may enable the user to initiate more than 10 battles per day for a fighter, and may DoS `updateBattleRecord()`.

## Proof of Concept
FighterFarm inherits from ERC721 which contains three transfer functions. Two of these are overridden to include a `require(_ableToTransfer(tokenId, to));`
```solidity
function _ableToTransfer(uint256 tokenId, address to) private view returns(bool) {
    return (
        _isApprovedOrOwner(msg.sender, tokenId) &&
        balanceOf(to) < MAX_FIGHTERS_ALLOWED &&
        !fighterStaked[tokenId]
    );
}
```
prevents holding more than `MAX_FIGHTERS_ALLOWED` fighters and prevents transfers of staked fighters.
But the public `safeTransferFrom()` which includes the `data` parameter is not overridden and therefore void of these new restrictions.

It is therefore possible to transfer fighters such that the recipient holds more than `MAX_FIGHTERS_ALLOWED` fighters, and to transfer staked fighters.

A consequence of this is that [the new owner has a fresh voltage](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L336) and battles can therefore be initiated without limit.

Since points are added to or deducted from `accumulatedPointsPerFighter` and `accumulatedPointsPerAddress` in parallel at every win or loss, transferring the fighter to a new address, for which then `accumulatedPointsPerAddress` is `0` (or just different) [`_addResultPoints()` will attempt to remove more points then possible](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L486), since the points are [calculated based on the `tokenId` rather than the `fighterOwner`](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L481-L484). This will then cause a DoS of `updateBattleRecord()` due to underflow.

## Recommended Mitigation Steps
Override `_transfer()`, `_beforeTokenTransfer()` or `_afterTokenTransfer()` instead.


## Assessed type

DoS
Frankenstein fighters can be created
Audit: 2024-02-ai-arena-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The system permits the creation or updating of a fighter type (e.g. _Champion_) with the new DNA and attributes of another type (e.g. _Dendroid_). The impact of the issue on the AI Arena web server is currently unknown; however, it appears to deviate from the intended behavior of the sytem.

## Description

This is a summary of the fighters according to the sponsor and its documentation:

_Champion_ features:

- `fighterType`: expected to be `0`.
- `weight`: from `65` to `95`.
- New DNA: is the original DNA.
- Physical attributes: derived from the new DNA.

_Dendroid_ features:

- More rare to than a _Champion_.
- `fighterType`: expected to be `1`.
- `weight`: from `65` to `95`.
- New DNA: expected to be `uint256(1)`.
- Physical attributes: expected to be `99` for all attributes.

The `FighterFarm.reRoll` function doesn't validate the `fighterType` argument against the token attributes, which allows setting _Dendroid_ data (e.g. DNA, new DNA, element, weight, generation) into a _Champion_ and the other way around.

```solidity
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 // @audit-issue `fighterType` should be checked against `dendroidBool`
        );
        _tokenURIs[tokenId] = "";
    }
}
```

The `FighterFarm.redeemMintPass` function allows to set unsupported fighter types (e.g. `2`, `3`) in `fighterTypes`, which have unexpected consequences in the `FighterFarm._createNewFighter` function due to using different fighter type values across the comparisons:

```solidity
uint256 newDna = fighterType == 0 ? dna : uint256(fighterType); // @audit-issue comparison against `0`
```

```solidity
bool dendroidBool = fighterType == 1; // @audit-issue co
GameItems can always be transferred
Audit: 2024-02-ai-arena-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Any game item (an ERC-1155) can be transferred even though the intention of its configuration says otherwise (the `GameItemAttributes.transferable` member is `false`).

## Description

The `GameItems` contract lacks measures in certain transfer-related functions to enforce the non-transferability status of items, allowing transfers even when explicitly disallowed by the game attribute configuration:

- `safeTransferFrom(address,address,uint256,uint256,bytes)` (overriden by `GameItems`): does implement the `GameItemAttributes.transferable` check.
- `safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)` (not overriden by `GameItems`, inherited from `ERC1155`): doesn't implement the `GameItemAttributes.transferable` check.

Therefore, game items can always be transferred by calling the `GameItems.safeBatchTransferFrom` function.

```solidity
/// @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)
{
    // @audit-issue this check is not present in `safeBatchTransferFrom`
    require(allGameItemAttributes[tokenId].transferable);
    super.safeTransferFrom(from, to, tokenId, amount, data);
}
```

## Proof Of Concept

Add the following tests in `GameItems.t.sol`:

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

    assertEq(_gameItemsContract.balanceOf(address(this), 0), 2);
    assertEq(_gameItemsContract.balanceOf(msg.sender, 0), 0);

    // Act - safeTransferFrom checks `transferable`
    vm.expectRevert();
    _gameItemsContract.safeTransferFrom(address(this), msg.sender, 0, 1, "");

    // Act - safeBatchTransferFrom does not check `transferable`
    uint256[] memory tokenIds = new uint256[](1);
    tokenIds[0] = 0;
    uint256[] memory amounts = new uint256[](1);
    amounts[0] = 1;
    _gameItemsContract.safeBatchTransferFrom(address(this), msg.sender, tokenIds, amounts, "");

    // Assert
    assertEq(_gameItemsContract.balanceOf(address(this), 0), 1);
    assertEq(_gameItemsContract.balanceOf(msg.sender, 0), 1);
}
```

## Tools Used

Forge tests and manually reviewed.

## Recommended Mitigation Steps

Override the `GameItems.safeBatchTransferFrom` function making sure each token is transferable.



## Assessed type

Invalid Validation
Exploiting Staking Factor Rounding Up and `curStakeAtRisk` Rounding Down with Minimal NRN Stake and Merging Pool Allocation
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#L519-L534
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L438-L439


# Vulnerability details

## Impact
Players exploit the staking logic to have a chance to win an NFT from the merging pool with zero risk.

## Proof of Concept
1. **Initiating the Exploit**:
   - Players discover that by staking the smallest possible amount of the game's currency (1 wei of NRN), they can engage in the game's battle mechanics with virtually no financial risk. This is made possible due to a safeguard in the `_getStakingFactor` function that rounds up the staking factor to 1 for any negligeable stake, ensuring players receive a minimum reward multiplier.
```javascript
    function _getStakingFactor(
        uint256 tokenId, 
        uint256 stakeAtRisk
    ) 
        private 
        view 
        returns (uint256) 
    {
      uint256 stakingFactor_ = FixedPointMathLib.sqrt(
          (amountStaked[tokenId] + stakeAtRisk) / 10**18
      );
      if (stakingFactor_ == 0) {
        stakingFactor_ = 1;
      }
      return stakingFactor_;
    } 
```
2. **Allocating Points to Avoid Losses**:
   - To circumvent the potential loss of points during defeats, players allocate 100% of their battle points to the merging pool. This strategy ensures that even in the event of a loss, the minimal stake does not significantly diminish, as the game's logic rounds down any deductions from such a small stake to zero.
```javascript
        /// Potential amount of NRNs to put at risk or retrieve from the stake-at-risk contract
        curStakeAtRisk = (bpsLostPerLoss * (amountStaked[tokenId] + stakeAtRisk)) / 10**4;
```

3. **Exploiting Wins for Merging Pool Benefits**:
   - When players win battles, their allocated points contribute to the merging pool instead of increasing their direct score. This indirect accumulation of benefits allows players to participate in the reward system without risking point loss from future battles. Over time, even with minimal initial investment, players can amass enough points in the merging pool to qualify for NFT rewards, exploiting the system's mechanics.

4. **Scaling the Strategy**:
   - The exploit's effectiveness is magnified when applied across multiple fighters controlled by the same player. By replicating this minimal stake and full point allocation strategy, a player can significantly increase their chances of winning NFT rewards across their roster of fighters with minimal aggregate investment.

5. **Systemic Impact**:
   - This strategy, particularly when employed en masse by numerous players, could lead to a substantial imbalance in the game's economic system. The intended risk-reward balance is disrupted, as players find a low-risk loophole that allows for potential rewards, diminishing the value and impact of more significant, engaged player i
Incrementing the generation may lead to a DOS.
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#L470


# Vulnerability details

## Description
Due to the design of the `FigterFarm` contract, the `numElements` for each generation is only set in the constructor. The contract does not provide any functionality to change `numElements` for subsequent generations; it only establishes `numElements` for the initial generation.

``` solidity
constructor(address ownerAddress, address delegatedAddress, address treasuryAddress_)
    ERC721("AI Arena Fighter", "FTR")
{
    _ownerAddress = ownerAddress;
    _delegatedAddress = delegatedAddress;
    treasuryAddress = treasuryAddress_;
    numElements[0] = 3;
} 
```

When a player mints a fighter, the `_createFighterBase` function is executed. During this process, the element attribute of the fighter is calculated based on the fighter's DNA and the `numElements` of the fighter's generation.

``` solidity
function _createFighterBase(
        uint256 dna, 
        uint8 fighterType
    ) 
        private 
        view 
        returns (uint256, uint256, uint256) 
    {
        uint256 element = dna % numElements[generation[fighterType]];
        uint256 weight = dna % 31 + 65;
        uint256 newDna = fighterType == 0 ? dna : uint256(fighterType);
        return (element, weight, newDna);
    }
```

If the admin decides to increment the generation of a fighter type, and `numElements` remains unchanged, the previously mentioned function will fail due to a 'modulo zero' error when attempting to use `numElements[nextGeneration]` in the calculation.

## Impact
* Players being unable to mint new fighters.

## Tools Used
Manual Review
Foundry

## Recommended Mitigation Steps
Create a function to modify the `numElements` in the next generation.





## Assessed type

DoS