diamond-detect 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +48 -31
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@ npx diamond-detect .
8
8
 
9
9
  ## Why this tool exists
10
10
 
11
- A Diamond proxy `delegatecall`s into many facet contracts, and **every facet shares the proxy's storage**. When two facets accidentally land at the same slot by reusing a Diamond Storage namespace, by drifting AppStorage layouts, by reusing an EIP-7201 id, or by writing literal slots in inline assembly the result is silent corruption: one facet's writes overwrite another's data with no error and no revert.
11
+ A Diamond proxy `delegatecall`s into many facet contracts, and **every facet shares the proxy's storage**. When two facets accidentally land at the same slot, whether by reusing a Diamond Storage namespace string, by hardcoding the same precomputed slot, by computing the same ERC-7201 namespace inline, by drifting AppStorage layouts, by reusing an EIP-7201 id, or by writing a literal slot directly in inline assembly, the result is silent corruption where one facet's writes overwrite another's data with no error and no revert.
12
12
 
13
13
  Slither catches general storage issues but doesn't speak Diamond. Most teams either hand-audit by spreadsheet or rely on a one-off script. `diamond-detect` is a focused, Diamond-specific analyzer you can drop into CI in three lines of YAML.
14
14
 
@@ -20,7 +20,7 @@ You should use it if:
20
20
  - You use Foundry to build (`out/` artifacts).
21
21
  - You want to catch namespace, AppStorage, EIP-7201, or inline-assembly slot collisions before they hit mainnet.
22
22
 
23
- You probably don't need it if you have only a handful of facets that all consume one canonical `LibAppStorage` and you read every storage layout diff manually. Even then, it's a 5-minute install worth running once.
23
+ You probably don't need it if you have only a handful of facets that all consume one canonical `LibAppStorage` and you read every storage layout diff manually. Even then, it's a 5-minute install that is worth running once.
24
24
 
25
25
  ## Install
26
26
 
@@ -60,37 +60,54 @@ This populates `out/` with artifact JSON files that include the AST and storage
60
60
  diamond-detect .
61
61
  ```
62
62
 
63
- If everything is fine you'll see:
63
+ If everything is fine you'll see a confirmation, plus every storage region the tool verified so you can see it actually inspected each one and that they all sit on distinct slots:
64
64
 
65
65
  ```
66
- scanned 12 contract artifact(s)
67
- ✓ no storage collisions detected
66
+ no storage collisions detected · 8 artifacts scanned
67
+
68
+ Verified 4 storage regions, each on its own slot:
69
+
70
+ • myapp.vaults erc7201 0x84d86c…b71bab LibVaults src/LibVaults.sol:12
71
+ • myapp.strategies namespace 0xa1b2c3…445566 LibStrategies src/LibStrategies.sol:9
72
+ • AAVE_STORAGE_SLOT precomputed 0x340080…215700 AaveFacet src/facets/AaveFacet.sol:28
73
+ • diamond.standard.diamond.storage namespace 0xc8fcad…2c131c LibDiamond src/libraries/LibDiamond.sol:8
74
+
75
+ Every facet keeps to its own namespace, and no two regions share a slot. Nicely done.
68
76
  ```
69
77
 
70
- If something is wrong you'll see one or more findings with the slot, the colliding contracts, and a hint at the cause:
78
+ If something is wrong you'll get one diagnostic per collision, with a code frame pointing at the exact line in every colliding file, the shared slot, and a hint at the cause:
71
79
 
72
80
  ```
73
- ERROR diamond-storage-namespace 0x84d86c34a05b71953e57fe7dafea685384b33934d9ddaebd0cf7709e74b71bab
74
- Diamond Storage namespace "myapp.strategies" is declared in 2 different sources, all resolving to the same slot.
75
- facets: LibStrategies, LibVaults
76
- at src/LibStrategies.sol
77
- at src/LibVaults.sol
81
+ error[diamond-storage-namespace]: Diamond Storage namespace "myapp.strategies" is declared in 2 different sources, all resolving to the same slot.
82
+ ╭─[src/LibStrategies.sol:5:5]
83
+
84
+ 5 │ bytes32 internal constant POSITION = keccak256("myapp.strategies");
85
+ · ────────────────────────────────────────────────────────────────── slot 0x84d86c…b71bab
86
+ ╰─
87
+ ╭─[src/LibVaults.sol:8:5]
88
+
89
+ 8 │ bytes32 internal constant POSITION = keccak256("myapp.strategies");
90
+ · ────────────────────────────────────────────────────────────────── same slot here
91
+ ╰─
92
+ = facets: LibStrategies, LibVaults
93
+ = slot: 0x84d86c34a05b71953e57fe7dafea685384b33934d9ddaebd0cf7709e74b71bab
94
+ = help: give every facet a unique storage seed; never reuse a namespace string, precomputed slot, or formula across facets
78
95
 
