slotdiff 0.1.0-alpha.1 → 0.3.0

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 (65) hide show
  1. package/CHANGELOG.md +50 -9
  2. package/README.md +219 -111
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +22 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/build-info.d.ts +45 -0
  8. package/dist/build-info.d.ts.map +1 -0
  9. package/dist/build-info.js +319 -0
  10. package/dist/build-info.js.map +1 -0
  11. package/dist/cli.d.ts +25 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +389 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/erc7201.d.ts +88 -0
  16. package/dist/erc7201.d.ts.map +1 -0
  17. package/dist/erc7201.js +509 -0
  18. package/dist/erc7201.js.map +1 -0
  19. package/dist/errors.d.ts +19 -0
  20. package/dist/errors.d.ts.map +1 -1
  21. package/dist/errors.js +28 -0
  22. package/dist/errors.js.map +1 -1
  23. package/dist/explorer.d.ts.map +1 -1
  24. package/dist/explorer.js +29 -10
  25. package/dist/explorer.js.map +1 -1
  26. package/dist/index.d.ts +6 -3
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +5 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/reconstruct.d.ts +70 -7
  31. package/dist/reconstruct.d.ts.map +1 -1
  32. package/dist/reconstruct.js +157 -20
  33. package/dist/reconstruct.js.map +1 -1
  34. package/dist/record.d.ts +13 -0
  35. package/dist/record.d.ts.map +1 -1
  36. package/dist/record.js +8 -0
  37. package/dist/record.js.map +1 -1
  38. package/dist/report.d.ts +29 -0
  39. package/dist/report.d.ts.map +1 -0
  40. package/dist/report.js +138 -0
  41. package/dist/report.js.map +1 -0
  42. package/dist/resolve.d.ts +16 -0
  43. package/dist/resolve.d.ts.map +1 -1
  44. package/dist/resolve.js +17 -2
  45. package/dist/resolve.js.map +1 -1
  46. package/dist/solc.js +20 -4
  47. package/dist/solc.js.map +1 -1
  48. package/dist/upgrade.d.ts +48 -0
  49. package/dist/upgrade.d.ts.map +1 -0
  50. package/dist/upgrade.js +36 -0
  51. package/dist/upgrade.js.map +1 -0
  52. package/package.json +9 -6
  53. package/src/bin.ts +25 -0
  54. package/src/build-info.ts +405 -0
  55. package/src/cli.ts +459 -0
  56. package/src/erc7201.ts +603 -0
  57. package/src/errors.ts +31 -0
  58. package/src/explorer.ts +40 -11
  59. package/src/index.ts +28 -3
  60. package/src/reconstruct.ts +279 -29
  61. package/src/record.ts +18 -0
  62. package/src/report.ts +186 -0
  63. package/src/resolve.ts +28 -3
  64. package/src/solc.ts +20 -4
  65. package/src/upgrade.ts +91 -0
