Token-0x

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

Inconsistent Yul return() Pattern in Internal Functions in `ERC20Internals.sol`

Author Revealed upon completion

[L-1] Inconsistent Yul return() Pattern in Internal Functions in ERC20Internals.sol

Description

  • The ERC20Internals contract mixes two different return patterns in its inline assembly:

    Pattern A: Direct Yul return() (Halts Execution)

    Used in:

    • totalSupply_()

    • _balanceOf()

    Example:

mstore(0x00, supply)
return(0x00, 0x20)

Pattern B: Assigning to Solidity Output Variables (Recommended Style)

Used in:

  • _approve()

  • _allowance()

  • _transfer()

  • _spendAllowance()

  • _mint()

  • _burn()

Example:

remaining := sload(allowanceSlot)

Both patterns work, but mixing them creates unnecessary inconsistency and introduces a subtle risk:

Risk

Direct Yul return():

  1. Halts execution immediately, skipping any future Solidity-level handling.

  2. Bypasses Solidity’s ABI encoding, which is safe only as long as function output stays a single 32-byte value.

  3. Becomes dangerous if the function is ever modified (e.g., multi-return values, additional logic).

This creates an unnecessary footgun for anyone extending or reusing the internal functions.

Likelihood:

  • Medium


    Impact:

Why This Matters

Internal functions usually follow the same return mechanism. Here, the inconsistency could lead to:

  • Misinterpretation of function behavior by future developers

  • Silent ABI-breaking changes if function internals are modified

  • Harder auditing due to mixed assembly styles

  • Unexpected behavior if future logic relies on post-assembly Solidity code

This is not a functional bug today, but it is a maintainability and safety concern.

Proof of Concept

Nil

Recommended Mitigation

Refactor the functions using raw Yul return() to use Solidity output variables instead:

Before:

return(0x00, 0x20)

After:

supply := sload(_totalSupply.slot)
function totalSupply_() internal view returns (uint256 supply) {
assembly {
supply := sload(_totalSupply.slot)
}
}

This matches the style used everywhere else and eliminates the execution-halting return().

Support

FAQs

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

Give us feedback!