Token-0x

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

Allows unauthorized token creation, leading to infinite supply inflation.

Author Revealed upon completion

Description

The transfer function contains a logic flaw where self-transfers (transferring tokens to one's own address) result in the recipient balance being incorrectly updated. Instead of maintaining the same balance, the function effectively doubles the transfer amount added to the balance due to improper handling of memory slots when from and to addresses are identical.

The bug occurs because:

  1. The function loads from balance (e.g., 100)

  2. Loads to balance (same as from, 100)

  3. Subtracts amount from from (100 - 10 = 90)

  4. Adds amount to to (90 + 10 = 110, overwriting the previous subtraction)

Offending pattern (conceptual):

uint256 fromBal = balanceOf[from];
uint256 toBal = balanceOf[to];
balanceOf[from] = fromBal - amount;
balanceOf[to] = toBal + amount; // when from==to this overwrites previous write

PoC (runnable Foundry test snippet)

function test_PoC_SelfTransfer_IncreasesBalance() public {
uint256 initial = 100 ether;
token.mint(alice, initial);
vm.startPrank(alice);
token.transfer(alice, 10 ether); // self-transfer
vm.stopPrank();
uint256 after = token.balanceOf(alice);
console.log("before:", initial);
console.log("after: ", after);
assertEq(after, initial + 10 ether, "PoC: Self-transfer increased balance");
}

Reproduction commands:

forge test --match-test test_PoC_SelfTransfer_IncreasesBalance -vv

Expected behavior: balanceOf(alice) remains 100 ether after a self-transfer of 10 ether (no net change).
Actual behavior: balanceOf(alice) becomes 110 ether.

Risk and Impact

  • Attackers can repeatedly self-transfer to inflate balances (practically mint tokens).

  • Total supply and accounting are corrupted, enabling exploitation of liquidity pools, governance, and other systems.

Recommended Mitigations

Short-term: short-circuit self-transfers to avoid double writes and still emit Transfer for compliance:

- uint256 fromBal = balanceOf[from];
- uint256 toBal = balanceOf[to];
- balanceOf[from] = fromBal - amount;
- balanceOf[to] = toBal + amount;
+ if (from == to) {
+ emit Transfer(from, to, amount);
+ return true; // or do nothing to maintain invariants
+ }
+ balanceOf[from] -= amount;
+ balanceOf[to] += amount;

Long-term: refactor to use combined updates or use OpenZeppelin's audited ERC20 implementation.

Support

FAQs

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

Give us feedback!