Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: low
Valid

`_mint` function does not emit Transfer event, breaking ERC20 standard compliance

Root + Impact

Description

  • According to the ERC20 standard, when tokens are minted, the contract must emit a Transfer event from address(0) to the recipient account. The _mint function at lines 134-156 should emit Transfer(address(0), account, value) after updating the balance and total supply, similar to how the _transfer function emits Transfer(from, to, value) events. This event emission is required for ERC20 compliance and enables off-chain systems to track token mints.

  • The _mint function updates the account balance and total supply but does not emit the required Transfer event. The function performs all state changes correctly but omits the event emission at the end of the assembly block. This breaks ERC20 standard compliance and prevents off-chain systems from detecting mint operations, causing integration failures with wallets, explorers, and protocols that rely on Transfer events to track token creation.

// Root cause in the codebase with @> marks to highlight the relevant section
function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0xec442f05))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
let ptr := mload(0x40)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
sstore(supplySlot, add(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, add(accountBalance, value))
@> // Missing: log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00, account)
}
}

Risk

Likelihood:

  • High - The vulnerability occurs whenever the _mint function is called, which happens during every token minting operation. Since minting is a core functionality of ERC20 tokens and is typically called frequently during token distribution, airdrops, or reward mechanisms, this issue affects all mint operations.

  • High - The missing event emission happens consistently during mint operations because the function lacks the event emission code entirely. Any contract or protocol that mints tokens using this implementation will fail to emit the required Transfer event, breaking compatibility with standard ERC20 tooling and infrastructure.

Impact:

  • High - ERC20 standard non-compliance: The contract fails to meet the ERC20 standard requirement for emitting Transfer events during minting. This breaks compatibility with standard ERC20 interfaces and can cause integration failures with wallets, DEX aggregators, and other DeFi protocols that expect Transfer events for all token operations, including mints.

  • Medium - Off-chain tracking and indexing failures: Wallets, block explorers, and indexing services rely on Transfer events to track token balances and transaction history. Without the Transfer event, these systems cannot detect mint operations, leading to incorrect balance displays, missing transaction history, and broken analytics for token holders and protocols.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test, Vm} from "forge-std/Test.sol";
import {Token} from "./Token.sol";
contract MintEventPOC is Test {
event Transfer(address indexed _from, address indexed _to, uint256 _value);
Token public token;
function setUp() public {
token = new Token();
}
function test_mintMissingTransferEvent() public {
address account = makeAddr("account");
// Mint tokens - should emit Transfer(address(0), account, amount)
// This test will fail because _mint doesn't emit Transfer event
vm.expectEmit(true, true, false, true);
emit Transfer(address(0), account, 100e18);
token.mint(account, 100e18);
}
function test_mintEventNotEmitted() public {
address account = makeAddr("account");
// Record events
vm.recordLogs();
token.mint(account, 100e18);
// Check that no Transfer event was emitted
Vm.Log[] memory logs = vm.getRecordedLogs();
assertEq(logs.length, 0, "No events emitted, but ERC20 requires Transfer event");
}
}

Explanation of the POC

The proof of concept demonstrates that when tokens are minted, no Transfer event is emitted. The test test_mintEventNotEmitted() records all events during a mint operation and verifies that no events are logged, proving the missing Transfer event. The test test_mintMissingTransferEvent() expects a Transfer(address(0), account, 100e18) event but fails because the function doesn't emit it.

Running the POC: Execute forge test --match-contract MintEventPOC -vvv. The tests will demonstrate that minting tokens does not emit the required Transfer event, breaking ERC20 compliance. The test test_mintEventNotEmitted() passes, confirming no events are emitted during minting.


Explanation of the Mitigation

The fix adds the missing Transfer event emission after updating the balance and total supply. The event is emitted using Yul's log3 opcode with the Transfer event signature hash (0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef), with address(0) as the from parameter and the account as the to parameter. This ensures ERC20 standard compliance and enables off-chain systems to properly track mint operations. After applying the mitigation, the POC tests should pass, confirming that Transfer events are now emitted during minting.

Recommended Mitigation

function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0xec442f05))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
let ptr := mload(0x40)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
sstore(supplySlot, add(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, add(accountBalance, value))
+
+ // Emit Transfer event: Transfer(address(0), account, value)
+ mstore(ptr, value)
+ log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00, account)
}
}
Updates

Lead Judging Commences

gaurangbrdv Lead Judge 19 days ago
Submission Judgement Published
Validated
Assigned finding tags:

missing event

missing event emission

Support

FAQs

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

Give us feedback!