agentleFS
iktok90-designiktok90-design/ai-smart-contract-auditor/skills/storage-layout/SKILL.md

storage-layout

Skill36 starsChanged 5 months agoMITView on GitHub

Detect storage-layout issues in upgradeable contracts — slot collisions, slot reuse, packing changes, missing gap. Activate when reviewing UUPS/Transparent proxies, OZ Upgradeable contracts, diamonds (EIP-2535), libraries with structs, or any contract using assembly to read storage slots.

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 2 are distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use EIP-1967 slots (bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)).

    line 30
  2. Very specific:

    Use namespaced storage (keccak256("myapp.storage.foo")).

    line 49
  3. Very specific:

    Run forge inspect <Contract> storageLayout for V1 and V2.

    line 73
  4. Distinctive:

    Use EIP-1967 / EIP-7201 for namespaced storage in libraries and diamonds.

    line 76
  5. Distinctive:

    Never modify or remove existing storage slots — only append.

    line 77

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Storage layout detection

When this applies

  • UUPS / Transparent proxy upgrades
  • OpenZeppelin *Upgradeable contracts
  • EIP-2535 Diamonds with shared storage
  • Libraries that read storage via assembly
  • Any change that modifies inheritance order or struct layout in an upgradeable contract
  • Initializers that depend on storage being zero

Detection patterns

Missing __gap (HIGH)

contract BaseUpgradeable {
    uint256 public foo;
    // no uint256[50] __gap;   ← can't append fields to derived contracts safely
}

OZ convention: each base contract reserves __gap for future fields.

Slot collision between proxy and implementation (CRITICAL)

Implementation defines address public owner in slot 0; proxy uses slot 0 for its admin. Hello, hijacked proxy. Use EIP-1967 slots (bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)).

Inheritance reorder breaks layout (HIGH)

// V1: contract X is A, B, C
// V2: contract X is A, C, B   ← B's vars now at C's old slots

Even one append to a parent breaks all descendants.

Struct field reorder/insert (HIGH)

Solidity packs adjacent same-bit-width fields. Inserting a bool between two uint256 adds a slot. Verify with forge inspect <Contract> storage.

Type widening (HIGH)

uint8 → uint256 changes packing.

Library uses fixed assembly slot (MEDIUM-HIGH)

assembly { sstore(0x0, value) }   // ← collides with the consumer's slot 0

Use namespaced storage (keccak256("myapp.storage.foo")).

Diamond storage namespace collision (HIGH)

Two facets using the same bytes32 STORAGE_POSITION. Always derive from a unique namespace string.

ERC-7201 namespaced storage missing on new code (MEDIUM)

Modern best-practice: use @custom:storage-location erc7201:my.namespace.

Severity rubric

PatternSeverity
Proxy/impl slot 0 collisionCritical
Slot reuse after upgrade (data corruption)Critical
Struct reorder in upgradeHigh
Inheritance reorderHigh
Missing __gap in upgradeable baseHigh
Diamond storage collisionHigh
Library writing to fixed low slotsHigh
ERC-7201 missing on new namespaced moduleMedium
Variable rename only (same slot)Info

Remediation patterns

  • Run forge inspect <Contract> storageLayout for V1 and V2; diff JSON. Any non-append change = abort.
  • OpenZeppelin Upgrades Plugin: validateUpgrade() catches most layout breaks at build time.
  • Each upgradeable base contract reserves uint256[N] private __gap at the end (size by convention).
  • Use EIP-1967 / EIP-7201 for namespaced storage in libraries and diamonds.
  • Never modify or remove existing storage slots — only append.

False-positive notes

  • Adding a constant or immutable variable doesn't change storage layout (lives in bytecode, not storage).
  • Pure function additions don't affect layout.
  • Internal helper changes within a function don't affect layout.

Related

  • [[initialization]] — re-init after upgrade
  • [[delegatecall-risks]] — proxy mechanics
  • [[upgrade-safety]] (command/skill bridge)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.