Token-0x

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

Missing Transfer Events in Mint and Burn

Author Revealed upon completion

Root + Impact

Description

  • The _mint() and _burn() functions do not emit Transfer events as required by EIP-20.

  • According to the standard, minting should emit Transfer(address(0), to, value) and burning should emit Transfer(from, address(0), value).

function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
// ... balance update logic ...
// @> NO Transfer event emitted!
// Missing: log3(ptr, 0x20, TRANSFER_TOPIC, 0, account)
}
}
function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
// ... balance update logic ...
// @> NO Transfer event emitted!
// Missing: log3(ptr, 0x20, TRANSFER_TOPIC, account, 0)
}
}

Risk

Likelihood:

  • Every mint and burn operation triggers this issue

  • 100% occurrence rate for these operations

Impact:

  • Block explorers (Etherscan) won't show mint/burn transactions

  • Wallets display incorrect transaction history

  • DeFi protocols relying on Transfer events for supply tracking will malfunction

  • The Graph indexers will have incomplete data

Proof of Concept

This test uses Foundry's vm.recordLogs() to capture all events emitted during a mint operation. After minting tokens, we iterate through the recorded logs searching for the Transfer event topic. The test confirms that no Transfer event is emitted, breaking EIP-20 compliance.

Place this test in test/MissingEventsPOC.t.sol and run with forge test --match-test test_MintDoesNotEmitTransferEvent -vv:

function test_MintDoesNotEmitTransferEvent() public {
vm.recordLogs();
token.mint(alice, 1000 ether);
Vm.Log[] memory entries = vm.getRecordedLogs();
bytes32 transferTopic = keccak256("Transfer(address,address,uint256)");
bool found = false;
for (uint i = 0; i < entries.length; i++) {
if (entries[i].topics[0] == transferTopic) found = true;
}
// No Transfer event was emitted
assertFalse(found);
}

Recommended Mitigation

Add log3 calls to emit the Transfer event at the end of both _mint() and _burn(). For minting, the from address should be address(0). For burning, the to address should be address(0). This matches OpenZeppelin's implementation and EIP-20 requirements.

function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
// ... existing logic ...
+ // Emit Transfer(address(0), account, value) per EIP-20
+ mstore(0x00, value)
+ log3(0x00, 0x20,
+ 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef,
+ 0, // from = address(0)
+ account) // to
}
}

Support

FAQs

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

Give us feedback!