slotdiff 0.0.0-stage → 0.1.0-alpha.1

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 (54) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +10 -0
  3. package/README.md +213 -2
  4. package/dist/bytecode.d.ts +46 -0
  5. package/dist/bytecode.d.ts.map +1 -0
  6. package/dist/bytecode.js +101 -0
  7. package/dist/bytecode.js.map +1 -0
  8. package/dist/chain.d.ts +20 -0
  9. package/dist/chain.d.ts.map +1 -0
  10. package/dist/chain.js +52 -0
  11. package/dist/chain.js.map +1 -0
  12. package/dist/diff.d.ts +15 -0
  13. package/dist/diff.d.ts.map +1 -0
  14. package/dist/diff.js +11 -0
  15. package/dist/diff.js.map +1 -0
  16. package/dist/errors.d.ts +18 -0
  17. package/dist/errors.d.ts.map +1 -0
  18. package/dist/errors.js +24 -0
  19. package/dist/errors.js.map +1 -0
  20. package/dist/explorer.d.ts +42 -0
  21. package/dist/explorer.d.ts.map +1 -0
  22. package/dist/explorer.js +131 -0
  23. package/dist/explorer.js.map +1 -0
  24. package/dist/index.d.ts +11 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +10 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/reconstruct.d.ts +27 -0
  29. package/dist/reconstruct.d.ts.map +1 -0
  30. package/dist/reconstruct.js +140 -0
  31. package/dist/reconstruct.js.map +1 -0
  32. package/dist/record.d.ts +48 -0
  33. package/dist/record.d.ts.map +1 -0
  34. package/dist/record.js +91 -0
  35. package/dist/record.js.map +1 -0
  36. package/dist/resolve.d.ts +54 -0
  37. package/dist/resolve.d.ts.map +1 -0
  38. package/dist/resolve.js +80 -0
  39. package/dist/resolve.js.map +1 -0
  40. package/dist/solc.d.ts +38 -0
  41. package/dist/solc.d.ts.map +1 -0
  42. package/dist/solc.js +194 -0
  43. package/dist/solc.js.map +1 -0
  44. package/package.json +70 -4
  45. package/src/bytecode.ts +141 -0
  46. package/src/chain.ts +75 -0
  47. package/src/diff.ts +31 -0
  48. package/src/errors.ts +24 -0
  49. package/src/explorer.ts +190 -0
  50. package/src/index.ts +36 -0
  51. package/src/reconstruct.ts +228 -0
  52. package/src/record.ts +127 -0
  53. package/src/resolve.ts +138 -0
  54. package/src/solc.ts +268 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0-alpha.1
