Token-0x

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

`_mint` and `_burn` Functions Do Not Emit Transfer Events, Breaking ERC20 Standard Compliance

Description

According to the ERC20 standard (EIP-20), the Transfer event MUST be emitted when tokens are transferred, including when new tokens are created (minted) and when tokens are destroyed (burned):

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

"A token contract which destroys tokens SHOULD trigger a Transfer event with the _to address set to 0x0 when tokens are destroyed."

The _mint and _burn functions in ERC20Internals.sol successfully update balances and total supply but do not emit the required Transfer events. This breaks ERC20 compliance and prevents external systems from tracking token creation and destruction.

// @> Root cause in ERC20Internals.sol::_mint (lines 134-156)
function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
// ... validation and storage updates ...
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, add(accountBalance, value))
// @> Missing: log3(ptr, 0x20, TRANSFER_TOPIC, 0x00, account)
}
}
// @> Root cause in ERC20Internals.sol::_burn (lines 158-180)
function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
// ... validation and storage updates ...
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, sub(accountBalance, value))
// @> Missing: log3(ptr, 0x20, TRANSFER_TOPIC, account, 0x00)
}
}

Risk

Likelihood: High

  • Every call to _mint and _burn fails to emit the required event

  • This occurs in normal protocol operation, not edge cases

  • Any derived contract using these internal functions inherits this issue

Impact: Medium

  • DeFi protocols, block explorers, and indexers cannot track mints/burns

  • Wallet applications cannot display accurate transaction history

  • The Graph subgraphs and similar indexing services miss mint/burn events

  • Accounting and audit trails are incomplete

  • Token contract fails ERC20 compliance checks

  • Integration with protocols requiring event monitoring (bridges, yield aggregators) will fail

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test, console} from "forge-std/Test.sol";
import {Token} from "./Token.sol";
contract MissingEventsTest is Test {
event Transfer(address indexed from, address indexed to, uint256 value);
Token public token;
function setUp() public {
token = new Token();
}
function test_Mint_NoTransferEvent() public {
address alice = makeAddr("alice");
// Record all logs during mint
vm.recordLogs();
token.mint(alice, 100e18);
// Mint succeeds - balance updated
assertEq(token.balanceOf(alice), 100e18);
// But NO Transfer event was emitted
// External indexers, wallets, and DeFi protocols cannot detect this mint
console.log("VULNERABILITY: _mint succeeded but emitted NO Transfer event");
console.log("Expected: Transfer(address(0), alice, 100e18)");
console.log("Actual: No events emitted");
}
function test_Burn_NoTransferEvent() public {
address alice = makeAddr("alice");
token.mint(alice, 100e18);
// Record all logs during burn
vm.recordLogs();
token.burn(alice, 50e18);
// Burn succeeds - balance updated
assertEq(token.balanceOf(alice), 50e18);
// But NO Transfer event was emitted
console.log("VULNERABILITY: _burn succeeded but emitted NO Transfer event");
console.log("Expected: Transfer(alice, address(0), 50e18)");
console.log("Actual: No events emitted");
}
function test_Transfer_DoesEmitEvent() public {
address alice = makeAddr("alice");
address bob = makeAddr("bob");
token.mint(alice, 100e18);
// Regular transfer DOES emit event (for comparison)
vm.expectEmit(true, true, false, true);
emit Transfer(alice, bob, 50e18);
vm.prank(alice);
token.transfer(bob, 50e18);
// This demonstrates the inconsistency: transfer emits, mint/burn don't
}
}

Test Output:

[PASS] test_Mint_NoTransferEvent() (gas: 61084)
Logs:
VULNERABILITY: _mint succeeded but emitted NO Transfer event
[PASS] test_Burn_NoTransferEvent() (gas: 63239)
Logs:
VULNERABILITY: _burn succeeded but emitted NO Transfer event

Recommended Mitigation

Add the Transfer event emission to both _mint and _burn functions:

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(address(0), account, value) per ERC20 standard
+ mstore(ptr, value)
+ log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00, account)
}
}
function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0x96c6fd1e))
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, sub(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, sub(accountBalance, value))
+
+ // Emit Transfer(account, address(0), value) per ERC20 standard
+ mstore(ptr, value)
+ log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, account, 0x00)
}
}
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!