Token-0x

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

Missing Transfer events on _mint and _burn — ERC-20 event invariants violated

Author Revealed upon completion

Root + Impact


Description

  • Minting should emit mint(address,value) and burning should emit burn(address, value) so off-chain systems can track supply and balance changes.

  • Without these events, wallets, indexers, and explorers cannot detect minted or burned tokens, causing incorrect balances and desynchronized supply tracking.

  • This breaks compatibility with standard ERC-20 integrations and can disrupt protocol functionality.

// Root cause in the codebase with @> marks to highlight the relevant section
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) //loading free memory pointer
let balanceSlot := _balances.slot //loading base slot of balances mapping
let supplySlot := _totalSupply.slot //loading total supply slot
let supply := sload(supplySlot) // loading current total supply
sstore(supplySlot, sub(supply, value)) // decreasing total supply
mstore(ptr, account) //store account at ptr
mstore(add(ptr, 0x20), balanceSlot) //storing balance base slot in next 32 bytes
let accountBalanceSlot := keccak256(ptr, 0x40) //calculating account balance slot
let accountBalance := sload(accountBalanceSlot) //loading current account balance
sstore(accountBalanceSlot, sub(accountBalance, value)) //updating account balance
}
}
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) //loading free memory pointer
let balanceSlot := _balances.slot //loading base slot of balances mapping
let supplySlot := _totalSupply.slot //loading total supply slot
let supply := sload(supplySlot) //loading current total supply
sstore(supplySlot, add(supply, value)) //updating total supply
mstore(ptr, account) //store account at ptr
mstore(add(ptr, 0x20), balanceSlot) //storing balance base slot in next 32 bytes
let accountBalanceSlot := keccak256(ptr, 0x40) //calculating account balance slot
let accountBalance := sload(accountBalanceSlot) //loading current account balance
sstore(accountBalanceSlot, add(accountBalance, value)) //updating account balance
}
}
// In the code we can clearly see that no logs are emitted, there isn't a vulnerable code to highlight
// It's just missing of events.

Risk

Likelihood:

  • The condition is deterministic: every callpath that mints or burns via these functions will fail to emit events.

  • Mint/burn operations are common (initial distribution, rewards, admin actions, burn mechanisms), so exposure is broad.

Impact:

  • This does not directly enable immediate theft or arbitrary balance corruption (funds are still stored on-chain), but it undermines transparency and integration correctness, which can lead to operational and financial problems.

  • Tokens are actually created/destroyed (balances and totalSupply change), but off-chain consumers may not record or react to those changes.

Proof of Concept

  • No PoC needed

Recommended Mitigation

  • Add burn and mint events

  • Emit them after successfull minting and burning

// Adding of events
event mint(address indexed minter, uint256 value);
event burn(address indexed burnfrom, uint256 value);
// Emit of event in mint function
function _mint(address account, uint256 value) internal {
// Rest of the code emit the event after the assembly block has ended
emit mint(account, value);
}
// Emit of event in burn function
function _burn(address account, uint256 value) internal {
// Rest of the code emit the event after the assembly block has ended
emit burn(account, value);
}

Support

FAQs

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

Give us feedback!