Token-0x

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

[M-01] Missing Transfer Event in _mint Function (Non-Compliant with ERC20 Standard)

Author Revealed upon completion

Description

  • The _mint function does not emit the required Transfer event when tokens are minted. According to the ERC20 standard, any token minting must emit a Transfer event with from set to the zero address and to set to the recipient address. Without this event, external observers cannot track token minting, breaking interoperability with wallets, block explorers, and other smart contracts.



Root Cause

The function uses Yul assembly for gas optimization but omits the emission of the Transfer event after updating the token balances and total supply.

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 Transfer event emission: Transfer(address(0), account, value)
}

Risk

Likelihood:

  • Every mint operation fails to emit the required event, affecting all mints.

  • The absence of events is immediately apparent to any external observer or tool that relies on events.

Impact:

  • Broken ERC20 Compliance: The token does not fully comply with the ERC20 standard, which may cause integration issues with exchanges, wallets, and other DeFi protocols.

  • Lack of Transparency: External parties cannot track minting activities, leading to a loss of transparency and auditability.

  • Interoperability Issues: Smart contracts that rely on Transfer events to react to token minting (e.g., staking contracts, token trackers) will not function correctly.

Proof of Concept

Recommended Mitigation

Emit a Transfer event with from set to address(0) and to set to the recipient after updating balances:

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))
+ mstore(0x00, value) // Store value in memory
+ log3(
+ 0x00, // data offset
+ 0x20, // data size (32 bytes for uint256)
+ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, // Transfer event signature
+ 0x00, // from = address(0)
+ account // to = recipient
+ )
}

Support

FAQs

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

Give us feedback!