ERC20Internals.sol::_transferNormal behavior: In a standard ERC-20 implementation, transferring tokens to oneself must be a no-op:\
the sender’s balance must remain unchanged, and totalSupply must not be affected.
- Self-transfers are valid operations and should not modify supply or mint tokens.
Actual behavior: When from == to, the _transfer function in ERC20Internals.sol produces inflation, because:
Both balance lookups load the same storage slot (balances[from]).
The function reads the old balance twice, subtracts value (first write), but then adds value to the old balance, not to the updated one (second write).
As a result, the final balance becomes:
while totalSupply remains unchanged.
An attacker can repeatedly self-transfer tokens to mint unlimited ERC-20 tokens, breaking all accounting and enabling full supply manipulation.
Issue in 127 line
Likelihood:
Self-transfer via transfer occurs whenever a user calls transfer(to, value) with to == msg.sender, which is a valid and commonly allowed ERC-20 pattern in wallets, airdrop scripts, and generic tooling that do not special-case self-transfers.
Self-transfer via transferFrom occurs whenever an approved spender calls transferFrom(from, to, value) with from == to, which is trivial once an allowance is granted (including spender == from), so any token holder can unilaterally trigger the inflation without special privileges.
Impact:
A malicious holder can repeatedly self-transfer to increase their own balance without increasing totalSupply, effectively minting an unbounded amount of tokens and completely breaking the ERC-20 accounting invariants.
With an inflated balance, the attacker can drain any protocol that trusts this token’s balance or totalSupply (DEX pools, lending markets, collateral systems, reward distributions, governance voting), leading to loss of funds from pools and treasuries and total loss of trust in the asset.
Just paste it in test/Token.t.sol
Output result:
The core issue comes from reading both fromAmount and toAmount before any storage updates and then writing twice to the same slot when from == to.
High-level early return (if (from == to) return true;) is preferred because it removes the vulnerable execution path before entering the assembly block, producing a safer and far more readable fix. This avoids additional branching inside Yul, reduces complexity, and eliminates the risk of introducing new low-level bugs while keeping the mitigation minimal and easy to verify.
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.