Normal behavior
In a robust ERC20:
transfer(from, to, value) updates balances in a way that preserves:
balance[to] monotonically increasing as they receive tokens.
No overflow/wrap: if the operation would push balance[to] above type(uint256).max, the transaction reverts instead of corrupting state.
Issue
In this implementation, _transfer checks that fromAmount >= value before subtracting from the sender, but it does not check whether toAmount + value overflows. The addition happens directly in Yul:
Because add(toAmount, value) is executed in assembly, Solidity 0.8.x’s checked arithmetic does not apply. When toAmount is type(uint256).max and value = 1, the new stored value becomes 0 instead of reverting.
Likelihood:
Likelihood:
Whenever the recipient’s balance has previously been pushed near type(uint256).max, any subsequent transfer to that address causes the recipient balance to wrap instead of reverting.
Impact:
Recipient balances can silently wrap from a huge value to a small one (including zero) as a consequence of incoming transfers, violating user expectations that receiving tokens never decreases their balance.
This PoC uses Foundry’s vm.store to simulate a recipient with a near-maximum balance, then performs a transfer that causes the recipient’s balance to wrap from type(uint256).max to 0.
Add the following test to Token.t.sol:
Enforce checked arithmetic for the recipient balance update in _transfer, either by:
Moving the logic to Solidity (benefiting from 0.8.x checks), or
Adding explicit overflow detection in Yul before storing.
Conceptual patch (staying in Yul and adding a check):
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.