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.
- package/README.md +48 -31
- 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
|
|
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
|
|
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
|
-
|
|
67
|
-
|
|
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
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
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
|
|
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
|
|
91
|
-
| `appstorage-fingerprint` | error | The same fully-qualified struct (e.g. `struct LibAppStorage.AppStorage`) has different layouts across facets
|
|
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
|
|
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
|
|
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):
|
|
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"
|
|
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"
|
|
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
|
|
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
|
|
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?**
|
|
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
|
|
210
|
-
|
|
211
|
-
| Slither | partial
|
|
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
|
|
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