Reproduced Exploit
TraitForge H-01 — wrong minting logic based on total token count across generations
Chain
Other
Category
untagged
Date
Jan 1970
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 37915-h-01-wrong-minting-logic-based-on-total-token-count-across-g. Standalone Foundry PoC and full write-up: 37915-h-01-wrong-minting-logic-based-on-total-token-count-across-g_exp in the
evm-hack-registrymirror.
Severity: High · Source: Code4rena 2024-07-traitforge · AuditVault finding #37915
Root cause#
TraitForgeNft.mintWithBudget is meant to let a whitelisted user batch-mint as many
tokens as their budget affords, up to the current generation's cap. Its loop guard,
however, compares the global _tokenIds counter (total tokens minted across all
generations) against the per-generation cap maxTokensPerGen:
// contracts/TraitForgeNft/TraitForgeNft.sol:215
while (budgetLeft >= mintPrice && _tokenIds < maxTokensPerGen) {
_mintInternal(msg.sender, mintPrice);
amountMinted++;
budgetLeft -= mintPrice;
mintPrice = calculateMintPrice();
}
Within generation 1 _tokenIds == generationMintCounts[1], so the guard behaves. But
the moment generation 1 fills (_tokenIds == maxTokensPerGen == 10_000), _tokenIds
keeps growing forever while every new generation resets its own counter to zero. From
generation 2 onward the guard _tokenIds < maxTokensPerGen is permanently false, so
mintWithBudget enters its loop body zero times and refunds the entire budget —
even though the fresh generation has all 10,000 slots open. The correct guard is
generationMintCounts[currentGeneration] < maxTokensPerGen.
The bug is a permanent DoS of the batch-mint path in every generation after the first:
mintToken (which has no _tokenIds guard) still works, so the only way to mint in
generation 2+ is one token per transaction.
What the PoC proves#
Everything on the mint path is the real, unmodified audited source, compiled from
code-423n4/2024-07-traitforge @ 72077d0 with the project's real
OpenZeppelin contracts 4.9.3: TraitForgeNft, EntropyGenerator, EntityForging,
Airdrop and NukeFund are all deployed and wired exactly as in production
(EntropyGenerator/Airdrop ownership handed to the NFT; NukeFund receives the mint
proceeds).
test/…_exp.sol:
- Real mint path — mints 3 generation-1 tokens and shows
_tokenIdsandgenerationMintCounts[1]move together. - Compress history — advances both counters to
maxTokensPerGen - 1(9,999) viavm.store(equivalent to replaying the remaining real gen-1 mints; the on-chain trace stays readable). No contract code is modified. - Real generation crossing — two more real
mintTokencalls fill generation 1 and trigger the real_incrementGeneration, moving to generation 2 with_tokenIds == 10_001. - Exploit — a whitelisted user calls
mintWithBudgetwith 1 ETH (enough for ~198 generation-2 tokens). Result: 0 tokens minted, the full 1 ETH refunded,generationMintCounts[2]untouched at 1 (9,999 slots still free). - Control — the same user's
mintTokensucceeds in generation 2, proving the generation is open andmintWithBudgetalone is bricked.
Concrete harm asserted: 0 NFTs minted where a correct guard mints ≥ 198 for the same 1 ETH budget, with generation 2 fully open.
Reproduce#
_shared/run-poc/run_poc.sh 37915-h-01-wrong-minting-logic-based-on-total-token-count-across-g_exp -vvvvv
The in-browser Playground synthetic (crypto-training) runs the same real
TraitForgeNft (via a subclass that only shrinks maxTokensPerGen 10000→3 so a second
generation is reachable in one call) and measures the concrete harm: mintWithBudget
mints 0 of a 20-token generation-2 batch, and all 20 are recoverable only by sending
20 separate mintToken transactions.
Sources: AuditVault finding #37915 · Code4rena report · vulnerable file
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 37915-h-01-wrong-minting-logic-based-on-total-token-count-across-g_exp (evm-hack-registry mirror).
- AuditVault finding: 37915-h-01-wrong-minting-logic-based-on-total-token-count-across-g.
- Upstream DeFiHackLabs PoC directory: src/test.
Alerts & third-party analyses
- DeFiHackLabs incident explorer: search "TraitForge H-01".
- Web3Sec X hacked database: search.
- Rekt leaderboard: search.
- Solodit incident search: search.
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.