Root + Impact
The _mint function does not emit the required Transfer(address(0), account, value) event, violating the ERC20 standard and breaking integration with wallets, explorers, and DeFi protocols.
Description
According to the ERC20 standard, all token movements must emit a Transfer event. For minting operations, this should be Transfer(address(0), recipient, amount). The current implementation updates balances but never emits this event.
@> src/helpers/ERC20Internals.sol
function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0xec442f05))
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, add(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, add(accountBalance, value))
@>
}
}
Risk
Likelihood:
Impact:
Block explorers (Etherscan, etc.) won't track token supply changes
Wallets won't detect incoming minted tokens
DeFi protocols relying on Transfer events will malfunction
Token is not fully ERC20 compliant
Proof of Concept
Here is the PoC to mint Tokens :
function test_mintNoEvent() public {
address account = makeAddr("account");
vm.expectEmit(true, true, false, true);
emit Transfer(address(0), account, 100e18);
token.mint(account, 100e18);
}
Recommended Mitigation
Add Transfer event emission using the log3 opcode at the end of the _mint function.
function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0xec442f05))
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, add(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
sstore(accountBalanceSlot, add(accountBalance, value))
+ // Emit Transfer(address(0), account, value)
+ mstore(ptr, value)
+ log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0, account)
}
}