package/CHANGELOG.md CHANGED
@@ -1,19 +1,60 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Fixed
6
+
7
+ These were in 0.2.0 (the solc one since 0.1.0); upgrade if you rely on the CLI or check verified source.
8
+
9
+ - CLI: an `<old>` address that is not an ERC-1967 or beacon proxy was taken as an implementation, so a proxy of another kind (a custom slot, a diamond) was compared as its own, nearly empty, code and could pass. It is now refused (exit 2) unless `--old-implementation` says it is an implementation.
10
+ - Storage placed at a fixed slot through a struct that is not an ERC-7201 namespace of the contract or its bases (a namespace declared in a library, as in the diamond and Solidstate pattern, or a struct with no annotation) is left out of OpenZeppelin's layout with only a warning, so a change to it passed. It now stops the analysis with `LayoutUnavailableError` (exit 2).
11
+ - solc ran in the caller's directory, so verified source could `import` a file under it (an `.env`, say) and solc would quote its first line in the compile error. It now runs in a new empty directory, and verified source given by `urls` instead of inline `content` is refused.
12
+ - Text from an explorer or from verified source could move the cursor, recolor the terminal or draw lines of its own (a fake "Safe to upgrade." under the error). Control characters are stripped from solc's errors, explorer messages are kept on one line, and verified source with control characters in its name, paths or text (tabs and line breaks in text aside), or any whitespace but a space in its name and paths, is refused (`LayoutUnavailableError`).
13
+
14
+ ### Breaking
15
+
16
+ - CLI: an `<old>` implementation address needs `--old-implementation` (above).
17
+ - `layoutFromSource` is removed: use `(await analyzeSource(input, solc, contract)).layout`, at the same cost.
18
+ - `NamespaceCheck.result` says `"unchecked"` (was `"unread"`), as `UpgradeCheck.result` does. New union members: `NamespaceSlot.status` `"unused"` (such namespaces were `unread`), `NamespacePair.problem` `"dropped"`.
19
+ - `checkNamespaces`: `next` needs its `layout`.
20
+ - Plain `Error` (was `LayoutIntegrityError`), as wrong arguments: a contract missing from the compiler input, an input source without `content`. `safety()` throws on an unknown `unsafeAllow` kind (OpenZeppelin ignored it).
21
+
22
+ ### Added
23
+
24
+ - `checkUpgrade(old, next, options?)`: layout, ERC-7201 slots and safety rules in one verdict (`pass`, `fail` or `unchecked`), as the CLI runs them. The proxy kind comes from `old`.
25
+ - `analyzeBuild(input, output, compiler)`: `analyzeSource` for a build already compiled (a build tool's), with no second full compile; one namespaced pass serves every contract. Its `metadata` must prove the output was compiled from those sources by that compiler (`LayoutIntegrityError` otherwise); incomplete output throws the new `IncompleteBuildError`.
26
+ - Layout records keep the proxy `kind` and each namespace's slot (`namespaces`), so a saved record can stand in for `old`; `proveLocalBuild` takes both. Optional: older records still load, their namespaces unchecked.
27
+ - `resolveProxyLayout` returns the proxy's `kind`; `SafetyReport.explain(color?)` and `NamespaceCheck.explain(color?)` (the CLI's namespace grid). New types: `SolcOutput`, `UpgradeSide`, `UpgradeOptions`, `UpgradeCheck`, `BuildAnalysis`, `BuildCompiler`.
28
+
29
+ ### Changed
30
+
31
+ - ERC-7201: a namespace with no `$.slot :=` assignment (kept reserved) is `unused` and compared by its annotation (unless the code places a mapping or an array pointer), so such upgrades exit 0 where 0.2.0 exited 2. Dropping the last assignment of a namespace whose deployed annotation is wrong fails (`dropped`), while the real slot is still known.
32
+ - CLI: unassigned namespaces with no problem are left out of the grid and counted in the summary ("N not assigned in code, compared by annotation"); an unread slot says which side could not be read.
33
+ - Stricter input: `analyzeSource` checks its own compile as `analyzeBuild` does (a compiler whose version differs from the one the metadata names is a `LayoutIntegrityError`); `parseRecord` refuses a namespace key not of the form `<formula>:<id>` and a malformed `kind` or `namespaces`; `checkNamespaces` refuses slots that do not fit the layout (`LayoutIntegrityError`); `getSolc` refuses a malformed entry in solc's build list.
34
+ - `SAFETY_ERROR_KINDS` and `SafetyErrorKind` are slotdiff's own list (a test compares it with OpenZeppelin's), so the published types no longer import an OpenZeppelin internal file.
35
+ - Releases: the publish workflow installs dependencies without their install scripts; a weekly workflow tests against the newest OpenZeppelin upgrades-core 1.x.
36
+
37
+ ## 0.2.0-alpha.1
38
+
39
+ - CLI: `slotdiff <old> <new>`, where each side is a contract in your build info or an address (a proxy, read as what it runs now, or an implementation; needs `--rpc` and `ETHERSCAN_API_KEY`). Checks the storage layout, the ERC-7201 slots and OpenZeppelin's safety rules for `<new>`, with the proxy kind taken from `<old>`'s implementation, so a UUPS implementation missing `upgradeToAndCall` fails. Exits 0 when every check passed, 1 when one failed, 2 when something could not be checked. `ETHERSCAN_API_KEY` is sent only to Etherscan, never to `--explorer-url`.
40
+ - `findBuildInfo`: reads Foundry, Hardhat 2 and Hardhat 3 build info. The build is chosen by content: only one whose target and imports still match the files on disk, so the stale files Foundry keeps after each edit are never used. Its `input` holds only what the target imports.
41
+ - ERC-7201 namespaces are compared by the slot the code really uses, not only the annotation: a namespace whose slot moves fails, whatever the annotations say, and a corrected annotation over an unchanged slot passes (`SourceAnalysis.namespaces`, `checkNamespaces`).
42
+ - `analyzeSource`: `layoutFromSource` plus OpenZeppelin's safety check from one compile, as the CLI runs it; `safety({ kind })` takes the proxy kind being upgraded.
43
+ - Explorer errors no longer repeat fetch's error text, which can quote the request URL and with it the API key.
44
+ - CLI colors and a banner on an interactive terminal only (on runs, `--help` and `--version`); in a pipe or CI the output stays plain and `--version` prints just the version. `NO_COLOR` and `FORCE_COLOR` are honored. Built on Node's `util.styleText`, so no new dependency.
45
+ - **Breaking:** Node 22.13+ (was 22.12+), for `util.styleText`'s terminal detection.
46
+ - Releases are published from GitHub Actions through npm trusted publishing, staged for a maintainer's 2FA approval, with a provenance attestation linking each version to its commit.
47
+ - `SECURITY.md`: how to report a vulnerability privately, and what slotdiff touches (network, processes, files, environment). The README and `--help` now say plainly that a pass is no guarantee and that slotdiff comes without warranty.
48
+
3
49
  ## 0.1.0-alpha.1
4
50
 
5
51
  First release.
6
52
 
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.
53
+ - `resolveProxyLayout` / `resolveLayout`: the storage layout of the code a proxy (ERC-1967 or beacon) runs now, rebuilt from verified standard-json source on any Etherscan-compatible explorer and proven against the deployed code.
10
54
  - `proveLocalBuild`: a layout record from your own build, only when it is the deployed build.
