Token-0x

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

Missing Transfer Events in Mint and Burn Functions

Author Revealed upon completion

Root + Impact

Description

  • The _mint and _burn functions do not emit Transfer events, which violates the ERC20 standard. According to the ERC20 specification, a Transfer event MUST be triggered when tokens are transferred, including zero value transfers. For minting, a Transfer event should be emitted with the 'from' address set to 0x0, and for burning, the 'to' address should be 0x0. This missing event emission breaks compatibility with wallets, DEXs, and blockchain explorers that rely on these events to track token supply changes.

    \

// Root cause in the codebase with @> marks to highlight the relevant section
function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
// ... minting logic ...
sstore(accountBalanceSlot, add(accountBalance, value))
// Missing: Transfer event from address(0) to account
}
}
function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
// ... burning logic ...
sstore(accountBalanceSlot, sub(accountBalance, value))
// Missing: Transfer event from account to address(0)
}
}

Risk

Likelihood:

  • The issue can be observed by minting/burning tokens and checking that no Transfer events are emitted:

    1. Deploy the contract

    2. Call _mint through an inheriting contract

    3. Check transaction logs - no Transfer event from 0x0

    4. Call _burn through an inheriting contract

    5. Check transaction logs - no Transfer event to 0x0

    6. Token tracking services will show incorrect supply

Impact:

  • DEXs, wallets, and block explorers will not properly track minting and burning operations, leading to incorrect balance displays and supply calculations. This breaks the ERC20 standard compliance and could prevent the token from being listed on major exchanges or integrated with DeFi protocols.

Proof of Concept

Add to test.s.sol

function test_mint_event() public {
uint256 amount = 100e18;
address account = makeAddr("account");
token.mint(account, amount);
uint256 balance = token.balanceOf(account);
assertEq(balance, amount);
assertEq(token.totalSupply(), amount);
vm.expectEmit(true, true, false, true);
}

Recommended Mitigation

+ add this code
function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
// ... existing validation and state changes ...
sstore(accountBalanceSlot, add(accountBalance, value))
// Emit Transfer(address(0), account, value)
mstore(0x00, value)
log3(0x00, 0x20,
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef,
0x00, // from = address(0)
account) // to
}
}
function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
// ... existing validation and state changes ...
sstore(accountBalanceSlot, sub(accountBalance, value))
// Emit Transfer(account, address(0), value)
mstore(0x00, value)
log3(0x00, 0x20,
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef,
account, // from
0x00) // to = address(0)
}
}

Support

FAQs

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

Give us feedback!