slotdiff 0.2.0-alpha.1 → 0.3.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 (58) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +55 -38
  3. package/dist/build-info.d.ts.map +1 -1
  4. package/dist/build-info.js +9 -3
  5. package/dist/build-info.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +59 -33
  8. package/dist/cli.js.map +1 -1
  9. package/dist/erc7201.d.ts +27 -11
  10. package/dist/erc7201.d.ts.map +1 -1
  11. package/dist/erc7201.js +182 -13
  12. package/dist/erc7201.js.map +1 -1
  13. package/dist/errors.d.ts +14 -0
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +20 -0
  16. package/dist/errors.js.map +1 -1
  17. package/dist/explorer.d.ts.map +1 -1
  18. package/dist/explorer.js +28 -9
  19. package/dist/explorer.js.map +1 -1
  20. package/dist/index.d.ts +4 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +3 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/reconstruct.d.ts +30 -15
  25. package/dist/reconstruct.d.ts.map +1 -1
  26. package/dist/reconstruct.js +140 -32
  27. package/dist/reconstruct.js.map +1 -1
  28. package/dist/record.d.ts +13 -0
  29. package/dist/record.d.ts.map +1 -1
  30. package/dist/record.js +8 -0
  31. package/dist/record.js.map +1 -1
  32. package/dist/report.d.ts +4 -1
  33. package/dist/report.d.ts.map +1 -1
  34. package/dist/report.js +37 -8
  35. package/dist/report.js.map +1 -1
  36. package/dist/resolve.d.ts +6 -1
  37. package/dist/resolve.d.ts.map +1 -1
  38. package/dist/resolve.js +6 -1
  39. package/dist/resolve.js.map +1 -1
  40. package/dist/solc.js +20 -4
  41. package/dist/solc.js.map +1 -1
  42. package/dist/upgrade.d.ts +48 -0
  43. package/dist/upgrade.d.ts.map +1 -0
  44. package/dist/upgrade.js +36 -0
  45. package/dist/upgrade.js.map +1 -0
  46. package/package.json +5 -5
  47. package/src/build-info.ts +9 -3
  48. package/src/cli.ts +69 -33
  49. package/src/erc7201.ts +217 -26
  50. package/src/errors.ts +22 -0
  51. package/src/explorer.ts +39 -10
  52. package/src/index.ts +11 -3
  53. package/src/reconstruct.ts +206 -57
  54. package/src/record.ts +18 -0
  55. package/src/report.ts +44 -13
  56. package/src/resolve.ts +12 -2
  57. package/src/solc.ts +20 -4
  58. package/src/upgrade.ts +91 -0
package/src/upgrade.ts ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The whole upgrade check in one call, as the CLI runs it: the storage layout,
3
+ * the ERC-7201 slots and OpenZeppelin's safety rules, with one verdict.
4
+ */
5
+
6
+ import type { StorageLayout } from "@openzeppelin/upgrades-core";
7
+ import { diffLayouts, type DiffOptions, type LayoutReport } from "./diff.js";
8
+ import { checkNamespaces, type NamespaceCheck, type NamespaceSlot } from "./erc7201.js";
9
+ import type { ProxyKind, SafetyErrorKind, SafetyReport, SourceAnalysis } from "./reconstruct.js";
10
+
11
+ export interface UpgradeOptions extends DiffOptions {
12
+ /** Error kinds to accept anyway (OZ's `unsafeAllow`). Prefer NatSpec exceptions in the source. */
13
+ unsafeAllow?: SafetyErrorKind[];
14
+ /** Skip the safety rules; the layout and slots are still checked. */
15
+ skipSafety?: boolean;
16
+ }
17
+
18
+ /** A layout with the slot of each namespace, such as a `LayoutRecord` or a `SourceAnalysis`. */
19
+ export interface UpgradeSide {
20
+ layout: StorageLayout;
21
+ /** Without them, the namespaces are unchecked. */
22
+ namespaces?: NamespaceSlot[];
23
+ }
24
+
25
+ export interface UpgradeCheck {
26
+ /** OpenZeppelin's comparison, with each namespace paired by the slot in code. */
27
+ layout: LayoutReport;
28
+ namespaces: NamespaceCheck;
29
+ /** Absent with `skipSafety`: then `pass` covers the layout and slots only. */
30
+ safety?: SafetyReport;
31
+ /**
32
+ * `fail`: a check failed. `unchecked`: none failed, but an ERC-7201 slot
33
+ * could not be read (`unread` in `namespaces`). `pass`: every check that ran passed.
34
+ */
35
+ result: "pass" | "fail" | "unchecked";
36
+ }
37
+
38
+ const PROXY_KINDS: readonly string[] = ["uups", "transparent", "beacon"];
39
+
40
+ /**
41
+ * Whether `next` can replace `old` behind a proxy. `old.kind` is the kind of
42
+ * the proxy being upgraded (`beacon` when a beacon serves it): taking it from
43
+ * `next` would let a UUPS implementation that lost its upgrade function pass.
44
+ * `old` may be a `SourceAnalysis`, or a `LayoutRecord` with its `kind` given.
45
+ * With `skipSafety`, `next` may be a record too, and `old.kind` is not needed.
46
+ */
47
+ export function checkUpgrade(
48
+ old: UpgradeSide & { kind: ProxyKind },
49
+ next: SourceAnalysis,
50
+ options?: UpgradeOptions,
51
+ ): UpgradeCheck;
52
+ export function checkUpgrade(
53
+ old: UpgradeSide & { kind?: ProxyKind },
54
+ next: UpgradeSide,
55
+ options: UpgradeOptions & { skipSafety: true },
56
+ ): UpgradeCheck;
57
+ export function checkUpgrade(
58
+ old: UpgradeSide & { kind?: ProxyKind },
59
+ next: UpgradeSide & { safety?: SourceAnalysis["safety"] },
60
+ options: UpgradeOptions = {},
61
+ ): UpgradeCheck {
62
+ if (!options.skipSafety) {
63
+ // Checked here for JavaScript callers: OpenZeppelin treats an unknown kind as uups.
64
+ if (!PROXY_KINDS.includes(old.kind as string)) {
65
+ throw new Error(
66
+ `checkUpgrade needs the kind of the proxy being upgraded (${PROXY_KINDS.join(", ")}), ` +
67
+ `got ${JSON.stringify(old.kind)}.`,
68
+ );
69
+ }
70
+ if (typeof next.safety !== "function") {
71
+ throw new Error("checkUpgrade needs an analysis of next (analyzeSource), or skipSafety.");
72
+ }
73
+ }
74
+ // OpenZeppelin matches namespaces by annotation; pairing them by slot first
75
+ // makes it compare the structs of the same storage.
76
+ const namespaces = checkNamespaces(old, next);
77
+ const layout = diffLayouts(namespaces.layout, next.layout, {
78
+ unsafeAllowRenames: options.unsafeAllowRenames,
79
+ });
80
+ const safety = options.skipSafety
81
+ ? undefined
82
+ : next.safety!({ kind: old.kind!, unsafeAllow: options.unsafeAllow });
83
+ // A failure outranks an unread slot, so a known problem is never reported as merely unchecked.
84
+ const result =
85
+ !layout.ok || namespaces.result === "fail" || safety?.ok === false
86
+ ? "fail"
87
+ : namespaces.result === "unchecked"
88
+ ? "unchecked"
89
+ : "pass";
90
+ return { layout, namespaces, ...(safety !== undefined ? { safety } : {}), result };
91
+ }