11
55
  - `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.
56
+ - `LayoutRecord`s are plain JSON that slotdiff never writes. Load them back with `verifyRecord` (checked against the chain id and deployed code) or `parseRecord` (offline).
57
+ - A match proves a layout only when the build embeds solc's metadata hash. Builds with `bytecodeHash: "none"` or `appendCBOR: false` are reported as `code-only` and refused.
58
+ - `getSolc`: native solc from a cache (Hardhat's included) or a checksum-verified download. Accepts release versions such as `0.8.24`, as Foundry's build info records them.
18
59
  - `layoutFromSource` accepts build-info input from Foundry and Hardhat.
19
60
  - Loads from ESM and CommonJS. Node 22.12+.
package/README.md CHANGED
@@ -1,30 +1,39 @@
1
1
  # slotdiff
2
2
 
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.
3
+ [![npm](https://img.shields.io/npm/v/slotdiff)](https://www.npmjs.com/package/slotdiff) [![CI](https://github.com/rokso/slotdiff/actions/workflows/ci.yml/badge.svg)](https://github.com/rokso/slotdiff/actions/workflows/ci.yml) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/rokso/slotdiff/badge)](https://scorecard.dev/viewer/?uri=github.com/rokso/slotdiff) [![License: MIT](https://img.shields.io/npm/l/slotdiff)](LICENSE)
4
+
5
+ Storage layout diff for upgradeable Solidity contracts, where either side can be a local build or the code a proxy actually runs on chain.
6
+
7
+ ```sh
8
+ npx slotdiff 0xProxy src/Vault.sol:Vault --rpc $RPC_URL # what the proxy runs vs your build
9
+ ```
10
+
11
+ For example, two local versions:
5
12
 
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
+ $ npx slotdiff src/Vault.sol:Vault src/VaultBad.sol:VaultBad
15
+ old src/Vault.sol:Vault
16
+ out/build-info/90f317b6c0a81633.json
17
+ new src/VaultBad.sol:VaultBad
18
+ out/build-info/90f317b6c0a81633.json
19
+
20
+ ✗ Storage layout incompatible
21
+ src/VaultBad.sol:4: Inserted `inserted`
22
+ > New variables should be placed after all existing inherited variables
23
+ ✓ Safety rules no problems found (transparent proxy)
24
+
25
+ Not safe to upgrade: the storage layout is incompatible.
13
26
  ```
14
27
 
15
28
  ## Why
16
29
 
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:
30
+ An upgrade is only safe relative to what the proxy runs **now**. Under lazy or multisig-queued upgrades that is often not your latest build, and the tools you have compare the wrong thing:
19
31
 
20
32
  - OpenZeppelin's `validate` compares two local builds. It has no way to know what is live.
21
33
  - `cast storage <address>` prints a live layout but does not compare it with anything.
22
34
  - Deployment artifacts describe the last build you deployed, not necessarily what the proxy runs.
23
35
 
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.
36
+ slotdiff reads the implementation behind the proxy, rebuilds its layout from verified source, and **proves** that source compiles to the deployed code before trusting it. It then compares with OpenZeppelin's own rules, including ERC-7201 namespaced storage. Framework-agnostic: Foundry, Hardhat 2 and 3, or a plain script.
28
37
 
29
38
  ## Install
30
39
 
@@ -32,62 +41,166 @@ Hardhat 2 and 3, or a plain script.
32
41
  npm install slotdiff