79
- 1 error(s), 0 warning(s)
96
+ 1 error · 2 artifacts scanned
80
97
  ```
81
98
 
82
99
  Exit code is `1` whenever a finding meets your `--severity` threshold (default `warn`), `0` otherwise, `2` on internal errors.
83
100
 
84
101
  ## What it detects
85
102
 
86
- Run [`examples/`](./examples/) to see each one in action every example ships a buggy `before/` and a fixed `after/`.
103
+ Run [`examples/`](./examples/) to see each one in action, since every example ships a buggy `before/` and a fixed `after/`.
87
104
 
88
105
  | Kind | Severity | What it catches |
89
106
  |---|---|---|
90
- | `diamond-storage-namespace` | error | Two libraries declare `bytes32 constant POSITION = keccak256("...")` with the same string. ([01-namespace-collision](./examples/01-namespace-collision/)) |
91
- | `appstorage-fingerprint` | error | The same fully-qualified struct (e.g. `struct LibAppStorage.AppStorage`) has different layouts across facets the stale-artifact / forgot-to-rebuild bug. ([02-appstorage-shift](./examples/02-appstorage-shift/)) |
107
+ | `diamond-storage-namespace` | error | Two facets resolve to the same Diamond Storage slot, whether the slot comes from `keccak256("...")`, a hardcoded precomputed literal (`bytes32 constant S = 0x..`), the inline ERC-7201 formula written without an annotation, or a direct `assembly { x.slot := <literal> }`. All four representations are compared in one space, so a literal in one facet that matches a formula or namespace in another is caught too. ([01-namespace-collision](./examples/01-namespace-collision/)) |
108
+ | `appstorage-fingerprint` | error | The same fully-qualified struct (e.g. `struct LibAppStorage.AppStorage`) has different layouts across facets, the stale-artifact or forgot-to-rebuild bug. ([02-appstorage-shift](./examples/02-appstorage-shift/)) |
92
109
  | `erc7201-namespace` | error | Two contracts annotate `@custom:storage-location erc7201:<id>` with the same id. ([03-erc7201-collision](./examples/03-erc7201-collision/)) |
93
- | `inheritance-overlap` | warn | Two facets have state at the same slot whose `(label, type)` differ e.g. `Ownable._owner` vs `MyOwnable.owner`. |
110
+ | `inheritance-overlap` | warn | Two facets have state at the same slot whose `(label, type)` differ, for example `Ownable._owner` vs `MyOwnable.owner`. |
94
111
  | `inline-assembly-slot` | info | A literal slot is written via `sstore(0x42, …)`. Usually intentional, but reported so you can confirm it doesn't overlap a computed Diamond Storage slot. |
95
112
 
96
113
  A clean baseline that exercises every analyzer and produces no findings is in [`examples/04-clean/`](./examples/04-clean/).
@@ -99,7 +116,7 @@ A clean baseline that exercises every analyzer and produces no findings is in [`
99
116
 
100
117
  ### Scope to your real facets with `--facets`
101
118
 
102
- By default `diamond-detect` analyzes every contract in `src/`. Diamond projects often have non-facet contracts there too registries, factories, libraries and the inheritance-overlap analyzer can produce noisy advisories for them. Tell it where your facets actually live:
119
+ By default `diamond-detect` analyzes every contract in `src/`. Diamond projects often have non-facet contracts there too (registries, factories, libraries), and the inheritance-overlap analyzer can produce noisy advisories for them. Tell it where your facets actually live:
103
120
 
104
121
  ```sh
105
122
  diamond-detect --facets 'src/facets/**' .
@@ -145,7 +162,7 @@ diamond-detect <path> Foundry project root or src/ folder
145
162
 
146
163
  ## Output formats
147
164
 
148
- - **Terminal** (default): coloured, one block per finding, summary footer.
165
+ - **Terminal** (default): a code-frame diagnostic per collision that underlines the exact slot declaration in every colliding file, with `= facets / = slot / = help` notes and a coloured summary footer. A clean run instead lists every storage region it verified, with its slot and location, so you can confirm nothing was skipped. Colour is auto-disabled when the output is piped or running in CI.
149
166
  - **JSON** (`--json`): a stable shape suitable for piping into other tools.
150
167
 
151
168
  ```json
@@ -158,8 +175,8 @@ diamond-detect <path> Foundry project root or src/ folder
158
175
  "slot": "0x...",
159
176
  "message": "...",
160
177
  "facets": ["LibStrategies", "LibVaults"],
