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
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");
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);
}
}