xanoutas/solidity-security-findings huggingface dataset:
Invariant maxTokensPerGen might be broken
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L280-L298


# Vulnerability details

## Impact
Invariant maxTokensPerGen might be broken. One generation might have more than maxTokensPerGen NFTs.

## Proof of Concept
When whitelist mints NFT tokens, we will check whether current generation's nft amount equals to `maxTokensPerGen`. If current generation's amount equals to or is larger than `maxTokensPerGen`, we will move to the next generation, and the newly NFT will be added into the next generation.
This logic will be true based on one assumption that `generationMintCounts` for next generation is less than `maxTokensPerGen`. But this assumption is not always true considering that users can forge NFTs in the next generation.
Considering the below scenario:
1. Alice and Bob mint lots of NFTs in generation 0.
2. Alice and Bob list for forging and forge in generation 1. For example, we mint 100 NFT in generation 1.
3. When Cathy as one whitelist wants to mint the 10001th NFT in generation 0, we will increase generation and reset the new generation's `generationMintCounts`.

The actual mint count in generation 1 might be larger than `maxTokensPerGen`.
```javascript
  function _mintInternal(address to, uint256 mintPrice) internal {
    if (generationMintCounts[currentGeneration] >= maxTokensPerGen) {
      _incrementGeneration();
    }
    ...
@=> generationMintCounts[currentGeneration]++;
    ...
   }
```
```javascript
  function _incrementGeneration() private {
    require(
      generationMintCounts[currentGeneration] >= maxTokensPerGen,
      'Generation limit not yet reached'
    );
    currentGeneration++;
@=> generationMintCounts[currentGeneration] = 0;
    // Price increment will increase per generation.
    priceIncrement = priceIncrement + priceIncrementByGen;
    entropyGenerator.initializeAlphaIndices();
    emit GenerationIncremented(currentGeneration);
  }
```
## Tools Used
Manual

## Recommended Mitigation Steps
When we increase the generation, don't reset `generationMintCounts` because there might be some valid NFTs in the newly generation.


## Assessed type

Other
Invariant maxTokensPerGen might be broken
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L280-L309


# Vulnerability details

## Impact
There is one invariant `maxTokensPerGen`. Each generation can have maximum `maxTokensPerGen`. But this invariant can be broken.

## Proof of Concept
There're two vulnerabilities that can cause the broken invariant `maxTokensPerGen`. I have already submitted the first vulnerability via 
https://code4rena.com/audits/2024-07-traitforge/submit?issue=I_kwDOMbZW6c6RXn-Y.

The following description is based on we have fixed the first problem, don't reset the `generationMintCounts` in _incrementGeneration().

When whitelist wants to mint NFT and current generation's mint count reaches the cap, we will increase the generation and take the newly NFT belong to the next generation. The problem is that it's possible that the next generation's mint count has already reaches cap. If we add this newly NFT in the next generation, the next generation's mint count will exceed the `maxTokensPerGen`.
Considering the below scenario:
- Alice, Bob and some other whitelist mint NFT tokens in generation 0.
- Alice and Bob try to list lots of forger NFT and forge some new NFT. These generated/forged NFT will belong to generation 1. Considering that some forger NFT has a very high forger potential(9, maximum), it's possible that forged NFT's amount will reach the mint cap `maxTokensPerGen` in generation 1.
- Now Alice wants to mint one NFT in generation 0 and generation 0's mint account reaches mint cap `maxTokensPerGen`. And the newly NFT will be added in generation 1. However, the generation 1's mint amount has already reaches the cap. This will cause the generation 1's mint amount will break the invariant `maxTokensPerGen`.