4
+
5
+ First release.
6
+
7
+ - `resolveProxyLayout` / `resolveLayout`: the storage layout of the code a proxy (ERC-1967 or
8
+ beacon) runs now, rebuilt from verified standard-json source on any Etherscan-compatible explorer
9
+ and proven against the deployed code.
10
+ - `proveLocalBuild`: a layout record from your own build, only when it is the deployed build.
11
+ - `diffLayouts`: OpenZeppelin's storage comparison, including ERC-7201 namespaced storage.
12
+ - `LayoutRecord`s are plain JSON that slotdiff never writes. Load them back with `verifyRecord`
13
+ (checked against the chain id and deployed code) or `parseRecord` (offline).
14
+ - A match proves a layout only when the build embeds solc's metadata hash. Builds with
15
+ `bytecodeHash: "none"` or `appendCBOR: false` are reported as `code-only` and refused.
16
+ - `getSolc`: native solc from a cache (Hardhat's included) or a checksum-verified download.
17
+ Accepts release versions such as `0.8.24`, as Foundry's build info records them.
18
+ - `layoutFromSource` accepts build-info input from Foundry and Hardhat.
19
+ - Loads from ESM and CommonJS. Node 22.12+.
package/LICENSE ADDED
@@ -0,0 +1,10 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rohit Solia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10
+
package/README.md CHANGED
@@ -1,3 +1,214 @@
1
- # Temporary Holding Version
1
+ # slotdiff
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Storage layout diff for upgradeable Solidity contracts, where either side can be a local build or
4
+ the code a proxy actually runs on chain.
5
+
6
+ ```ts
7
+ const live = await resolveProxyLayout(provider, proxy, { explorer: { apiKey } });
8
+ const report = diffLayouts(
9
+ live.record.layout,
10
+ await layoutFromSource(input, solc, "src/Vault.sol:Vault"),
11
+ );
12
+ if (!report.ok) console.log(report.explain());
13
+ ```
14
+
15
+ ## Why
16
+
17
+ An upgrade is only safe relative to what the proxy runs **now**. Under lazy or multisig-queued
18
+ upgrades that is often not your latest build, and the tools you have compare the wrong thing:
19
+
20
+ - OpenZeppelin's `validate` compares two local builds. It has no way to know what is live.
21
+ - `cast storage <address>` prints a live layout but does not compare it with anything.
22
+ - Deployment artifacts describe the last build you deployed, not necessarily what the proxy runs.
23
+
24
+ slotdiff reads the implementation behind the proxy, rebuilds its layout from verified source, and
25
+ **proves** that source compiles to the deployed code before trusting it. It then compares with
26
+ OpenZeppelin's own rules, including ERC-7201 namespaced storage. Framework-agnostic: Foundry,
27
+ Hardhat 2 and 3, or a plain script.
28
+
29
+ ## Install
30
+
31
+ ```sh
32
+ npm install slotdiff
33
+ ```
34
+
35
+ Node 22.12+. Works from ESM and CommonJS.
36
+
37
+ ## Usage
38
+
39
+ ### Live proxy vs a local build
40
+
41
+ ```ts
42
+ import { readFileSync } from "node:fs";
43
+ import { JsonRpcProvider } from "ethers";
44
+ import { diffLayouts, getSolc, layoutFromSource, resolveProxyLayout } from "slotdiff";
45
+
46
+ // Any object with send(method, params): an ethers JsonRpcProvider or Hardhat's network provider.
47
+ const provider = new JsonRpcProvider(process.env.RPC_URL);
48
+ const apiKey = process.env.ETHERSCAN_API_KEY;
49
+
50
+ // 1. The layout of whatever the proxy runs now, proven against its deployed code.
51
+ const live = await resolveProxyLayout(provider, "0xProxy", { explorer: { apiKey } });
52
+
53
+ // 2. The layout of the new version, from your build info (see below for each tool).
54
+ const buildInfo = JSON.parse(readFileSync("out/build-info/<id>.json", "utf8"));
55
+ const solc = await getSolc(buildInfo.solcLongVersion);
56
+ const next = await layoutFromSource(buildInfo.input, solc, "src/Vault.sol:Vault");
57
+
58
+ // 3. Compare.
59
+ const report = diffLayouts(live.record.layout, next);
60
+ if (!report.ok) {
61
+ console.error(report.explain());
62
+ process.exit(1);
63
+ }
64
+ ```
65
+
66
+ Where the build info lives:
67
+
68
+ | Tool | Build info | Contract name |
69
+ | --------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------- |
70
+ | Foundry | `out/build-info/*.json`, only with `build_info = true` in `foundry.toml` or `forge build --build-info` | `src/Vault.sol:Vault` |
71
+ | Hardhat 2 | `artifacts/build-info/*.json` | `contracts/Vault.sol:Vault` |
72
+ | Hardhat 3 | `artifacts/build-info/<id>.json`; the compiler output is in `<id>.output.json` next to it | `project/contracts/Vault.sol:Vault` |
73
+
74
+ Each holds `input` and `solcLongVersion`. Use the source name exactly as it appears in `input.sources`.
75
+
76
+ `explain()` gives OpenZeppelin's report, for example:
77
+
78
+ ```
79
+ src/Vault.sol:7: Inserted `inserted`
80
+ > New variables should be placed after all existing inherited variables
81
+ ```
82
+
83
+ The explorer defaults to Etherscan's v2 API, where one key covers every chain it indexes. For another
84
+ Etherscan-compatible explorer, such as Blockscout, pass `{ apiUrl }` (a key is optional there).
85
+
86
+ ### Keep records, skip the rebuild next time
87
+
88
+ Rebuilding means fetching source and compiling. A `LayoutRecord` is plain JSON: store it wherever
89
+ suits you (committed next to your deployments, a CI cache) and load it back with `verifyRecord`,
90
+ which refuses it unless this chain still runs the exact code it was proven against.
91
+
92
+ ```ts
93
+ import { readFileSync, writeFileSync } from "node:fs";
94
+ import { readImplementation, resolveLayout, verifyRecord } from "slotdiff";
95
+
96
+ const impl = await readImplementation(provider, "0xProxy"); // what the proxy runs now
97
+ const path = `layouts/${impl}.json`;
98
+
99
+ let record;
100
+ try {
101
+ record = await verifyRecord(provider, impl, JSON.parse(readFileSync(path, "utf8")));
102
+ } catch {
103
+ record = await resolveLayout(provider, impl, { explorer: { apiKey } });
104
+ writeFileSync(path, JSON.stringify(record, null, 2));
105
+ }
106
+ ```
107
+
108
+ Key records by **implementation**, and read the implementation from the proxy every time. A record
109
+ for an old implementation still verifies after the proxy is upgraded, because that code is still
110
+ deployed at its own address.
111
+
112
+ Without an RPC, `parseRecord(value, address)` checks a record's shape and proof offline. It cannot
113
+ tell whether the proxy has moved on, so prefer `verifyRecord` whenever you can reach the chain.
114
+
115
+ A record is bound to the deployed code, not to its own `layout` field: an edited layout in a saved
116
+ record still verifies. Treat saved records as trusted input and review changes to them like code.
117
+
118
+ ### No explorer: prove your own build
119
+
120
+ If you deployed it yourself, your build can serve as the source. `proveLocalBuild` compares the
121
+ deployed code with your compiler output and returns a record only when they prove to be the same
122
+ build.
123
+
124
+ ```ts
125
+ import { proveLocalBuild } from "slotdiff";
126
+
127
+ // Hardhat 3: read `output` from `<id>.output.json` instead.
128
+ const compiled = buildInfo.output.contracts["src/Vault.sol"].Vault.evm.deployedBytecode;
129
+ const { bytecodeMatch, record } = await proveLocalBuild(provider, impl, {
130
+ contract: "src/Vault.sol:Vault",
131
+ compiler: solc.longVersion,
132
+ layout: await layoutFromSource(buildInfo.input, solc, "src/Vault.sol:Vault"),
133
+ deployedBytecode: compiled.object,
134
+ immutableReferences: compiled.immutableReferences,
135
+ });
136
+ if (record === undefined) throw new Error(`Not the deployed build (${bytecodeMatch})`);
137
+ ```
138
+
139
+ ## What counts as proof
140
+
141
+ A layout is only trusted when the compiled code matches the deployed code as `exact` or
142
+ `immutables-only` (identical once immutables and library addresses are masked), **and** the build
143
+ embeds solc's metadata hash. That hash covers every source file and setting, so matching code then
144
+ means matching source.
145
+
146
+ Anything weaker is refused, because variables no code reads, retyped fields and gap sizes never
147
+ reach the bytecode, so a different layout can compile to the same code:
148
+
149
+ | Match | Meaning | Proof? |
150
+ | ----------------- | ----------------------------------------------------------------------- | ------ |
151
+ | `exact` | Byte-identical | Yes |
152
+ | `immutables-only` | Identical once immutables and library links are masked | Yes |
153
+ | `metadata-only` | Identical only after stripping metadata: the source differed | No |
154
+ | `code-only` | Identical, but built with `bytecodeHash: "none"` or `appendCBOR: false` | No |
155
+ | `none` | Different code | No |
156
+
157
+ Contracts built without a metadata hash (common for deterministic CREATE2 deployments) therefore
158
+ cannot be proven. For explorer source, the verified compiler settings are checked as well, so a
159
+ build without a hash is refused even if its code happens to end in bytes shaped like one. With
160
+ `proveLocalBuild`, the hash is read from your own compiler output.
161
+
162
+ ## Errors
163
+
164
+ slotdiff fails closed: it never treats "could not check" as "safe".
165
+
166
+ - `LayoutUnavailableError`: no answer. Not a proxy, unverified source, explorer outage or rate
167
+ limit, no compiler for the platform. Falling back to another source is reasonable.
168
+ - `LayoutIntegrityError`: an answer that cannot be trusted. Source that does not compile to the
169
+ deployed code, a weak match, a record from another chain or for other code. Never fall back on
170
+ this one.
171
+
172
+ A few problems that are yours to fix throw a plain `Error`: a rejected explorer API key, a compiler
173
+ binary that fails to run (for example the wrong architecture), and a record in a format from a
174
+ newer slotdiff.
175
+
176
+ ## API
177
+
178
+ | Export | Purpose |
179
+ | ------------------------------------------------------------ | ------------------------------------------------------------------------ |
180
+ | `diffLayouts(before, after, { unsafeAllowRenames? })` | OpenZeppelin's storage comparison. `ok` and `explain()` |
181
+ | `resolveProxyLayout(provider, proxy, options)` | Proven layout of what a proxy (ERC-1967 or beacon) runs now |
182
+ | `resolveLayout(provider, address, options)` | Proven layout of the code at an implementation address |
183
+ | `proveLocalBuild(provider, address, build)` | A record from your own build, only when it is the deployed build |
184
+ | `verifyRecord(provider, address, value)` | Load a saved record, checked against the chain |
185
+ | `parseRecord(value, address?)` | Load a saved record, checked offline |
186
+ | `layoutFromSource(input, solc, "file.sol:Name")` | Layout from standard-json input, including ERC-7201 namespaces. No proof |
187
+ | `getSolc(version)` | Native solc, from a cache or downloaded and checksum-verified |
188
+ | `readProxy`, `readImplementation`, `readCode`, `readChainId` | Chain reads |
189
+ | `fetchVerifiedSource(chainId, address, explorer)` | Verified standard-json source from an Etherscan-compatible explorer |
190
+ | `compareDeployedBytecode`, `isProvingMatch` | The match classification above |
191
+
192
+ `getSolc` takes a long (`0.8.24+commit.e11b9ed9`) or release (`0.8.24`) version. Compilers are
193
+ cached under your OS cache directory (`slotdiff/compilers`, or `cacheDir`), and Hardhat's compiler
194
+ cache is reused when present.
195
+
196
+ Renames and retypes follow OpenZeppelin's NatSpec tags, `@custom:oz-renamed-from` and
197
+ `@custom:oz-retyped-from`.
198
+
199
+ ## Limitations
200
+
201
+ - Explorer source must be verified as standard-json. Flattened and multi-file verifications do not
202
+ record every compiler setting, so they are refused.
203
+ - Solidity only.
204
+ - Struct members cannot be renamed or retyped: Solidity has no NatSpec on struct members, so
205
+ OpenZeppelin's tags cannot reach them.
206
+ - ERC-7201 namespaces are located by their `@custom:storage-location erc7201:<id>` annotation, as
207
+ OpenZeppelin does. The `bytes32` slot constant your code actually uses is not checked against
208
+ it, so moving the constant while leaving the annotation unchanged is not detected. Keep the two in
209
+ sync, for example by deriving the constant with the ERC-7201 formula in a test.
210
+ - Library only for now. A CLI is planned.
211
+
212
+ ## License
213
+
214
+ MIT
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Bytecode comparison utilities.
3
+ *
4
+ * Solidity appends a CBOR-encoded metadata hash to the end of every compiled
5
+ * contract's bytecode. The last two bytes encode the length of that metadata
6
+ * section. Stripping it allows comparing contracts that are logically identical
7
+ * but were compiled in different environments (different metadata).
8
+ */
9
+ export declare function stripBytecodeMetadata(bytecode: string): string;
10
+ /**
11
+ * solc's `evm.deployedBytecode.immutableReferences`: AST id to the byte spans
12
+ * (relative to the deployed bytecode) that the constructor fills in.
13
+ */
14
+ export type ImmutableReferences = Record<string, ReadonlyArray<{
15
+ start: number;
16
+ length: number;
17
+ }>>;
18
+ /**
19
+ * - `exact`: byte-identical.
20
+ * - `immutables-only`: identical once immutable (and library-link) spans are
21
+ * masked. Same code, deployment-specific values. Every UUPS implementation
22
+ * lands here because `UUPSUpgradeable.__self` is immutable.
23
+ * - `metadata-only`: identical after masking and stripping the CBOR metadata,
24
+ * so the compiler input was not byte-identical (source hashes or settings).
25
+ * NOT proof of storage layout: variables no code reads, gap sizes and field
26
+ * names do not reach the bytecode, so different layouts can share code.
27
+ * - `code-only`: identical (exactly or once masked), but the build embeds no
28
+ * source hash (`bytecodeHash: "none"` or `appendCBOR: false`), so nothing
29
+ * ties the code to its source. NOT proof of layout, for the same reason.
30
+ * - `none`: different code.
31
+ *
32
+ * `exact` and `immutables-only` therefore always mean the source is proven too.
33
+ */
34
+ export type DeployedBytecodeMatch = "exact" | "immutables-only" | "metadata-only" | "code-only" | "none";
35
+ /** The matches that prove a source is the deployed code, and so prove its layout. */
36
+ export type ProvingMatch = "exact" | "immutables-only";
37
+ export declare function isProvingMatch(match: DeployedBytecodeMatch): match is ProvingMatch;
38
+ /**
39
+ * Compares runtime code read with `eth_getCode` against compiler output.
40
+ *
41
+ * On-chain code has immutables filled in while compiler output has them
42
+ * zeroed, and the difference sits in the code body where metadata stripping
43
+ * cannot reach, so the spans are zeroed on both sides before comparing.
44
+ */
45
+ export declare function compareDeployedBytecode(onchain: string, compiled: string, immutableReferences?: ImmutableReferences): DeployedBytecodeMatch;
46
+ //# sourceMappingURL=bytecode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytecode.d.ts","sourceRoot":"","sources":["../src/bytecode.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK9D;AA8CD;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC;AAEnG;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,qBAAqB,GAC/B,OAAO,GAAG,iBAAiB,GAAG,eAAe,GAAG,WAAW,GAAG,MAAM,CAAC;AAEvE,qFAAqF;AACrF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,iBAAiB,CAAC;AAEvD,wBAAgB,cAAc,CAAC,KAAK,EAAE,qBAAqB,GAAG,KAAK,IAAI,YAAY,CAElF;AAKD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,mBAAmB,GAAE,mBAAwB,GAC5C,qBAAqB,CAoBvB"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Bytecode comparison utilities.
3
+ *
4
+ * Solidity appends a CBOR-encoded metadata hash to the end of every compiled
5
+ * contract's bytecode. The last two bytes encode the length of that metadata
6
+ * section. Stripping it allows comparing contracts that are logically identical
7
+ * but were compiled in different environments (different metadata).
8
+ */
9
+ export function stripBytecodeMetadata(bytecode) {
10
+ const buf = toBuffer(bytecode);
11
+ const start = cborStart(buf);
12
+ if (start === undefined)
13
+ return bytecode;
14
+ return "0x" + buf.subarray(0, start).toString("hex");
15
+ }
16
+ function toBuffer(bytecode) {
17
+ return Buffer.from(bytecode.startsWith("0x") ? bytecode.slice(2) : bytecode, "hex");
18
+ }
19
+ // Where solc's CBOR metadata starts, or undefined when the tail is not CBOR.
20
+ function cborStart(buf) {
21
+ if (buf.length < 2)
22
+ return undefined;
23
+ const metadataLength = buf.readUInt16BE(buf.length - 2);
24
+ if (metadataLength === 0 || metadataLength + 2 > buf.length)
25
+ return undefined;
26
+ // solc's metadata is a CBOR map (major type 5). Without that marker the
27
+ // tail is code (e.g. compiled with appendCBOR: false), and stripping it
28
+ // would hide real differences.
29
+ const start = buf.length - 2 - metadataLength;
30
+ return buf[start] >> 5 === 5 ? start : undefined;
31
+ }
32
+ // The CBOR keys solc uses for the metadata hash, each with its byte-string header.
33
+ const SOURCE_HASH_KEYS = [
34
+ Buffer.from("6469706673" + "5822", "hex"), // "ipfs", 34 bytes
35
+ Buffer.from("65627a7a7230" + "5820", "hex"), // "bzzr0", 32 bytes
36
+ Buffer.from("65627a7a7231" + "5820", "hex"), // "bzzr1", 32 bytes
37
+ ];
38
+ /**
39
+ * Whether compiled code embeds the hash of its metadata, which covers every
40
+ * source file and the settings. Only then does matching code also mean
41
+ * matching source. `bytecodeHash: "none"` and `appendCBOR: false` drop it.
42
+ */
43
+ function hasSourceHash(bytecode) {
44
+ const buf = toBuffer(bytecode);
45
+ const start = cborStart(buf);
46
+ if (start === undefined)
47
+ return false;
48
+ const cbor = buf.subarray(start, buf.length - 2);
49
+ return SOURCE_HASH_KEYS.some((key) => cbor.includes(key));
50
+ }
51
+ function normalize(s) {
52
+ return s.toLowerCase().replace(/^0x/, "");
53
+ }
54
+ export function isProvingMatch(match) {
55
+ return match === "exact" || match === "immutables-only";
56
+ }
57
+ // solc leaves `__$<34 hex>$__` (20 bytes) where an external library address is linked.
58
+ const LINK_PLACEHOLDER = /__\$[0-9a-fA-F]{34}\$__/g;
59
+ /**
60
+ * Compares runtime code read with `eth_getCode` against compiler output.
61
+ *
62
+ * On-chain code has immutables filled in while compiler output has them
63
+ * zeroed, and the difference sits in the code body where metadata stripping
64
+ * cannot reach, so the spans are zeroed on both sides before comparing.
65
+ */
66
+ export function compareDeployedBytecode(onchain, compiled, immutableReferences = {}) {
67
+ const live = normalize(onchain);
68
+ const built = normalize(compiled);
69
+ if (live === "" || built === "")
70
+ return "none";
71
+ if (live === built)
72
+ return hasSourceHash(built) ? "exact" : "code-only";
73
+ const spans = Object.values(immutableReferences).flat();
74
+ for (const m of built.matchAll(LINK_PLACEHOLDER)) {
75
+ spans.push({ start: m.index / 2, length: 20 });
76
+ }
77
+ const builtHex = built.replace(LINK_PLACEHOLDER, "0".repeat(40));
78
+ const liveMasked = maskSpans(live, spans);
79
+ const builtMasked = maskSpans(builtHex, spans);
80
+ if (liveMasked === undefined || builtMasked === undefined)
81
+ return "none";
82
+ if (liveMasked === builtMasked)
83
+ return hasSourceHash(builtHex) ? "immutables-only" : "code-only";
84
+ if (stripBytecodeMetadata(liveMasked) === stripBytecodeMetadata(builtMasked)) {
85
+ return "metadata-only";
86
+ }
87
+ return "none";
88
+ }
89
+ // Returns undefined when the hex is malformed or a span falls outside the code.
90
+ function maskSpans(hex, spans) {
91
+ const buf = Buffer.from(hex, "hex");
92
+ if (buf.length * 2 !== hex.length)
93
+ return undefined;
94
+ for (const { start, length } of spans) {
95
+ if (start + length > buf.length)
96
+ return undefined;
97
+ buf.fill(0, start, start + length);
98
+ }
99
+ return buf.toString("hex");
100
+ }
101
+ //# sourceMappingURL=bytecode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytecode.js","sourceRoot":"","sources":["../src/bytecode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /** The one JSON-RPC method used; Hardhat's network provider and ethers' JsonRpcProvider both fit. */
2
+ export interface EthProvider {
3
+ send(method: string, params: unknown[]): Promise<unknown>;
4
+ }
5
+ export interface ProxyState {
6
+ implementation: string;
7
+ /** Set for a beacon proxy; `implementation` is then the beacon's. */
8
+ beacon?: string;
9
+ }
10
+ /**
11
+ * Reads what a proxy runs right now: the ERC-1967 implementation slot, or the
12
+ * beacon's implementation for a beacon proxy. Undefined when neither slot is
13
+ * set, i.e. the address is not a proxy this tool understands.
14
+ */
15
+ export declare function readProxy(provider: EthProvider, address: string): Promise<ProxyState | undefined>;
16
+ export declare function readImplementation(provider: EthProvider, proxy: string): Promise<string>;
17
+ export declare function readCode(provider: EthProvider, address: string): Promise<string>;
18
+ export declare function readChainId(provider: EthProvider): Promise<number>;
19
+ export declare function codeSha256(code: string): string;
20
+ //# sourceMappingURL=chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"AAWA,qGAAqG;AACrG,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAED,MAAM,WAAW,UAAU;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAgBjC;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ9F;AAED,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMtF;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAExE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C"}
package/dist/chain.js ADDED
@@ -0,0 +1,52 @@
1
+ import { EIP1967BeaconNotFound, EIP1967ImplementationNotFound, getBeaconAddress, getImplementationAddress, getImplementationAddressFromBeacon, } from "@openzeppelin/upgrades-core";
2
+ import { createHash } from "node:crypto";
3
+ import { LayoutUnavailableError } from "./errors.js";
4
+ /**
5
+ * Reads what a proxy runs right now: the ERC-1967 implementation slot, or the
6
+ * beacon's implementation for a beacon proxy. Undefined when neither slot is
7
+ * set, i.e. the address is not a proxy this tool understands.
8
+ */
9
+ export async function readProxy(provider, address) {
10
+ const oz = provider;
11
+ try {
12
+ return { implementation: (await getImplementationAddress(oz, address)).toLowerCase() };
13
+ }
14
+ catch (e) {
15
+ if (!(e instanceof EIP1967ImplementationNotFound))
16
+ throw e;
17
+ }
18
+ let beacon;
19
+ try {
20
+ beacon = (await getBeaconAddress(oz, address)).toLowerCase();
21
+ }
22
+ catch (e) {
23
+ if (e instanceof EIP1967BeaconNotFound)
24
+ return undefined;
25
+ throw e;
26
+ }
27
+ const implementation = (await getImplementationAddressFromBeacon(oz, beacon)).toLowerCase();
28
+ return { implementation, beacon };
29
+ }
30
+ export async function readImplementation(provider, proxy) {
31
+ const state = await readProxy(provider, proxy);
32
+ if (state === undefined) {
33
+ throw new LayoutUnavailableError(`${proxy} has no ERC-1967 implementation or beacon slot set on this chain.`);
34
+ }
35
+ return state.implementation;
36
+ }
37
+ export async function readCode(provider, address) {
38
+ const code = (await provider.send("eth_getCode", [address, "latest"]));
39
+ if (code === "0x" || code === "") {
40
+ throw new LayoutUnavailableError(`No code at ${address} on this chain.`);
41
+ }
42
+ return code.toLowerCase();
43
+ }
44
+ export async function readChainId(provider) {
45
+ return Number(BigInt((await provider.send("eth_chainId", []))));
46
+ }
47
+ export function codeSha256(code) {
48
+ return createHash("sha256")
49
+ .update(Buffer.from(code.replace(/^0x/, ""), "hex"))
50
+ .digest("hex");
51
+ }
52
+ //# sourceMappingURL=chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chain.js","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,6BAA6B,EAC7B,gBAAgB,EAChB,wBAAwB,EACxB,kCAAkC,GAEnC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAarD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAqB,EACrB,OAAe;IAEf,MAAM,EAAE,GAAG,QAAuC,CAAC;IACnD,IAAI,CAAC;QACH,OAAO,EAAE,cAAc,EAAE,CAAC,MAAM,wBAAwB,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;IACzF,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,CAAC,CAAC,YAAY,6BAA6B,CAAC;YAAE,MAAM,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,MAAM,gBAAgB,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,qBAAqB;YAAE,OAAO,SAAS,CAAC;QACzD,MAAM,CAAC,CAAC;IACV,CAAC;IACD,MAAM,cAAc,GAAG,CAAC,MAAM,kCAAkC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5F,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAqB,EAAE,KAAa;IAC3E,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,sBAAsB,CAC9B,GAAG,KAAK,mEAAmE,CAC5E,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,cAAc,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAqB,EAAE,OAAe;IACnE,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAW,CAAC;IACjF,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QACjC,MAAM,IAAI,sBAAsB,CAAC,cAAc,OAAO,iBAAiB,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAqB;IACrD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,CAAW,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;SACnD,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC"}
package/dist/diff.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { getStorageUpgradeReport, type StorageLayout } from "@openzeppelin/upgrades-core";
2
+ /** OZ's storage comparison. `ok` is the verdict; `explain()` describes each problem. */
3
+ export type LayoutReport = ReturnType<typeof getStorageUpgradeReport>;
4
+ export interface DiffOptions {
5
+ /** Accept renamed variables without `@custom:oz-renamed-from` (OZ's `unsafeAllowRenames`). */
6
+ unsafeAllowRenames?: boolean;
7
+ }
8
+ /**
9
+ * Whether `after` can safely replace `before` behind a proxy, by OZ's rules:
10
+ * appends pass; inserts, removals, reorders and incompatible type changes
11
+ * fail, in regular and ERC-7201 namespaced storage. Renames and retypes pass
12
+ * when `after` approves them (`@custom:oz-renamed-from` / `oz-retyped-from`).
13
+ */
14
+ export declare function diffLayouts(before: StorageLayout, after: StorageLayout, options?: DiffOptions): LayoutReport;
15
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EAEvB,KAAK,aAAa,EACnB,MAAM,6BAA6B,CAAC;AAErC,wFAAwF;AACxF,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAEtE,MAAM,WAAW,WAAW;IAC1B,8FAA8F;IAC9F,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,aAAa,EACpB,OAAO,GAAE,WAAgB,GACxB,YAAY,CAMd"}
package/dist/diff.js ADDED
@@ -0,0 +1,11 @@
1
+ import { getStorageUpgradeReport, withValidationDefaults, } from "@openzeppelin/upgrades-core";
2
+ /**
3
+ * Whether `after` can safely replace `before` behind a proxy, by OZ's rules:
4
+ * appends pass; inserts, removals, reorders and incompatible type changes
5
+ * fail, in regular and ERC-7201 namespaced storage. Renames and retypes pass
6
+ * when `after` approves them (`@custom:oz-renamed-from` / `oz-retyped-from`).
7
+ */
8
+ export function diffLayouts(before, after, options = {}) {
9
+ return getStorageUpgradeReport(before, after, withValidationDefaults({ unsafeAllowRenames: options.unsafeAllowRenames }));
10
+ }
11
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,sBAAsB,GAEvB,MAAM,6BAA6B,CAAC;AAUrC;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,MAAqB,EACrB,KAAoB,EACpB,UAAuB,EAAE;IAEzB,OAAO,uBAAuB,CAC5B,MAAM,EACN,KAAK,EACL,sBAAsB,CAAC,EAAE,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAC3E,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The chain cannot supply a layout: no implementation behind the proxy slots,
3
+ * no code at the address, unverified source, an unsupported verification
4
+ * format, or no explorer configured. Callers may fall back to another source.
5
+ */
6
+ export declare class LayoutUnavailableError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ /**
10
+ * The answer cannot be trusted: rebuilt bytecode does not match the deployed
11
+ * code, a record describes different code or another chain, or compilation
12
+ * failed. Never fall back on this one, since a silent fallback would compare
13
+ * against a layout nothing is running.
14
+ */
15
+ export declare class LayoutIntegrityError extends Error {
16
+ constructor(message: string);
17
+ }
18
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B"}
package/dist/errors.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The chain cannot supply a layout: no implementation behind the proxy slots,
3
+ * no code at the address, unverified source, an unsupported verification
4
+ * format, or no explorer configured. Callers may fall back to another source.
5
+ */
6
+ export class LayoutUnavailableError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "LayoutUnavailableError";
10
+ }
11
+ }
12
+ /**
13
+ * The answer cannot be trusted: rebuilt bytecode does not match the deployed
14
+ * code, a record describes different code or another chain, or compilation
15
+ * failed. Never fall back on this one, since a silent fallback would compare
16
+ * against a layout nothing is running.
17
+ */
18
+ export class LayoutIntegrityError extends Error {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = "LayoutIntegrityError";
22
+ }
23
+ }
24
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Verified-source lookup against an Etherscan-compatible explorer API.
3
+ *
4
+ * Only standard-json verifications are accepted. Flattened and multi-file
5
+ * verifications do not record every compiler setting (`viaIR` among them), so
6
+ * rebuilding from them means guessing; the bytecode proof would reject a wrong
7
+ * guess, but refusing up front gives a clearer error.
8
+ */
9
+ import type { SolcInput } from "@openzeppelin/upgrades-core";
10
+ export declare const ETHERSCAN_V2_API_URL = "https://api.etherscan.io/v2/api";
11
+ export interface ExplorerConfig {
12
+ /** Etherscan-compatible endpoint. Defaults to Etherscan v2, which serves every chain it indexes. */
13
+ apiUrl?: string;
14
+ apiKey?: string;
15
+ }
16
+ export interface VerifiedSource {
17
+ input: SolcInput;
18
+ /** e.g. `0.8.25+commit.b61c2a91` */
19
+ solcLongVersion: string;
20
+ /** Simple contract name as verified. */
21
+ contractName: string;
22
+ }
23
+ type FetchLike = (url: string) => Promise<{
24
+ ok: boolean;
25
+ status: number;
26
+ json(): Promise<unknown>;
27
+ }>;
28
+ interface SourceCodeEntry {
29
+ SourceCode?: string;
30
+ ContractName?: string;
31
+ CompilerVersion?: string;
32
+ }
33
+ /**
34
+ * Etherscan needs a key; a custom Etherscan-compatible endpoint (e.g. a
35
+ * Blockscout instance) may not.
36
+ */
37
+ export declare function canQueryExplorer(config: ExplorerConfig): boolean;
38
+ export declare function fetchVerifiedSource(chainId: number, address: string, config: ExplorerConfig, fetchImpl?: FetchLike): Promise<VerifiedSource>;
39
+ /** Exported for tests. */
40
+ export declare function parseSourceCodeEntry(entry: SourceCodeEntry, address: string): VerifiedSource;
41
+ export {};
42
+ //# sourceMappingURL=explorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explorer.d.ts","sourceRoot":"","sources":["../src/explorer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAG7D,eAAO,MAAM,oBAAoB,oCAAoC,CAAC;AAEtE,MAAM,WAAW,cAAc;IAC7B,oGAAoG;IACpG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,CAAC;IACjB,oCAAoC;IACpC,eAAe,EAAE,MAAM,CAAC;IACxB,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,KAAK,SAAS,GAAG,CACf,GAAG,EAAE,MAAM,KACR,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC;AAQxE,UAAU,eAAe;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAQD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAEhE;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,EACtB,SAAS,GAAE,SAAiB,GAC3B,OAAO,CAAC,cAAc,CAAC,CAgBzB;AAyDD,0BAA0B;AAC1B,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAoC5F"}