gate-evm-tools 1.0.20 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -228,6 +228,31 @@ npx gate-tool check --output ./reports/result.json
228
228
 
229
229
  当合约通过 calldata 方式由其他人(如多签钱包)执行升级后,本地的 `.openzeppelin` 文件不会自动更新。使用此命令可以从链上读取最新状态并更新本地文件。
230
230
 
231
+ `sync` 和 `validate` 会在加载 OpenZeppelin upgrades 之前确定 manifest 目录。约定路由如下:
232
+
233
+ | 网络名 | Manifest 目录 | 含义 |
234
+ |------|------|------|
235
+ | `bscDev` 等以 `Dev` 结尾的网络 | `.openzeppelin.dev` | development 发布阶段 |
236
+ | `bscProd` 等以 `Prod` 结尾的网络 | `.openzeppelin` | production 发布阶段 |
237
+ | 其他远程网络 | 显式 `MANIFEST_DEFAULT_DIR` | 项目根目录下单层 `.openzeppelin.<name>` |
238
+
239
+ Dev/Prod 可以使用同一个 chainId 和 RPC,但代表两套独立合约状态。其他自定义网络必须显式设置安全的项目内目录,例如:
240
+
241
+ ```bash
242
+ MANIFEST_DEFAULT_DIR=.openzeppelin.sepolia \
243
+ npx gate-tool sync --proxy 0x1234... --contract CounterUUPS --network sepolia
244
+ ```
245
+
246
+ `--network` 和 `-n` 都会在加载 command tree 前参与路由;自动化和迁移脚本建议始终使用完整的 `--network`。本地 `hardhat`、`localhost`、`local`、`anvil` 的 manifest 写入不支持:OpenZeppelin 会把 ephemeral development manifest 固定放到系统临时目录,所以 `sync`/`validate` 会明确拒绝,而不是声称写入某个项目内目录。
247
+
248
+ 如果约定目录与 `MANIFEST_DEFAULT_DIR` 冲突、目录不明确、路径越界或经过符号链接,写 manifest 的命令会 fail closed,不会继续执行。
249
+
250
+ `sync` 和 `validate` 对同一 canonical manifest 使用同一把 gate-tool 锁。事务同时快照 canonical 文件与 OpenZeppelin fallback(如 `unknown-{chainId}.json`);若两者同时存在会在写入前拒绝,若 OpenZeppelin 把 fallback 移到 canonical 后流程失败,则按原始文件名和原始字节完整回滚。
251
+
252
+ `sync` 自己的补充写入使用原子替换。结果、精确 before/after 文件名和 hash 写入项目根目录的 per-manifest 报告,例如 development BSC 为 `gate-tool-sync-report.openzeppelin.dev.bsc.json`,production BSC 为 `gate-tool-sync-report.openzeppelin.bsc.json`。返回值中的 `syncReport` 是本次报告的权威路径;不要假设一个由 Dev/Prod 共享的固定报告名。
253
+
254
+ 最新 full 模版项目优先使用 `workflow:checkcode` 做只读验收,由模版负责选择精确的 manifest 文件并调用 gate-tool。
255
+
231
256
  ```bash
232
257
  # 同步合约状态
233
258
  npx gate-tool sync \
package/bin/cli.js CHANGED
@@ -1,9 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import('../dist/index.js').then(module => {
4
- module.run();
3
+ import { prepareManifestEnvironment } from './manifest-bootstrap.js';
4
+
5
+ Promise.resolve().then(() => {
6
+ prepareManifestEnvironment();
7
+ return import('../dist/index.js');
8
+ }).then(module => {
9
+ return module.run();
5
10
  }).catch(err => {
6
11
  console.error('Failed to start CLI:', err);
7
12
  process.exit(1);
8
13
  });
9
-
@@ -0,0 +1,88 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const MANIFEST_MUTATING_COMMANDS = new Set(['sync', 'validate']);
5
+ const SAFE_MANIFEST_DIR = /^\.openzeppelin(?:\.[a-z0-9][a-z0-9-]*)?$/;
6
+
7
+ const argumentValue = (argv, name) => {
8
+ for (let index = 2; index < argv.length; index += 1) {
9
+ const value = argv[index];
10
+ if (value === name) return argv[index + 1];
11
+ if (value.startsWith(`${name}=`)) return value.slice(name.length + 1);
12
+ }
13
+ return undefined;
14
+ };
15
+
16
+ const commandName = (argv) => argv.slice(2).find((value) => !value.startsWith('-'));
17
+
18
+ const conventionalManifestDir = (network) => {
19
+ if (!network) return undefined;
20
+ if (/prod$/i.test(network)) return '.openzeppelin';
21
+ if (/dev$/i.test(network)) return '.openzeppelin.dev';
22
+ return undefined;
23
+ };
24
+
25
+ const isLocalDevelopmentNetwork = (network) => (
26
+ /^(hardhat|localhost|local|anvil)$/i.test(network || '')
27
+ );
28
+
29
+ const assertConfinedManifestDirectory = (cwd, manifestDir) => {
30
+ if (!SAFE_MANIFEST_DIR.test(manifestDir)) {
31
+ throw new Error(`Unsafe MANIFEST_DEFAULT_DIR: ${manifestDir}`);
32
+ }
33
+ const projectRoot = path.resolve(cwd);
34
+ const candidate = path.resolve(projectRoot, manifestDir);
35
+ if (path.dirname(candidate) !== projectRoot) {
36
+ throw new Error(`Manifest directory escapes the project: ${manifestDir}`);
37
+ }
38
+ if (fs.existsSync(candidate)) {
39
+ const stat = fs.lstatSync(candidate);
40
+ if (stat.isSymbolicLink()) {
41
+ throw new Error(`Manifest directory cannot be a symlink: ${manifestDir}`);
42
+ }
43
+ if (!stat.isDirectory()) {
44
+ throw new Error(`Manifest path must be a directory: ${manifestDir}`);
45
+ }
46
+ }
47
+ };
48
+
49
+ export const resolveManifestBootstrap = ({
50
+ argv = process.argv,
51
+ env = process.env,
52
+ cwd = process.cwd(),
53
+ } = {}) => {
54
+ const command = commandName(argv);
55
+ const network = (
56
+ argumentValue(argv, '--network') ?? argumentValue(argv, '-n')
57
+ )?.trim();
58
+ const mutatesManifest = MANIFEST_MUTATING_COMMANDS.has(command);
59
+ if (mutatesManifest && isLocalDevelopmentNetwork(network)) {
60
+ throw new Error(
61
+ `Local development manifest mutation is unsupported for ${network}: `
62
+ + 'OpenZeppelin stores ephemeral development manifests outside the project',
63
+ );
64
+ }
65
+ const conventional = conventionalManifestDir(network);
66
+ const configured = env.MANIFEST_DEFAULT_DIR?.trim();
67
+ if (configured && conventional && configured !== conventional) {
68
+ throw new Error(
69
+ `MANIFEST_DEFAULT_DIR conflict for ${network}: expected ${conventional}, received ${configured}`,
70
+ );
71
+ }
72
+ const manifestDir = configured || conventional;
73
+ if (mutatesManifest && !manifestDir) {
74
+ throw new Error(
75
+ `Cannot resolve OpenZeppelin manifest directory for network ${network || '<missing>'}; `
76
+ + 'set MANIFEST_DEFAULT_DIR explicitly',
77
+ );
78
+ }
79
+ if (manifestDir) assertConfinedManifestDirectory(cwd, manifestDir);
80
+ return { command, network, manifestDir, mutatesManifest };
81
+ };
82
+
83
+ export const prepareManifestEnvironment = (options = {}) => {
84
+ const env = options.env ?? process.env;
85
+ const resolved = resolveManifestBootstrap({ ...options, env });
86
+ if (resolved.manifestDir) env.MANIFEST_DEFAULT_DIR = resolved.manifestDir;
87
+ return resolved;
88
+ };
@@ -3,7 +3,7 @@ import ora from 'ora';
3
3
  import prompts from 'prompts';
4
4
  import { findConfigFile, readConfigFile, saveReport, mergeReports } from '../utils/files.js';
5
5
  import { printSummary, printProgress, printConfig } from '../utils/output.js';
6
- import { BytecodeChecker } from '../core/bytecodeChecker.js';
6
+ import { BytecodeChecker } from '../core/BytecodeChecker.js';
7
7
  import { isHardhatConfigImportError, detectAndFixWithPrompt, showManualFixGuide } from '../utils/configFixer.js';
8
8
  import fs from 'fs-extra';
9
9
  import path from 'path';
@@ -3,16 +3,13 @@ type HardhatRuntimeEnvironment = any;
3
3
  interface LocalBytecodeData {
4
4
  bytecode: string;
5
5
  abi: any[];
6
+ immutableReferences: ImmutableReferences;
6
7
  }
7
- interface ConstructorInput {
8
- type: string;
9
- name: string;
10
- inputs?: ConstructorInput[];
11
- }
12
- interface Constructor {
13
- type: string;
14
- inputs: ConstructorInput[];
8
+ interface ImmutableReference {
9
+ start: number;
10
+ length: number;
15
11
  }
12
+ type ImmutableReferences = Record<string, ImmutableReference[]>;
16
13
  interface LibraryLinkInfo {
17
14
  placeholder: string;
18
15
  position: number;
@@ -21,15 +18,8 @@ interface LibraryLinkInfo {
21
18
  interface ComparisonResult {
22
19
  matches: boolean;
23
20
  reason: string;
24
- constructorDetails?: any[];
25
21
  libraryLinks?: LibraryLinkInfo[];
26
22
  }
27
- interface ConstructorCheckResult {
28
- isConstructorDiff: boolean;
29
- reason: string;
30
- differences?: any[];
31
- diffCount?: number;
32
- }
33
23
  export declare class BytecodeChecker {
34
24
  private hre;
35
25
  private addressData;
@@ -61,37 +51,11 @@ export declare class BytecodeChecker {
61
51
  */
62
52
  private getProxyLocalBytecode;
63
53
  /**
64
- * 检查是否是constructor参数差异(主要是immutable变量)
65
- */
66
- checkConstructorParamsDifference(onchainCode: string, localCode: string, abi?: any[] | null): ConstructorCheckResult;
67
- /**
68
- * 基于ABI的智能分析
54
+ * 使用 artifact 的 fully-qualified identity 读取编译器生成的 immutableReferences。
55
+ * build-info 缺失时保留空引用,使完全一致的 bytecode 仍可通过;一旦存在差异,
56
+ * compareBytecode 会按 fail-closed 规则拒绝缺少编译器证据的匹配。
69
57
  */
70
- smartAnalyzeWithABI(onchainCode: string, localCode: string, abi: any[]): ConstructorCheckResult;
71
- /**
72
- * 检测immutable变量
73
- */
74
- detectImmutableVariables(onchainCode: string, localCode: string): ConstructorCheckResult;
75
- /**
76
- * 检测constructor参数
77
- */
78
- detectConstructorParams(onchainCode: string, localCode: string, constructor: Constructor): ConstructorCheckResult;
79
- /**
80
- * 去重参数:按顺序匹配参数
81
- */
82
- deduplicateParams(allMatches: any[], params: ConstructorInput[]): any[];
83
- /**
84
- * 查找地址参数
85
- */
86
- findAddressParams(onchainCode: string, localCode: string, param: ConstructorInput): any[];
87
- /**
88
- * 查找数值参数
89
- */
90
- findUintParams(onchainCode: string, localCode: string, param: ConstructorInput): any[];
91
- /**
92
- * 回退分析方法
93
- */
94
- fallbackAnalyze(onchainCode: string, localCode: string): ConstructorCheckResult;
58
+ private getArtifactLocalBytecode;
95
59
  /**
96
60
  * 标准化bytecode(移除metadata)
97
61
  */
@@ -129,10 +93,14 @@ export declare class BytecodeChecker {
129
93
  local: string;
130
94
  selfAddress: string;
131
95
  } | null;
96
+ /**
97
+ * 只屏蔽编译器声明的 immutable 区间,并比较其余 runtime bytecode。
98
+ */
99
+ private compareWithImmutableReferences;
132
100
  /**
133
101
  * 比较两个bytecode
134
102
  */
135
- compareBytecode(onchainBytecode: string, localBytecode: string, abi?: any[] | null): ComparisonResult;
103
+ compareBytecode(onchainBytecode: string, localBytecode: string, _abi?: any[] | null, immutableReferences?: ImmutableReferences): ComparisonResult;
136
104
  /**
137
105
  * 检查单个合约
138
106
  *
@@ -162,4 +130,4 @@ export declare class BytecodeChecker {
162
130
  generateNetworkContractResults(results: CheckResult[]): Record<string, Record<string, any>>;
163
131
  }
164
132
  export {};
165
- //# sourceMappingURL=bytecodeChecker.d.ts.map
133
+ //# sourceMappingURL=BytecodeChecker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BytecodeChecker.d.ts","sourceRoot":"","sources":["../../src/core/BytecodeChecker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1D,KAAK,yBAAyB,GAAG,GAAG,CAAC;AAErC,UAAU,iBAAiB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,GAAG,EAAE,CAAC;IACX,mBAAmB,EAAE,mBAAmB,CAAC;CAC1C;AAED,UAAU,kBAAkB;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAAC;AAEhE,UAAU,eAAe;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,eAAe,EAAE,CAAC;CAClC;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAA4B;IACvC,OAAO,CAAC,WAAW,CAAiB;gBAExB,GAAG,EAAE,yBAAyB,EAAE,WAAW,EAAE,cAAc;IAKvE;;;;;;;;;OASG;IACH,OAAO,CAAC,iBAAiB;IAuBzB;;OAEG;IACG,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgB3E;;;;OAIG;IACG,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAaxE;;;OAGG;YACW,qBAAqB;IAWnC;;;;OAIG;YACW,wBAAwB;IAsCtC;;OAEG;IACH,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAiB3C;;;;;;;;OAQG;IACH,4BAA4B,CAC1B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,eAAe,EAAE,CAAA;KAAE;IAqCtE;;;;;;;;;;;;;OAaG;IACH,2BAA2B,CACzB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAmBjE;;OAEG;IACH,OAAO,CAAC,8BAA8B;IAoHtC;;OAEG;IACH,eAAe,CACb,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACrB,IAAI,GAAE,GAAG,EAAE,GAAG,IAAW,EACzB,mBAAmB,GAAE,mBAAwB,GAC5C,gBAAgB;IA6DnB;;;;;OAKG;IACG,aAAa,CAAC,YAAY,EAAE;QAChC,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,WAAW,CAAC;IAuDxB;;OAEG;IACG,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAsBxE;;OAEG;IACG,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAuBzE;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAejD;;OAEG;IACH,8BAA8B,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CA4B5F"}