Eggstravaganza

First Flight #37
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Invalid

Misleading `totalSupply` in NFT contract

Vulnerability Details

Bug Description

The EggstravaganzaNFT contract includes a totalSupply State variable intended to track the number of NFTs minted:

function mintEgg(address to, uint256 tokenId) external returns (bool) {
require(msg.sender == gameContract, "Unauthorized minter");
_mint(to, tokenId);
totalSupply += 1;
return true;
}

However, during proof-of-code testing, I observed that totalSupply does not increase at all, even when minting new tokens. This indicates a critical functional bug — either the increment operation is not executing as expected, or the value is never being persisted correctly on-chain.

This behavior directly undermines the reliability of totalSupply, as it does not reflect even the number of minted tokens, let alone burned ones. This is more severe than my original concern of not decrementing on burns. The metric is fundamentally broken and provides a misleading view of the token collection's circulating supply.

Additionally, if the _mint() function were to revert or fail silently, totalSupply could fall out of sync with actual token ownership. Because totalSupply is manually maintained and not derived from internal mappings or enumerated token sets, it is error-prone and misaligned with the actual contract state.

Impact

  • False Supply Reporting: The totalSupply always returns zero or a static value regardless of how many tokens are minted or burned. Consumers relying on this data - such as EggHunt games - will receive invalid information.

  • Severe Trust and Transparency Issues: Users cannot determine how many NFTs exist, damaging transparency. Buyers may overestimate scarcity or miss opportunities due to incorrect supply assumptions.

  • Incompatibility with Ecosystem Tools: Many external platforms and token standards expect totalSupply() to reflect the true number of tokens. This bug will break assumptions and integration logic for third-party systems.

  • Potential Logic Failures in Dependent Contracts: If other contracts or scripts use totalSupply logic (e.g., fair drops, caps, vesting), they will behave incorrectly — potentially introducing exploitable paths elsewhere.

Tools Used

  • Manual Review

Recommended Mitigation Steps

  1. Fix the Incrementation Bug in mintEgg: Confirm that the line totalSupply += 1; is being executed as expected. If compiler optimizations or code overrides interfere, consider making the update within a _beforeTokenTransfer() hook to ensure it executes on successful mints only.

  2. Use a Mappings-Based Approach or ERC721Enumerable: Implement a reliable token count by deriving the supply from the number of valid token IDs or owners. The ERC721Enumerable extension from OpenZeppelin is a well-tested solution that tracks total supply, token indices, and ownership.

  3. Update totalSupply on Burn: If manual tracking continues to be used, _burn() must be overridden to decrement totalSupply. Without this, the metric will become inaccurate over time.

    function _burn(uint256 tokenId) internal override {
    super._burn(tokenId);
    totalSupply -= 1;
    }
  4. Emit Events for Mint/Burn: Emitting Minted and Burned events with total supply snapshots can help off-chain indexers recover from inconsistencies.

POC

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import "forge-std/Test.sol";
import "../src/EggstravaganzaNFT.sol";
contract EggstravaganzaNFTTest is Test {
EggstravaganzaNFT nftContract;
address owner = address(0x1);
address gameContract = address(0x2);
address user = address(0x3);
function setUp() public {
// Deploy NFT contract as owner
vm.startPrank(owner);
nftContract = new EggstravaganzaNFT("Eggstravaganza", "EGG");
// Set game contract
nftContract.setGameContract(gameContract);
vm.stopPrank();
}
function testTotalSupplyNotTracking() public {
// 1. Mint some NFTs from the game contract
vm.startPrank(gameContract);
nftContract.mintEgg(user, 1);
nftContract.mintEgg(user, 2);
nftContract.mintEgg(user, 3);
vm.stopPrank();
// Check total supply after minting - expected to be 0 based on trace
uint256 reportedSupply = nftContract.totalSupply();
emit log_named_uint("Reported totalSupply", reportedSupply);
// Check actual number of tokens
uint256 userBalance = nftContract.balanceOf(user);
emit log_named_uint("Actual tokens in circulation", userBalance);
// Verify the discrepancy
assertEq(userBalance, 3, "User should have 3 tokens");
assertEq(reportedSupply, 0, "totalSupply is not tracking minted tokens");
// The problem is even worse than originally thought - totalSupply isn't incrementing at all
emit log_string("VULNERABILITY: totalSupply is not incremented on mint operations");
}
function testManualTokenCount() public {
// Mint 5 NFTs
vm.startPrank(gameContract);
for(uint256 i = 1; i <= 5; i++) {
nftContract.mintEgg(user, i);
}
vm.stopPrank();
// Calculate real token count by checking tokens owned by all addresses
uint256 realTokenCount = nftContract.balanceOf(user);
// Check reported supply
uint256 reportedSupply = nftContract.totalSupply();
// Verify discrepancy
assertEq(realTokenCount, 5, "User should have 5 tokens");
assertTrue(reportedSupply != realTokenCount, "Supply reporting is inaccurate");
emit log_named_uint("Reported totalSupply", reportedSupply);
emit log_named_uint("Actual tokens in circulation", realTokenCount);
emit log_named_uint("Discrepancy", realTokenCount - reportedSupply);
}
}
Updates

Lead Judging Commences

m3dython Lead Judge 2 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.