xanoutas/solidity-security-findings huggingface dataset:
TraitForgeNFT Contract Fails Due to Access Control Issue in EntropyGenerator Contract
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The EntropyGenerator contract's initializeAlphaIndices function is restricted by the onlyOwner modifier, which means only the contract owner can call this function. When the TraitForgeNFT contract attempts to call initializeAlphaIndices via _mintInternal, it fails with the error 'Ownable: caller is not the owner'. This issue prevents the mintToken function from completing successfully when the maximum number of tokens per generation has been reached.

Since the TraitForgeNFT contract is not set as the owner of the EntropyGenerator contract, it cannot trigger the initializeAlphaIndices function, causing a critical failure in the minting process. This affects the functionality of the NFT contract and may lead to an inability to mint tokens as expected.

## Proof of Concept
This is the test code of the error.

    describe('test', () => {
      it('test calculating mint price', async () => {
        const maxTokensPerGen = await nft.maxTokensPerGen();
        for(let i = 0; i < maxTokensPerGen; i++) {
          await nft.connect(owner).mintToken(merkleInfo.whitelist[0].proof, {
            value: ethers.parseEther('1'),
          });
        }

        await expect(
          nft.connect(user1).mintToken(merkleInfo.whitelist[1].proof, {
            value: ethers.parseEther('1'),
          })
        ).to.be.revertedWith('Ownable: caller is not the owner');
      });
    });

## Tools Used
Manual code review

## Recommended Mitigation Steps
Consider updating the EntropyGenerator contract’s ownership to include the TraitForgeNFT contract.

    constructor(address _traitForgetNft) {
      allowedCaller = _traitForgetNft;
      initializeAlphaIndices();

      transferOwnership(_traitForgetNft);
    }


## Assessed type

Access Control
Incorrect Access Control Prevents Generation Increment
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/EntropyGenerator/EntropyGenerator.sol#L206


# Vulnerability details

## Impact

The generation incrementing process is not functioning as expected due to incorrect access control in the `EntropyGenerator.initializeAlphaIndices()` function. Specifically, the `onlyOwner` modifier restricts access, causing the call to revert when `msg.sender` is the `TraitForgeNft` contract.

As a result, NFTs cannot be minted beyond the first generation, capping the total number of NFTs at 10,000. Additionally, any operation (e.g., forging) that triggers the `_incrementGeneration()` function will fail, impeding the contract's intended functionality and limiting the platform's capability to evolve NFTs over multiple generations.

## Proof of Concept

The following test demonstrates the failure when attempting to mint NFTs beyond the first generation. The error occurs because the `initializeAlphaIndices()` function reverts due to incorrect access control:

