code-423n4 / 2024-07-traitforge-validation

1 stars 0 forks source link

Potential Uninitialized `entropySlots` Reading in `getNextEntropy`, Causing 0 Entropy Mint #945

Closed c4-bot-9 closed 2 months ago

c4-bot-9 commented 2 months ago

Lines of code

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

Vulnerability details

Title

Potential Uninitialized entropySlots Reading in getNextEntropy

Impact

The getNextEntropy function can be called at any time without waiting for the write entropy batches process to finish. This could lead to the function returning an uninitialized entropy value of 000000, resulting in users losing funds to mint useless tokens and not being eligible for future airdrops as they get 0 shares. This vulnerability can severely impact the users' trust and the protocol's functionality.

Proof of Concept


101:  function getNextEntropy() public onlyAllowedCaller returns (uint256) { 
102:    require(currentSlotIndex <= maxSlotIndex, 'Max slot index reached.');
103:@>     uint256 entropy = getEntropy(currentSlotIndex, currentNumberIndex); 
104:
    ...
120:  }

POC

Apply following POC via git apply POC.patch and run yarn test. The test confirms getNextEntropy did return entropy 0 instead of revert.

diff --git a/test/EntropyGenerator.test.ts b/test/EntropyGenerator.test.ts
index 69551f5..f7e8698 100644
--- a/test/EntropyGenerator.test.ts
+++ b/test/EntropyGenerator.test.ts
@@ -3,7 +3,7 @@ import { ethers } from 'hardhat';
 import { EntropyGenerator } from '../typechain-types';
 import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers';

-describe('EntropyGenerator', function () {
+describe.only('EntropyGenerator', function () {
   let entropyGenerator: EntropyGenerator;
   let owner: HardhatEthersSigner;
   let allowedCaller: HardhatEthersSigner;
@@ -32,6 +32,15 @@ describe('EntropyGenerator', function () {
     expect(updatedCaller).to.equal(allowedCaller);
   });

+  it('getNextEntropy returns 0 entropy if entropySlots not properly init', async function () {
+    // call getNextEntropy when write entropy batches not finished
+    await expect(
+      entropyGenerator.connect(allowedCaller).getNextEntropy()
+    ).to.emit(entropyGenerator, 'EntropyRetrieved').withArgs(0);
+
+    // @audit: should revert getNextEntropy if entropySlots not properly init
+  });
+
   it('should write entropy batches 1', async function () {
     // Write entropy batch 1
     await entropyGenerator.writeEntropyBatch1();

Tools Used

Hardhat

Recommended Mitigation Steps

Only allow getNextEntropy call if currentSlotIndex < lastInitializedIndex to ensure that the entropy slots are properly initialized:

  function getNextEntropy() public onlyAllowedCaller returns (uint256) {
...
+    require(currentSlotIndex < lastInitializedIndex, 'Slot not initialized');
    uint256 entropy = getEntropy(currentSlotIndex, currentNumberIndex);

Assessed type

Other

hungdoo commented 2 months ago

I respectfully request a reconsideration of this finding.

As evidenced in the deployment script, the TraitForgeNft contract is fully set up and ready for the minting process prior to the start of the Writing EntropyBatch operations. This sequence highlights a significant risk that must be addressed.

task('deploy-all', 'Deploy all the contracts').setAction(async (_, hre) => {
  await nft.setEntropyGenerator(await entropyGenerator.getAddress());
  await nft.setEntityForgingContract(await entityForging.getAddress());
  await nft.setNukeFundContract(await nukeFund.getAddress());
  await nft.setAirdropContract(await airdrop.getAddress());
  await entityTrading.setNukeFundAddress(await nukeFund.getAddress());
  await entityForging.setNukeFundAddress(await nukeFund.getAddress());
  await airdrop.setTraitToken(await token.getAddress());
  await airdrop.transferOwnership(await nft.getAddress());

  console.log('Generating Merkle Tree...');
  const { rootHash } = generateMerkleTree(WHITELIST);
  await nft.setRootHash(rootHash);
  console.log('Generated & Set the root hash successfully.');
...
  console.log('Writing EntropyBatch...');
  const tx1 = await entropyGenerator.writeEntropyBatch1();
  tx1.wait();
  const tx2 = await entropyGenerator.writeEntropyBatch2();
  tx2.wait();
  const tx3 = await entropyGenerator.writeEntropyBatch3();
  tx3.wait();
  console.log('Writing EntropyBatch done.');
});

The writeEntropyBatch1,2,3 calls, which populate the entropy, are protected by a safeguard mechanism against an entropy value of 999999 (see EntropyGenerator.sol: Line 56 and EntropyGenerator.sol: Line 76). Should any of these calls revert due to this safeguard, the batch writing process will need to be retried.

However, because the minting setup is already completed and live, there exists a critical window where mint transactions could be processed before the entropy batches are fully written. During this window, any mint transactions would result in 0 entropy (reading of uninitialized entropySlots), leading to unintended outcomes, as detailed in my original finding.

TForge1 commented 1 month ago

this is a valid issue as there could be a timeframe where there is not initialised entropy. better yet, we should put a batch in constructor to be sure of mitigation.

koolexcrypto commented 1 month ago

Thank you everyone for the feedback.

will be moved to the main repo as a valid issue