Token-0x

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

Missing Transfer Events in _mint and _burn - ERC20 Standard Violation

Author Revealed upon completion

Root + Impact

Description

According to the ERC20 standard (EIP-20), minting and burning operations MUST emit Transfer events

  • Minting SHOULD emit: Transfer(address(0), account, value) - "A token contract which creates new tokens SHOULD trigger a Transfer event with the _from address set to 0x0 when tokens are created."

  • Burning SHOULD emit: Transfer(account, address(0), value) - When tokens are destroyed, should emit Transfer with _to = 0x0

// Root cause in the codebase with @> marks to highlight the relevant section
function _mint(address account, uint256 value) internal {
.
.
.
@> sstore(accountBalanceSlot, add(accountBalance, value))
@> // emit event
}
.
.
.
function _burn(address account, uint256 value) internal {
.
.
.
@> sstore(accountBalanceSlot, sub(accountBalance, value))
@> // emit event
}
}

Risk

Likelihood:

  • Any direct or indirect call to the _mint function.

  • Any direct or indirect call to the _burn function.

Impact:

  • Standard Compliance Violation - Direct violation of ERC20 standard recommendations

  • Off-chain systems broken - Indexers, block explorers, and analytics tools cannot track mint/burn operations

  • Incorrect token supply - Wallets and DApps will show wrong total supply (missing minted tokens, including burned tokens)

  • DeFi integration failures - Protocols that monitor Transfer events for supply changes will fail

  • User confusion - Tokens can appear/disappear without traceable events

  • Audit trail broken - No on-chain record of token creation/destruction

Proof of Concept

These tests demonstrate that the _mint and _burn functions do not emit the standard Transfer event.

function test_mintEmitsTransferEvent() public {
uint256 amount = 100e18;
address account = makeAddr("account");
vm.expectEmit(true, true, false, true);
emit Transfer(address(0), account, amount);
token.mint(account, amount);
}
function test_burnEmitsTransferEvent() public {
uint256 amount = 100e18;
address account = makeAddr("account");
token.mint(account, amount);
vm.expectEmit(true, true, false, true);
emit Transfer(address(0), account, amount);
token.burn(account, amount);
}

Recommended Mitigation

Add Transfer event emissions to the _burn and _mint functions.

function _burn(address account, uint256 value) internal {
.
.
.
sstore(accountBalanceSlot, sub(accountBalance, value))
+ mstore(ptr, value)
+ log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, account, 0)
}
}
function _mint(address account, uint256 value) internal {
.
.
.
sstore(accountBalanceSlot, add(accountBalance, value))
+ mstore(ptr, value)
+ log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0, account)
}
}

Support

FAQs

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

Give us feedback!