Root + Impact
The _burn function does not emit the required Transfer(account, address(0), value) event, violating the ERC20 standard and breaking integration with external services.
Description
Similar to the mint function, the burn function should emit a Transfer event with the recipient being address(0).
This is a standard convention for ERC20 burns.
@> src/helpers/ERC20Internals.sol
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)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
sstore(supplySlot, sub(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, sub(accountBalance, value))
@>
}
}
Risk
Likelihood:
Impact:
Burn operations invisible to external tracking systems
Supply tracking by explorers becomes inaccurate
DeFi protocols depending on Transfer events for burns will fail
Proof of Concept
function test_burnNoEvent() public {
address account = makeAddr("account");
token.mint(account, 100e18);
vm.expectEmit(true, true, false, true);
emit Transfer(account, address(0), 50e18);
token.burn(account, 50e18);
}
Recommended Mitigation
Add Transfer event emission using the log3 opcode at the end of the _burn function.
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)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
sstore(supplySlot, sub(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, sub(accountBalance, value))
+ // Emit Transfer(account, address(0), value)
+ mstore(ptr, value)
+ log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, account, 0)
}
}