```javascript
  function _mintInternal(address to, uint256 mintPrice) internal {
    if (generationMintCounts[currentGeneration] >= maxTokensPerGen) {
      _incrementGeneration();
    }

    _tokenIds++;
    uint256 newItemId = _tokenIds;
    _mint(to, newItemId);
    uint256 entropyValue = entropyGenerator.getNextEntropy();
    ...
}
```
```javascript
  function _incrementGeneration() private {
    require(
      generationMintCounts[currentGeneration] >= maxTokensPerGen,
      'Generation limit not yet reached'
    );
    currentGeneration++;
    generationMintCounts[currentGeneration] = 0;
    // Price increment will increase per generation.
    priceIncrement = priceIncrement + priceIncrementByGen;
    entropyGenerator.initializeAlphaIndices();
    emit GenerationIncremented(currentGeneration);
  }
```
```javascript
  function forge(
    address newOwner,
    uint256 parent1Id,
    uint256 parent2Id,
    string memory
  ) external whenNotPaused nonReentrant returns (uint256) {
    require(
      msg.sender == address(entityForgingContract),
      'unauthorized caller'
    );
    uint256 newGeneration = getTokenGeneration(parent1Id) + 1;
    ...

``
Minting tokens will be impossible when `generationMintCounts` cap is reached for the first generation
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L353


# Vulnerability details

## Impact
Each generation has a predefined cap on the number of tokens that can be minted. Once this cap is reached, the [_incrementGeneration](https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L345-L355) function is triggered to advance the game to the next generation:
```solidity
function _incrementGeneration() private {
    require(
      generationMintCounts[currentGeneration] >= maxTokensPerGen,
      'Generation limit not yet reached'
    );
    currentGeneration++;
    generationMintCounts[currentGeneration] = 0;
    priceIncrement = priceIncrement + priceIncrementByGen;
    entropyGenerator.initializeAlphaIndices(); // @audit call to restricted function on `EntropyGenerator` contract
    emit GenerationIncremented(currentGeneration);
}
```
The issue is that the function calls `EntropyGenerator::initializeAlphaIndices` which is a [restricted function](https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/EntropyGenerator/EntropyGenerator.sol#L206C58-L206C67) limited to the conract owner:
```solidity
// EntropyGenerator

