Token-0x

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

ERC20 _mint / _burn Missing Transfer Event

Author Revealed upon completion

ERC20 _mint / _burn Missing Transfer Event + High

Description

  • Normal ERC-20 behavior requires that any mint or burn of tokens must emit a Transfer event. For minting, the event should be from address(0) to the recipient. For burning, the event should be from the account to address(0).

In the current ERC20Internals contract, the _mint and _burn functions update balances and total supply correctly but do not emit the required Transfer events, violating the ERC-20 standard.

function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
let ptr := mload(0x40)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
sstore(supplySlot, add(supply, value)) @> No Transfer event emitted
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, add(accountBalance, value))
}
}
function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
let ptr := mload(0x40)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
sstore(supplySlot, sub(supply, value)) @> No Transfer event emitted
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, sub(accountBalance, value))
}
}

Risk

Likelihood:

  • Every call to _mint or _burn will omit the Transfer event, violating ERC-20 expectations.

  • Large token values may risk silent arithmetic issues in assembly.

Impact:

  • Wallets, block explorers, and dApps will not detect minted or burned tokens.

  • External systems relying on events for token tracking (like DeFi contracts) may break.

Proof of Concept

The mint, burn logic doesn't emit Transfer event which is a standard workflow.

ERC20 token = new ERC20("Test", "TST");
// Mint tokens to an account
token.mint(msg.sender, 100); // No Transfer event emitted
// Burn tokens from an account
token.burn(msg.sender, 100); // No Transfer event emitted

Recommended Mitigation

  • Emit Transfer events using inline assembly immediately after balances and _totalSupply are updated.

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))
+ let newSupply := add(supply, value)
+ sstore(supplySlot, newSupply)
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
- sstore(accountBalanceSlot, add(accountBalance, value))
+ let newBalance := add(accountBalance, value)
+ sstore(accountBalanceSlot, newBalance)
+ // Emit Transfer event: from 0x0 to account
+ mstore(0x00, value)
+ log3(0x00, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0, 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))
+ let newSupply := sub(supply, value)
+ sstore(supplySlot, newSupply)
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
- sstore(accountBalanceSlot, sub(accountBalance, value))
+ let newBalance := sub(accountBalance, value)
+ sstore(accountBalanceSlot, newBalance)
+ // Emit Transfer event: from account to 0x0
+ mstore(0x00, value)
+ log3(0x00, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, account, 0)
}
}

Support

FAQs

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

Give us feedback!