Token-0x

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

Scratch space usage in totalSupply_

Author Revealed upon completion

Description

  • Read-only getters should use the free memory pointer (0x40) region when preparing return data, avoiding writes to the scratch space (slots 0x00–0x3F). The scratch space is reserved for Solidity/Yul temporaries (e.g., keccak256, ABI encoding helpers), and clobbering it can create subtle maintenance hazards and reduce composability.

  • In totalSupply_, the implementation writes return data straight into scratch space (mstore(0x00, supply)) and returns from there (return(0x00, 0x20)) instead of using the free memory pointer. This is considered bad practice: while it may work today, it increases fragility (any added code that relies on scratch space around this call could be affected) and diverges from conventional memory hygiene.

// src/helpers/ERC20Internals.sol
function totalSupply_() internal view returns (uint256) {
assembly {
let slot := _totalSupply.slot
let supply := sload(slot)
// @> BAD PRACTICE: writing return data into scratch space
mstore(0x00, supply)
return(0x00, 0x20)
}
}

Risk

Likelihood: Low

  • The function is called frequently by off‑chain indexers, frontends, and on‑chain integrations; any future refactoring that assumes scratch space is untouched could conflict.

  • Assembly-heavy codebases often expand; introducing additional helpers that rely on scratch space around getter calls is a common scenario.

Impact: Low

  • Maintenance hazard / fragility: Clobbering scratch space can cause hard‑to‑diagnose bugs when additional inline assembly or compiler-generated helpers are added near this code.

  • Deviation from best practices: Using the free memory pointer keeps memory layout predictable and interoperable with other routines (including ABI helpers), improving safety and readability.

Proof of Concept

// No runtime revert needed to demonstrate.
// The issue is a memory hygiene violation rather than a functional bug.
// The snippet above shows return data prepared in scratch space instead of free memory.
// Future code that expects scratch space to be intact may malfunction.

Recommended Mitigation

  • Return via the free memory pointer. Load ptr := mload(0x40), store the value into ptr, and return (ptr, 0x20).

  • This follows standard memory hygiene and avoids touching scratch space.

function totalSupply_() internal view returns (uint256) {
assembly {
let slot := _totalSupply.slot
let supply := sload(slot)
- mstore(0x00, supply)
- return(0x00, 0x20)
+ // Use free memory pointer for return buffer
+ let ptr := mload(0x40)
+ mstore(ptr, supply)
+ return(ptr, 0x20)
}
}

Support

FAQs

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

Give us feedback!