gate-evm-tools 1.0.25 → 1.0.27
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 +33 -11
- package/bin/workflow.cjs +19 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +21 -57
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/checkBatch.d.ts +4 -0
- package/dist/commands/checkBatch.d.ts.map +1 -0
- package/dist/commands/checkBatch.js +57 -0
- package/dist/commands/checkBatch.js.map +1 -0
- package/dist/commands/checkOptions.d.ts +3 -0
- package/dist/commands/checkOptions.d.ts.map +1 -1
- package/dist/commands/checkOptions.js +10 -1
- package/dist/commands/checkOptions.js.map +1 -1
- package/dist/commands/prepare.d.ts +1 -2
- package/dist/commands/prepare.d.ts.map +1 -1
- package/dist/commands/prepare.js +2 -17
- package/dist/commands/prepare.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +2 -17
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +2 -17
- package/dist/commands/validate.js.map +1 -1
- package/dist/core/BytecodeChecker.d.ts.map +1 -1
- package/dist/core/BytecodeChecker.js +16 -1
- package/dist/core/BytecodeChecker.js.map +1 -1
- package/dist/core/legacyProxyArtifact.d.ts +6 -0
- package/dist/core/legacyProxyArtifact.d.ts.map +1 -0
- package/dist/core/legacyProxyArtifact.js +36 -0
- package/dist/core/legacyProxyArtifact.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/hardhat.d.ts +4 -0
- package/dist/utils/hardhat.d.ts.map +1 -0
- package/dist/utils/hardhat.js +9 -0
- package/dist/utils/hardhat.js.map +1 -0
- package/dist-runtime/config/hardhatAdapters.d.ts +5 -0
- package/dist-runtime/config/hardhatAdapters.js +53 -0
- package/dist-runtime/config/hardhatAdapters.js.map +1 -0
- package/dist-runtime/config/networks.d.ts +8 -0
- package/dist-runtime/config/networks.js +269 -0
- package/dist-runtime/config/networks.js.map +1 -0
- package/dist-runtime/config/openzeppelin.d.ts +28 -0
- package/dist-runtime/config/openzeppelin.js +144 -0
- package/dist-runtime/config/openzeppelin.js.map +1 -0
- package/dist-runtime/config/rpc.d.ts +7 -0
- package/dist-runtime/config/rpc.js +19 -0
- package/dist-runtime/config/rpc.js.map +1 -0
- package/dist-runtime/config/zksyncSolcPath.d.ts +1 -0
- package/dist-runtime/config/zksyncSolcPath.js +61 -0
- package/dist-runtime/config/zksyncSolcPath.js.map +1 -0
- package/dist-runtime/migrate.d.ts +13 -0
- package/dist-runtime/migrate.js +208 -0
- package/dist-runtime/migrate.js.map +1 -0
- package/dist-runtime/package.json +1 -0
- package/dist-runtime/projectHardhat.d.ts +4 -0
- package/dist-runtime/projectHardhat.js +16 -0
- package/dist-runtime/projectHardhat.js.map +1 -0
- package/dist-runtime/runtime-identity.json +4 -0
- package/dist-runtime/template-migration.json +293 -0
- package/dist-runtime/workflow/checkCode.d.ts +1 -0
- package/dist-runtime/workflow/checkCode.js +16 -0
- package/dist-runtime/workflow/checkCode.js.map +1 -0
- package/dist-runtime/workflow/checkCodeFinalize.d.ts +1 -0
- package/dist-runtime/workflow/checkCodeFinalize.js +11 -0
- package/dist-runtime/workflow/checkCodeFinalize.js.map +1 -0
- package/dist-runtime/workflow/checkCodePlan.d.ts +1 -0
- package/dist-runtime/workflow/checkCodePlan.js +11 -0
- package/dist-runtime/workflow/checkCodePlan.js.map +1 -0
- package/dist-runtime/workflow/cli.d.ts +1 -0
- package/dist-runtime/workflow/cli.js +302 -0
- package/dist-runtime/workflow/cli.js.map +1 -0
- package/dist-runtime/workflow/commitContractInfo.d.ts +1 -0
- package/dist-runtime/workflow/commitContractInfo.js +64 -0
- package/dist-runtime/workflow/commitContractInfo.js.map +1 -0
- package/dist-runtime/workflow/create2Hashes.d.ts +1 -0
- package/dist-runtime/workflow/create2Hashes.js +76 -0
- package/dist-runtime/workflow/create2Hashes.js.map +1 -0
- package/dist-runtime/workflow/forkSourceProxy.d.ts +1 -0
- package/dist-runtime/workflow/forkSourceProxy.js +151 -0
- package/dist-runtime/workflow/forkSourceProxy.js.map +1 -0
- package/dist-runtime/workflow/importState.d.ts +1 -0
- package/dist-runtime/workflow/importState.js +193 -0
- package/dist-runtime/workflow/importState.js.map +1 -0
- package/dist-runtime/workflow/lib/authority.d.ts +28 -0
- package/dist-runtime/workflow/lib/authority.js +122 -0
- package/dist-runtime/workflow/lib/authority.js.map +1 -0
- package/dist-runtime/workflow/lib/calldata.d.ts +2 -0
- package/dist-runtime/workflow/lib/calldata.js +8 -0
- package/dist-runtime/workflow/lib/calldata.js.map +1 -0
- package/dist-runtime/workflow/lib/calls.d.ts +23 -0
- package/dist-runtime/workflow/lib/calls.js +316 -0
- package/dist-runtime/workflow/lib/calls.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/args.d.ts +2 -0
- package/dist-runtime/workflow/lib/checkCode/args.js +68 -0
- package/dist-runtime/workflow/lib/checkCode/args.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/command.d.ts +5 -0
- package/dist-runtime/workflow/lib/checkCode/command.js +35 -0
- package/dist-runtime/workflow/lib/checkCode/command.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/dependencies.d.ts +6 -0
- package/dist-runtime/workflow/lib/checkCode/dependencies.js +125 -0
- package/dist-runtime/workflow/lib/checkCode/dependencies.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/finalize.d.ts +17 -0
- package/dist-runtime/workflow/lib/checkCode/finalize.js +174 -0
- package/dist-runtime/workflow/lib/checkCode/finalize.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/index.d.ts +3 -0
- package/dist-runtime/workflow/lib/checkCode/index.js +20 -0
- package/dist-runtime/workflow/lib/checkCode/index.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCode/plan.d.ts +22 -0
- package/dist-runtime/workflow/lib/checkCode/plan.js +145 -0
- package/dist-runtime/workflow/lib/checkCode/plan.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCodeReport.d.ts +25 -0
- package/dist-runtime/workflow/lib/checkCodeReport.js +102 -0
- package/dist-runtime/workflow/lib/checkCodeReport.js.map +1 -0
- package/dist-runtime/workflow/lib/checkCodeRunner.d.ts +87 -0
- package/dist-runtime/workflow/lib/checkCodeRunner.js +964 -0
- package/dist-runtime/workflow/lib/checkCodeRunner.js.map +1 -0
- package/dist-runtime/workflow/lib/cliTool.d.ts +10 -0
- package/dist-runtime/workflow/lib/cliTool.js +25 -0
- package/dist-runtime/workflow/lib/cliTool.js.map +1 -0
- package/dist-runtime/workflow/lib/config.d.ts +33 -0
- package/dist-runtime/workflow/lib/config.js +100 -0
- package/dist-runtime/workflow/lib/config.js.map +1 -0
- package/dist-runtime/workflow/lib/contractInfoCandidates.d.ts +82 -0
- package/dist-runtime/workflow/lib/contractInfoCandidates.js +441 -0
- package/dist-runtime/workflow/lib/contractInfoCandidates.js.map +1 -0
- package/dist-runtime/workflow/lib/create2.d.ts +49 -0
- package/dist-runtime/workflow/lib/create2.js +194 -0
- package/dist-runtime/workflow/lib/create2.js.map +1 -0
- package/dist-runtime/workflow/lib/create2Hashes.d.ts +22 -0
- package/dist-runtime/workflow/lib/create2Hashes.js +57 -0
- package/dist-runtime/workflow/lib/create2Hashes.js.map +1 -0
- package/dist-runtime/workflow/lib/deploymentInfo.d.ts +40 -0
- package/dist-runtime/workflow/lib/deploymentInfo.js +240 -0
- package/dist-runtime/workflow/lib/deploymentInfo.js.map +1 -0
- package/dist-runtime/workflow/lib/deploymentRecords.d.ts +137 -0
- package/dist-runtime/workflow/lib/deploymentRecords.js +374 -0
- package/dist-runtime/workflow/lib/deploymentRecords.js.map +1 -0
- package/dist-runtime/workflow/lib/deployments.d.ts +21 -0
- package/dist-runtime/workflow/lib/deployments.js +240 -0
- package/dist-runtime/workflow/lib/deployments.js.map +1 -0
- package/dist-runtime/workflow/lib/erc1967.d.ts +5 -0
- package/dist-runtime/workflow/lib/erc1967.js +29 -0
- package/dist-runtime/workflow/lib/erc1967.js.map +1 -0
- package/dist-runtime/workflow/lib/errors.d.ts +6 -0
- package/dist-runtime/workflow/lib/errors.js +77 -0
- package/dist-runtime/workflow/lib/errors.js.map +1 -0
- package/dist-runtime/workflow/lib/executionId.d.ts +7 -0
- package/dist-runtime/workflow/lib/executionId.js +26 -0
- package/dist-runtime/workflow/lib/executionId.js.map +1 -0
- package/dist-runtime/workflow/lib/executionPlan.d.ts +85 -0
- package/dist-runtime/workflow/lib/executionPlan.js +450 -0
- package/dist-runtime/workflow/lib/executionPlan.js.map +1 -0
- package/dist-runtime/workflow/lib/executionProgress.d.ts +5 -0
- package/dist-runtime/workflow/lib/executionProgress.js +100 -0
- package/dist-runtime/workflow/lib/executionProgress.js.map +1 -0
- package/dist-runtime/workflow/lib/external.d.ts +62 -0
- package/dist-runtime/workflow/lib/external.js +282 -0
- package/dist-runtime/workflow/lib/external.js.map +1 -0
- package/dist-runtime/workflow/lib/externalExecutionScanner.d.ts +64 -0
- package/dist-runtime/workflow/lib/externalExecutionScanner.js +638 -0
- package/dist-runtime/workflow/lib/externalExecutionScanner.js.map +1 -0
- package/dist-runtime/workflow/lib/forkSnapshot.d.ts +34 -0
- package/dist-runtime/workflow/lib/forkSnapshot.js +167 -0
- package/dist-runtime/workflow/lib/forkSnapshot.js.map +1 -0
- package/dist-runtime/workflow/lib/gateTool.d.ts +16 -0
- package/dist-runtime/workflow/lib/gateTool.js +177 -0
- package/dist-runtime/workflow/lib/gateTool.js.map +1 -0
- package/dist-runtime/workflow/lib/gateToolSync.d.ts +22 -0
- package/dist-runtime/workflow/lib/gateToolSync.js +253 -0
- package/dist-runtime/workflow/lib/gateToolSync.js.map +1 -0
- package/dist-runtime/workflow/lib/lock.d.ts +52 -0
- package/dist-runtime/workflow/lib/lock.js +269 -0
- package/dist-runtime/workflow/lib/lock.js.map +1 -0
- package/dist-runtime/workflow/lib/migrationState.d.ts +103 -0
- package/dist-runtime/workflow/lib/migrationState.js +920 -0
- package/dist-runtime/workflow/lib/migrationState.js.map +1 -0
- package/dist-runtime/workflow/lib/operatorExecution.d.ts +28 -0
- package/dist-runtime/workflow/lib/operatorExecution.js +134 -0
- package/dist-runtime/workflow/lib/operatorExecution.js.map +1 -0
- package/dist-runtime/workflow/lib/parameters.d.ts +58 -0
- package/dist-runtime/workflow/lib/parameters.js +322 -0
- package/dist-runtime/workflow/lib/parameters.js.map +1 -0
- package/dist-runtime/workflow/lib/pendingDeploymentReuse.d.ts +29 -0
- package/dist-runtime/workflow/lib/pendingDeploymentReuse.js +277 -0
- package/dist-runtime/workflow/lib/pendingDeploymentReuse.js.map +1 -0
- package/dist-runtime/workflow/lib/proxy.d.ts +18 -0
- package/dist-runtime/workflow/lib/proxy.js +19 -0
- package/dist-runtime/workflow/lib/proxy.js.map +1 -0
- package/dist-runtime/workflow/lib/registryCommit.d.ts +33 -0
- package/dist-runtime/workflow/lib/registryCommit.js +319 -0
- package/dist-runtime/workflow/lib/registryCommit.js.map +1 -0
- package/dist-runtime/workflow/lib/release.d.ts +57 -0
- package/dist-runtime/workflow/lib/release.js +256 -0
- package/dist-runtime/workflow/lib/release.js.map +1 -0
- package/dist-runtime/workflow/lib/releaseFinalize.d.ts +14 -0
- package/dist-runtime/workflow/lib/releaseFinalize.js +571 -0
- package/dist-runtime/workflow/lib/releaseFinalize.js.map +1 -0
- package/dist-runtime/workflow/lib/releaseParameters.d.ts +43 -0
- package/dist-runtime/workflow/lib/releaseParameters.js +184 -0
- package/dist-runtime/workflow/lib/releaseParameters.js.map +1 -0
- package/dist-runtime/workflow/lib/reporter.d.ts +39 -0
- package/dist-runtime/workflow/lib/reporter.js +452 -0
- package/dist-runtime/workflow/lib/reporter.js.map +1 -0
- package/dist-runtime/workflow/lib/resultPaths.d.ts +17 -0
- package/dist-runtime/workflow/lib/resultPaths.js +29 -0
- package/dist-runtime/workflow/lib/resultPaths.js.map +1 -0
- package/dist-runtime/workflow/lib/runtimeVersion.d.ts +10 -0
- package/dist-runtime/workflow/lib/runtimeVersion.js +46 -0
- package/dist-runtime/workflow/lib/runtimeVersion.js.map +1 -0
- package/dist-runtime/workflow/lib/signer.d.ts +10 -0
- package/dist-runtime/workflow/lib/signer.js +105 -0
- package/dist-runtime/workflow/lib/signer.js.map +1 -0
- package/dist-runtime/workflow/lib/signerIdentity.d.ts +1 -0
- package/dist-runtime/workflow/lib/signerIdentity.js +23 -0
- package/dist-runtime/workflow/lib/signerIdentity.js.map +1 -0
- package/dist-runtime/workflow/lib/simulation.d.ts +35 -0
- package/dist-runtime/workflow/lib/simulation.js +282 -0
- package/dist-runtime/workflow/lib/simulation.js.map +1 -0
- package/dist-runtime/workflow/lib/simulationCallers.d.ts +44 -0
- package/dist-runtime/workflow/lib/simulationCallers.js +260 -0
- package/dist-runtime/workflow/lib/simulationCallers.js.map +1 -0
- package/dist-runtime/workflow/lib/sourceRpcPolicy.d.ts +1 -0
- package/dist-runtime/workflow/lib/sourceRpcPolicy.js +43 -0
- package/dist-runtime/workflow/lib/sourceRpcPolicy.js.map +1 -0
- package/dist-runtime/workflow/lib/state.d.ts +6 -0
- package/dist-runtime/workflow/lib/state.js +25 -0
- package/dist-runtime/workflow/lib/state.js.map +1 -0
- package/dist-runtime/workflow/lib/staticValidation.d.ts +22 -0
- package/dist-runtime/workflow/lib/staticValidation.js +124 -0
- package/dist-runtime/workflow/lib/staticValidation.js.map +1 -0
- package/dist-runtime/workflow/lib/zksyncForkCapability.d.ts +27 -0
- package/dist-runtime/workflow/lib/zksyncForkCapability.js +52 -0
- package/dist-runtime/workflow/lib/zksyncForkCapability.js.map +1 -0
- package/dist-runtime/workflow/lib/zksyncForkRunner.d.ts +57 -0
- package/dist-runtime/workflow/lib/zksyncForkRunner.js +344 -0
- package/dist-runtime/workflow/lib/zksyncForkRunner.js.map +1 -0
- package/dist-runtime/workflow/reconcileExternal.d.ts +1 -0
- package/dist-runtime/workflow/reconcileExternal.js +87 -0
- package/dist-runtime/workflow/reconcileExternal.js.map +1 -0
- package/dist-runtime/workflow/reconcileExternalRun.d.ts +1 -0
- package/dist-runtime/workflow/reconcileExternalRun.js +77 -0
- package/dist-runtime/workflow/reconcileExternalRun.js.map +1 -0
- package/dist-runtime/workflow/releaseFinalize.d.ts +1 -0
- package/dist-runtime/workflow/releaseFinalize.js +11 -0
- package/dist-runtime/workflow/releaseFinalize.js.map +1 -0
- package/dist-runtime/workflow/run.d.ts +40 -0
- package/dist-runtime/workflow/run.js +858 -0
- package/dist-runtime/workflow/run.js.map +1 -0
- package/dist-runtime/workflow/staticValidate.d.ts +1 -0
- package/dist-runtime/workflow/staticValidate.js +26 -0
- package/dist-runtime/workflow/staticValidate.js.map +1 -0
- package/dist-runtime/workflow/steps/call.d.ts +3 -0
- package/dist-runtime/workflow/steps/call.js +9 -0
- package/dist-runtime/workflow/steps/call.js.map +1 -0
- package/dist-runtime/workflow/steps/check.d.ts +3 -0
- package/dist-runtime/workflow/steps/check.js +9 -0
- package/dist-runtime/workflow/steps/check.js.map +1 -0
- package/dist-runtime/workflow/steps/custom.d.ts +3 -0
- package/dist-runtime/workflow/steps/custom.js +33 -0
- package/dist-runtime/workflow/steps/custom.js.map +1 -0
- package/dist-runtime/workflow/steps/deploy.d.ts +3 -0
- package/dist-runtime/workflow/steps/deploy.js +595 -0
- package/dist-runtime/workflow/steps/deploy.js.map +1 -0
- package/dist-runtime/workflow/steps/upgrade.d.ts +3 -0
- package/dist-runtime/workflow/steps/upgrade.js +465 -0
- package/dist-runtime/workflow/steps/upgrade.js.map +1 -0
- package/dist-runtime/workflow/steps/view.d.ts +3 -0
- package/dist-runtime/workflow/steps/view.js +9 -0
- package/dist-runtime/workflow/steps/view.js.map +1 -0
- package/package.json +26 -5
- package/runtime/config/hardhatAdapters.ts +50 -0
- package/runtime/config/networks.ts +296 -0
- package/runtime/config/openzeppelin.ts +173 -0
- package/runtime/config/rpc.ts +27 -0
- package/runtime/config/zksyncSolcPath.ts +59 -0
- package/runtime/hardhat-types.d.ts +2 -0
- package/runtime/migrate.ts +154 -0
- package/runtime/projectHardhat.ts +10 -0
- package/runtime/template-migration.json +293 -0
- package/runtime/workflow/checkCode.ts +13 -0
- package/runtime/workflow/checkCodeFinalize.ts +8 -0
- package/runtime/workflow/checkCodePlan.ts +8 -0
- package/runtime/workflow/cli.ts +356 -0
- package/runtime/workflow/commitContractInfo.ts +63 -0
- package/runtime/workflow/create2Hashes.ts +79 -0
- package/runtime/workflow/forkSourceProxy.ts +193 -0
- package/runtime/workflow/importState.ts +190 -0
- package/runtime/workflow/lib/authority.ts +170 -0
- package/runtime/workflow/lib/calldata.ts +9 -0
- package/runtime/workflow/lib/calls.ts +407 -0
- package/runtime/workflow/lib/checkCode/args.ts +76 -0
- package/runtime/workflow/lib/checkCode/command.ts +36 -0
- package/runtime/workflow/lib/checkCode/dependencies.ts +112 -0
- package/runtime/workflow/lib/checkCode/finalize.ts +198 -0
- package/runtime/workflow/lib/checkCode/index.ts +18 -0
- package/runtime/workflow/lib/checkCode/plan.ts +163 -0
- package/runtime/workflow/lib/checkCodeReport.ts +133 -0
- package/runtime/workflow/lib/checkCodeRunner.ts +1225 -0
- package/runtime/workflow/lib/cliTool.ts +31 -0
- package/runtime/workflow/lib/config.ts +130 -0
- package/runtime/workflow/lib/contractInfoCandidates.ts +690 -0
- package/runtime/workflow/lib/create2.ts +278 -0
- package/runtime/workflow/lib/create2Hashes.ts +83 -0
- package/runtime/workflow/lib/deploymentInfo.ts +286 -0
- package/runtime/workflow/lib/deploymentRecords.ts +576 -0
- package/runtime/workflow/lib/deployments.ts +276 -0
- package/runtime/workflow/lib/erc1967.ts +57 -0
- package/runtime/workflow/lib/errors.ts +95 -0
- package/runtime/workflow/lib/executionId.ts +35 -0
- package/runtime/workflow/lib/executionPlan.ts +550 -0
- package/runtime/workflow/lib/executionProgress.ts +127 -0
- package/runtime/workflow/lib/external.ts +372 -0
- package/runtime/workflow/lib/externalExecutionScanner.ts +892 -0
- package/runtime/workflow/lib/forkSnapshot.ts +231 -0
- package/runtime/workflow/lib/gateTool.ts +261 -0
- package/runtime/workflow/lib/gateToolSync.ts +398 -0
- package/runtime/workflow/lib/lock.ts +388 -0
- package/runtime/workflow/lib/migrationState.ts +1246 -0
- package/runtime/workflow/lib/operatorExecution.ts +174 -0
- package/runtime/workflow/lib/parameters.ts +437 -0
- package/runtime/workflow/lib/pendingDeploymentReuse.ts +428 -0
- package/runtime/workflow/lib/proxy.ts +45 -0
- package/runtime/workflow/lib/registryCommit.ts +455 -0
- package/runtime/workflow/lib/release.ts +329 -0
- package/runtime/workflow/lib/releaseFinalize.ts +711 -0
- package/runtime/workflow/lib/releaseParameters.ts +248 -0
- package/runtime/workflow/lib/reporter.ts +502 -0
- package/runtime/workflow/lib/resultPaths.ts +49 -0
- package/runtime/workflow/lib/runtimeVersion.ts +28 -0
- package/runtime/workflow/lib/signer.ts +124 -0
- package/runtime/workflow/lib/signerIdentity.ts +22 -0
- package/runtime/workflow/lib/simulation.ts +415 -0
- package/runtime/workflow/lib/simulationCallers.ts +335 -0
- package/runtime/workflow/lib/sourceRpcPolicy.ts +41 -0
- package/runtime/workflow/lib/state.ts +25 -0
- package/runtime/workflow/lib/staticValidation.ts +142 -0
- package/runtime/workflow/lib/zksyncForkCapability.ts +80 -0
- package/runtime/workflow/lib/zksyncForkRunner.ts +505 -0
- package/runtime/workflow/reconcileExternal.ts +86 -0
- package/runtime/workflow/reconcileExternalRun.ts +88 -0
- package/runtime/workflow/releaseFinalize.ts +8 -0
- package/runtime/workflow/run.ts +1072 -0
- package/runtime/workflow/staticValidate.ts +23 -0
- package/runtime/workflow/steps/call.ts +10 -0
- package/runtime/workflow/steps/check.ts +10 -0
- package/runtime/workflow/steps/custom.ts +43 -0
- package/runtime/workflow/steps/deploy.ts +771 -0
- package/runtime/workflow/steps/upgrade.ts +598 -0
- package/runtime/workflow/steps/view.ts +10 -0
|
@@ -0,0 +1,1225 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import {
|
|
5
|
+
CheckCodeEvidenceEntry,
|
|
6
|
+
updateCheckCodeEvidenceIndex,
|
|
7
|
+
} from "./checkCodeReport";
|
|
8
|
+
import type { DeploymentInfo } from "./deploymentInfo";
|
|
9
|
+
|
|
10
|
+
export type ManifestSyncStatus =
|
|
11
|
+
| "not_required"
|
|
12
|
+
| "already_current"
|
|
13
|
+
| "synced"
|
|
14
|
+
| "proxy_kind_unknown"
|
|
15
|
+
| "failed";
|
|
16
|
+
|
|
17
|
+
export type CheckCodeStatus =
|
|
18
|
+
| "compile_failed"
|
|
19
|
+
| "check_failed"
|
|
20
|
+
| "sync_partial"
|
|
21
|
+
| "succeeded";
|
|
22
|
+
|
|
23
|
+
export type DeploymentScheme = "evm" | "zksync-eravm";
|
|
24
|
+
|
|
25
|
+
export type EffectiveContractInfo = Record<string, Record<string, string>>;
|
|
26
|
+
|
|
27
|
+
export interface CheckCodeOptions {
|
|
28
|
+
root: string;
|
|
29
|
+
runId: string;
|
|
30
|
+
artifactHash: string;
|
|
31
|
+
networks?: string[];
|
|
32
|
+
contracts?: string[];
|
|
33
|
+
deploymentAudit?: {
|
|
34
|
+
deploymentId: string;
|
|
35
|
+
registryCommitId: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CheckCodeDeploymentAuditEvidence {
|
|
40
|
+
deploymentId: string;
|
|
41
|
+
registryCommitId: string;
|
|
42
|
+
registryCommitPath: string;
|
|
43
|
+
registryCommitHash: string;
|
|
44
|
+
selections: Array<{ network: string; executionId: string }>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface CommandResult {
|
|
48
|
+
status: number;
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CommandEvidence {
|
|
53
|
+
command: string;
|
|
54
|
+
args: string[];
|
|
55
|
+
status: number;
|
|
56
|
+
network: string;
|
|
57
|
+
scheme: DeploymentScheme;
|
|
58
|
+
manifestDir?: string;
|
|
59
|
+
manifestFile?: string;
|
|
60
|
+
error?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CheckCodeEntry {
|
|
64
|
+
network: string;
|
|
65
|
+
scheme: DeploymentScheme;
|
|
66
|
+
instanceId: string;
|
|
67
|
+
contractInfoKey: string;
|
|
68
|
+
actualContract: string;
|
|
69
|
+
address: string;
|
|
70
|
+
proxyKind?: string;
|
|
71
|
+
implementationAddress?: string;
|
|
72
|
+
manifestDir: string;
|
|
73
|
+
manifestFile: string;
|
|
74
|
+
syncStatus: ManifestSyncStatus;
|
|
75
|
+
beforeHash: string;
|
|
76
|
+
afterHash: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface CheckCodeRunReport {
|
|
80
|
+
version: 1;
|
|
81
|
+
runId: string;
|
|
82
|
+
status: CheckCodeStatus;
|
|
83
|
+
artifactHash: string;
|
|
84
|
+
sourceContractInfoPath: "contractInfo.json";
|
|
85
|
+
deploymentAudit?: CheckCodeDeploymentAuditEvidence;
|
|
86
|
+
effectiveContractInfoPath?: string;
|
|
87
|
+
rawCheckReportPath?: string;
|
|
88
|
+
entries: CheckCodeEntry[];
|
|
89
|
+
commands: CommandEvidence[];
|
|
90
|
+
startedAt: string;
|
|
91
|
+
completedAt: string;
|
|
92
|
+
error?: string;
|
|
93
|
+
reportPath: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface CheckCodeDependencies {
|
|
97
|
+
runCommand: (
|
|
98
|
+
command: string,
|
|
99
|
+
args: string[],
|
|
100
|
+
options?: { cwd: string; env?: NodeJS.ProcessEnv },
|
|
101
|
+
) => Promise<CommandResult> | CommandResult;
|
|
102
|
+
loadContractInfo: () => EffectiveContractInfo;
|
|
103
|
+
loadDeploymentInfo: () => DeploymentInfo;
|
|
104
|
+
resolveScheme: (network: string) => DeploymentScheme;
|
|
105
|
+
resolveManifestDir: (network: string) => string;
|
|
106
|
+
resolveManifestFile: (
|
|
107
|
+
network: string,
|
|
108
|
+
manifestDir: string,
|
|
109
|
+
) => Promise<string> | string;
|
|
110
|
+
getImplementation: (
|
|
111
|
+
network: string,
|
|
112
|
+
address: string,
|
|
113
|
+
) => Promise<string | undefined>;
|
|
114
|
+
getStorageLayoutHash: (
|
|
115
|
+
contractName: string,
|
|
116
|
+
) => Promise<string>;
|
|
117
|
+
now?: () => string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const SAFE_MANIFEST_DIR = /^\.openzeppelin(?:\.[a-z0-9][a-z0-9-]*)?$/;
|
|
121
|
+
const SAFE_MANIFEST_FILE = /^\.openzeppelin(?:\.[a-z0-9][a-z0-9-]*)?\/[A-Za-z0-9][A-Za-z0-9._-]*\.json$/;
|
|
122
|
+
const HASH = /^[0-9a-f]{64}$/;
|
|
123
|
+
|
|
124
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => (
|
|
125
|
+
Boolean(value) && typeof value === "object" && !Array.isArray(value)
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const exactKeys = (
|
|
129
|
+
value: Record<string, unknown>,
|
|
130
|
+
required: string[],
|
|
131
|
+
optional: string[],
|
|
132
|
+
label: string,
|
|
133
|
+
): void => {
|
|
134
|
+
const keys = Object.keys(value);
|
|
135
|
+
if (
|
|
136
|
+
required.some((key) => !keys.includes(key))
|
|
137
|
+
|| keys.some((key) => !required.includes(key) && !optional.includes(key))
|
|
138
|
+
) {
|
|
139
|
+
throw new Error(`${label} is not canonical: unsupported or missing fields`);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const isoTimestamp = (value: unknown, label: string): string => {
|
|
144
|
+
if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
|
|
145
|
+
throw new Error(`${label} must be an ISO timestamp`);
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const manifestDir = (value: unknown, label: string): string => {
|
|
151
|
+
if (typeof value !== "string" || !SAFE_MANIFEST_DIR.test(value)) {
|
|
152
|
+
throw new Error(`${label} is not a canonical manifest directory`);
|
|
153
|
+
}
|
|
154
|
+
return value;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const manifestFile = (value: unknown, directory: string, label: string): string => {
|
|
158
|
+
if (
|
|
159
|
+
typeof value !== "string"
|
|
160
|
+
|| !SAFE_MANIFEST_FILE.test(value)
|
|
161
|
+
|| !value.startsWith(`${directory}/`)
|
|
162
|
+
) {
|
|
163
|
+
throw new Error(`${label} must belong to its canonical manifest directory`);
|
|
164
|
+
}
|
|
165
|
+
return value;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const validateCheckCodeRunReport = (value: unknown): CheckCodeRunReport => {
|
|
169
|
+
if (!isRecord(value)) throw new Error("CheckCode report must be an object");
|
|
170
|
+
exactKeys(value, [
|
|
171
|
+
"version",
|
|
172
|
+
"runId",
|
|
173
|
+
"status",
|
|
174
|
+
"artifactHash",
|
|
175
|
+
"sourceContractInfoPath",
|
|
176
|
+
"entries",
|
|
177
|
+
"commands",
|
|
178
|
+
"startedAt",
|
|
179
|
+
"completedAt",
|
|
180
|
+
"reportPath",
|
|
181
|
+
], ["deploymentAudit", "effectiveContractInfoPath", "rawCheckReportPath", "error"], "CheckCode report");
|
|
182
|
+
if (value.version !== 1) throw new Error("CheckCode report version must be 1");
|
|
183
|
+
if (value.sourceContractInfoPath !== "contractInfo.json") {
|
|
184
|
+
throw new Error("CheckCode report sourceContractInfoPath must be contractInfo.json");
|
|
185
|
+
}
|
|
186
|
+
if (value.deploymentAudit !== undefined) {
|
|
187
|
+
if (!isRecord(value.deploymentAudit)) {
|
|
188
|
+
throw new Error("CheckCode report deploymentAudit must be an object");
|
|
189
|
+
}
|
|
190
|
+
exactKeys(value.deploymentAudit, [
|
|
191
|
+
"deploymentId",
|
|
192
|
+
"registryCommitId",
|
|
193
|
+
"registryCommitPath",
|
|
194
|
+
"registryCommitHash",
|
|
195
|
+
"selections",
|
|
196
|
+
], [], "CheckCode report deploymentAudit");
|
|
197
|
+
if (
|
|
198
|
+
typeof value.deploymentAudit.deploymentId !== "string"
|
|
199
|
+
|| typeof value.deploymentAudit.registryCommitId !== "string"
|
|
200
|
+
|| typeof value.deploymentAudit.registryCommitPath !== "string"
|
|
201
|
+
|| !HASH.test(String(value.deploymentAudit.registryCommitHash))
|
|
202
|
+
|| !Array.isArray(value.deploymentAudit.selections)
|
|
203
|
+
|| value.deploymentAudit.selections.length === 0
|
|
204
|
+
) {
|
|
205
|
+
throw new Error("CheckCode report deploymentAudit is invalid");
|
|
206
|
+
}
|
|
207
|
+
const expectedPath = `scripts/tasks/${value.deploymentAudit.deploymentId}/results/registry/${value.deploymentAudit.registryCommitId}.json`;
|
|
208
|
+
if (value.deploymentAudit.registryCommitPath !== expectedPath) {
|
|
209
|
+
throw new Error("CheckCode report deploymentAudit registry path is invalid");
|
|
210
|
+
}
|
|
211
|
+
const selectionKeys = value.deploymentAudit.selections.map((raw, index) => {
|
|
212
|
+
if (!isRecord(raw)) throw new Error(`CheckCode deploymentAudit selection ${index} is invalid`);
|
|
213
|
+
exactKeys(raw, ["network", "executionId"], [], `CheckCode deploymentAudit selection ${index}`);
|
|
214
|
+
if (typeof raw.network !== "string" || typeof raw.executionId !== "string") {
|
|
215
|
+
throw new Error(`CheckCode deploymentAudit selection ${index} is invalid`);
|
|
216
|
+
}
|
|
217
|
+
return `${raw.network}\0${raw.executionId}`;
|
|
218
|
+
});
|
|
219
|
+
if (
|
|
220
|
+
new Set(value.deploymentAudit.selections.map((item) => String((item as Record<string, unknown>).network))).size
|
|
221
|
+
!== value.deploymentAudit.selections.length
|
|
222
|
+
|| JSON.stringify(selectionKeys) !== JSON.stringify([...selectionKeys].sort())
|
|
223
|
+
) {
|
|
224
|
+
throw new Error("CheckCode deploymentAudit selections must be unique and sorted");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (
|
|
228
|
+
value.status !== "compile_failed"
|
|
229
|
+
&& value.status !== "check_failed"
|
|
230
|
+
&& value.status !== "sync_partial"
|
|
231
|
+
&& value.status !== "succeeded"
|
|
232
|
+
) {
|
|
233
|
+
throw new Error("CheckCode report status is invalid");
|
|
234
|
+
}
|
|
235
|
+
if (!Array.isArray(value.entries) || !Array.isArray(value.commands)) {
|
|
236
|
+
throw new Error("CheckCode report entries and commands must be arrays");
|
|
237
|
+
}
|
|
238
|
+
const entries = value.entries.map((raw, index): CheckCodeEntry => {
|
|
239
|
+
if (!isRecord(raw)) throw new Error(`CheckCode entry ${index} must be an object`);
|
|
240
|
+
exactKeys(raw, [
|
|
241
|
+
"network",
|
|
242
|
+
"scheme",
|
|
243
|
+
"instanceId",
|
|
244
|
+
"contractInfoKey",
|
|
245
|
+
"actualContract",
|
|
246
|
+
"address",
|
|
247
|
+
"manifestDir",
|
|
248
|
+
"manifestFile",
|
|
249
|
+
"syncStatus",
|
|
250
|
+
"beforeHash",
|
|
251
|
+
"afterHash",
|
|
252
|
+
], ["proxyKind", "implementationAddress"], `CheckCode entry ${index}`);
|
|
253
|
+
if (
|
|
254
|
+
(raw.scheme !== "evm" && raw.scheme !== "zksync-eravm")
|
|
255
|
+
|| raw.syncStatus !== "not_required"
|
|
256
|
+
&& raw.syncStatus !== "already_current"
|
|
257
|
+
&& raw.syncStatus !== "synced"
|
|
258
|
+
&& raw.syncStatus !== "proxy_kind_unknown"
|
|
259
|
+
&& raw.syncStatus !== "failed"
|
|
260
|
+
) {
|
|
261
|
+
throw new Error(`CheckCode entry ${index} syncStatus is invalid`);
|
|
262
|
+
}
|
|
263
|
+
const entryManifestDir = manifestDir(raw.manifestDir, `CheckCode entry ${index}.manifestDir`);
|
|
264
|
+
manifestFile(raw.manifestFile, entryManifestDir, `CheckCode entry ${index}.manifestFile`);
|
|
265
|
+
for (const key of [
|
|
266
|
+
"network",
|
|
267
|
+
"instanceId",
|
|
268
|
+
"contractInfoKey",
|
|
269
|
+
"actualContract",
|
|
270
|
+
"address",
|
|
271
|
+
"manifestFile",
|
|
272
|
+
]) {
|
|
273
|
+
if (typeof raw[key] !== "string" || !String(raw[key]).trim()) {
|
|
274
|
+
throw new Error(`CheckCode entry ${index}.${key} is required`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (!HASH.test(String(raw.beforeHash)) || !HASH.test(String(raw.afterHash))) {
|
|
278
|
+
throw new Error(`CheckCode entry ${index} manifest hash is invalid`);
|
|
279
|
+
}
|
|
280
|
+
return raw as unknown as CheckCodeEntry;
|
|
281
|
+
});
|
|
282
|
+
const commands = value.commands.map((raw, index): CommandEvidence => {
|
|
283
|
+
if (!isRecord(raw)) throw new Error(`CheckCode command ${index} must be an object`);
|
|
284
|
+
exactKeys(raw, ["command", "args", "status", "network", "scheme"], ["manifestDir", "manifestFile", "error"], `CheckCode command ${index}`);
|
|
285
|
+
if (
|
|
286
|
+
typeof raw.command !== "string"
|
|
287
|
+
|| !Array.isArray(raw.args)
|
|
288
|
+
|| !Number.isInteger(raw.status)
|
|
289
|
+
|| typeof raw.network !== "string"
|
|
290
|
+
|| !raw.network.trim()
|
|
291
|
+
|| (raw.scheme !== "evm" && raw.scheme !== "zksync-eravm")
|
|
292
|
+
) {
|
|
293
|
+
throw new Error(`CheckCode command ${index} is invalid`);
|
|
294
|
+
}
|
|
295
|
+
if ((raw.manifestDir === undefined) !== (raw.manifestFile === undefined)) {
|
|
296
|
+
throw new Error(`CheckCode command ${index} manifestDir and manifestFile must appear together`);
|
|
297
|
+
}
|
|
298
|
+
if (raw.manifestDir !== undefined) {
|
|
299
|
+
const commandManifestDir = manifestDir(raw.manifestDir, `CheckCode command ${index}.manifestDir`);
|
|
300
|
+
if (raw.manifestFile !== undefined) {
|
|
301
|
+
manifestFile(raw.manifestFile, commandManifestDir, `CheckCode command ${index}.manifestFile`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return raw as unknown as CommandEvidence;
|
|
305
|
+
});
|
|
306
|
+
if (value.status === "succeeded") {
|
|
307
|
+
if (
|
|
308
|
+
entries.some((entry) => ![
|
|
309
|
+
"not_required",
|
|
310
|
+
"already_current",
|
|
311
|
+
"synced",
|
|
312
|
+
].includes(entry.syncStatus))
|
|
313
|
+
|| commands.some((command) => command.status !== 0)
|
|
314
|
+
|| value.error !== undefined
|
|
315
|
+
) {
|
|
316
|
+
throw new Error("Succeeded CheckCode report contains unsuccessful evidence");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (value.status !== "succeeded" && (typeof value.error !== "string" || !value.error)) {
|
|
320
|
+
throw new Error("Unsuccessful CheckCode report requires an error");
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
...(value as unknown as CheckCodeRunReport),
|
|
324
|
+
entries,
|
|
325
|
+
commands,
|
|
326
|
+
startedAt: isoTimestamp(value.startedAt, "CheckCode startedAt"),
|
|
327
|
+
completedAt: isoTimestamp(value.completedAt, "CheckCode completedAt"),
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const safeRunId = (runId: string): string => {
|
|
332
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(runId)) {
|
|
333
|
+
throw new Error("CheckCode runId contains unsupported characters");
|
|
334
|
+
}
|
|
335
|
+
return runId;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const safeBindingId = (value: string, label: string): string => {
|
|
339
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,199}$/.test(value)) {
|
|
340
|
+
throw new Error(`${label} contains unsupported characters`);
|
|
341
|
+
}
|
|
342
|
+
return value;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const normalizeForJson = (value: unknown): unknown => {
|
|
346
|
+
if (Array.isArray(value)) return value.map(normalizeForJson);
|
|
347
|
+
if (isRecord(value)) {
|
|
348
|
+
return Object.fromEntries(
|
|
349
|
+
Object.keys(value).sort().map((key) => [key, normalizeForJson(value[key])]),
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return value;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const hashValue = (value: unknown): string => crypto
|
|
356
|
+
.createHash("sha256")
|
|
357
|
+
.update(JSON.stringify(normalizeForJson(value)))
|
|
358
|
+
.digest("hex");
|
|
359
|
+
|
|
360
|
+
const writeJsonAtomic = (filePath: string, value: unknown): void => {
|
|
361
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
362
|
+
const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
363
|
+
try {
|
|
364
|
+
fs.writeFileSync(temporary, JSON.stringify(value, null, 2) + "\n");
|
|
365
|
+
fs.renameSync(temporary, filePath);
|
|
366
|
+
} catch (error) {
|
|
367
|
+
if (fs.existsSync(temporary)) fs.unlinkSync(temporary);
|
|
368
|
+
throw error;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const readJson = (filePath: string, label: string): Record<string, unknown> => {
|
|
373
|
+
let value: unknown;
|
|
374
|
+
try {
|
|
375
|
+
value = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
376
|
+
} catch (error) {
|
|
377
|
+
throw new Error(`${label} is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
378
|
+
}
|
|
379
|
+
if (!isRecord(value)) throw new Error(`${label} must be an object`);
|
|
380
|
+
return value;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const prepareDeploymentAudit = (
|
|
384
|
+
options: CheckCodeOptions,
|
|
385
|
+
): CheckCodeDeploymentAuditEvidence | undefined => {
|
|
386
|
+
if (!options.deploymentAudit) return undefined;
|
|
387
|
+
if (options.contracts?.length) {
|
|
388
|
+
throw new Error("Deployment CheckCode audit cannot filter contracts");
|
|
389
|
+
}
|
|
390
|
+
const deploymentId = safeBindingId(
|
|
391
|
+
options.deploymentAudit.deploymentId,
|
|
392
|
+
"deploymentAudit deploymentId",
|
|
393
|
+
);
|
|
394
|
+
const registryCommitId = safeBindingId(
|
|
395
|
+
options.deploymentAudit.registryCommitId,
|
|
396
|
+
"deploymentAudit registryCommitId",
|
|
397
|
+
);
|
|
398
|
+
const registryCommitPath = [
|
|
399
|
+
"scripts",
|
|
400
|
+
"tasks",
|
|
401
|
+
deploymentId,
|
|
402
|
+
"results",
|
|
403
|
+
"registry",
|
|
404
|
+
`${registryCommitId}.json`,
|
|
405
|
+
].join("/");
|
|
406
|
+
const registryFile = path.join(options.root, ...registryCommitPath.split("/"));
|
|
407
|
+
const registry = readJson(registryFile, "Registry commit report");
|
|
408
|
+
if (
|
|
409
|
+
registry.version !== 1
|
|
410
|
+
|| registry.status !== "committed"
|
|
411
|
+
|| registry.taskId !== deploymentId
|
|
412
|
+
|| registry.commitId !== registryCommitId
|
|
413
|
+
|| !Array.isArray(registry.selections)
|
|
414
|
+
|| registry.selections.length === 0
|
|
415
|
+
) {
|
|
416
|
+
throw new Error("Registry commit report does not match the deployment audit");
|
|
417
|
+
}
|
|
418
|
+
const selections = registry.selections.map((raw, index) => {
|
|
419
|
+
if (!isRecord(raw)) throw new Error(`Registry commit selection ${index} is invalid`);
|
|
420
|
+
return {
|
|
421
|
+
network: safeBindingId(String(raw.network || ""), `Registry selection ${index} network`),
|
|
422
|
+
executionId: safeBindingId(
|
|
423
|
+
String(raw.executionId || ""),
|
|
424
|
+
`Registry selection ${index} executionId`,
|
|
425
|
+
),
|
|
426
|
+
};
|
|
427
|
+
});
|
|
428
|
+
const sortedSelections = [...selections].sort((left, right) => left.network.localeCompare(right.network));
|
|
429
|
+
if (
|
|
430
|
+
new Set(selections.map((selection) => selection.network)).size !== selections.length
|
|
431
|
+
|| JSON.stringify(selections) !== JSON.stringify(sortedSelections)
|
|
432
|
+
) {
|
|
433
|
+
throw new Error("Registry commit selections must be unique and sorted");
|
|
434
|
+
}
|
|
435
|
+
if (options.networks?.length) {
|
|
436
|
+
const requested = [...new Set(options.networks)].sort();
|
|
437
|
+
const committed = selections.map((selection) => selection.network).sort();
|
|
438
|
+
if (JSON.stringify(requested) !== JSON.stringify(committed)) {
|
|
439
|
+
throw new Error("Deployment CheckCode networks must exactly match the registry commit");
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const contractInfo = readJson(path.join(options.root, "contractInfo.json"), "contractInfo.json");
|
|
443
|
+
if (registry.afterHash !== hashValue(contractInfo)) {
|
|
444
|
+
throw new Error("Formal contractInfo does not match the registry commit");
|
|
445
|
+
}
|
|
446
|
+
const taskDir = path.join(options.root, "scripts", "tasks", deploymentId);
|
|
447
|
+
const lock = readJson(path.join(taskDir, "parameters.lock.json"), "parameters.lock.json");
|
|
448
|
+
if (lock.version !== 2 || lock.deploymentId !== deploymentId || !Array.isArray(lock.executions)) {
|
|
449
|
+
throw new Error("Deployment CheckCode requires canonical parameters.lock.json");
|
|
450
|
+
}
|
|
451
|
+
const status = readJson(path.join(taskDir, "status.json"), "deployment status.json");
|
|
452
|
+
if (status.status !== "deployed_pending_code_audit" || !isRecord(status.networks)) {
|
|
453
|
+
throw new Error("Deployment is not pending code audit");
|
|
454
|
+
}
|
|
455
|
+
const sourceRecordPaths = Array.isArray(registry.sourceRecordPaths)
|
|
456
|
+
? new Set(registry.sourceRecordPaths.map(String))
|
|
457
|
+
: new Set<string>();
|
|
458
|
+
for (const selection of selections) {
|
|
459
|
+
const executions = lock.executions.filter((raw) => (
|
|
460
|
+
isRecord(raw) && raw.executionId === selection.executionId
|
|
461
|
+
));
|
|
462
|
+
if (executions.length !== 1) {
|
|
463
|
+
throw new Error(`Deployment lock is missing execution ${selection.executionId}`);
|
|
464
|
+
}
|
|
465
|
+
const execution = executions[0];
|
|
466
|
+
if (
|
|
467
|
+
!isRecord(execution.releaseFingerprint)
|
|
468
|
+
|| execution.releaseFingerprint.artifactHash !== options.artifactHash
|
|
469
|
+
) {
|
|
470
|
+
throw new Error(`CheckCode artifactHash does not match ${selection.network} execution lock`);
|
|
471
|
+
}
|
|
472
|
+
const networkStatus = status.networks[selection.network];
|
|
473
|
+
if (
|
|
474
|
+
!isRecord(networkStatus)
|
|
475
|
+
|| networkStatus.deployStatus !== "deployed_pending_code_audit"
|
|
476
|
+
|| networkStatus.registryExecutionId !== selection.executionId
|
|
477
|
+
) {
|
|
478
|
+
throw new Error(`${selection.network} is not pending audit for the committed execution`);
|
|
479
|
+
}
|
|
480
|
+
const recordPath = `scripts/tasks/${deploymentId}/results/${selection.network}.${selection.executionId}.deployment.json`;
|
|
481
|
+
if (!sourceRecordPaths.has(recordPath)) {
|
|
482
|
+
throw new Error(`Registry commit does not bind deployment record ${recordPath}`);
|
|
483
|
+
}
|
|
484
|
+
const record = readJson(path.join(options.root, ...recordPath.split("/")), "Deployment record");
|
|
485
|
+
if (
|
|
486
|
+
record.taskId !== deploymentId
|
|
487
|
+
|| record.network !== selection.network
|
|
488
|
+
|| record.executionId !== selection.executionId
|
|
489
|
+
|| record.status !== "applied"
|
|
490
|
+
) {
|
|
491
|
+
throw new Error(`Deployment record is not applied for ${selection.network}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
deploymentId,
|
|
496
|
+
registryCommitId,
|
|
497
|
+
registryCommitPath,
|
|
498
|
+
registryCommitHash: crypto.createHash("sha256").update(fs.readFileSync(registryFile)).digest("hex"),
|
|
499
|
+
selections,
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const completeDeploymentAudit = (
|
|
504
|
+
root: string,
|
|
505
|
+
report: CheckCodeRunReport,
|
|
506
|
+
audit: CheckCodeDeploymentAuditEvidence,
|
|
507
|
+
): void => {
|
|
508
|
+
const taskDir = path.join(root, "scripts", "tasks", audit.deploymentId);
|
|
509
|
+
const statusFile = path.join(taskDir, "status.json");
|
|
510
|
+
const reportFile = path.join(root, ...report.reportPath.split("/"));
|
|
511
|
+
const reportHash = crypto.createHash("sha256").update(fs.readFileSync(reportFile)).digest("hex");
|
|
512
|
+
const status = readJson(statusFile, "deployment status.json");
|
|
513
|
+
const currentNetworks = isRecord(status.networks) ? status.networks : {};
|
|
514
|
+
const networks = { ...currentNetworks };
|
|
515
|
+
for (const selection of audit.selections) {
|
|
516
|
+
const current = currentNetworks[selection.network];
|
|
517
|
+
if (!isRecord(current) || current.registryExecutionId !== selection.executionId) {
|
|
518
|
+
throw new Error(`${selection.network} deployment status changed during CheckCode`);
|
|
519
|
+
}
|
|
520
|
+
networks[selection.network] = {
|
|
521
|
+
...current,
|
|
522
|
+
deployStatus: "completed",
|
|
523
|
+
checkCodeStatus: "succeeded",
|
|
524
|
+
checkCodeRunId: report.runId,
|
|
525
|
+
checkCodeReportPath: report.reportPath,
|
|
526
|
+
updatedAt: report.completedAt,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
writeJsonAtomic(statusFile, {
|
|
530
|
+
...status,
|
|
531
|
+
status: "completed",
|
|
532
|
+
networks,
|
|
533
|
+
codeAudit: {
|
|
534
|
+
status: "succeeded",
|
|
535
|
+
runId: report.runId,
|
|
536
|
+
reportPath: report.reportPath,
|
|
537
|
+
reportHash,
|
|
538
|
+
artifactHash: report.artifactHash,
|
|
539
|
+
registryCommitId: audit.registryCommitId,
|
|
540
|
+
registryCommitPath: audit.registryCommitPath,
|
|
541
|
+
registryCommitHash: audit.registryCommitHash,
|
|
542
|
+
selections: audit.selections,
|
|
543
|
+
completedAt: report.completedAt,
|
|
544
|
+
},
|
|
545
|
+
updatedAt: report.completedAt,
|
|
546
|
+
});
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const directoryHash = (directory: string): string => {
|
|
550
|
+
if (!fs.existsSync(directory)) return hashValue([]);
|
|
551
|
+
if (fs.lstatSync(directory).isSymbolicLink()) {
|
|
552
|
+
throw new Error(`Manifest directory cannot be a symlink: ${directory}`);
|
|
553
|
+
}
|
|
554
|
+
const files: string[] = [];
|
|
555
|
+
const visit = (current: string): void => {
|
|
556
|
+
for (const name of fs.readdirSync(current).sort()) {
|
|
557
|
+
const filePath = path.join(current, name);
|
|
558
|
+
const stat = fs.lstatSync(filePath);
|
|
559
|
+
if (stat.isSymbolicLink()) throw new Error(`Manifest path cannot contain symlinks: ${filePath}`);
|
|
560
|
+
if (stat.isDirectory()) visit(filePath);
|
|
561
|
+
else if (stat.isFile()) files.push(filePath);
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
visit(directory);
|
|
565
|
+
const hash = crypto.createHash("sha256");
|
|
566
|
+
for (const filePath of files) {
|
|
567
|
+
hash.update(path.relative(directory, filePath).split(path.sep).join("/"));
|
|
568
|
+
hash.update("\0");
|
|
569
|
+
hash.update(fs.readFileSync(filePath));
|
|
570
|
+
hash.update("\0");
|
|
571
|
+
}
|
|
572
|
+
return hash.digest("hex");
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const confinedManifestFile = (input: {
|
|
576
|
+
root: string;
|
|
577
|
+
directory: string;
|
|
578
|
+
requestedFile: string;
|
|
579
|
+
}): { absolute: string; relative: string } => {
|
|
580
|
+
const expectedDirectory = path.resolve(input.root, input.directory);
|
|
581
|
+
const absolute = path.isAbsolute(input.requestedFile)
|
|
582
|
+
? path.resolve(input.requestedFile)
|
|
583
|
+
: path.resolve(input.root, input.requestedFile);
|
|
584
|
+
if (path.dirname(absolute) !== expectedDirectory) {
|
|
585
|
+
throw new Error(`Manifest file escapes ${input.directory}: ${input.requestedFile}`);
|
|
586
|
+
}
|
|
587
|
+
const name = path.basename(absolute);
|
|
588
|
+
if (!/^[a-z0-9][a-z0-9-]*\.json$/.test(name)) {
|
|
589
|
+
throw new Error(`Manifest filename is not canonical: ${name}`);
|
|
590
|
+
}
|
|
591
|
+
if (fs.existsSync(absolute)) {
|
|
592
|
+
const stat = fs.lstatSync(absolute);
|
|
593
|
+
if (stat.isSymbolicLink()) throw new Error(`Manifest file cannot be a symlink: ${absolute}`);
|
|
594
|
+
if (!stat.isFile()) throw new Error(`Manifest path is not a regular file: ${absolute}`);
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
absolute,
|
|
598
|
+
relative: path.relative(input.root, absolute).split(path.sep).join("/"),
|
|
599
|
+
};
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
const filterContractInfo = (
|
|
603
|
+
contractInfo: EffectiveContractInfo,
|
|
604
|
+
networks: string[] | undefined,
|
|
605
|
+
contracts: string[] | undefined,
|
|
606
|
+
): EffectiveContractInfo => {
|
|
607
|
+
const networkFilter = networks ? new Set(networks) : undefined;
|
|
608
|
+
const contractFilter = contracts ? new Set(contracts) : undefined;
|
|
609
|
+
return Object.fromEntries(Object.entries(contractInfo)
|
|
610
|
+
.filter(([network]) => !networkFilter || networkFilter.has(network))
|
|
611
|
+
.map(([network, entries]) => [
|
|
612
|
+
network,
|
|
613
|
+
Object.fromEntries(Object.entries(entries).filter(([key]) => {
|
|
614
|
+
if (!contractFilter) return true;
|
|
615
|
+
const parts = key.split(":");
|
|
616
|
+
return contractFilter.has(key)
|
|
617
|
+
|| contractFilter.has(parts[0])
|
|
618
|
+
|| contractFilter.has(parts.at(-1) || key);
|
|
619
|
+
})),
|
|
620
|
+
])
|
|
621
|
+
.filter(([, entries]) => Object.keys(entries).length > 0));
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
const registryEntries = (contractInfo: EffectiveContractInfo): Array<{
|
|
625
|
+
network: string;
|
|
626
|
+
instanceId: string;
|
|
627
|
+
contractInfoKey: string;
|
|
628
|
+
actualContract: string;
|
|
629
|
+
address: string;
|
|
630
|
+
}> => Object.entries(contractInfo).flatMap(([network, entries]) => (
|
|
631
|
+
Object.entries(entries).map(([contractInfoKey, address]) => {
|
|
632
|
+
const parts = contractInfoKey.split(":");
|
|
633
|
+
return {
|
|
634
|
+
network,
|
|
635
|
+
instanceId: parts[0],
|
|
636
|
+
contractInfoKey,
|
|
637
|
+
actualContract: parts.at(-1) || contractInfoKey,
|
|
638
|
+
address,
|
|
639
|
+
};
|
|
640
|
+
})
|
|
641
|
+
)).sort((left, right) => (
|
|
642
|
+
`${left.network}.${left.contractInfoKey}`.localeCompare(`${right.network}.${right.contractInfoKey}`)
|
|
643
|
+
));
|
|
644
|
+
|
|
645
|
+
const manifestDocument = (filePath: string): Record<string, unknown> => {
|
|
646
|
+
if (!fs.existsSync(filePath)) return {};
|
|
647
|
+
try {
|
|
648
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
649
|
+
return isRecord(parsed) ? parsed : {};
|
|
650
|
+
} catch {
|
|
651
|
+
return {};
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
const manifestIsCurrent = (input: {
|
|
656
|
+
filePath: string;
|
|
657
|
+
proxyAddress: string;
|
|
658
|
+
implementationAddress: string;
|
|
659
|
+
storageLayoutHash: string;
|
|
660
|
+
}): boolean => {
|
|
661
|
+
const proxy = input.proxyAddress.toLowerCase();
|
|
662
|
+
const implementation = input.implementationAddress.toLowerCase();
|
|
663
|
+
let hasProxy = false;
|
|
664
|
+
let hasImplementation = false;
|
|
665
|
+
let hasLayout = false;
|
|
666
|
+
const document = manifestDocument(input.filePath);
|
|
667
|
+
if (Array.isArray(document.proxies)) {
|
|
668
|
+
hasProxy = document.proxies.some((item) => (
|
|
669
|
+
isRecord(item)
|
|
670
|
+
&& typeof item.address === "string"
|
|
671
|
+
&& item.address.toLowerCase() === proxy
|
|
672
|
+
));
|
|
673
|
+
}
|
|
674
|
+
if (isRecord(document.impls)) {
|
|
675
|
+
for (const rawImplementation of Object.values(document.impls)) {
|
|
676
|
+
if (!isRecord(rawImplementation)) continue;
|
|
677
|
+
const addresses = [rawImplementation.address]
|
|
678
|
+
.concat(Array.isArray(rawImplementation.allAddresses) ? rawImplementation.allAddresses : [])
|
|
679
|
+
.filter((address): address is string => typeof address === "string")
|
|
680
|
+
.map((address) => address.toLowerCase());
|
|
681
|
+
if (!addresses.includes(implementation)) continue;
|
|
682
|
+
hasImplementation = true;
|
|
683
|
+
if (rawImplementation.layout !== undefined) {
|
|
684
|
+
hasLayout = hashValue(rawImplementation.layout) === input.storageLayoutHash;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return hasProxy && hasImplementation && hasLayout;
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
692
|
+
const TRANSACTION_HASH = /^0x[0-9a-fA-F]{64}$/;
|
|
693
|
+
|
|
694
|
+
const sameAddress = (left: unknown, right: unknown): boolean => (
|
|
695
|
+
typeof left === "string"
|
|
696
|
+
&& typeof right === "string"
|
|
697
|
+
&& ADDRESS.test(left)
|
|
698
|
+
&& ADDRESS.test(right)
|
|
699
|
+
&& left.toLowerCase() === right.toLowerCase()
|
|
700
|
+
);
|
|
701
|
+
|
|
702
|
+
const sameJson = (left: unknown, right: unknown): boolean => (
|
|
703
|
+
JSON.stringify(normalizeForJson(left)) === JSON.stringify(normalizeForJson(right))
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
const confinedRegularFile = (
|
|
707
|
+
root: string,
|
|
708
|
+
requested: unknown,
|
|
709
|
+
label: string,
|
|
710
|
+
): string => {
|
|
711
|
+
if (typeof requested !== "string" || !requested || path.isAbsolute(requested)) {
|
|
712
|
+
throw new Error(`${label} must be a project-relative path`);
|
|
713
|
+
}
|
|
714
|
+
const projectRoot = path.resolve(root);
|
|
715
|
+
const absolute = path.resolve(projectRoot, requested);
|
|
716
|
+
const relative = path.relative(projectRoot, absolute);
|
|
717
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
718
|
+
throw new Error(`${label} escapes the project root`);
|
|
719
|
+
}
|
|
720
|
+
let current = projectRoot;
|
|
721
|
+
for (const segment of relative.split(path.sep)) {
|
|
722
|
+
current = path.join(current, segment);
|
|
723
|
+
const stat = fs.lstatSync(current);
|
|
724
|
+
if (stat.isSymbolicLink()) throw new Error(`${label} cannot contain a symlink`);
|
|
725
|
+
}
|
|
726
|
+
const stat = fs.lstatSync(absolute);
|
|
727
|
+
if (!stat.isFile()) throw new Error(`${label} must be a regular file`);
|
|
728
|
+
return absolute;
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
const readConfinedJson = (
|
|
732
|
+
root: string,
|
|
733
|
+
requested: unknown,
|
|
734
|
+
label: string,
|
|
735
|
+
): { absolute: string; bytes: Buffer; data: Record<string, unknown> } => {
|
|
736
|
+
const absolute = confinedRegularFile(root, requested, label);
|
|
737
|
+
const bytes = fs.readFileSync(absolute);
|
|
738
|
+
let parsed: unknown;
|
|
739
|
+
try {
|
|
740
|
+
parsed = JSON.parse(bytes.toString("utf8"));
|
|
741
|
+
} catch (error) {
|
|
742
|
+
throw new Error(`${label} is invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
743
|
+
}
|
|
744
|
+
if (!isRecord(parsed)) throw new Error(`${label} must be an object`);
|
|
745
|
+
return { absolute, bytes, data: parsed };
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
const readRequiredString = (
|
|
749
|
+
input: Record<string, unknown>,
|
|
750
|
+
key: string,
|
|
751
|
+
label: string,
|
|
752
|
+
): string => {
|
|
753
|
+
const value = input[key];
|
|
754
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`${label}.${key} is required`);
|
|
755
|
+
return value;
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const assertZkUupsSyncEvidence = (input: {
|
|
759
|
+
root: string;
|
|
760
|
+
network: string;
|
|
761
|
+
manifestDir: string;
|
|
762
|
+
manifest: { absolute: string; relative: string };
|
|
763
|
+
metadata: NonNullable<DeploymentInfo["networks"][string][string]>;
|
|
764
|
+
selected: ReturnType<typeof registryEntries>[number];
|
|
765
|
+
implementation: string;
|
|
766
|
+
}): void => {
|
|
767
|
+
if (input.metadata.source.kind !== "deployment") {
|
|
768
|
+
throw new Error("zkSync UUPS CheckCode requires a deployment record source");
|
|
769
|
+
}
|
|
770
|
+
const source = input.metadata.source;
|
|
771
|
+
const expectedRecordPath = [
|
|
772
|
+
"scripts",
|
|
773
|
+
"tasks",
|
|
774
|
+
source.deploymentId,
|
|
775
|
+
"results",
|
|
776
|
+
`${input.network}.${source.executionId}.deployment.json`,
|
|
777
|
+
].join("/");
|
|
778
|
+
if (
|
|
779
|
+
source.recordPath !== expectedRecordPath
|
|
780
|
+
|| input.metadata.contractInfoKey !== input.selected.contractInfoKey
|
|
781
|
+
|| input.metadata.actualContract !== input.selected.actualContract
|
|
782
|
+
|| !sameAddress(input.metadata.address, input.selected.address)
|
|
783
|
+
|| !sameAddress(input.metadata.implementationAddress, input.implementation)
|
|
784
|
+
) {
|
|
785
|
+
throw new Error("zkSync deployment metadata does not match the applied target");
|
|
786
|
+
}
|
|
787
|
+
const record = readConfinedJson(input.root, source.recordPath, "zkSync deployment record").data;
|
|
788
|
+
if (
|
|
789
|
+
record.taskId !== source.deploymentId
|
|
790
|
+
|| record.executionId !== source.executionId
|
|
791
|
+
|| record.network !== input.network
|
|
792
|
+
|| record.status !== "applied"
|
|
793
|
+
|| !Array.isArray(record.changes)
|
|
794
|
+
) {
|
|
795
|
+
throw new Error("zkSync deployment record does not match the applied target");
|
|
796
|
+
}
|
|
797
|
+
const matchingChanges = record.changes.filter((raw) => (
|
|
798
|
+
isRecord(raw)
|
|
799
|
+
&& raw.status === "applied"
|
|
800
|
+
&& raw.instanceId === input.selected.instanceId
|
|
801
|
+
&& raw.actualContract === input.metadata.actualContract
|
|
802
|
+
&& raw.contractInfoKey === input.selected.contractInfoKey
|
|
803
|
+
&& sameAddress(raw.proxyAddress ?? raw.address ?? raw.candidateAddress, input.selected.address)
|
|
804
|
+
&& sameAddress(raw.implementationAddress, input.implementation)
|
|
805
|
+
)) as Record<string, unknown>[];
|
|
806
|
+
if (matchingChanges.length !== 1) {
|
|
807
|
+
throw new Error("zkSync deployment record must contain exactly one matching applied UUPS change");
|
|
808
|
+
}
|
|
809
|
+
const change = matchingChanges[0];
|
|
810
|
+
if (!isRecord(change.gateToolSync)) {
|
|
811
|
+
throw new Error("zkSync UUPS deployment change is missing immutable GateTool sync evidence");
|
|
812
|
+
}
|
|
813
|
+
const gateToolSync = change.gateToolSync;
|
|
814
|
+
const outputPath = readRequiredString(gateToolSync, "outputPath", "zkSync GateTool sync evidence");
|
|
815
|
+
if (!isRecord(gateToolSync.data)) {
|
|
816
|
+
throw new Error("zkSync GateTool sync evidence data is required");
|
|
817
|
+
}
|
|
818
|
+
const evidence = readConfinedJson(input.root, outputPath, "zkSync GateTool sync evidence");
|
|
819
|
+
if (!sameJson(evidence.data, gateToolSync.data)) {
|
|
820
|
+
throw new Error("zkSync GateTool sync evidence file does not match the applied deployment record");
|
|
821
|
+
}
|
|
822
|
+
const expectedEvidenceBytes = Buffer.from(`${JSON.stringify(evidence.data, null, 2)}\n`);
|
|
823
|
+
if (!evidence.bytes.equals(expectedEvidenceBytes)) {
|
|
824
|
+
throw new Error("zkSync GateTool sync evidence must use canonical immutable JSON");
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const data = evidence.data;
|
|
828
|
+
if (
|
|
829
|
+
data.version !== 1
|
|
830
|
+
|| data.status !== "succeeded"
|
|
831
|
+
|| data.scheme !== "zksync-eravm"
|
|
832
|
+
|| data.network !== input.network
|
|
833
|
+
|| data.target !== input.network
|
|
834
|
+
|| data.manifestDir !== input.manifestDir
|
|
835
|
+
|| data.contract !== input.metadata.actualContract
|
|
836
|
+
|| !sameAddress(data.proxy, input.selected.address)
|
|
837
|
+
|| !sameAddress(data.implementation, input.implementation)
|
|
838
|
+
|| !sameJson(data.constructorArgs, change.constructorArgs ?? [])
|
|
839
|
+
|| typeof data.transactionHash !== "string"
|
|
840
|
+
|| !TRANSACTION_HASH.test(data.transactionHash)
|
|
841
|
+
|| typeof change.txHash !== "string"
|
|
842
|
+
|| data.transactionHash.toLowerCase() !== change.txHash.toLowerCase()
|
|
843
|
+
) {
|
|
844
|
+
throw new Error("zkSync GateTool sync evidence does not match the applied UUPS deployment");
|
|
845
|
+
}
|
|
846
|
+
if (!isRecord(data.manifestTransaction)) {
|
|
847
|
+
throw new Error("zkSync GateTool sync evidence is missing the manifest transaction");
|
|
848
|
+
}
|
|
849
|
+
const transaction = data.manifestTransaction;
|
|
850
|
+
if (
|
|
851
|
+
transaction.version !== 1
|
|
852
|
+
|| transaction.status !== "committed"
|
|
853
|
+
|| transaction.manifestFile !== input.manifest.relative
|
|
854
|
+
|| typeof transaction.afterHash !== "string"
|
|
855
|
+
|| !HASH.test(transaction.afterHash)
|
|
856
|
+
) {
|
|
857
|
+
throw new Error("zkSync GateTool manifest transaction is not canonical committed evidence");
|
|
858
|
+
}
|
|
859
|
+
const syncReportPath = readRequiredString(data, "syncReportPath", "zkSync GateTool sync evidence");
|
|
860
|
+
const syncReport = confinedRegularFile(input.root, syncReportPath, "zkSync manifest transaction snapshot");
|
|
861
|
+
const expectedSnapshot = Buffer.from(`${JSON.stringify(transaction, null, 2)}\n`);
|
|
862
|
+
const snapshot = fs.readFileSync(syncReport);
|
|
863
|
+
if (
|
|
864
|
+
!snapshot.equals(expectedSnapshot)
|
|
865
|
+
|| data.syncReportHash !== crypto.createHash("sha256").update(snapshot).digest("hex")
|
|
866
|
+
) {
|
|
867
|
+
throw new Error("zkSync manifest transaction snapshot does not match immutable sync evidence");
|
|
868
|
+
}
|
|
869
|
+
if (
|
|
870
|
+
crypto.createHash("sha256").update(fs.readFileSync(input.manifest.absolute)).digest("hex")
|
|
871
|
+
!== transaction.afterHash
|
|
872
|
+
) {
|
|
873
|
+
throw new Error("zkSync manifest bytes no longer match the applied GateTool sync evidence");
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
export const runCheckCode = async (
|
|
878
|
+
options: CheckCodeOptions,
|
|
879
|
+
dependencies: CheckCodeDependencies,
|
|
880
|
+
): Promise<CheckCodeRunReport> => {
|
|
881
|
+
const runId = safeRunId(options.runId);
|
|
882
|
+
const now = dependencies.now || (() => new Date().toISOString());
|
|
883
|
+
const startedAt = now();
|
|
884
|
+
const deploymentAudit = prepareDeploymentAudit(options);
|
|
885
|
+
const runDir = path.join(options.root, "scripts", "checkcode", "runs", runId);
|
|
886
|
+
const reportFile = path.join(runDir, "report.json");
|
|
887
|
+
const effectiveContractInfoFile = path.join(runDir, "effective-contract-info.json");
|
|
888
|
+
const rawCheckReportFile = path.join(runDir, "gate-tool-check.json");
|
|
889
|
+
const commands: CommandEvidence[] = [];
|
|
890
|
+
const entries: CheckCodeEntry[] = [];
|
|
891
|
+
const formalContractInfoPath = path.join(options.root, "contractInfo.json");
|
|
892
|
+
const formalContractInfoBefore = fs.existsSync(formalContractInfoPath)
|
|
893
|
+
? fs.readFileSync(formalContractInfoPath)
|
|
894
|
+
: undefined;
|
|
895
|
+
|
|
896
|
+
const assertFormalContractInfoUnchanged = (): void => {
|
|
897
|
+
const exists = fs.existsSync(formalContractInfoPath);
|
|
898
|
+
if (
|
|
899
|
+
(formalContractInfoBefore === undefined && exists)
|
|
900
|
+
|| (formalContractInfoBefore !== undefined && !exists)
|
|
901
|
+
|| (
|
|
902
|
+
formalContractInfoBefore !== undefined
|
|
903
|
+
&& exists
|
|
904
|
+
&& !formalContractInfoBefore.equals(fs.readFileSync(formalContractInfoPath))
|
|
905
|
+
)
|
|
906
|
+
) {
|
|
907
|
+
throw new Error("Formal contractInfo.json changed during CheckCode");
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
const report = (status: CheckCodeStatus, error?: string): CheckCodeRunReport => {
|
|
912
|
+
assertFormalContractInfoUnchanged();
|
|
913
|
+
const value = validateCheckCodeRunReport({
|
|
914
|
+
version: 1,
|
|
915
|
+
runId,
|
|
916
|
+
status,
|
|
917
|
+
artifactHash: options.artifactHash,
|
|
918
|
+
sourceContractInfoPath: "contractInfo.json",
|
|
919
|
+
...(deploymentAudit ? { deploymentAudit } : {}),
|
|
920
|
+
...(fs.existsSync(effectiveContractInfoFile)
|
|
921
|
+
? { effectiveContractInfoPath: path.relative(options.root, effectiveContractInfoFile).split(path.sep).join("/") }
|
|
922
|
+
: {}),
|
|
923
|
+
...(fs.existsSync(rawCheckReportFile)
|
|
924
|
+
? { rawCheckReportPath: path.relative(options.root, rawCheckReportFile).split(path.sep).join("/") }
|
|
925
|
+
: {}),
|
|
926
|
+
entries,
|
|
927
|
+
commands,
|
|
928
|
+
startedAt,
|
|
929
|
+
completedAt: now(),
|
|
930
|
+
...(error ? { error } : {}),
|
|
931
|
+
reportPath: path.relative(options.root, reportFile).split(path.sep).join("/"),
|
|
932
|
+
});
|
|
933
|
+
writeJsonAtomic(reportFile, value);
|
|
934
|
+
return value;
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
const runCommand = async (
|
|
938
|
+
command: string,
|
|
939
|
+
args: string[],
|
|
940
|
+
evidence: {
|
|
941
|
+
network: string;
|
|
942
|
+
scheme: DeploymentScheme;
|
|
943
|
+
manifestDir?: string;
|
|
944
|
+
manifestFile?: string;
|
|
945
|
+
env?: NodeJS.ProcessEnv;
|
|
946
|
+
evidenceArgs?: string[];
|
|
947
|
+
},
|
|
948
|
+
): Promise<CommandResult> => {
|
|
949
|
+
let result: CommandResult;
|
|
950
|
+
try {
|
|
951
|
+
result = await dependencies.runCommand(command, args, {
|
|
952
|
+
cwd: options.root,
|
|
953
|
+
env: evidence.env,
|
|
954
|
+
});
|
|
955
|
+
} catch (error) {
|
|
956
|
+
result = { status: 1, error: error instanceof Error ? error.message : String(error) };
|
|
957
|
+
}
|
|
958
|
+
commands.push({
|
|
959
|
+
command,
|
|
960
|
+
args: evidence.evidenceArgs || args,
|
|
961
|
+
status: result.status,
|
|
962
|
+
network: evidence.network,
|
|
963
|
+
scheme: evidence.scheme,
|
|
964
|
+
...(evidence.manifestDir ? { manifestDir: evidence.manifestDir } : {}),
|
|
965
|
+
...(evidence.manifestFile ? { manifestFile: evidence.manifestFile } : {}),
|
|
966
|
+
...(result.error ? { error: result.error } : {}),
|
|
967
|
+
});
|
|
968
|
+
return result;
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
let contractInfo: EffectiveContractInfo;
|
|
972
|
+
let deploymentInfo: DeploymentInfo;
|
|
973
|
+
try {
|
|
974
|
+
contractInfo = filterContractInfo(
|
|
975
|
+
dependencies.loadContractInfo(),
|
|
976
|
+
deploymentAudit
|
|
977
|
+
? deploymentAudit.selections.map((selection) => selection.network)
|
|
978
|
+
: options.networks,
|
|
979
|
+
options.contracts,
|
|
980
|
+
);
|
|
981
|
+
deploymentInfo = dependencies.loadDeploymentInfo();
|
|
982
|
+
writeJsonAtomic(effectiveContractInfoFile, contractInfo);
|
|
983
|
+
} catch (error) {
|
|
984
|
+
return report("check_failed", error instanceof Error ? error.message : String(error));
|
|
985
|
+
}
|
|
986
|
+
const selectedEntries = registryEntries(contractInfo);
|
|
987
|
+
if (
|
|
988
|
+
selectedEntries.length === 0
|
|
989
|
+
&& (Boolean(options.networks?.length) || Boolean(options.contracts?.length))
|
|
990
|
+
) {
|
|
991
|
+
return report("check_failed", "CheckCode scope contains no contracts");
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
const selectedNetworks = [...new Set(selectedEntries.map((entry) => entry.network))].sort();
|
|
995
|
+
const schemes = new Map<string, DeploymentScheme>();
|
|
996
|
+
try {
|
|
997
|
+
for (const network of selectedNetworks) {
|
|
998
|
+
const scheme = dependencies.resolveScheme(network);
|
|
999
|
+
if (scheme !== "evm" && scheme !== "zksync-eravm") {
|
|
1000
|
+
throw new Error(`CheckCode scheme for ${network} must be evm or zksync-eravm`);
|
|
1001
|
+
}
|
|
1002
|
+
schemes.set(network, scheme);
|
|
1003
|
+
}
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
return report("check_failed", error instanceof Error ? error.message : String(error));
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
let checkedNetwork: string | undefined;
|
|
1009
|
+
for (const selected of selectedEntries) {
|
|
1010
|
+
const scheme = schemes.get(selected.network)!;
|
|
1011
|
+
if (checkedNetwork !== selected.network) {
|
|
1012
|
+
const env = {
|
|
1013
|
+
...process.env,
|
|
1014
|
+
HARDHAT_NETWORK: selected.network,
|
|
1015
|
+
GATE_WORKFLOW_TARGET: selected.network,
|
|
1016
|
+
};
|
|
1017
|
+
const compile = await runCommand("npm", [
|
|
1018
|
+
"run",
|
|
1019
|
+
"compile",
|
|
1020
|
+
"--",
|
|
1021
|
+
"--network",
|
|
1022
|
+
selected.network,
|
|
1023
|
+
], { network: selected.network, scheme, env });
|
|
1024
|
+
if (compile.status !== 0) {
|
|
1025
|
+
return report("compile_failed", compile.error || `Compilation failed for ${selected.network}`);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const check = await runCommand("npx", [
|
|
1029
|
+
"--no-install",
|
|
1030
|
+
"gate-tool",
|
|
1031
|
+
"check",
|
|
1032
|
+
"--scheme",
|
|
1033
|
+
scheme,
|
|
1034
|
+
"--network",
|
|
1035
|
+
selected.network,
|
|
1036
|
+
"--config",
|
|
1037
|
+
effectiveContractInfoFile,
|
|
1038
|
+
"--output",
|
|
1039
|
+
rawCheckReportFile,
|
|
1040
|
+
], {
|
|
1041
|
+
network: selected.network,
|
|
1042
|
+
scheme,
|
|
1043
|
+
env,
|
|
1044
|
+
evidenceArgs: [
|
|
1045
|
+
"--no-install",
|
|
1046
|
+
"gate-tool",
|
|
1047
|
+
"check",
|
|
1048
|
+
"--scheme",
|
|
1049
|
+
scheme,
|
|
1050
|
+
"--network",
|
|
1051
|
+
selected.network,
|
|
1052
|
+
"--config",
|
|
1053
|
+
path.relative(options.root, effectiveContractInfoFile).split(path.sep).join("/"),
|
|
1054
|
+
"--output",
|
|
1055
|
+
path.relative(options.root, rawCheckReportFile).split(path.sep).join("/"),
|
|
1056
|
+
],
|
|
1057
|
+
});
|
|
1058
|
+
if (check.status !== 0) {
|
|
1059
|
+
return report("check_failed", check.error || `gate-tool check failed for ${selected.network}`);
|
|
1060
|
+
}
|
|
1061
|
+
checkedNetwork = selected.network;
|
|
1062
|
+
}
|
|
1063
|
+
let resolvedManifestDir: string;
|
|
1064
|
+
try {
|
|
1065
|
+
resolvedManifestDir = manifestDir(
|
|
1066
|
+
dependencies.resolveManifestDir(selected.network),
|
|
1067
|
+
`Manifest directory for ${selected.network}`,
|
|
1068
|
+
);
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
return report("sync_partial", error instanceof Error ? error.message : String(error));
|
|
1071
|
+
}
|
|
1072
|
+
const directory = path.join(options.root, resolvedManifestDir);
|
|
1073
|
+
const beforeHash = directoryHash(directory);
|
|
1074
|
+
let exactManifest: { absolute: string; relative: string };
|
|
1075
|
+
try {
|
|
1076
|
+
exactManifest = confinedManifestFile({
|
|
1077
|
+
root: options.root,
|
|
1078
|
+
directory: resolvedManifestDir,
|
|
1079
|
+
requestedFile: await dependencies.resolveManifestFile(
|
|
1080
|
+
selected.network,
|
|
1081
|
+
resolvedManifestDir,
|
|
1082
|
+
),
|
|
1083
|
+
});
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
return report("sync_partial", error instanceof Error ? error.message : String(error));
|
|
1086
|
+
}
|
|
1087
|
+
const metadata = deploymentInfo.networks[selected.network]?.[selected.instanceId];
|
|
1088
|
+
let implementationAddress: string | undefined;
|
|
1089
|
+
try {
|
|
1090
|
+
implementationAddress = await dependencies.getImplementation(
|
|
1091
|
+
selected.network,
|
|
1092
|
+
selected.address,
|
|
1093
|
+
);
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
entries.push({
|
|
1096
|
+
...selected,
|
|
1097
|
+
scheme,
|
|
1098
|
+
manifestDir: resolvedManifestDir,
|
|
1099
|
+
manifestFile: exactManifest.relative,
|
|
1100
|
+
syncStatus: "failed",
|
|
1101
|
+
beforeHash,
|
|
1102
|
+
afterHash: directoryHash(directory),
|
|
1103
|
+
});
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const base = {
|
|
1108
|
+
...selected,
|
|
1109
|
+
scheme,
|
|
1110
|
+
actualContract: metadata?.actualContract || selected.actualContract,
|
|
1111
|
+
...(metadata?.proxyKind ? { proxyKind: metadata.proxyKind } : {}),
|
|
1112
|
+
...(implementationAddress ? { implementationAddress } : {}),
|
|
1113
|
+
manifestDir: resolvedManifestDir,
|
|
1114
|
+
manifestFile: exactManifest.relative,
|
|
1115
|
+
beforeHash,
|
|
1116
|
+
};
|
|
1117
|
+
if (metadata?.proxyKind === "uups" && !implementationAddress) {
|
|
1118
|
+
entries.push({ ...base, syncStatus: "failed", afterHash: directoryHash(directory) });
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
if (scheme === "zksync-eravm" && metadata?.proxyKind === "uups") {
|
|
1122
|
+
try {
|
|
1123
|
+
assertZkUupsSyncEvidence({
|
|
1124
|
+
root: options.root,
|
|
1125
|
+
network: selected.network,
|
|
1126
|
+
manifestDir: resolvedManifestDir,
|
|
1127
|
+
manifest: exactManifest,
|
|
1128
|
+
metadata,
|
|
1129
|
+
selected,
|
|
1130
|
+
implementation: implementationAddress!,
|
|
1131
|
+
});
|
|
1132
|
+
entries.push({ ...base, syncStatus: "already_current", afterHash: beforeHash });
|
|
1133
|
+
} catch {
|
|
1134
|
+
entries.push({ ...base, syncStatus: "failed", afterHash: directoryHash(directory) });
|
|
1135
|
+
}
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
if (!implementationAddress || metadata?.proxyKind && metadata.proxyKind !== "uups") {
|
|
1139
|
+
entries.push({ ...base, syncStatus: "not_required", afterHash: directoryHash(directory) });
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
if (!metadata || metadata.proxyKind !== "uups") {
|
|
1143
|
+
entries.push({ ...base, syncStatus: "proxy_kind_unknown", afterHash: directoryHash(directory) });
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
if (scheme !== "evm") {
|
|
1147
|
+
entries.push({ ...base, syncStatus: "failed", afterHash: directoryHash(directory) });
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
let storageLayoutHash: string;
|
|
1151
|
+
try {
|
|
1152
|
+
storageLayoutHash = await dependencies.getStorageLayoutHash(metadata.actualContract);
|
|
1153
|
+
} catch (error) {
|
|
1154
|
+
entries.push({ ...base, syncStatus: "failed", afterHash: directoryHash(directory) });
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
if (manifestIsCurrent({
|
|
1158
|
+
filePath: exactManifest.absolute,
|
|
1159
|
+
proxyAddress: selected.address,
|
|
1160
|
+
implementationAddress,
|
|
1161
|
+
storageLayoutHash,
|
|
1162
|
+
})) {
|
|
1163
|
+
entries.push({ ...base, syncStatus: "already_current", afterHash: beforeHash });
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
const sync = await runCommand("npx", [
|
|
1167
|
+
"--no-install",
|
|
1168
|
+
"gate-tool",
|
|
1169
|
+
"sync",
|
|
1170
|
+
"--proxy",
|
|
1171
|
+
selected.address,
|
|
1172
|
+
"--contract",
|
|
1173
|
+
metadata.actualContract,
|
|
1174
|
+
"--network",
|
|
1175
|
+
selected.network,
|
|
1176
|
+
"--scheme",
|
|
1177
|
+
"evm",
|
|
1178
|
+
"--target",
|
|
1179
|
+
selected.network,
|
|
1180
|
+
"--skip-tx-hash",
|
|
1181
|
+
], {
|
|
1182
|
+
network: selected.network,
|
|
1183
|
+
scheme,
|
|
1184
|
+
manifestDir: resolvedManifestDir,
|
|
1185
|
+
manifestFile: exactManifest.relative,
|
|
1186
|
+
env: {
|
|
1187
|
+
...process.env,
|
|
1188
|
+
HARDHAT_NETWORK: selected.network,
|
|
1189
|
+
GATE_WORKFLOW_TARGET: selected.network,
|
|
1190
|
+
MANIFEST_DEFAULT_DIR: resolvedManifestDir,
|
|
1191
|
+
},
|
|
1192
|
+
});
|
|
1193
|
+
const afterHash = directoryHash(directory);
|
|
1194
|
+
entries.push({
|
|
1195
|
+
...base,
|
|
1196
|
+
syncStatus: sync.status === 0 && afterHash !== beforeHash ? "synced" : "failed",
|
|
1197
|
+
afterHash,
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
if (entries.some((entry) => (
|
|
1202
|
+
entry.syncStatus === "failed" || entry.syncStatus === "proxy_kind_unknown"
|
|
1203
|
+
))) {
|
|
1204
|
+
return report("sync_partial", "One or more manifest outcomes require review");
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const checkedAt = now();
|
|
1208
|
+
updateCheckCodeEvidenceIndex(
|
|
1209
|
+
options.root,
|
|
1210
|
+
entries.map((entry): CheckCodeEvidenceEntry => ({
|
|
1211
|
+
status: "passed",
|
|
1212
|
+
network: entry.network,
|
|
1213
|
+
scheme: entry.scheme,
|
|
1214
|
+
instanceId: entry.instanceId,
|
|
1215
|
+
contractInfoKey: entry.contractInfoKey,
|
|
1216
|
+
address: entry.address,
|
|
1217
|
+
artifactHash: options.artifactHash,
|
|
1218
|
+
runId,
|
|
1219
|
+
checkedAt,
|
|
1220
|
+
})),
|
|
1221
|
+
);
|
|
1222
|
+
const succeeded = report("succeeded");
|
|
1223
|
+
if (deploymentAudit) completeDeploymentAudit(options.root, succeeded, deploymentAudit);
|
|
1224
|
+
return succeeded;
|
|
1225
|
+
};
|