function initializeAlphaIndices() public whenNotPaused onlyOwner {//...);
```
The intended owner for the `EntropyGenerator` is NOT the `TraitForgeNft` contract, but a multisig contract held by the team as confirmed by the sponsor. Even if the team transfers the ownership of `EntropyGenerator` to `TraitForgeNft` to prevent the DoS, **there is no way to return back the ownership to the multisig account**. Therefore a damage **will always occure** regardless of the team's decision:
1. If the team transfers the ownership of `EntropyGenerator` to `TraitForgeNft` to enable further minting and prevent DoS, there is no way to return the ownership to the multisig account.
2. If the `EntropyGenerator`'s owner is intended to always be the multisig held by the team, a DoS will occur once the maximum tokens per generation are reached for the first generation, as the current generation cannot be increased anymore.
## Tools Used
Manual Review

## Recommended Mitigation Stepseeeeeee
Since `EntropyGenerator::initializeAlphaIndices` is called either at deployment or by the `TraitForgeNft` contract to increase the generation, consider restricting the function to `onlyAllowedCaller` instead of `onlyOwner`. Below is a suggested code update:
```diff
// EntropyGenerator contract


constructor(address _traitForgetNft) {
  allowedCaller = _traitForgetNft;
-  initializeAlphaIndices();
+  _initializeAlphaIndices();
}

+function initializeAlphaIndices() public whenNotPaused onlyAllowedCaller {
+ _initializeAlphaIndices();
+}

-function initializeAlphaIndices() public whenNotPaused onlyOwner {
+function _initializeAlphaIndices() inter
TraitForgeNft.sol :: mintToken() allows the maxGeneration limit to be bypassed, enabling to the users to mint an unlimited number of generations.
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L181-L200
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L202-L225
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L280-L283


# Vulnerability details

## Impact
**`mintToken()`** and **`mintWithBudget()`** are used to mint NFTs, requiring a proof if within a whitelist window. After this period, anyone can mint an NFT using these functions. The issue is that **`maxGeneration`** is not checked, allowing the possibility of bypassing limits and incrementing generations indefinitely.

## Proof of Concept
In this example, I used **`mintToken()`**, but the same issue applies to **`mintWithBudget()`**.
```Solidity
function mintToken(
    bytes32[] calldata proof
  )
    public
    payable
    whenNotPaused
    nonReentrant
    onlyWhitelisted(proof, keccak256(abi.encodePacked(msg.sender)))
  {
    uint256 mintPrice = calculateMintPrice();
    require(msg.value >= mintPrice, 'Insufficient ETH send for minting.');

@>  _mintInternal(msg.sender, mintPrice);

    uint256 excessPayment = msg.value - mintPrice;
    if (excessPayment > 0) {
      (bool refundSuccess, ) = msg.sender.call{ value: excessPayment }('');
      require(refundSuccess, 'Refund of excess payment failed.');
    }
  }
```
As you can see, it calls **`_mintInternal()`** to mint the NFT.
```Solidity
function _mintInternal(address to, uint256 mintPrice) internal {
    if (generationMintCounts[currentGeneration] >= maxTokensPerGen) {
      _incrementGeneration();
    }

    _tokenIds++;
    uint256 newItemId = _tokenIds;
    _mint(to, newItemId);
    uint256 entropyValue = entropyGenerator.getNextEntropy();

    tokenCreationTimestamps[newItemId] = block.timestamp;
    tokenEntropy[newItemId] = entropyValue;
    tokenGenerations[newItemId] = currentGeneration;
    generationMintCounts[currentGeneration]++;
    initialOwners[newItemId] = to;

    if (!airdropContract.airdropStarted()) {
      airdropContract.addUserAmount(to, entropyValue);
    }

    emit Minted(
      msg.sender,
      newItemId,
      currentGeneration,
      entropyValue,
      mintPrice
    );

    _distributeFunds(mintPrice);
  }
```
As you can see, **`maxGeneration`**, which is meant to limit the number of generations that can be minted, is never checked. This oversight allows users to mint an unlimited number of generations.

The following POC demonstrates the issue. Copy the provided code into **`TraitForgeNft.test.ts`**. 

To run the test modify **`TraitForgeNft.sol`** setting **`currentGeneration`** to 10 to quickly reach the **`maxGeneration`** limit. Also, set **`maxTokensPerGen`** to 5 to avoid iterating 10,000 times. This is done to simplify testing.

Additionally, update **`EntropyGene
`_incrementGeneration` doesn't account for nfts created from forge
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L345


# Vulnerability details

 ## Impact
it is mentioned in the docs `There are a total of 10 Generations (extended as the game goes on), which can be minted in parallel via a breeding mechanism.` however when the function `_incrementGeneration` is called in mint function after the current generation has reached it's limit, the `generationMintCounts` for the next generation is set to 0 without accounting for nfts that were minted by forging.
```javascript
function _incrementGeneration() private {
    require(generationMintCounts[currentGeneration] >= maxTokensPerGen, "Generation limit not yet reached");
    currentGeneration++;
    generationMintCounts[currentGeneration] = 0;
    priceIncrement = priceIncrement + priceIncrementByGen;
    entropyGenerator.initializeAlphaIndices();
    emit GenerationIncremented(currentGeneration);
}
```
this means the next generation might have more nfts than what `generationMintCounts[currentGeneration]` shows and hence could lead to more tokens in a generation than the intended maximum`.
Users aware of this issue could strategically forge tokens of the next generation before the current generation ends, essentially getting "uncounted" tokens.
## Proof of Concept
In the following test We assume `MaxTokensPerGen` is 100 for the sipmlicity of the test, we show that Current generation is 1, with 98 tokens minted (out of 100 max for this example)
2 tokens of generation 2 have been created through forging
The last 100 tokens of `generation 1` are minted, and a new token from `generation 2` is also minted triggering `_incrementGeneration`, the `generationMintCounts` is 1 and didn't account for the 2 tokens created through forging.
```javascript
function testMintAndGenerationIncrementWithForging() public {
        bytes32[] memory proof = new bytes32[](0); // Adjust based on your Merkle proof setup
        vm.startPrank(owner);
        traitForgeNft.setWhitelistEndTime(block.timestamp);
        traitForgeNft.setMaxGeneration(100);

        vm.stopPrank();
        vm.warp(block.timestamp + 2 hours);
        vm.startPrank(user);
        vm.deal(user, 100 ether);

        uint256 initialGeneration = traitForgeNft.getGeneration();
        console.log("initialGeneration",initialGeneration);

        // Mint 9,900 tokens for generation 1
        for (uint256 i = 0; i < 98; i++) {
            traitForgeNft.mintToken{value: 1 ether}(proof);
        }
        vm.stopPrank();
        // Forge some tokens for generation 2
        vm.startPrank(address(entityForging));
        traitForgeNft.forge(address(this), 1, 2, "tokenURI");
        traitForgeNft.forge(address(this), 3, 4, "tokenURI");
        vm.stopPrank();
        // // Ensure some tokens are forged for generation 2
        uint256 forgedTokenGen1 = traitForgeNft.getTokenGeneration(99);
        uint256 forgedTokenGen2 =
TraitForgeNft contract cannot increment generations due to the wrong modifier in EntropyGenerator
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/EntropyGenerator/EntropyGenerator.sol#L206-L216
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L353


# Vulnerability details

## Impact
`EntropyGenerator.sol` contract sets the allowed caller variable at the time of deployment. This is supposed to be `TraitForgeNft.sol` contract so that different functions can be called from it. However, the function `initializeAlphaIndices()` has `onlyOwner` modifier from Ownable, unlike other functions which have `onlyAllowedCaller` modifier. This will cause many functions in `TraitForgeNft.sol` to revert when the function `_incrementGeneration()` is called (such as `mintToken()`, `mintWithBudget()` or `forge()`) causing a denial of service. The `_incrementGeneration()` will get called always when there is a need to switch from one generation to another, i.e. when the maximum number of tokens per generation is minted. This means, that users will be able to mint only the first generation. 

It is confirmed by the sponsor that the Multisig wallet is supposed to be the owner of all contracts, except from the `Airdrop.sol` contract (this one is supposed to be owned by `TraitForgeNft.sol` contract). 

## Proof of Concept
For the sake of simplicity add this function to the `TraitForgeNft.sol` contract
``` solidity 
 function _testCall() public {
    entropyGenerator.initializeAlphaIndices();
  }
```
And add this test to the TraitForgeNft.test.ts
``` solidity 
it('should not be possible to initializeAlphaIndices() if not owner', async() => {
      await expect(nft._testCall()).to.be.reverted;
    });
```

## Tools Used
VS Code, Hardhat, Manual Review
## Recommended Mitigation Steps
Use `onlyAllowedCaller` modifier instead of `onlyOwner` on `initializeAlphaIndices()` so that `TraitForgeNft.sol` is allowed to call it.











## Assessed type

DoS
Incorrect Usage of `taxCut` for Fee Calculation
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/NukeFund/NukeFund.sol#L41
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/EntityTrading/EntityTrading.sol#L72
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/EntityForging/EntityForging.sol#L146


# Vulnerability details

## Impact
The `taxCut` parameter is used in [EntityForging](https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/EntityForging/EntityForging.sol#L146), [EntityTrading](https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/EntityTrading/EntityTrading.sol#L72), and [NukeFund](https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/NukeFund/NukeFund.sol#L41) to calculate fees distributed to various ecosystem actors, such as developer fees and NukeFund fees. For example, in `EntityForging`:
```solidity
// EntityForgin::forgeWithListed

uint256 devFee = forgingFee / taxCut;
```
The issue is that the fees are incorrectly calculated by dividing the amount by `taxCut`. This calculation is accurate when `taxCut` is set to `10` (representing 10%), but the `taxCut` can change frequently through the `setTaxCut` function. If the `taxCut` is set to a value different from `10`, the fees will be incorrectly calculated. For instance, if the taxCut is set to `2` (representing 2%), the `devFee` above will actually be `50%` instead of `2%`.

## Tools Used
Manual Review
## Recommended Mitigation Steps
Consider updating the fee calculation formula as follows:
```solidity
uint256 devFee = (forgingFee * taxCut) / 100; // EntityForgin::forgeWithListed
uint256 devShare = (msg.value * taxCut) / 100; // NukeFund::receive()
uint256 nukeFundContribution = (msg.value * taxCut) / 100; // EntityTrading::buyNFT
```


## Assessed type

Math
TraitForgeNft.sol :: mintWithBudget() can only mint the first generation being useless for the next ones.
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L202-L225
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L22


# Vulnerability details

## Impact
**`mintWithBudget()`** is used to mint multiple NFTs in a single transaction. However, the function checks **`_tokenIds`** instead of **`generationMintCounts[currentGeneration]`**, which restricts minting to the first generation only, rendering the function useless for subsequent generations.

## Proof of Concept
**`mintWithBudget()`** is implemented as follows.
```Solidity
function mintWithBudget(
    bytes32[] calldata proof
  )
    public
    payable
    whenNotPaused
    nonReentrant
    onlyWhitelisted(proof, keccak256(abi.encodePacked(msg.sender)))
  {
    uint256 mintPrice = calculateMintPrice();
    uint256 amountMinted = 0;
    uint256 budgetLeft = msg.value;

@>  while (budgetLeft >= mintPrice && _tokenIds < maxTokensPerGen) {
      _mintInternal(msg.sender, mintPrice);
      amountMinted++;
      budgetLeft -= mintPrice;
      mintPrice = calculateMintPrice();
    }
    if (budgetLeft > 0) {
      (bool refundSuccess, ) = msg.sender.call{ value: budgetLeft }('');
      require(refundSuccess, 'Refund failed.');
    }
  }
```
The issue arises from the while loop in the function, which has two conditions:
 
The remaining ether is sufficient to pay for the NFT, and **`_tokenIds < maxTokensPerGen`**.
 
The second condition is the problem because **`_tokenIds`** is a common variable across all generations. 
Once **`_tokenIds`** reaches 10,000 (first generation), it prevents the loop from executing further since **`maxTokensPerGen`** is hardcoded to 10,000, the maximum number of tokens per generation. This flaw renders the function unusable beyond the first generation.

To demonstrate the problem, copy the following POC into **`TraitForgeNft.test.ts`**. To avoid an "out of gas" exception, modify **`maxTokensPerGen`** to 10 in **`TraitForgeNft.sol`**.
```Solidity
it('mintWithBudget can only mint the first generation', async () => {
      fastForward(24 * 60 * 60 + 1);

      //mint the first generation
      for (let i = 0; i < 10; i++) {
        
        const tx = await nft.connect(user2).mintWithBudget(merkleInfo.whitelist[1].proof, {
          value: ethers.parseEther('1'),
        });
        await tx.wait(); 
      }
      
      //user has 10 NFT 
      expect(await nft.balanceOf(user2.address)).to.be.eq(10);

      //user wants to mint the first NFT of the second generation, but it cannot be done.
      await nft.connect(user2).mintWithBudget(merkleInfo.whitelist[1].proof, {
        value: ethers.parseEther('1'),
      });

      //user cant mint the first NFT of the second generation
      expect(await nft.balanceOf(user2.address)).to.be.eq(10);
    });
```
## Tools Used
Manual review.
## Recommende