33
42
  ```
34
43
 
35
- Node 22.12+. Works from ESM and CommonJS.
44
+ Node 22.13+. Works from ESM and CommonJS.
45
+
46
+ ## Command line
47
+
48
+ ```sh
49
+ slotdiff <old> <new> [--rpc <url>]
50
+ ```
51
+
52
+ Each side is a contract in your build info (`src/Vault.sol:Vault`, or just `Vault` when only one contract has that name) or an address: a proxy (ERC-1967 or beacon), read as the implementation it runs now, or for `<new>` an implementation, such as one waiting in a multisig queue. Any mix works. An `<old>` address that is not such a proxy is refused, since a proxy of another kind (a diamond, a custom slot) would be compared as its own code: pass `--old-implementation` when `<old>` is an implementation, or give such a proxy by its implementation's address. An address needs `--rpc` and `ETHERSCAN_API_KEY` (or `--explorer-url` for another explorer), since its source comes from the explorer. The RPC is a flag because it picks the chain for each run; the key lives in the environment because one Etherscan key covers every chain. It is sent only to Etherscan: with `--explorer-url`, put that explorer's own key in the URL's query if it needs one.
53
+
54
+ A local side is read from the build info your tool already wrote; slotdiff does not compile your project. Build first, from the project root or with `--root`. Foundry writes build info only with `build_info = true` in `foundry.toml` or `forge build --build-info`. A build older than the sources on disk is refused, never used (each file the contract imports is compared, except a dependency slotdiff cannot find from the root, such as one hoisted to a monorepo's top `node_modules`). `--build-info <file>` names one build-info file to use as is, such as a CI artifact from another checkout; the output then says it was not compared with the sources on disk.
55
+
56
+ Three checks run:
57
+
58
+ - **Storage layout**, by OpenZeppelin's rules, as above, with ERC-7201 namespaces matched by the slot the code really uses.
59
+ - **ERC-7201 slots**: each namespace keeps its data in place and `<new>`'s annotations match its code (see below).
60
+ - **Safety rules** for `<new>`, OpenZeppelin's checks that it can run behind a proxy: constructors, `selfdestruct`, `delegatecall`, initial values, immutables and initializers (an initializer order problem is only a warning, as in OpenZeppelin). Source exceptions (`@custom:oz-upgrades-unsafe-allow`) are honored, and `--unsafe-allow <kinds>` accepts more. For an address, the verified source is checked. `--no-safety` skips it.
61
+
62
+ OpenZeppelin matches ERC-7201 namespaces by their `@custom:storage-location erc7201:<id>` annotation alone. slotdiff also reads the slot the code assigns (`assembly { $.slot := LOCATION }`, with `LOCATION` a hex constant, the ERC-7201 formula, or `erc7201("id")`) and compares namespaces by that slot. The check fails when `<new>` would read a namespace from another slot than the deployed code wrote it to, whatever the annotations say, and when an annotation in `<new>` disagrees with its code. In `<old>`, already deployed, a disagreement is reported and the namespace is matched by its real slot, so correcting a wrong annotation is not reported as a deleted namespace. A namespace with no `$.slot := ...` assignment (one kept reserved, as OpenZeppelin advises for a struct no longer used) is compared by its annotation, as OpenZeppelin does. Dropping the last assignment of a namespace whose deployed annotation does not match its data fails: correct the annotation first, since later versions would be compared by it (correcting it and dropping the assignment in the same upgrade fails too). A slot slotdiff cannot read (set from a value it does not evaluate, such as one computed at run time) means the upgrade could not be checked: exit code 2.
63
+
64
+ For example, after correcting an annotation that never matched the deployed code:
65
+
66
+ ```
67
+ ERC-7201 namespaces
68
+ annotation slot from annotation slot in code
69
+ old example.vault 0xd192…a100 0x0d90…d100 ✗ annotation does not match code
70
+ new example.vault.real 0x0d90…d100 0x0d90…d100 ✓
71
+ Same slot in code on both sides: the storage did not move. Compared as one namespace.
72
+
73
+ ✓ Storage layout compatible
74
+ ✓ ERC-7201 slots compared by the slot in code (see above)
75
+ ✓ Safety rules no problems found (transparent proxy)
76
+
77
+ Safe to upgrade.
78
+ ```
79
+
80
+ Changing the constant to match the wrong annotation instead would move the data:
81
+
82
+ ```
83
+ ERC-7201 namespaces
84
+ annotation slot from annotation slot in code
85
+ old example.vault 0xd192…a100 0x0d90…d100 ✗ annotation does not match code
86
+ new example.vault 0xd192…a100 0xd192…a100 ✓
87
+ The new code moves this namespace from 0x0d90…d100 to 0xd192…a100: its data would be left behind. Keep the slot the deployed code uses, under an annotation that matches it.
88
+
89
+ ✓ Storage layout compatible
90
+ ✗ ERC-7201 slots problems found in <new> (see above)
91
+ ✓ Safety rules no problems found (transparent proxy)
92
+
93
+ Not safe to upgrade: an ERC-7201 namespace fails the slot check.
94
+ ```
95
+
96
+ The proxy kind comes from `<old>`'s implementation: UUPS when it has `upgradeTo` or `upgradeToAndCall`, beacon for a beacon proxy, transparent otherwise. So a UUPS implementation that lost its upgrade function, which would leave the proxy unable to upgrade again, is caught:
97
+
98
+ ```
99
+ $ npx slotdiff src/UupsV1.sol:UupsV1 src/UupsV2Lost.sol:UupsV2Lost
100
+ old src/UupsV1.sol:UupsV1
101
+ out/build-info/fb5d5362dc531baa.json
102
+ new src/UupsV2Lost.sol:UupsV2Lost
103
+ out/build-info/fb5d5362dc531baa.json
104
+
105
+ ✓ Storage layout compatible
106
+ ✗ Safety rules problems found (uups proxy)
107
+ src/UupsV2Lost.sol:3: Implementation is missing a public `upgradeTo(address)` or `upgradeToAndCall(address,bytes)` function
108
+ Inherit UUPSUpgradeable to include one or both of these functions in your contract
109
+ @openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol
110
+ https://zpl.in/upgrades/error-008
111
+
112
+ Not safe to upgrade: <new> breaks the safety rules.
113
+ ```
114
+
115
+ Exit codes: `0` every check ran and passed (or was skipped with `--no-safety`), `1` a check failed, `2` something could not be checked (never reported as compatible). `slotdiff --help` lists every option.
116
+
117
+ Colors and the banner appear only on an interactive terminal, never in a pipe, a log or CI. `NO_COLOR` turns colors off; `FORCE_COLOR` turns them on, for CI logs that render them.
36
118
 
37
119
  ## Usage
38
120
 
39
121
  ### Live proxy vs a local build
40
122
 
41
123
  ```ts
42
- import { readFileSync } from "node:fs";
43
124
  import { JsonRpcProvider } from "ethers";
44
- import { diffLayouts, getSolc, layoutFromSource, resolveProxyLayout } from "slotdiff";
125
+ import { analyzeSource, checkUpgrade, findBuildInfo, getSolc, resolveProxyLayout } from "slotdiff";
45
126
 