161
- "locations": [{ "file": "src/LibStrategies.sol" }],
162
- "detail": { "namespaces": ["myapp.strategies"], "declarations": [...] }
178
+ "locations": [{ "file": "src/LibStrategies.sol", "line": 5, "src": "120:54:0" }],
179
+ "detail": { "namespaces": ["myapp.strategies"], "variableNames": ["POSITION"], "declarations": [...] }
163
180
  }
164
181
  ]
165
182
  }
@@ -194,25 +211,25 @@ Tighten with `--severity error` if you only want to fail CI on hard collisions.
194
211
 
195
212
  ## Troubleshooting
196
213
 
197
- **"warning: no AST found in any artifact"** your build didn't include AST output. Set `ast = true` in `foundry.toml` (under `[profile.default]`) and rebuild. Without AST, the namespace, EIP-7201, and inline-assembly analyzers can't run; only storage-layout-based ones (`appstorage-fingerprint`, `inheritance-overlap`) will fire.
214
+ **"warning: no AST found in any artifact"**: your build didn't include AST output. Set `ast = true` in `foundry.toml` (under `[profile.default]`) and rebuild. Without AST, the namespace, EIP-7201, and inline-assembly analyzers can't run; only storage-layout-based ones (`appstorage-fingerprint`, `inheritance-overlap`) will fire.
198
215
 
199
- **"Foundry out/ directory not found"** you haven't run `forge build` yet, or you pointed `diamond-detect` at the wrong directory. Pass either the project root (the directory with `foundry.toml`) or any subdirectory of it.
216
+ **"Foundry out/ directory not found"**: you haven't run `forge build` yet, or you pointed `diamond-detect` at the wrong directory. Pass either the project root (the directory with `foundry.toml`) or any subdirectory of it.
200
217
 
201
- **Scans `0` artifacts** the loader is filtering everything. If your facets live under non-standard paths (e.g. `src/diamond/**` and you also have files in `lib/diamond-3-hardhat/`), check whether the default-ignore is hiding them. Use `--no-default-ignore` to confirm, then add narrower `--ignore` patterns.
218
+ **Scans `0` artifacts**: the loader is filtering everything. If your facets live under non-standard paths (e.g. `src/diamond/**` and you also have files in `lib/diamond-3-hardhat/`), check whether the default-ignore is hiding them. Use `--no-default-ignore` to confirm, then add narrower `--ignore` patterns.
202
219
 
203
- **Lots of `inheritance-overlap` warnings on registries / factories** those are non-facet contracts. Scope the analyzer with `--facets 'src/facets/**'` (or wherever your facets live).
220
+ **Lots of `inheritance-overlap` warnings on registries / factories**: those are non-facet contracts. Scope the analyzer with `--facets 'src/facets/**'` (or wherever your facets live).
204
221
 
205
- **Findings only when I rebuild?** `forge build` is incremental. If you change a struct definition but don't touch the consumers, their artifacts stay stale and the analyzer doesn't see the new layout. Wipe with `forge clean && forge build` if you suspect drift.
222
+ **Findings only when I rebuild?** `forge build` is incremental. If you change a struct definition but don't touch the consumers, their artifacts stay stale and the analyzer doesn't see the new layout. Wipe with `forge clean && forge build` if you suspect drift.
206
223
 
207
224
  ## Comparison
208
225
 
209
- | Tool | Diamond Storage namespaces | EIP-7201 ids | AppStorage drift | Hardcoded sstore slots |
210
- |---|---|---|---|---|
211
- | Slither | partial general slot detector, not Diamond-aware | no | no | yes (separate detector) |
212
- | Hand-audit / spreadsheet | yes, manually | yes, manually | hard to spot | yes |
213
- | `diamond-detect` | yes | yes | yes | yes |
226
+ | Tool | Diamond Storage namespaces | Precomputed / inline-formula slots | EIP-7201 ids | AppStorage drift | Hardcoded assembly slots |
227
+ |---|---|---|---|---|---|
228
+ | Slither | partial, a general slot detector that is not Diamond-aware | no | no | no | partial, raw `sstore` only |
229
+ | Hand-audit / spreadsheet | yes, manually | error-prone by hand | yes, manually | hard to spot | yes, manually |
230
+ | `diamond-detect` | yes | yes | yes | yes | yes |
214
231
 
215
- Slither remains excellent for general Solidity static analysis. Use both.
232
+ Slither's storage layout does not model Diamond namespaced storage, which lives at hashed slots reached through assembly, so it cannot see a Diamond storage collision at all. It remains excellent for general Solidity static analysis, so run both.
216
233
 
217
234
  ## Roadmap
218
235
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diamond-detect",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Static analyzer for EIP-2535 Diamond storage-slot collisions across facets",
5
5
  "homepage": "https://github.com/jayeshy14/Diamond-Storage-Detector#readme",
6
6
  "repository": {