/storage-layout-safety
Type Thought-template (instantiate before use) - Trigger Pattern STORAGE_LAYOUT flag detected
$ npx -y skills add PlamenTSV/plamen --skill storage-layout-safety --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/storage-layout-safety
Context preview
The summary Claude sees to decide when to auto-load this skill.
Type Thought-template (instantiate before use) - Trigger Pattern STORAGE_LAYOUT flag detected
SKILL.md
storage-layout-safety.SKILL.mdname: "storage-layout-safety"
description: "Type Thought-template (instantiate before use) - Trigger Pattern STORAGE_LAYOUT flag detected"
Skill: Storage Layout Safety
> **Type**: Thought-template (instantiate before use) > **Trigger Pattern**: STORAGE_LAYOUT flag detected > **Inject Into**: depth-state-trace, depth-edge-case > **Finding prefix**: `[SLS-N]` > **Rules referenced**: R1, R4, R8, R10, R14
Covers: memory vs storage confusion, lost writes, proxy/upgrade storage collisions, inline assembly slot safety, and storage semantic corruption.
This vulnerability class exists ONLY on EVM - type-safe VMs (Move, Solana's Borsh model) enforce layout correctness at the runtime level. EVM's untyped 256-bit slot model permits silent corruption when layouts diverge.
---
Trigger Patterns
proxy|upgradeable|diamond|delegatecall|EIP1967|StorageSlot|
sstore|sload|assembly\s*\{|tstore|tload|reinitializer|
UUPSUpgradeable|TransparentUpgradeableProxy|BeaconProxy---
Step 1: Storage Surface Inventory
Map the contract's persistent state surface before analyzing bugs:
| # | Variable | Type | Slot Assignment | Written By | Read By | Proxy-Relevant? | |---|----------|------|----------------|-----------|---------|-----------------|
For each state variable, determine:
- Sequential layout (compiler-assigned) vs manual slot (EIP-1967, custom `bytes32` constant)?
- Accessed via Solidity or via assembly `sstore`/`sload`?
- For structs: trace slot computation (base + offset). For mappings: `keccak256(key . slot)`. For arrays: `keccak256(slot) + index`.
Tag: `[TRACE:variable={name} → slot={computation} → writers={functions}]`
---
Step 2: Memory vs Storage Confusion
For each function operating on structs or complex types:
2a. Reference Type Assignment
Trace every local variable of struct, array, or mapping type:
- Declared as `storage` or `memory`?
- If `memory`: is the function INTENDING to modify persistent state? If yes → lost write (copy modified in memory, never persisted).
- If `storage`: does every code path that modifies the reference complete without early return before the write?
2b. Parameter Data Location
For each function accepting struct/array parameters:
- Is the parameter `memory` or `calldata`?
- Does the function modify the parameter expecting persistence? `function update(MyStruct memory s)` modifies `s.field` but `s` is a memory copy - original unchanged.
2c. Library Forwarding
For libraries called via `using ... for`:
- Does the library function take `storage` or `memory` references?
- Mismatch between caller expectation and library signature → silent behavioral change.
Tag: `[TRACE:function={name} → var={var} → location={memory/storage} → write_persisted={YES/NO}]`
---
Step 3: Proxy Storage Layout Analysis
3a. Implementation vs Proxy Slot Overlap
- Map slots used by PROXY (admin, implementation, beacon).
- Map slots used by IMPLEMENTATION (state variables from slot 0).
- Any overlap? For EIP-1967: verify randomized slots match spec (`bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)`).
3b. Upgrade Layout Continuity
For each upgrade path (V1 → V2):
- V1 variables in SAME slots in V2? (no reordering, no type changes, no removed mid-sequence variables)
- New variables APPENDED after existing? (not inserted)
- Inheritance order identical? (different order = different slot assignment)
- `__gap` storage slots reserved? New variables consuming gap correctly?
3c. Diamond / Namespaced Storage
For EIP-2535 or namespaced storage:
- Each facet uses unique namespace (keccak256 of distinct string)?
- Can two facets share the same namespace accidentally?
- Storage structs within namespace consistent across facet upgrades?
Tag: `[TRACE:proxy_slot={N} → impl_var={name} → collision={YES/NO}]`
---
Step 4: Assembly Storage Safety
For each inline assembly block using `sstore` or `sload`:
4a. Slot Computation
- Target slot hardcoded, constant-derived, or influenced by external input?
- If input-influenced → can attacker target ARBITRARY slots? Is slot value bounded/validated before `sstore`?
4b. Value Encoding
- Correctly handles types < 32 bytes? (`sstore` writes full 32 bytes - masking/shifting correct for packed slots?)
- For packed storage (multiple variables in one slot): does assembly preserve neighboring values?
4c. Transient Storage (EIP-1153)
If `tstore`/`tload` used:
- Correctly distinguished from `sstore`/`sload`? (transient cleared after tx, permanent is not)
- Critical state accidentally stored with `tstore` instead of `sstore`?
Tag: `[BOUNDARY:user_input={MAX} → computed_slot={value} → target={what_gets_overwritten}]`
4d. Hardcoded Offset into ABI-Encoded Data
**Processing**: ENUMERATE all `calldataload`/`mload(add(` sites with literal offsets + all byte-slicing with hardcoded N on dynamic-type data → PROCESS each against the criteria below → COVERAGE GATE before moving to Step 5.
**Scope**: Any code that reads from ABI-encoded data using hardcoded byte offsets rather than following offset pointers. This includes:
- `calldataload(N)` in assembly (raw calldata)
- `mload(add(data, N))` in assembly (bytes memory/calldata variable)
- `data[N:]` or `data[N:N+32]` byte-slicing in Solidity with hardcoded N
- Hardcoded offset arithmetic into nested `bytes` fields after `abi.decode`
Grep: `calldataload\(` with a numeric literal, `mload(add(` with a literal offset on a bytes variable, fixed-offset byte-slicing on decoded `bytes` data.
| Read Site | Mechanism | Offset | Hardcoded? | Into Dynamic-Type Content? | Value Used For | Same Value Read via abi.decode? | |-----------|-----------|--------|-----------|---------------------------|----------------|-------------------------------|
**Root cause**: ABI encoding is a convention, not enforced by the EVM. Dynamic types (`bytes`, `string`, `T[]`) use offset pointers — the content can be placed anywhere the pointer says. Hardcoded offsets assume
Read more
name: "storage-layout-safety" description: "Type Thought-template (instantiate before use) - Trigger Pattern STORAGE_LAYOUT flag detected"
Skill: Storage Layout Safety
> **Type**: Thought-template (instantiate before use) > **Trigger Pattern**: STORAGE_LAYOUT flag detected > **Inject Into**: depth-state-trace, depth-edge-case > **Finding prefix**: `[SLS-N]` > **Rules referenced**: R1, R4, R8, R10, R14
Covers: memory vs storage confusion, lost writes, proxy/upgrade storage collisions, inline assembly slot safety, and storage semantic corruption.
This vulnerability class exists ONLY on EVM - type-safe VMs (Move, Solana's Borsh model) enforce layout correctness at the runtime level. EVM's untyped 256-bit slot model permits silent corruption when layouts diverge.
---
Trigger Patterns
proxy|upgradeable|diamond|delegatecall|EIP1967|StorageSlot|
sstore|sload|assembly\s*\{|tstore|tload|reinitializer|
UUPSUpgradeable|TransparentUpgradeableProxy|BeaconProxy---
Step 1: Storage Surface Inventory
Map the contract's persistent state surface before analyzing bugs:
| # | Variable | Type | Slot Assignment | Written By | Read By | Proxy-Relevant? | |---|----------|------|----------------|-----------|---------|-----------------|
For each state variable, determine:
- Sequential layout (compiler-assigned) vs manual slot (EIP-1967, custom `bytes32` constant)?
- Accessed via Solidity or via assembly `sstore`/`sload`?
- For structs: trace slot computation (base + offset). For mappings: `keccak256(key . slot)`. For arrays: `keccak256(slot) + index`.
Tag: `[TRACE:variable={name} → slot={computation} → writers={functions}]`
---
Step 2: Memory vs Storage Confusion
For each function operating on structs or complex types:
2a. Reference Type Assignment
Trace every local variable of struct, array, or mapping type:
- Declared as `storage` or `memory`?
- If `memory`: is the function INTENDING to modify persistent state? If yes → lost write (copy modified in memory, never persisted).
- If `storage`: does every code path that modifies the reference complete without early return before the write?
2b. Parameter Data Location
For each function accepting struct/array parameters:
- Is the parameter `memory` or `calldata`?
- Does the function modify the parameter expecting persistence? `function update(MyStruct memory s)` modifies `s.field` but `s` is a memory copy - original unchanged.
2c. Library Forwarding
For libraries called via `using ... for`:
- Does the library function take `storage` or `memory` references?
- Mismatch between caller expectation and library signature → silent behavioral change.
Tag: `[TRACE:function={name} → var={var} → location={memory/storage} → write_persisted={YES/NO}]`
---
Step 3: Proxy Storage Layout Analysis
3a. Implementation vs Proxy Slot Overlap
- Map slots used by PROXY (admin, implementation, beacon).
- Map slots used by IMPLEMENTATION (state variables from slot 0).
- Any overlap? For EIP-1967: verify randomized slots match spec (`bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)`).
3b. Upgrade Layout Continuity
For each upgrade path (V1 → V2):
- V1 variables in SAME slots in V2? (no reordering, no type changes, no removed mid-sequence variables)
- New variables APPENDED after existing? (not inserted)
- Inheritance order identical? (different order = different slot assignment)
- `__gap` storage slots reserved? New variables consuming gap correctly?
3c. Diamond / Namespaced Storage
For EIP-2535 or namespaced storage:
- Each facet uses unique namespace (keccak256 of distinct string)?
- Can two facets share the same namespace accidentally?
- Storage structs within namespace consistent across facet upgrades?
Tag: `[TRACE:proxy_slot={N} → impl_var={name} → collision={YES/NO}]`
---
Step 4: Assembly Storage Safety
For each inline assembly block using `sstore` or `sload`:
4a. Slot Computation
- Target slot hardcoded, constant-derived, or influenced by external input?
- If input-influenced → can attacker target ARBITRARY slots? Is slot value bounded/validated before `sstore`?
4b. Value Encoding
- Correctly handles types < 32 bytes? (`sstore` writes full 32 bytes - masking/shifting correct for packed slots?)
- For packed storage (multiple variables in one slot): does assembly preserve neighboring values?
4c. Transient Storage (EIP-1153)
If `tstore`/`tload` used:
- Correctly distinguished from `sstore`/`sload`? (transient cleared after tx, permanent is not)
- Critical state accidentally stored with `tstore` instead of `sstore`?
Tag: `[BOUNDARY:user_input={MAX} → computed_slot={value} → target={what_gets_overwritten}]`
4d. Hardcoded Offset into ABI-Encoded Data
**Processing**: ENUMERATE all `calldataload`/`mload(add(` sites with literal offsets + all byte-slicing with hardcoded N on dynamic-type data → PROCESS each against the criteria below → COVERAGE GATE before moving to Step 5.
**Scope**: Any code that reads from ABI-encoded data using hardcoded byte offsets rather than following offset pointers. This includes:
- `calldataload(N)` in assembly (raw calldata)
- `mload(add(data, N))` in assembly (bytes memory/calldata variable)
- `data[N:]` or `data[N:N+32]` byte-slicing in Solidity with hardcoded N
- Hardcoded offset arithmetic into nested `bytes` fields after `abi.decode`
Grep: `calldataload\(` with a numeric literal, `mload(add(` with a literal offset on a bytes variable, fixed-offset byte-slicing on decoded `bytes` data.
| Read Site | Mechanism | Offset | Hardcoded? | Into Dynamic-Type Content? | Value Used For | Same Value Read via abi.decode? | |-----------|-----------|--------|-----------|---------------------------|----------------|-------------------------------|
**Root cause**: ABI encoding is a convention, not enforced by the EVM. Dynamic types (`bytes`, `string`, `T[]`) use offset pointers — the content can be placed anywhere the pointer says. Hardcoded offsets assume
Autonomous Web3 security auditor for Claude Code and OpenAI Codex CLI. Orchestrates 18-100 AI agents across 40+ phases to produce audit reports with verified PoC exploits — for smart contracts and L1 node-client infrastructure.
Repo: PlamenTSV/plamen
Other skills on plamen.
- /ability-analysis
Trigger Pattern Always (Aptos Move) - foundational security check - Inject Into Breadth agents, depth agents
Open skill - /bit-shift-safety
Trigger Pattern Always (Aptos Move) - Move VM aborts on shift = bit width - Inject Into Breadth agents, depth-edge-case
Open skill - /centralization-risk
Trigger Protocol has privileged roles (admin, operator, governance, resource account owner) - Covers Single points of failure, privilege escalation, external governance dependen...
Open skill - /cross-chain-timing
Trigger Pattern wormhole|layerzero|ccip|bridge|cross_chain|vaa|guardian|emitter|relay|remote_chain|payload|nonce.sequence - Inject Into Breadth agents, depth-external
Open skill - /dependency-audit
Trigger EXTERNAL_LIB flag detected (protocol uses third-party Move dependencies) - Used by Breadth agents, depth-external
Open skill - /economic-design-audit
Trigger Pattern MONETARY_PARAMETER flag (required) - Inject Into Breadth agents (merged via M4 hierarchy)
Open skill