46
127
  // Any object with send(method, params): an ethers JsonRpcProvider or Hardhat's network provider.
47
128
  const provider = new JsonRpcProvider(process.env.RPC_URL);
48
129
  const apiKey = process.env.ETHERSCAN_API_KEY;
49
130
 
50
- // 1. The layout of whatever the proxy runs now, proven against its deployed code.
131
+ // 1. Whatever the proxy runs now: its layout and ERC-7201 slots, proven against its deployed code.
51
132
  const live = await resolveProxyLayout(provider, "0xProxy", { explorer: { apiKey } });
52
133
 
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");
134
+ // 2. The new version, from your build info (Foundry, Hardhat 2 or 3).
135
+ const build = await findBuildInfo(process.cwd(), "src/VaultV2.sol:VaultV2");
136
+ const next = await analyzeSource(build.input, await getSolc(build.solcVersion), build.contract);
57
137
 
58
- // 3. Compare.
59
- const report = diffLayouts(live.record.layout, next);
60
- if (!report.ok) {
61
- console.error(report.explain());
138
+ // 3. The layout, the ERC-7201 slots and the safety rules, with the proxy's own kind.
139
+ const check = checkUpgrade({ ...live.record, kind: live.kind }, next);
140
+ if (check.result !== "pass") {
141
+ // "fail", or "unchecked" when an ERC-7201 slot could not be read: neither is a pass.
142
+ console.error(check.layout.explain(), check.namespaces.explain(), check.safety?.explain());
62
143
  process.exit(1);
63
144
  }
64
145
  ```
65
146
 
66
- Where the build info lives:
147
+ `findBuildInfo` picks the build whose sources still match the files on disk, so the stale build info Foundry keeps after each edit is never used, and its `input` holds only the target and what it imports. Pass `{ file }` to use one file as is (for example a CI artifact). Where the build info lives:
67
148
 
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` |
149
+ | Tool | Build info | Contract name |
150
+ | --- | --- | --- |
151
+ | Foundry | `out/build-info/*.json`, only with `build_info = true` in `foundry.toml` or `forge build --build-info` | `src/Vault.sol:Vault` |
152
+ | Hardhat 2 | `artifacts/build-info/*.json` | `contracts/Vault.sol:Vault` |
153
+ | Hardhat 3 | `artifacts/build-info/<id>.json`; the compiler output is in `<id>.output.json` next to it | `project/contracts/Vault.sol:Vault` |
73
154
 
74
- Each holds `input` and `solcLongVersion`. Use the source name exactly as it appears in `input.sources`.
155
+ With `findBuildInfo`, Hardhat 3's `project/` prefix is optional.
75
156
 
76
- `explain()` gives OpenZeppelin's report, for example:
157
+ `check.layout.explain()` gives OpenZeppelin's report, for example:
77
158
 
78
159
  ```
79
160
  src/Vault.sol:7: Inserted `inserted`
80
161
  > New variables should be placed after all existing inherited variables
81
162
  ```
82
163
 
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).
164
+ The explorer defaults to Etherscan's v2 API, where one key covers every chain it indexes. For another Etherscan-compatible explorer, such as Blockscout, pass `{ apiUrl }` (a key is optional there).
165
+
166
+ ### Two local builds
167
+
168
+ `analyzeSource` gives a contract's layout, the slot each ERC-7201 namespace really uses and OpenZeppelin's safety check, from one compile. `checkUpgrade` runs all three checks on two analyses, or a record and an analysis, and gives one verdict, as the command line does.
169
+
170
+ ```ts
171
+ import { analyzeSource, checkUpgrade, findBuildInfo, getSolc } from "slotdiff";
172
+
173
+ async function analyze(contract: string) {
174
+ const build = await findBuildInfo(process.cwd(), contract);
175
+ return analyzeSource(build.input, await getSolc(build.solcVersion), build.contract);
176
+ }
177
+ const old = await analyze("src/Vault.sol:Vault");
178
+ const next = await analyze("src/VaultV2.sol:VaultV2");
179
+
180
+ const check = checkUpgrade(old, next); // result: "pass", "fail" or "unchecked"
181
+ if (check.result !== "pass") {
182
+ console.error(check.layout.explain(), check.namespaces.explain(), check.safety?.explain());
183
+ }
184
+ ```
185
+
186
+ The safety rules take the proxy kind from `old`, the proxy being upgraded, never from `next`: a UUPS implementation that lost its upgrade function would otherwise look transparent and pass. For a beacon proxy, pass `{ ...old, kind: "beacon" }`. `checkUpgrade` also takes `unsafeAllowRenames`, `unsafeAllow` (error kinds to accept, listed in `SAFETY_ERROR_KINDS`, on top of `@custom:oz-upgrades-unsafe-allow` in the source) and `skipSafety`. `unchecked` means nothing failed but an ERC-7201 slot could not be read (`check.namespaces.result` is `unchecked` too, and that pair's `problem` is `unread`). With `skipSafety`, `pass` covers the layout and slots only, and `check.safety` is absent. The pieces are there too: `checkNamespaces`, `diffLayouts` and the analysis's own `safety({ kind })`.
187
+
188
+ ### Reuse a build you already compiled
189
+
190
+ A build tool has just compiled the project, so compiling it again only to analyze it is wasted time. `analyzeBuild` takes that compiler input and output and analyzes every contract in it from one more, much lighter pass (OpenZeppelin's namespaced layouts). The output needs every source's `ast` and each contract's `metadata`, `storageLayout` and `evm.bytecode` (`object`, `linkReferences`); an interface or library may lack a `storageLayout`. Ask for them in `outputSelection` (Hardhat 3 leaves out `metadata` and `storageLayout` by default, Hardhat 2 `storageLayout`; Foundry needs `extra_output = ["storageLayout"]`). Output missing any of these throws `IncompleteBuildError`, so a build tool can ask for a fresh compile. The metadata proves the output was compiled from these sources by this compiler version (not that it used the input's settings, so pass the input the build tool compiled): an output from an earlier build is refused, never half analyzed. Hardhat 3 wraps the output in `<id>.output.json`; pass its `output` field.
191
+
192
+ ```ts
193
+ import { analyzeBuild, checkUpgrade, getSolc } from "slotdiff";
194
+
195
+ const build = await analyzeBuild(input, output, await getSolc(solcLongVersion));
196
+ const check = checkUpgrade(old, build.contract("project/contracts/Vault.sol:Vault"));
197
+ ```
198
+
199
+ The third argument runs that pass: a `getSolc` compiler, or your build tool's own, as `{ longVersion, compile(input) }`.
85
200
 
86
201
  ### Keep records, skip the rebuild next time
87
202
 
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.
203
+ Rebuilding means fetching source and compiling. A `LayoutRecord` is plain JSON: store it wherever suits you (committed next to your deployments, a CI cache) and load it back with `verifyRecord`, which refuses it unless this chain still runs the exact code it was proven against.
91
204
 
92
205
  ```ts
93
206
  import { readFileSync, writeFileSync } from "node:fs";
@@ -105,109 +218,104 @@ try {
105
218
  }
106
219
  ```
107
220
 
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.
221
+ Key records by **implementation**, and read the implementation from the proxy every time. A record for an old implementation still verifies after the proxy is upgraded, because that code is still deployed at its own address.
222
+
223
+ A record also keeps the proxy `kind` its code implies and the slot each ERC-7201 namespace really uses (`namespaces`), so it can stand in for `old` in `checkUpgrade`. Pass the proxy's own kind when you know it: `resolveProxyLayout` returns it as `kind` (`beacon` behind a beacon). Otherwise fall back to `"uups"`, never `"transparent"`: `"uups"` only adds the requirement that `next` keeps its upgrade function, so at worst it fails a transparent proxy's upgrade, while `"transparent"` would pass a UUPS implementation that lost it.
224
+
225
+ ```ts
226
+ const check = checkUpgrade({ ...record, kind: record.kind ?? "uups" }, next);
227
+ ```
228
+
229
+ Older records have neither field, and their namespaces come out unchecked rather than trusted by annotation. To compare two records, as a deploy step might, pass `{ skipSafety: true }`: `next` then needs no analysis and `old` no kind.
111
230
 
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.
231
+ Without an RPC, `parseRecord(value, address)` checks a record's shape and proof offline. It cannot tell whether the proxy has moved on, so prefer `verifyRecord` whenever you can reach the chain.
114
232
 
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.
233
+ A record is bound to the deployed code, not to what it says about that code: an edited `layout`, `kind` or `namespaces` in a saved record still verifies. Each changes the verdict (an edited `kind` of `"transparent"` drops the check that a UUPS implementation keeps its upgrade function). Treat saved records as trusted input and review changes to them like code.
117
234
 
118
235
  ### No explorer: prove your own build
119
236
 
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.
237
+ If you deployed it yourself, your build can serve as the source. `proveLocalBuild` compares the deployed code with your compiler output and returns a record only when they prove to be the same build.
123
238
 
124
239
  ```ts
125
- import { proveLocalBuild } from "slotdiff";
240
+ import { analyzeSource, findBuildInfo, getSolc, proveLocalBuild } from "slotdiff";
126
241
 
127
- // Hardhat 3: read `output` from `<id>.output.json` instead.
128
- const compiled = buildInfo.output.contracts["src/Vault.sol"].Vault.evm.deployedBytecode;
242
+ const build = await findBuildInfo(process.cwd(), "src/Vault.sol:Vault");
243
+ const solc = await getSolc(build.solcVersion);
244
+ const { layout, kind, namespaces } = await analyzeSource(build.input, solc, build.contract);
129
245
  const { bytecodeMatch, record } = await proveLocalBuild(provider, impl, {
130
- contract: "src/Vault.sol:Vault",
246
+ contract: build.contract,
131
247
  compiler: solc.longVersion,
132
- layout: await layoutFromSource(buildInfo.input, solc, "src/Vault.sol:Vault"),
133
- deployedBytecode: compiled.object,
134
- immutableReferences: compiled.immutableReferences,
248
+ layout,
249
+ kind, // so the record can stand in for `old` in checkUpgrade
250
+ namespaces,
251
+ deployedBytecode: build.deployedBytecode!,
252
+ immutableReferences: build.immutableReferences,
135
253
  });
136
254
  if (record === undefined) throw new Error(`Not the deployed build (${bytecodeMatch})`);
137
255
  ```
138
256
 
139
257
  ## What counts as proof
140
258
 
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.
259
+ A layout is only trusted when the compiled code matches the deployed code as `exact` or `immutables-only` (identical once immutables and library addresses are masked), **and** the build embeds solc's metadata hash. That hash covers every source file and setting, so matching code then means matching source.
145
260
 
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:
261
+ Anything weaker is refused, because variables no code reads, retyped fields and gap sizes never reach the bytecode, so a different layout can compile to the same code:
148
262
 
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 |
263
+ | Match | Meaning | Proof? |
264
+ | --- | --- | --- |
265
+ | `exact` | Byte-identical | Yes |
266
+ | `immutables-only` | Identical once immutables and library links are masked | Yes |
267
+ | `metadata-only` | Identical only after stripping metadata: the source differed | No |
268
+ | `code-only` | Identical, but built with `bytecodeHash: "none"` or `appendCBOR: false` | No |
269
+ | `none` | Different code | No |
156
270
 
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.
271
+ Contracts built without a metadata hash (common for deterministic CREATE2 deployments) therefore cannot be proven. For explorer source, the verified compiler settings are checked as well, so a build without a hash is refused even if its code happens to end in bytes shaped like one. With `proveLocalBuild`, the hash is read from your own compiler output.
161
272
 
162
273
  ## Errors
163
274
 
164
275
  slotdiff fails closed: it never treats "could not check" as "safe".
165
276
 
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.
277
+ - `LayoutUnavailableError`: no answer. Not a proxy, unverified source, explorer outage or rate limit, no compiler for the platform. Falling back to another source is reasonable.
278
+ - `LayoutIntegrityError`: an answer that cannot be trusted. Source that does not compile to the deployed code, a weak match, a record from another chain or for other code, a record or analysis that contradicts itself (namespace slots that do not fit its layout). Never fall back on this one.
171
279
 
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.
280
+ Problems that are yours to fix throw a plain `Error`, or `IncompleteBuildError` (an `Error`) for a build missing output `analyzeBuild` needs: a rejected explorer API key, a compiler binary that fails to run (for example the wrong architecture), a record in a format from a newer slotdiff, and wrong arguments, such as a contract that is not in the compiler input, an unknown proxy kind given to `checkUpgrade`, an unknown `unsafeAllow` kind, or `checkUpgrade` asked for the safety rules without an analysis of `next`.
175
281
 
176
282
  ## API
177
283
 
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`.
284
+ | Export | Purpose |
285
+ | --- | --- |
286
+ | `diffLayouts(before, after, { unsafeAllowRenames? })` | OpenZeppelin's storage comparison. `ok` and `explain()` |
287
+ | `resolveProxyLayout(provider, proxy, options)` | Proven layout of what a proxy (ERC-1967 or beacon) runs now |
288
+ | `resolveLayout(provider, address, options)` | Proven layout of the code at an implementation address |
289
+ | `proveLocalBuild(provider, address, build)` | A record from your own build, only when it is the deployed build |
290
+ | `verifyRecord(provider, address, value)` | Load a saved record, checked against the chain |
291
+ | `parseRecord(value, address?)` | Load a saved record, checked offline |
292
+ | `analyzeSource(input, solc, "file.sol:Name")` | The layout, proxy `kind`, ERC-7201 `namespaces` and `safety({ kind })` |
293
+ | `analyzeBuild(input, output, compiler)` | The same for every contract of a build already compiled, with no second full compile |
294
+ | `checkUpgrade(old, next, options?)` | Layout, ERC-7201 slots and safety rules in one verdict: `pass`, `fail` or `unchecked` |
295
+ | `checkNamespaces(old, next)` | ERC-7201 namespaces paired by real slot; a moved one fails. `explain()` |
296
+ | `findBuildInfo(root, contract, { file? })` | Your build info for a contract: input, compiler version, deployed code |
297
+ | `getSolc(version)` | Native solc, from a cache or downloaded and checksum-verified |
298
+ | `readProxy`, `readImplementation`, `readCode`, `readChainId` | Chain reads |
299
+ | `fetchVerifiedSource(chainId, address, explorer)` | Verified standard-json source from an Etherscan-compatible explorer |
300
+ | `compareDeployedBytecode`, `isProvingMatch` | The match classification above |
301
+
302
+ `getSolc` takes a long (`0.8.24+commit.e11b9ed9`) or release (`0.8.24`) version. Compilers are cached under your OS cache directory (`slotdiff/compilers`, or `cacheDir`), and Hardhat's compiler cache is reused when present.
303
+
304
+ Renames and retypes follow OpenZeppelin's NatSpec tags, `@custom:oz-renamed-from` and `@custom:oz-retyped-from`.
198
305
 
199
306
  ## Limitations
200
307
 
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.
308
+ - Explorer source must be verified as standard-json. Flattened and multi-file verifications do not record every compiler setting, so they are refused.
203
309
  - 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.
310
+ - Storage placed at a fixed slot through a struct that is not an ERC-7201 namespace of the contract or its bases (a namespace declared in a library, as in the diamond pattern, or a struct with no annotation) is not in OpenZeppelin's layout, so slotdiff cannot compare it and stops (exit 2) rather than pass it.
311
+ - Struct members cannot be renamed or retyped: Solidity has no NatSpec on struct members, so OpenZeppelin's tags cannot reach them.
312
+ - An ERC-7201 slot is read only from a `$.slot := ...` assignment whose value is a constant (a hex literal, the ERC-7201 formula, or `erc7201("id")`), directly or through a local variable set once or a function with no arguments that returns one. Any other slot (computed at run time, a library's constant) is not read, and the upgrade is reported as not checked. A namespace with no `$.slot := ...` assignment is compared by its annotation alone, as with OpenZeppelin, unless the code places a mapping or an array pointer anywhere: its data may live there, so the upgrade is reported as not checked. Data an even older version left at another slot, or data reached another way (raw `sload` and `sstore`, or a pointer whose slot is passed in, as OpenZeppelin's `StorageSlot` takes it), goes unnoticed.
313
+
314
+ ## No guarantee
315
+
316
+ slotdiff is provided as is, without warranty of any kind (see [LICENSE](LICENSE)). It checks what its rules and OpenZeppelin's can check. "Safe to upgrade" means those checks found no problem, not that the upgrade is safe in every way. It does not replace review, a test upgrade on a fork, or an audit. The authors accept no liability for any loss, including lost or locked funds, arising from using slotdiff or relying on its results.
317
+
318
+ To report a security problem, or to see what slotdiff touches on your machine (network, processes, files, environment), read [SECURITY.md](SECURITY.md).
211
319
 
212
320
  ## License
213
321
 
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import { styleText } from "node:util";
3
+ import { run } from "./cli.js";
4
+ // A reader that stops early, such as `| head`, is not an error.
5
+ process.stdout.on("error", (e) => {
6
+ if (e.code !== "EPIPE")
7
+ throw e;
8
+ process.exit();
9
+ });
10
+ run(process.argv.slice(2), {
11
+ stdout: (line) => console.log(line),
12
+ stderr: (line) => console.error(line),
13
+ tty: process.stdout.isTTY === true,
14
+ // Node's own rules for color: a terminal that has them, NO_COLOR, FORCE_COLOR.
15
+ color: styleText("red", "x", { stream: process.stdout }) !== "x",
16
+ }).then((code) => {
17
+ process.exitCode = code;
18
+ }, (e) => {
19
+ console.error(e);
20
+ process.exitCode = 2;
21
+ });
22
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,gEAAgE;AAChE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAwB,EAAE,EAAE;IACtD,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;QAAE,MAAM,CAAC,CAAC;IAChC,OAAO,CAAC,IAAI,EAAE,CAAC;AACjB,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IACzB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IACnC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;IACrC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;IAClC,+EAA+E;IAC/E,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,KAAK,GAAG;CACjE,CAAC,CAAC,IAAI,CACL,CAAC,IAAI,EAAE,EAAE;IACP,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,EACD,CAAC,CAAU,EAAE,EAAE;IACb,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CACF,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Reads a project's own build info: Foundry, Hardhat 2 and Hardhat 3.
3
+ *
4
+ * The build is chosen by content, never by modification time or file count.
5
+ * Foundry keeps every build-info file it ever wrote, so after a few edits
6
+ * several hold the target, most of them describing older sources. Only a build
7
+ * whose sources still match the files on disk is used.
8
+ */
9
+ import type { SolcInput } from "@openzeppelin/upgrades-core";
10
+ import type { ImmutableReferences } from "./bytecode.js";
11
+ export interface BuildInfoOptions {
12
+ /**
13
+ * One build-info file to use as is, relative to the project root. Its
14
+ * sources are not compared with the files on disk, so it may come from
15
+ * another checkout, such as a CI artifact.
16
+ */
17
+ file?: string;
18
+ }
19
+ export interface BuildInfoContract {
20
+ /** The build-info file it was read from. */
21
+ file: string;
22
+ /**
23
+ * Fully qualified name as solc saw it, `source.sol:Name`. Hardhat 3 prefixes
24
+ * project sources with `project/`.
25
+ */
26
+ contract: string;
27
+ /** As recorded: a long version, or a release version for Foundry. Both work with `getSolc`. */
28
+ solcVersion: string;
29
+ /**
30
+ * The compiler input, reduced to the target and what it imports, so that a
31
+ * layout compiles only those sources rather than the whole project.
32
+ */
33
+ input: SolcInput;
34
+ /** Absent when the build info holds no compiler output. */
35
+ deployedBytecode?: string;
36
+ immutableReferences?: ImmutableReferences;
37
+ }
38
+ /**
39
+ * Finds `contract` in the build info under `root` and returns what a layout
40
+ * or a bytecode proof needs. `contract` is `path/File.sol:Name`, with the path
41
+ * as written in the project (Hardhat 3's `project/` prefix is optional), or a
42
+ * bare `Name` when only one contract has it.
43
+ */
44
+ export declare function findBuildInfo(root: string, contract: string, options?: BuildInfoOptions): Promise<BuildInfoContract>;
45
+ //# sourceMappingURL=build-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-info.d.ts","sourceRoot":"","sources":["../src/build-info.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAG7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C;AAkCD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,iBAAiB,CAAC,CAgH5B"}