Token-0x

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

Missing Event Emissions (Mint and Burn)

Author Revealed upon completion

Description

The _mint() and _burn() functions do not emit Transfer events. This breaks ERC20 compliance and causes off-chain indexers (Etherscan, The Graph) to miss critical token operations, leading to incomplete transfer histories and broken user accounting.


Root Cause

// src/helpers/ERC20Internals.sol
//@> function _mint(address account, uint256 value) internal {
//@> assembly ("memory-safe") {
//@> // ... minting logic ...
//@> }
//@> // ❌ NO EVENT EMITTED!
//@> }
//@> function _burn(address account, uint256 value) internal {
//@> assembly ("memory-safe") {
//@> // ... burning logic ...
//@> }
//@> // ❌ NO EVENT EMITTED!
//@> }

Risk Analysis

Likelihood: HIGH

  • Functions are called on every mint/burn operation

  • Off-chain systems continuously scan for events

  • Missing events affect all integrations

Impact:

  • Indexing failures: Etherscan won't show mint/burn transactions

  • Broken accounting: Wallets show incorrect balances

  • DeFi integration issues: Lending/DEX protocols miss supply changes

  • Regulatory compliance: No complete event logs for audits


Proof of Concept

pragma solidity ^0.8.24;
import {Test} from "forge-std/Test.sol";
import {Token} from "./Token.sol";
contract EventEmissionTest is Test {
Token token;
event Transfer(address indexed from, address indexed to, uint256 value);
function setUp() public {
token = new Token();
}
function test_mintDoesNotEmitTransferEvent() public {
address user = makeAddr("user");
// ❌ This test FAILS - expected event never emitted
vm.expectEmit(true, true, false, true);
emit Transfer(address(0), user, 100 ether);
token.mint(user, 100 ether);
}
}

Result: [FAIL] - Expected event not found


Recommended Mitigation

// src/ERC20.sol
contract ERC20 is IERC20Errors, ERC20Internals {
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(address indexed owner, address indexed spender, uint256 value);
function transfer(address to, uint256 value) public virtual returns (bool success) {
success = _transfer(msg.sender, to, value);
+ if (success) emit Transfer(msg.sender, to, value);
}
function transferFrom(address from, address to, uint256 value) public virtual returns (bool success) {
address spender = msg.sender;
_spendAllowance(from, spender, value);
success = _transfer(from, to, value);
+ if (success) emit Transfer(from, to, value);
}
function approve(address spender, uint256 value) public virtual returns (bool success) {
address owner = msg.sender;
success = _approve(owner, spender, value);
+ if (success) emit Approval(owner, spender, value);
}
}
// test/Token.sol
contract Token is ERC20 {
+ event Transfer(address indexed from, address indexed to, uint256 value);
function mint(address account, uint256 value) public {
_mint(account, value);
+ emit Transfer(address(0), account, value);
}
function burn(address account, uint256 value) public {
_burn(account, value);
+ emit Transfer(account, address(0), value);
}
}

Support

FAQs

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

Give us feedback!