```javascript
import {
  Airdrop,
  DevFund,
  EntityForging,
  EntropyGenerator,
  EntityTrading,
  NukeFund,
  TraitForgeNft,
} from '../typechain-types';
import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers';
import generateMerkleTree from '../scripts/genMerkleTreeLib';
import { fastForward } from '../utils/evm';
import { entropyGenerator } from '../typechain-types/contracts';

const { expect } = require('chai');
const { ethers } = require('hardhat');

describe('TraitForgeNFT', () => {
  let entityForging: EntityForging;
  let nft: TraitForgeNft;
  let owner: HardhatEthersSigner;
  let user1: HardhatEthersSigner;
  let user2: HardhatEthersSigner;
  let entityTrading: EntityTrading;
  let nukeFund: NukeFund;
  let devFund: DevFund;
  let merkleInfo: any;
  let entropyGenerator: EntropyGenerator;

  before(async () => {
    [owner, user1, user2] = await ethers.getSigners();

    // Deploy TraitForgeNft contract
    const TraitForgeNft = await ethers.getContractFactory('TraitForgeNft');
    nft = (await TraitForgeNft.deploy()) as TraitForgeNft;

    // Deploy Airdrop contract
    const airdropFactory = await ethers.getContractFactory('Airdrop');
    const airdrop = (await airdropFactory.deploy()) as Airdrop;

    await nft.setAirdropContract(await airdrop.getAddress());

    await airdrop.transferOwnership(await nft.getAddress());

    // Deploy EntropyGenerator contract
    const EntropyGenerator = await ethers.getContractFactory(
      'EntropyGenerator'
    );
    entropyGenerator = (await EntropyGenerator.deploy(
      await nft.getAddress()
    )) as EntropyGenerator;

    await entropyGenerator.writeEntropyBatch1();

    await nft.setEntropyGenerator(await entropyGenerator.getAddress());

    // Deploy EntityForging contract
    const EntityForging = await ethers.getContractFactory('EntityForging');
    entityForging = (await EntityForging.deploy(
      await nft.getAddress()
    )) as EntityForging;
`mintToken` and `mintWithBudget` allow to mint beyond `maxGeneration`
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description
There is a bug when `minting NFT` which allow to mint beyond the `maxGeneration` limit which is unexpected and seems to warrant `High` severity.

The root cause is due to the fact that `_incrementGeneration` is lacking check against `maxGeneration` which allow to mint to the moon.

## Impact
Player is able to mint beyond `maxGeneration` which break a major invariant into the game.

## PoC
Add the following changes `TraitForgeNft.test.ts` and run `yarn test`

```diff
  before(async () => {
    [owner, user1, user2] = await ethers.getSigners();

    // Deploy TraitForgeNft contract
    const TraitForgeNft = await ethers.getContractFactory('TraitForgeNft');
    nft = (await TraitForgeNft.deploy()) as TraitForgeNft;

    // Deploy Airdrop contract
    const airdropFactory = await ethers.getContractFactory('Airdrop');
    const airdrop = (await airdropFactory.deploy()) as Airdrop;

    await nft.setAirdropContract(await airdrop.getAddress());

    await airdrop.transferOwnership(await nft.getAddress());

    // Deploy EntityForging contract
    const EntropyGenerator = await ethers.getContractFactory(
      'EntropyGenerator'
    );
    const entropyGenerator = (await EntropyGenerator.deploy(
      await nft.getAddress()
    )) as EntropyGenerator;

    await entropyGenerator.writeEntropyBatch1();

    await nft.setEntropyGenerator(await entropyGenerator.getAddress());
+   await entropyGenerator.transferOwnership(await nft.getAddress()); // This is to fix another problem that I have reported which otherwise prevent doing this test.
	
	...
```	

Modify `TraitForgeNft::maxTokensPerGen` to let say 5 and you will still see the problem as this test mint `maxGeneration + 2`.

```solidity
    it.only('mint beyond maxGeneration', async () => {
      // 1)  Allow anyone to mint
      fastForward(24 * 60 * 60 + 1);

      // 2) Pull the data from the contract
      const maxTokensPerGen = await nft.maxTokensPerGen();
      const startPrice = await nft.startPrice();
      const priceIncrement = await nft.priceIncrement();
      const priceIncrementByGen = await nft.priceIncrementByGen();
      const maxGeneration = await nft.maxGeneration();

      console.log(`maxGeneration:        ${maxGeneration}`);
      console.log(`maxTokensPerGen:      ${maxTokensPerGen}`);
      console.log(`startPrice:           ${startPrice}`);
      console.log(`priceIncrement:       ${priceIncrement}`);
      console.log(`priceIncrementByGen:  ${priceIncrementByGen}`);

      // 3) Ensure no NFT has been minted yet
      expect(await nft.currentGeneration()).to.be.eq(1); 
      expect(await nft.generationMintCounts(1)).to.be.eq(0); 

      // 4) Try to Mint max gen + 2
      let countToMint = maxTokensPerGen * (maxGeneration + BigInt(2)); 
      let previousMintPrice = startPrice;
      for (let i = 0; i < countToMin
Incorrect Token ID Check in `mintWithBudget` Function Limits NFT Minting Beyond 10,000
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact 
In the `mintWithBudget` function of the `TraitForgeNft.sol` contract, one of the minting conditions is `_tokenIds < maxTokensPerGen`. The issue is that the `_tokenIds` state variable continually increases with calls to `_mintInternal` and `_mintNewEntity`. According to the whitepaper, ["The game is capped at 10 Generations for now. Generations can only forge with each other and up to a max of 10000 for each generation."](https://docs.google.com/document/d/1pihtkKyyxobFWdaNU4YfAy56Q7WIMbFJjSHUAfRm6BA/edit). The `_tokenIds` can continuously increase up to 10 * 10000, while `maxTokensPerGen` is only 10000. This discrepancy will prevent minting of NFTs greater than 10000 using the `mintWithBudget` function. It is recommended to change `_tokenIds < maxTokensPerGen` to `generationMintCounts[currentGeneration] < maxTokensPerGen`, or remove the check, as `mintWithBudget` calls the `_mintInternal` function, which handles the `generationMintCounts[currentGeneration] >= maxTokensPerGen` condition.

## Proof of Concept
In the `mintWithBudget` function of the `TraitForgeNft.sol` contract, one of the minting conditions is `_tokenIds < maxTokensPerGen`. 
[contracts/TraitForgeNft/TraitForgeNft.sol:mintWithBudget_L215](https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L215C1-L215C69)
```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;

@audit=>    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 problem is that the `_tokenIds` state variable keeps increasing with each call to `_mintInternal` and `_mintNewEntity`, without any limit on the number of `_tokenIds`. And `mintWithBudget` calls the `_mintInternal` function, which handles the `generationMintCounts[currentGeneration] >= maxTokensPerGen` condition.
[contracts/TraitForgeNft/TraitForgeNft.sol:_mintInternal_L281-285](https://github.com/code-423n4/2024-07-traitforge/blob/279b2887
`TraitForgeNft` is only able to `mint generation 1` due to wrong access control
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description
There is a bug in `TraitForgeNft` which break `_incrementGeneration` and will make it always revert when trying to mint generation 2 as missing proper access control which seems to warrant `High` severity.

The root cause is due to the fact that `_incrementGeneration` is calling `EntropyGenerator::initializeAlphaIndices` which is assuming that TraitForgeNft is it's owner, but it is not. The problem is actually the onlyOwner modifier, `onlyAllowedCaller` should also be able to access this function, but in the end it require both (because of the call in the contructor).


```solidity
  function _incrementGeneration() private {
    require(
      generationMintCounts[currentGeneration] >= maxTokensPerGen,
      'Generation limit not yet reached'
    );
    currentGeneration++;
    generationMintCounts[currentGeneration] = 0;
    priceIncrement = priceIncrement + priceIncrementByGen;
    entropyGenerator.initializeAlphaIndices();  // <-----------------------------
    emit GenerationIncremented(currentGeneration);
  }
```  

```solidity
  function initializeAlphaIndices() public whenNotPaused onlyOwner { // <----------------------------- onlyOwner modifier make the call revert when trying to increment to gen2
    uint256 hashValue = uint256(
      keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))
    );

    uint256 slotIndexSelection = (hashValue % 258) + 512;
    uint256 numberIndexSelection = hashValue % 13;

    slotIndexSelectionPoint = slotIndexSelection;
    numberIndexSelectionPoint = numberIndexSelection;
  }
```  

## Impact
Game will be deployed/started without any problem. The problem will arise when players try to mint generation 2, `the minting or forging will always revert` at that point, which will cause an headache for the dev team as they would need to migrate the actual game to a new contract, which will be very annoying. 

The High severity here is justified by the fact that this would be only discovered after day(s) (until gen1 is fully minted), and players will have money locked already in the contract, and since this contract is not upgradable, it will be a painfull endeavor. Imagine having everything pump-up for the game launch, investing in audits, marketing, etc, but a core element of the game stop working once generation 1 is all minted (due to the competition, that might go actually fast, it would still be a major issue).

## PoC
Add the following changes `TraitForgeNft.test.ts` and run `yarn test`
Modify `TraitForgeNft::maxTokensPerGen` to let say 5 to see the problem faster.

```solidity
    it.only('mint multiple generations', async () => {
      // 1)  Allow anyone to mint
      fastForward(24 * 60 * 60 + 1);

      // 2) P
More than `maxTokensPerGen` NFTs can be minted
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L351


# Vulnerability details

## Impact
The system has a max NFT per generation count - `maxTokensPerGen` set to 10k initially. However due to an error more NFTs than this max can be minted, breaking a core invariant.

## Proof of Concept
When [_mintNewEntity](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L311) or [_mintInternal](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L280) are called they track how many NFTs are minted, and if they are equal to `maxTokensPerGen` they trigger [_incrementGeneration](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L331-L335) to increase the gen and start the process all over, but in the next gen.

```solidity
   if (generationMintCounts[gen] >= maxTokensPerGen && gen == currentGeneration) {
      _incrementGeneration();
   }
```

However [_incrementGeneration](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L345-L355) sets `generationMintCounts` of the next generation to 0.

```solidity
   function _incrementGeneration() private {
      require(generationMintCounts[currentGeneration] >= maxTokensPerGen, "Generation limit not yet reached");
      currentGeneration++;
      generationMintCounts[currentGeneration] = 0;
```

This would not have been a problem if the only NFTs that can be minted are from `currentGeneration`. However [_mintNewEntity](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L311), triggered by [forge](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L153), has the ability to forge 2 NFTs together and mint the generation after them. In a way you put 2 NFTs of gen 3 and you get 1 of gen 4.

The above shows how users can mint NFTs using [forge](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L153) for `currentGeneration + 1` (or even above), but when the `currentGeneration` reaches it's limit and gets upgraded to the next gen, all of these NFTs from that new gen will be removed from `generationMintCounts`, allowing another 10k NFTs to be minted. 

## Tools Used
Manual review

## Recommended Mitigation Steps
Don't update `generationMintCounts` inside [_incrementGeneration](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L351).

```diff
    function _incrementGeneration() private {
        require(generationMintCounts[currentGeneration] >= maxTokensPerGen, "Generation limit not yet reached");
        currentGeneration++;
-       generationMintCounts[currentGeneration] = 0;
        priceIncrement = priceIncrement + priceIncrementByGen;
        entropyGenerator
`mintWithBudget` will not work from gen 2 and onwards
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L215


# Vulnerability details

## Impact
[mintWithBudget](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L202), a function that allows users to mint 2 or more NFTs at a time, will not work past the first generation.

## Proof of Concept
[mintWithBudget](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L202) is the same as [mintToken](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L181), with one caveat - it can mint 2 or more NFTs in 1 TX. 

```solidity
   while (budgetLeft >= mintPrice && _tokenIds < maxTokensPerGen) {
      _mintInternal(msg.sender, mintPrice);
      amountMinted++;
      budgetLeft -= mintPrice;
      mintPrice = calculateMintPrice();
   }
```

However there is an issue inside it's while loop. The while will mint only if `_tokenIds < maxTokensPerGen`, restricting `_tokenIds` to 10k. However `_tokenIds` is one variable for all generations, meaning that first gen will mint from 0 to 10k, 2nd gen from 10k to 20k, and so on.

This restriction will make this function usable only for the first generation and once the second gen starts `_tokenIds >= maxTokensPerGen` and the while will not trigger.

## Tools Used
Manual review

## Recommended Mitigation Steps
You can remove this part from the while `_tokenIds < maxTokensPerGen`, as [_mintInternal](https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L281-L283) will just move to the next gen when that one has minted all of it's tokens. 

```diff
-  while (budgetLeft >= mintPrice && _tokenIds < maxTokensPerGen) {
+  while (budgetLeft >= mintPrice) {
      _mintInternal(msg.sender, mintPrice);
      amountMinted++;
      budgetLeft -= mintPrice;
      mintPrice = calculateMintPrice();
   }
```





## Assessed type

Error
[H-2] Minting will halt after 1st Gen due to DoS because of incorrect access modifier in `EntropyGenerator::initializeAlphaIndices` function
Audit: 2024-07-traitforge-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/EntropyGenerator/EntropyGenerator.sol#L206-L217
https://github.com/code-423n4/2024-07-traitforge/blob/main/contracts/TraitForgeNft/TraitForgeNft.sol#L345-L355


# Vulnerability details

## Impact
Minting will halt after 10000 NFTs of Gen-1 are minted, the next generation can't be minted any further due to `onlyOwner` access modifier.

## Proof of Concept
The `owner` of the `EntropyGenerator` contract is the team's multisig. `EntropyGenerator::initializeAlphaIndices` would be called in the constructor, as it will be an internal call and `msg.sender` is the deployer, i.e. the owner.

```javascript
  function initializeAlphaIndices() public whenNotPaused onlyOwner {
```

However, the `TraitForgeNft::_incrementGeneration` function calls `EntropyGenerator::initializeAlphaIndices`. This is an external call, and `msg.sender` would be the `TraitForgeNft` contract.
```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);
  }
```

## Tools Used
Manual Review

## Recommended Mitigation Steps

Simply changing `onlyOwner` to `onlyAllowedCaller` modifier would cause the constructor to fail.

It'll be better to create a new modifier:

```diff
+   modifier onlyOwnerOrAllowedCaller() {
+       require(msg.sender == allowedCaller || msg.sender == owner(), 'Caller is not owner or allowed');
+      _;
+   }
```


## Assessed type

Access Control