Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: high
Likelihood: high

[H-01] - Missing Transfer Event in `_mint()` breaks ERC20 compliance and off-chain tracking

Author Revealed upon completion

Root + Impact

Description

The _mint() function in ERC20Internals.sol does not emit a Transfer event from address(0) as required by the ERC20 standard.

According to the ERC20 specification: "A token contract which creates new tokens SHOULD trigger a Transfer event with the _from address set to 0x0 when tokens are created."

The current implementation mints tokens and updates balances correctly but fails to emit the required event, making mints invisible to off-chain systems.

// Root cause in the codebase (ERC20Internals.sol, lines 134-156)
function _mint(address to, uint256 value) internal {
assembly {
// ... validation and balance updates ...
// Updates storage correctly BUT...
sstore(balanceSlot, add(toAmount, value))
sstore(_totalSupply.slot, add(sload(_totalSupply.slot), value))
// @> BUG: No Transfer event emitted!
// Should emit: Transfer(address(0), to, value)
}
}

Note: The function updates state correctly but never emits the required Transfer(address(0), to, value) event.

Risk

Likelihood: High
Every mint operation is affected. This is not an edge case - it affects 100% of token creation operations.

Impact: High

  • ERC20 Non-compliance: The token does not meet the ERC20 standard

  • Broken integrations: Block explorers, wallets, and DeFi protocols cannot track mints

  • Audit failures: Token will fail formal ERC20 compliance audits

  • Exchange listing issues: Centralized exchanges require ERC20 compliance

Proof of Concept

The exploit was confirmed using a Foundry test that demonstrates no Transfer event is emitted during minting.

  1. Setup:

    • Deploy the Token contract

    • Set up event recording using vm.recordLogs()

  2. Action:

    • Call token.mint(alice, 100e18) to mint 100 tokens to alice

    • Retrieve all emitted logs

  3. Result:

    • No Transfer event found in logs

    • Tokens were successfully minted and balance updated

    • Off-chain systems have no visibility into this mint

Supporting Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test, console2} from "forge-std/Test.sol";
import {VmSafe} from "forge-std/Vm.sol";
import {Token} from "./Token.sol";
contract VulnerabilityPoC is Test {
Token public token;
event Transfer(address indexed from, address indexed to, uint256 value);
function setUp() public {
token = new Token();
}
function test_H1_MintMissingTransferEvent() public {
address alice = makeAddr("alice");
uint256 amount = 100e18;
// Record logs
vm.recordLogs();
// Mint tokens
token.mint(alice, amount);
// Get emitted logs
VmSafe.Log[] memory logs = vm.getRecordedLogs();
// Check that NO Transfer event was emitted
bool transferEventFound = false;
bytes32 transferEventSig = keccak256("Transfer(address,address,uint256)");
for (uint256 i = 0; i < logs.length; i++) {
if (logs[i].topics[0] == transferEventSig) {
transferEventFound = true;
break;
}
}
// BUG: Transfer event should be emitted but it's not
assertFalse(transferEventFound, "Transfer event was found (unexpected fix)");
// Verify tokens were minted
assertEq(token.balanceOf(alice), amount);
assertEq(token.totalSupply(), amount);
}
}

Test Results:

Test: test_H1_MintMissingTransferEvent()
Status: PASS ✅
Gas Used: 59,332
Logs:
- No Transfer event emitted during mint
- Tokens successfully minted (balance = 100e18)
- Total supply updated correctly
- Off-chain tracking completely broken

Recommended Mitigation

Add the Transfer event emission to the _mint() function after updating the balance and total supply.

function _mint(address to, uint256 value) internal {
assembly {
// Existing validation...
if iszero(to) {
mstore(0x00, 0x00)
revert(0x00, 0x04)
}
// Existing balance/supply updates...
let balanceSlot := keccak256(0x00, 0x40)
let toAmount := sload(balanceSlot)
sstore(balanceSlot, add(toAmount, value))
sstore(_totalSupply.slot, add(sload(_totalSupply.slot), value))
+ // Emit Transfer event: Transfer(address(0), to, value)
+ mstore(0x00, value)
+ log3(
+ 0x00,
+ 0x20,
+ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, // Transfer topic
+ 0, // from = address(0)
+ to // to
+ )
}
}

This ensures the token emits the standard Transfer event for all minting operations, restoring ERC20 compliance and enabling off-chain tracking.

Support

FAQs

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

Give us feedback!