gate-evm-tools 1.0.31 → 1.2.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.
- package/README.md +34 -20
- package/bin/cli.js +3 -0
- package/bin/delivery.cjs +8 -7
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +26 -2
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/plan/index.d.ts.map +1 -1
- package/dist/commands/plan/index.js +1 -1
- package/dist/commands/plan/index.js.map +1 -1
- package/dist/core/preparation/batch.d.ts +26 -0
- package/dist/core/preparation/batch.d.ts.map +1 -0
- package/dist/core/preparation/batch.js +72 -0
- package/dist/core/preparation/batch.js.map +1 -0
- package/dist/core/preparation/evm.d.ts +23 -4
- package/dist/core/preparation/evm.d.ts.map +1 -1
- package/dist/core/preparation/evm.js +19 -3
- package/dist/core/preparation/evm.js.map +1 -1
- package/dist/core/preparation/evmManifestSync.d.ts +5 -0
- package/dist/core/preparation/evmManifestSync.d.ts.map +1 -1
- package/dist/core/preparation/evmManifestSync.js +15 -7
- package/dist/core/preparation/evmManifestSync.js.map +1 -1
- package/dist/core/preparation/index.d.ts +2 -2
- package/dist/core/preparation/index.d.ts.map +1 -1
- package/dist/core/preparation/manifest.d.ts +2 -0
- package/dist/core/preparation/manifest.d.ts.map +1 -1
- package/dist/core/preparation/manifest.js +2 -2
- package/dist/core/preparation/manifest.js.map +1 -1
- package/dist/core/preparation/normalize.d.ts.map +1 -1
- package/dist/core/preparation/normalize.js +53 -11
- package/dist/core/preparation/normalize.js.map +1 -1
- package/dist/core/preparation/transparent.d.ts +23 -0
- package/dist/core/preparation/transparent.d.ts.map +1 -0
- package/dist/core/preparation/transparent.js +103 -0
- package/dist/core/preparation/transparent.js.map +1 -0
- package/dist/core/preparation/types.d.ts +47 -5
- package/dist/core/preparation/types.d.ts.map +1 -1
- package/dist/core/preparation/zksync.d.ts.map +1 -1
- package/dist/core/preparation/zksync.js +2 -0
- package/dist/core/preparation/zksync.js.map +1 -1
- package/dist/custody/credential.d.ts.map +1 -1
- package/dist/custody/credential.js +1 -6
- package/dist/custody/credential.js.map +1 -1
- package/dist/deployment-evm/index.d.ts +4 -0
- package/dist/deployment-evm/index.d.ts.map +1 -0
- package/dist/deployment-evm/index.js +3 -0
- package/dist/deployment-evm/index.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +5 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workflow/index.d.ts +4 -1
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +2 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist-cjs/core/preparation/batch.js +76 -0
- package/dist-cjs/core/preparation/batch.js.map +1 -0
- package/dist-cjs/core/preparation/evm.js +25 -7
- package/dist-cjs/core/preparation/evm.js.map +1 -1
- package/dist-cjs/core/preparation/evmManifestSync.js +15 -7
- package/dist-cjs/core/preparation/evmManifestSync.js.map +1 -1
- package/dist-cjs/core/preparation/manifest.js +2 -2
- package/dist-cjs/core/preparation/manifest.js.map +1 -1
- package/dist-cjs/core/preparation/normalize.js +53 -11
- package/dist-cjs/core/preparation/normalize.js.map +1 -1
- package/dist-cjs/core/preparation/transparent.js +110 -0
- package/dist-cjs/core/preparation/transparent.js.map +1 -0
- package/dist-cjs/core/preparation/zksync.js +2 -0
- package/dist-cjs/core/preparation/zksync.js.map +1 -1
- package/dist-cjs/custody/credential.js +1 -6
- package/dist-cjs/custody/credential.js.map +1 -1
- package/dist-cjs/deployment-evm/index.js +10 -0
- package/dist-cjs/deployment-evm/index.js.map +1 -0
- package/dist-cjs/sync/index.js +5 -0
- package/dist-cjs/sync/index.js.map +1 -1
- package/dist-cjs/workflow/index.js +11 -1
- package/dist-cjs/workflow/index.js.map +1 -1
- package/dist-runtime/config/hardhatAdapters.js +2 -1
- package/dist-runtime/config/hardhatAdapters.js.map +1 -1
- package/dist-runtime/delivery/aggregate-bootstrap.js +3 -1
- package/dist-runtime/delivery/aggregate-bootstrap.js.map +1 -1
- package/dist-runtime/delivery/aggregate-channel.js +7 -3
- package/dist-runtime/delivery/aggregate-channel.js.map +1 -1
- package/dist-runtime/delivery/aggregate-diagnostics.d.ts +14 -0
- package/dist-runtime/delivery/aggregate-diagnostics.js +73 -0
- package/dist-runtime/delivery/aggregate-diagnostics.js.map +1 -0
- package/dist-runtime/delivery/aggregate-inputs.d.ts +13 -9
- package/dist-runtime/delivery/aggregate-source.js +11 -3
- package/dist-runtime/delivery/aggregate-source.js.map +1 -1
- package/dist-runtime/delivery/aggregate-worker.js +12 -4
- package/dist-runtime/delivery/aggregate-worker.js.map +1 -1
- package/dist-runtime/delivery/aggregate.js +2 -2
- package/dist-runtime/delivery/aggregate.js.map +1 -1
- package/dist-runtime/delivery/business-extension.d.ts +10 -0
- package/dist-runtime/delivery/business-extension.js +38 -0
- package/dist-runtime/delivery/business-extension.js.map +1 -0
- package/dist-runtime/delivery/calldata-archive.d.ts +18 -0
- package/dist-runtime/delivery/calldata-archive.js +31 -0
- package/dist-runtime/delivery/calldata-archive.js.map +1 -0
- package/dist-runtime/delivery/calldata-details.d.ts +47 -0
- package/dist-runtime/delivery/calldata-details.js +94 -0
- package/dist-runtime/delivery/calldata-details.js.map +1 -0
- package/dist-runtime/delivery/calldata-format.d.ts +4 -0
- package/dist-runtime/delivery/calldata-format.js +17 -0
- package/dist-runtime/delivery/calldata-format.js.map +1 -0
- package/dist-runtime/delivery/calldata-preparations.d.ts +13 -0
- package/dist-runtime/delivery/calldata-preparations.js +85 -0
- package/dist-runtime/delivery/calldata-preparations.js.map +1 -0
- package/dist-runtime/delivery/calldata-review.d.ts +44 -0
- package/dist-runtime/delivery/calldata-review.js +131 -0
- package/dist-runtime/delivery/calldata-review.js.map +1 -0
- package/dist-runtime/delivery/codec-pin.d.ts +2 -2
- package/dist-runtime/delivery/codec-pin.js +4 -4
- package/dist-runtime/delivery/codec-pin.js.map +1 -1
- package/dist-runtime/delivery/compiler-files.js +3 -1
- package/dist-runtime/delivery/compiler-files.js.map +1 -1
- package/dist-runtime/delivery/compiler-pins.js +1 -1
- package/dist-runtime/delivery/compiler.js +17 -16
- package/dist-runtime/delivery/compiler.js.map +1 -1
- package/dist-runtime/delivery/config.d.ts +1 -1
- package/dist-runtime/delivery/config.js +27 -2
- package/dist-runtime/delivery/config.js.map +1 -1
- package/dist-runtime/delivery/configuration-definition.d.ts +9 -0
- package/dist-runtime/delivery/configuration-definition.js +33 -6
- package/dist-runtime/delivery/configuration-definition.js.map +1 -1
- package/dist-runtime/delivery/configuration.js +7 -4
- package/dist-runtime/delivery/configuration.js.map +1 -1
- package/dist-runtime/delivery/contract-source.d.ts +14 -0
- package/dist-runtime/delivery/contract-source.js +138 -0
- package/dist-runtime/delivery/contract-source.js.map +1 -0
- package/dist-runtime/delivery/custom-format.js +51 -24
- package/dist-runtime/delivery/custom-format.js.map +1 -1
- package/dist-runtime/delivery/development-inputs.d.ts +14 -0
- package/dist-runtime/delivery/development-inputs.js +76 -0
- package/dist-runtime/delivery/development-inputs.js.map +1 -0
- package/dist-runtime/delivery/development.d.ts +3 -1
- package/dist-runtime/delivery/development.js +24 -10
- package/dist-runtime/delivery/development.js.map +1 -1
- package/dist-runtime/delivery/eoa.d.ts +1 -0
- package/dist-runtime/delivery/execution-identity.d.ts +15 -0
- package/dist-runtime/delivery/execution-identity.js +33 -0
- package/dist-runtime/delivery/execution-identity.js.map +1 -0
- package/dist-runtime/delivery/existing-code.js +14 -6
- package/dist-runtime/delivery/existing-code.js.map +1 -1
- package/dist-runtime/delivery/history-package.d.ts +2 -0
- package/dist-runtime/delivery/history-package.js +70 -0
- package/dist-runtime/delivery/history-package.js.map +1 -0
- package/dist-runtime/delivery/history-registration.d.ts +1 -1
- package/dist-runtime/delivery/inputs.js +22 -15
- package/dist-runtime/delivery/inputs.js.map +1 -1
- package/dist-runtime/delivery/journal.js +18 -10
- package/dist-runtime/delivery/journal.js.map +1 -1
- package/dist-runtime/delivery/parameter-defaults.d.ts +10 -0
- package/dist-runtime/delivery/parameter-defaults.js +86 -0
- package/dist-runtime/delivery/parameter-defaults.js.map +1 -0
- package/dist-runtime/delivery/parameters.d.ts +2 -0
- package/dist-runtime/delivery/parameters.js +22 -0
- package/dist-runtime/delivery/parameters.js.map +1 -1
- package/dist-runtime/delivery/planning.d.ts +4 -1
- package/dist-runtime/delivery/planning.js +19 -3
- package/dist-runtime/delivery/planning.js.map +1 -1
- package/dist-runtime/delivery/preparation.js +43 -23
- package/dist-runtime/delivery/preparation.js.map +1 -1
- package/dist-runtime/delivery/production-environment.js +2 -1
- package/dist-runtime/delivery/production-environment.js.map +1 -1
- package/dist-runtime/delivery/production-finalization.js +23 -9
- package/dist-runtime/delivery/production-finalization.js.map +1 -1
- package/dist-runtime/delivery/production-intent.d.ts +2 -0
- package/dist-runtime/delivery/production-intent.js +16 -0
- package/dist-runtime/delivery/production-intent.js.map +1 -0
- package/dist-runtime/delivery/production.d.ts +129 -8
- package/dist-runtime/delivery/production.js +60 -11
- package/dist-runtime/delivery/production.js.map +1 -1
- package/dist-runtime/delivery/project-files.d.ts +3 -3
- package/dist-runtime/delivery/project-files.js +19 -14
- package/dist-runtime/delivery/project-files.js.map +1 -1
- package/dist-runtime/delivery/project-rpc.d.ts +19 -0
- package/dist-runtime/delivery/project-rpc.js +138 -0
- package/dist-runtime/delivery/project-rpc.js.map +1 -0
- package/dist-runtime/delivery/project.d.ts +3 -1
- package/dist-runtime/delivery/project.js +21 -8
- package/dist-runtime/delivery/project.js.map +1 -1
- package/dist-runtime/delivery/public-calldata.d.ts +25 -0
- package/dist-runtime/delivery/public-calldata.js +97 -0
- package/dist-runtime/delivery/public-calldata.js.map +1 -0
- package/dist-runtime/delivery/release-graph.d.ts +7 -3
- package/dist-runtime/delivery/release-graph.js +58 -25
- package/dist-runtime/delivery/release-graph.js.map +1 -1
- package/dist-runtime/delivery/release-history-capture.js +22 -26
- package/dist-runtime/delivery/release-history-capture.js.map +1 -1
- package/dist-runtime/delivery/release-history.d.ts +3 -2
- package/dist-runtime/delivery/release-history.js +97 -50
- package/dist-runtime/delivery/release-history.js.map +1 -1
- package/dist-runtime/delivery/release-inputs.d.ts +33 -0
- package/dist-runtime/delivery/release-inputs.js +129 -0
- package/dist-runtime/delivery/release-inputs.js.map +1 -0
- package/dist-runtime/delivery/release.d.ts +38 -14
- package/dist-runtime/delivery/release.js +81 -29
- package/dist-runtime/delivery/release.js.map +1 -1
- package/dist-runtime/delivery/safe.d.ts +1 -0
- package/dist-runtime/delivery/safe.js +13 -2
- package/dist-runtime/delivery/safe.js.map +1 -1
- package/dist-runtime/delivery/same-fork-baseline.d.ts +1 -1
- package/dist-runtime/delivery/same-fork-baseline.js +5 -4
- package/dist-runtime/delivery/same-fork-baseline.js.map +1 -1
- package/dist-runtime/delivery/setup.d.ts +1 -1
- package/dist-runtime/delivery/setup.js +21 -6
- package/dist-runtime/delivery/setup.js.map +1 -1
- package/dist-runtime/delivery/source.d.ts +1 -0
- package/dist-runtime/delivery/source.js +12 -10
- package/dist-runtime/delivery/source.js.map +1 -1
- package/dist-runtime/delivery/task-identity.d.ts +23 -0
- package/dist-runtime/delivery/task-identity.js +109 -0
- package/dist-runtime/delivery/task-identity.js.map +1 -0
- package/dist-runtime/delivery/toolchain.d.ts +3 -0
- package/dist-runtime/delivery/toolchain.js +52 -0
- package/dist-runtime/delivery/toolchain.js.map +1 -0
- package/dist-runtime/delivery/validation.js +7 -2
- package/dist-runtime/delivery/validation.js.map +1 -1
- package/dist-runtime/runtime-identity.json +2 -2
- package/dist-runtime/vendor/plan-codec/index.cjs +97 -20
- package/dist-runtime/vendor/plan-codec/index.d.cts +6 -0
- package/dist-runtime/vendor/plan-codec/provenance.json +9 -8
- package/dist-runtime/workflow/lib/calls.js +1 -0
- package/dist-runtime/workflow/lib/calls.js.map +1 -1
- package/dist-runtime/workflow/lib/checkCodeRunner.js +7 -4
- package/dist-runtime/workflow/lib/checkCodeRunner.js.map +1 -1
- package/dist-runtime/workflow/lib/context.d.ts +1 -0
- package/dist-runtime/workflow/lib/create2.d.ts +4 -26
- package/dist-runtime/workflow/lib/create2.js +2 -67
- package/dist-runtime/workflow/lib/create2.js.map +1 -1
- package/dist-runtime/workflow/lib/deploymentInfo.d.ts +2 -0
- package/dist-runtime/workflow/lib/deploymentInfo.js +6 -0
- package/dist-runtime/workflow/lib/deploymentInfo.js.map +1 -1
- package/dist-runtime/workflow/lib/deploymentRecords.d.ts +2 -0
- package/dist-runtime/workflow/lib/deploymentRecords.js +1 -1
- package/dist-runtime/workflow/lib/deploymentRecords.js.map +1 -1
- package/dist-runtime/workflow/lib/externalExecutionScanner.js +2 -1
- package/dist-runtime/workflow/lib/externalExecutionScanner.js.map +1 -1
- package/dist-runtime/workflow/lib/gateTool.js +2 -2
- package/dist-runtime/workflow/lib/gateTool.js.map +1 -1
- package/dist-runtime/workflow/lib/gateToolSync.d.ts +3 -0
- package/dist-runtime/workflow/lib/gateToolSync.js +5 -0
- package/dist-runtime/workflow/lib/gateToolSync.js.map +1 -1
- package/dist-runtime/workflow/lib/parameters.js +5 -0
- package/dist-runtime/workflow/lib/parameters.js.map +1 -1
- package/dist-runtime/workflow/lib/proxy.d.ts +1 -0
- package/dist-runtime/workflow/lib/proxy.js.map +1 -1
- package/dist-runtime/workflow/run.js +1 -3
- package/dist-runtime/workflow/run.js.map +1 -1
- package/dist-runtime/workflow/steps/deploy.js +53 -0
- package/dist-runtime/workflow/steps/deploy.js.map +1 -1
- package/dist-runtime/workflow/steps/upgrade.js +24 -10
- package/dist-runtime/workflow/steps/upgrade.js.map +1 -1
- package/docs/compiler-qualification.md +8 -2
- package/docs/delivery-aggregate.md +2 -0
- package/docs/delivery-calldata.md +27 -0
- package/docs/delivery-development.md +6 -0
- package/docs/delivery-execution-store.md +1 -1
- package/docs/delivery-inputs.md +55 -6
- package/docs/delivery-parameters.md +1 -1
- package/docs/delivery-release.md +77 -7
- package/docs/delivery-safe.md +7 -4
- package/docs/delivery-setup.md +72 -20
- package/docs/delivery-source.md +8 -8
- package/docs/direct-package-acceptance.md +2 -0
- package/docs/openzeppelin-acceptance.md +86 -0
- package/docs/openzeppelin-baseline.json +27 -0
- package/docs/openzeppelin-integration.md +115 -0
- package/docs/plans/2026-10-28-openzeppelin-toolkit-consolidation.md +396 -0
- package/docs/plans/2026-11-02-transparent-proxy-delivery.md +78 -0
- package/docs/superpowers/plans/2026-08-17-custody-hardhat-signers.md +1 -1
- package/docs/workflow-reference.md +12 -4
- package/docs/workflow-runtime.md +6 -0
- package/package.json +19 -5
- package/runtime/config/hardhatAdapters.ts +2 -1
- package/runtime/delivery/aggregate-bootstrap.ts +3 -1
- package/runtime/delivery/aggregate-channel.ts +6 -3
- package/runtime/delivery/aggregate-diagnostics.ts +63 -0
- package/runtime/delivery/aggregate-source.ts +6 -2
- package/runtime/delivery/aggregate-worker.ts +15 -5
- package/runtime/delivery/aggregate.ts +2 -2
- package/runtime/delivery/business-extension.ts +33 -0
- package/runtime/delivery/calldata-archive.ts +25 -0
- package/runtime/delivery/calldata-details.ts +90 -0
- package/runtime/delivery/calldata-format.ts +14 -0
- package/runtime/delivery/calldata-preparations.ts +76 -0
- package/runtime/delivery/calldata-review.ts +126 -0
- package/runtime/delivery/codec-pin.ts +3 -3
- package/runtime/delivery/compiler-files.ts +3 -1
- package/runtime/delivery/compiler-pins.ts +1 -1
- package/runtime/delivery/compiler.ts +18 -17
- package/runtime/delivery/config.ts +25 -2
- package/runtime/delivery/configuration-definition.ts +34 -7
- package/runtime/delivery/configuration.ts +8 -5
- package/runtime/delivery/contract-source.ts +112 -0
- package/runtime/delivery/custom-format.ts +20 -5
- package/runtime/delivery/development-inputs.ts +42 -0
- package/runtime/delivery/development.ts +14 -11
- package/runtime/delivery/execution-identity.ts +32 -0
- package/runtime/delivery/existing-code.ts +7 -6
- package/runtime/delivery/history-package.ts +50 -0
- package/runtime/delivery/inputs.ts +18 -11
- package/runtime/delivery/journal.ts +14 -10
- package/runtime/delivery/parameter-defaults.ts +75 -0
- package/runtime/delivery/parameters.ts +18 -0
- package/runtime/delivery/planning.ts +19 -5
- package/runtime/delivery/preparation.ts +17 -1
- package/runtime/delivery/production-environment.ts +2 -1
- package/runtime/delivery/production-finalization.ts +19 -10
- package/runtime/delivery/production-intent.ts +14 -0
- package/runtime/delivery/production.ts +51 -11
- package/runtime/delivery/project-files.ts +16 -11
- package/runtime/delivery/project-rpc.ts +116 -0
- package/runtime/delivery/project.ts +19 -7
- package/runtime/delivery/public-calldata.ts +74 -0
- package/runtime/delivery/release-graph.ts +44 -14
- package/runtime/delivery/release-history-capture.ts +19 -9
- package/runtime/delivery/release-history.ts +76 -22
- package/runtime/delivery/release-inputs.ts +107 -0
- package/runtime/delivery/release.ts +56 -18
- package/runtime/delivery/safe.ts +14 -3
- package/runtime/delivery/same-fork-baseline.ts +5 -4
- package/runtime/delivery/setup.ts +20 -8
- package/runtime/delivery/source.ts +10 -10
- package/runtime/delivery/task-identity.ts +85 -0
- package/runtime/delivery/toolchain.ts +48 -0
- package/runtime/delivery/validation.ts +7 -2
- package/runtime/vendor/plan-codec/index.cjs +97 -20
- package/runtime/vendor/plan-codec/index.d.cts +6 -0
- package/runtime/vendor/plan-codec/provenance.json +9 -8
- package/runtime/workflow/lib/calls.ts +1 -0
- package/runtime/workflow/lib/checkCodeRunner.ts +7 -3
- package/runtime/workflow/lib/context.ts +1 -0
- package/runtime/workflow/lib/create2.ts +7 -108
- package/runtime/workflow/lib/deploymentInfo.ts +8 -0
- package/runtime/workflow/lib/deploymentRecords.ts +3 -1
- package/runtime/workflow/lib/externalExecutionScanner.ts +2 -1
- package/runtime/workflow/lib/gateTool.ts +2 -2
- package/runtime/workflow/lib/gateToolSync.ts +8 -0
- package/runtime/workflow/lib/parameters.ts +4 -0
- package/runtime/workflow/lib/proxy.ts +1 -0
- package/runtime/workflow/run.ts +1 -2
- package/runtime/workflow/steps/deploy.ts +42 -0
- package/runtime/workflow/steps/upgrade.ts +19 -11
- package/bin/workflow.cjs +0 -19
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {getCreate2Address,Interface} from 'ethers';
|
|
4
4
|
import {withDefaults} from '@openzeppelin/hardhat-upgrades/dist/utils/options';
|
|
5
|
-
import {BytecodeChecker,artifactIdentity,hashStorageLayout,mergeEvmManifestCandidate,parseTargetManifest,resolveTargetManifestPaths} from 'gate-evm-tools/workflow';
|
|
5
|
+
import {TRANSPARENT_PROXY_ARTIFACT,PROXY_ADMIN_ARTIFACT,ERC1967_ADMIN_SLOT,PROXY_ADMIN_INTERFACE,transparentAdminAddress,assertTransparentAdminImmutable,BytecodeChecker,artifactIdentity,hashStorageLayout,mergeEvmManifestCandidate,parseTargetManifest,resolveTargetManifestPaths} from 'gate-evm-tools/workflow';
|
|
6
6
|
import {canonicalJson,deepFreeze,semanticHash} from '../workflow/lib/canonicalData';
|
|
7
7
|
import {validateContractInfo,resolveDeployment} from '../workflow/lib/deployments';
|
|
8
8
|
import {validateDeploymentInfo} from '../workflow/lib/deploymentInfo';
|
|
@@ -67,14 +67,14 @@ export async function finalizeProductionExecution(input:{root:string;ready:Produ
|
|
|
67
67
|
const p=entry.prepared.plan,r=entry.prepared.request;const compiled=local(p.contract);same(artifactIdentity(compiled.artifact,'evm'),p.artifact,'Prepared sealed artifact');
|
|
68
68
|
for(const deployment of p.deployments){
|
|
69
69
|
if(deployment.initCodeHash&&getCreate2Address(r.factory,deployment.salt,deployment.initCodeHash).toLowerCase()!==deployment.address.toLowerCase())throw Error('Production CREATE2 address mismatch');
|
|
70
|
-
await compare(deployment.address,deployment.role==='proxy'?'@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy':p.contract,`${entry.change.instanceId}:${deployment.role}`);
|
|
70
|
+
await compare(deployment.address,deployment.role==='proxy'?(p.proxyKind==='transparent'?TRANSPARENT_PROXY_ARTIFACT:'@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy'):p.contract,`${entry.change.instanceId}:${deployment.role}`);
|
|
71
71
|
}
|
|
72
72
|
const address=p.deployments.find(d=>d.role==='proxy')?.address??('proxy' in r?r.proxy:p.deployments.find(d=>d.role==='contract')?.address);
|
|
73
73
|
if(!address)throw Error('Production prepared address missing');
|
|
74
|
-
changes.set(entry.change.instanceId,{...entry.change,address,...(p.proxyKind
|
|
74
|
+
changes.set(entry.change.instanceId,{...entry.change,address,...(p.proxyKind?{proxyAddress:address,proxyKind:p.proxyKind}:{})});
|
|
75
75
|
}
|
|
76
76
|
const candidates:any[]=[],expected:ExpectedDevelopmentCode[]=[],effective:Record<string,Record<string,string>>={[plan.target]:{}},checked=new Map<string,{address:string;contract:string;key:string}>();
|
|
77
|
-
function expect(address:string,contract:string,key:string){const row={key,contract,address:address.toLowerCase()},old=checked.get(key);if(old)same(old,row,'Production CheckCode key');else{checked.set(key,row);expected.push(row);}}
|
|
77
|
+
function expect(address:string,contract:string,key:string){const row={key,contract:local(contract).artifact.contractName,address:address.toLowerCase()},old=checked.get(key);if(old)same(old,row,'Production CheckCode key');else{checked.set(key,row);expected.push(row);}}
|
|
78
78
|
const deployed=[...changes.values()],unchangedAliases=new Set<string>();
|
|
79
79
|
const registrationContext={base,target:plan.target,deploymentId:plan.deploymentId,executionId:plan.executionId,recordPath:recordPath(ready),updatedAt:new Date(Number(BigInt(anchor.timestamp))*1000).toISOString(),changes:deployed,preparations};
|
|
80
80
|
// Apply the same validated upgrade replacement before resolving code coverage.
|
|
@@ -84,11 +84,20 @@ export async function finalizeProductionExecution(input:{root:string;ready:Produ
|
|
|
84
84
|
const rows=Object.entries(registry).filter(([,a])=>a.toLowerCase()===address);if(rows.length!==1)throw Error('Production code target requires unique frozen registration');
|
|
85
85
|
const [key]=rows[0],alias=key.split(':')[0],m=metadata[alias],contract=m?.actualContract??key.split(':').at(-1)!;
|
|
86
86
|
effective[plan.target][key]=address;if(!changes.has(alias))unchangedAliases.add(alias);
|
|
87
|
-
if(m?.proxyKind&&m.proxyKind!=='uups')throw Error('Unsupported production proxy kind');
|
|
88
|
-
if(m?.proxyKind==='uups'){
|
|
87
|
+
if(m?.proxyKind&&m.proxyKind!=='uups'&&m.proxyKind!=='transparent')throw Error('Unsupported production proxy kind');
|
|
88
|
+
if(m?.proxyKind==='uups'||m?.proxyKind==='transparent'){
|
|
89
89
|
const storage=await rpc('eth_getStorageAt',[address,slot,block]);if(typeof storage!=='string'||!/^0x0{24}[a-fA-F0-9]{40}$/.test(storage))throw Error('Invalid finalized UUPS implementation slot');
|
|
90
90
|
const implementation='0x'+storage.slice(-40).toLowerCase();if(implementation!==m.implementationAddress?.toLowerCase())throw Error('Final production UUPS implementation mismatch');
|
|
91
|
-
expect(implementation,contract,alias);expect(address,'ERC1967Proxy',`${alias}_Proxy`);
|
|
91
|
+
expect(implementation,contract,alias);expect(address,m.proxyKind==='transparent'?TRANSPARENT_PROXY_ARTIFACT:'ERC1967Proxy',`${alias}_Proxy`);
|
|
92
|
+
if(m.proxyKind==='transparent'){
|
|
93
|
+
const admin=m.proxyAdminAddress?.toLowerCase();if(!admin||admin!==transparentAdminAddress(address)||!m.proxyAdminOwner)throw Error('Missing frozen Transparent admin identity');
|
|
94
|
+
const value=await rpc('eth_getStorageAt',[address,ERC1967_ADMIN_SLOT,block]);if(typeof value!=='string'||!/^0x0{24}[a-fA-F0-9]{40}$/.test(value)||'0x'+value.slice(-40).toLowerCase()!==admin)throw Error('Final production Transparent admin mismatch');
|
|
95
|
+
assertTransparentAdminImmutable(await rpc('eth_getCode',[address,block]) as string,local(TRANSPARENT_PROXY_ARTIFACT).immutableReferences,admin);
|
|
96
|
+
const owner=PROXY_ADMIN_INTERFACE.decodeFunctionResult('owner',await rpc('eth_call',[{to:admin,data:PROXY_ADMIN_INTERFACE.encodeFunctionData('owner')},block]) as string)[0];
|
|
97
|
+
const version=PROXY_ADMIN_INTERFACE.decodeFunctionResult('UPGRADE_INTERFACE_VERSION',await rpc('eth_call',[{to:admin,data:PROXY_ADMIN_INTERFACE.encodeFunctionData('UPGRADE_INTERFACE_VERSION')},block]) as string)[0];
|
|
98
|
+
if(owner.toLowerCase()!==m.proxyAdminOwner.toLowerCase()||version!=='5.0.0')throw Error('Final production ProxyAdmin owner/version mismatch');
|
|
99
|
+
expect(admin,PROXY_ADMIN_ARTIFACT,`${alias}_ProxyAdmin`);
|
|
100
|
+
}
|
|
92
101
|
const artifact=local(contract).entry;
|
|
93
102
|
const paths=resolveTargetManifestPaths(root,plan.target,Number(plan.chainId),{}),manifestPath=path.relative(root,paths.file).split(path.sep).join('/');
|
|
94
103
|
if(paths.file!==paths.fallbackFile&&fileBytes(root,path.relative(root,paths.fallbackFile))!==null)throw Error('Noncanonical manifest fallback requires explicit migration');
|
|
@@ -98,7 +107,7 @@ export async function finalizeProductionExecution(input:{root:string;ready:Produ
|
|
|
98
107
|
if(!request||request.kind==='create2-deploy')throw Error('Missing UUPS preparation selection request');
|
|
99
108
|
// Match the compiler's preparationSelection identity. Canonical request data
|
|
100
109
|
// contains no undefined values; omit official optional top-level safety flags.
|
|
101
|
-
const options=Object.fromEntries(Object.entries(withDefaults({kind:
|
|
110
|
+
const options=Object.fromEntries(Object.entries(withDefaults({kind:request.proxyKind,constructorArgs:request.constructorArgs,...(request.unsafeAllow?{unsafeAllow:request.unsafeAllow}: {})} as Parameters<typeof withDefaults>[0])).filter(([,value])=>value!==undefined));
|
|
102
111
|
const encodedArgs=new Interface(artifact.artifact.abi).encodeDeploy(request.constructorArgs),optionsHash=semanticHash(options);
|
|
103
112
|
const selections=compiler.selections.filter(s=>s.fqn===artifact.fqn&&s.artifactDigest===artifact.digest&&s.encodedArgs===encodedArgs&&semanticHash(s.options)===optionsHash);
|
|
104
113
|
if(selections.length!==1)throw Error('Missing unique frozen OZ implementation selection');
|
|
@@ -108,11 +117,11 @@ export async function finalizeProductionExecution(input:{root:string;ready:Produ
|
|
|
108
117
|
// regardless of selections made for other instances of the same artifact.
|
|
109
118
|
const frozen=configuration.manifests?.filter(f=>f.path===manifestPath);if(frozen?.length!==1||frozen[0].contents===null)throw Error('Missing frozen existing UUPS manifest');
|
|
110
119
|
const manifest=parseTargetManifest(Buffer.from(frozen[0].contents)),proxies=manifest.proxies.filter(p=>p.address.toLowerCase()===address),implementations=Object.entries(manifest.impls).filter(([,v])=>v&&[v.address,...(v.allAddresses??[])].some(a=>a.toLowerCase()===implementation));
|
|
111
|
-
if(proxies.length!==1||proxies[0].kind!==
|
|
120
|
+
if(proxies.length!==1||proxies[0].kind!==m.proxyKind||implementations.length!==1)throw Error('Invalid frozen existing UUPS manifest registration');
|
|
112
121
|
versionKey=implementations[0][0];layout=implementations[0][1]!.layout;
|
|
113
122
|
}
|
|
114
123
|
if(typeof versionKey!=='string'||!/^[a-f0-9]{64}$/.test(versionKey))throw Error('Invalid frozen OZ version');
|
|
115
|
-
candidates.push({path:manifestPath,proxy:address,implementation,versionKey,layout,layoutHash:hashStorageLayout(layout)});
|
|
124
|
+
candidates.push({path:manifestPath,proxy:address,proxyKind:m.proxyKind,implementation,versionKey,layout,layoutHash:hashStorageLayout(layout)});
|
|
116
125
|
}else expect(address,contract,alias);
|
|
117
126
|
}
|
|
118
127
|
if(!expected.length)throw Error('Empty production CheckCode coverage');
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Re-simulation may change operator routing, but cannot replace released transaction intent. */
|
|
2
|
+
import { canonicalJson } from '../workflow/lib/canonicalData';
|
|
3
|
+
import { parseCalldataReviews } from './calldata-review';
|
|
4
|
+
import type { DeliveryCollection } from './planning';
|
|
5
|
+
|
|
6
|
+
export function assertReleasedCalldataIntent(actual: DeliveryCollection, released: DeliveryCollection): void {
|
|
7
|
+
const current = parseCalldataReviews(actual.calldata, actual.plan.transactions);
|
|
8
|
+
const frozen = parseCalldataReviews(released.calldata, released.plan.transactions);
|
|
9
|
+
if (canonicalJson(current) !== canonicalJson(frozen))
|
|
10
|
+
throw Error('Final simulation changed released calldata ABI intent');
|
|
11
|
+
for (const key of ['derivationSeed', 'values'] as const)
|
|
12
|
+
if (canonicalJson(actual.plan.derivedBindings[key]) !== canonicalJson(released.plan.derivedBindings[key]))
|
|
13
|
+
throw Error('Final simulation changed released derivation');
|
|
14
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Public release-only production boundary. Host capabilities never enter JSON options. */
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import {customExecutionInput} from './execution-identity';
|
|
4
|
+
import {assertReleasedCalldataIntent} from './production-intent';
|
|
3
5
|
import os from 'node:os';
|
|
4
6
|
import path from 'node:path';
|
|
5
7
|
import { randomUUID } from 'node:crypto';
|
|
@@ -16,6 +18,10 @@ import { canonicalJson, deepFreeze, exactObject, semanticHash } from '../workflo
|
|
|
16
18
|
import { createDeliveryEoaExecution, type EoaExecutionOptions, type EoaHashReferences } from './eoa';
|
|
17
19
|
import type { ReleaseGraphOptions } from './release-graph';
|
|
18
20
|
import type { DeliveryCollection } from './planning';
|
|
21
|
+
import { parseCalldataReviews } from './calldata-review';
|
|
22
|
+
import { projectDeliveryCalls, mergeDeliveryCalls } from './public-calldata';
|
|
23
|
+
import { archiveDeliveryCalls } from './calldata-archive';
|
|
24
|
+
import { serializeDeliveryCalls } from './calldata-format';
|
|
19
25
|
import { finalizeProductionExecution, retainedProductionFinalizationAnchor, type FinalizedAnchor } from './production-finalization';
|
|
20
26
|
export { productionChildEnvironment } from './production-environment';
|
|
21
27
|
export interface ProductionOptions extends ReleaseGraphOptions {
|
|
@@ -58,9 +64,12 @@ export function inspectDeliveryProduction(raw: ProductionOptions, command: Produ
|
|
|
58
64
|
if (record.schema !== 'gate-delivery-production' || record.version !== 1 || record.status !== 'READY' || record.generation !== 'complete' || semanticHash(body) !== artifactHash)
|
|
59
65
|
throw Error('Invalid production READY artifact');
|
|
60
66
|
const plan = parseDeliveryPlan(record.collection.plan);
|
|
67
|
+
parseCalldataReviews(record.collection.calldata, plan.transactions);
|
|
68
|
+
const deliveryCalls=projectDeliveryCalls({plan,calldata:record.collection.calldata,operations:current.release.operations as any[],preparations:record.collection.preparations});
|
|
69
|
+
if(canonicalJson(deliveryCalls)!==canonicalJson(record.deliveryCalls))throw Error('Frozen production public projection mismatch');
|
|
61
70
|
if (record.releaseHash !== current.releaseHash || plan.inputsHash !== current.release.inputsHash || plan.sourceDefinitionHash !== current.release.sourceDefinitionHash || plan.executionId !== current.input.executionId || plan.target !== current.input.target || plan.operator !== current.input.operator.toLowerCase() || plan.environment !== 'prod' || plan.chainId !== String(current.target.chainId))
|
|
62
71
|
throw Error('Production artifact context mismatch');
|
|
63
|
-
return deepFreeze({ status: 'ready' as const, plan, collection: record.collection as DeliveryCollection, finalization: record.finalization, releaseHash: current.releaseHash, artifactHash, sha256: byteHash(bytes), path: artifactPath(current.input.executionId), ...(current.target.safe ? { safeTarget: current.target.safe } : {}) });
|
|
72
|
+
return deepFreeze({ status: 'ready' as const, plan, collection: record.collection as DeliveryCollection, deliveryCalls, operations: current.release.operations as any[], finalization: record.finalization, releaseHash: current.releaseHash, artifactHash, sha256: byteHash(bytes), path: artifactPath(current.input.executionId), ...(current.target.safe ? { safeTarget: current.target.safe } : {}) });
|
|
64
73
|
}
|
|
65
74
|
/** Instantiate the delivered business operations, never accept replacement operations or parameters. */
|
|
66
75
|
export async function prepareDeliveryProduction(raw: ProductionOptions) {
|
|
@@ -91,7 +100,16 @@ export async function prepareDeliveryProduction(raw: ProductionOptions) {
|
|
|
91
100
|
const parameter = release.parameters.find((p: any) => p.target === input.target);
|
|
92
101
|
if (!parameter)
|
|
93
102
|
throw Error('Release parameters missing');
|
|
94
|
-
|
|
103
|
+
// Production consumes the released CREATE2 derivation, while its execution
|
|
104
|
+
// identity and durable progress remain local to this execution.
|
|
105
|
+
const candidate = JSON.parse(fs.readFileSync(path.join(root, release.candidate.path), 'utf8'));
|
|
106
|
+
const qualification = candidate.aggregate.collections.find((c: any) => c.plan.target === input.target);
|
|
107
|
+
if (!qualification) throw Error('Released target qualification missing');
|
|
108
|
+
const qualificationPlan = parseDeliveryPlan(qualification.plan);
|
|
109
|
+
if (qualificationPlan.inputsHash !== release.inputsHash || qualificationPlan.target !== input.target)
|
|
110
|
+
throw Error('Released derivation context mismatch');
|
|
111
|
+
const derivationSeed = qualificationPlan.derivedBindings.derivationSeed;
|
|
112
|
+
const staged = await worker.request('stage', { ...common, role: 'target', handshake: ready.handshake, target: current.target, params: parameter.resolved.values, steps: release.operations, deploymentId: input.deploymentId, businessRoot: `scripts/tasks/${input.deploymentId}`, executionId: input.executionId, derivationSeed, sourceDefinitionHash: release.sourceDefinitionHash });
|
|
95
113
|
check();
|
|
96
114
|
if (staged.ready !== true)
|
|
97
115
|
throw Error('Production generation not READY');
|
|
@@ -99,15 +117,15 @@ export async function prepareDeliveryProduction(raw: ProductionOptions) {
|
|
|
99
117
|
check();
|
|
100
118
|
const sourceChecked = await source.request('check');
|
|
101
119
|
check();
|
|
102
|
-
const candidate = JSON.parse(fs.readFileSync(path.join(root, release.candidate.path), 'utf8'));
|
|
103
120
|
const expected = candidate.aggregate.inputs.targets.find((t: any) => t.target === input.target);
|
|
104
|
-
for (const [actual, wanted] of [[checked.compiler.stableDigest, expected.compiler.stableDigest], [checked.custom, expected.custom], [checked.configuration.configurationRuntimeHash, expected.configuration.configurationRuntimeHash], [sourceChecked.configuration.configurationRuntimeHash, expected.sourceConfiguration.configurationRuntimeHash]])
|
|
121
|
+
for (const [actual, wanted] of [[checked.compiler.stableDigest, expected.compiler.stableDigest], [customExecutionInput(checked.custom), customExecutionInput(expected.custom)], [checked.configuration.configurationRuntimeHash, expected.configuration.configurationRuntimeHash], [sourceChecked.configuration.configurationRuntimeHash, expected.sourceConfiguration.configurationRuntimeHash]])
|
|
105
122
|
if (canonicalJson(actual) !== canonicalJson(wanted))
|
|
106
123
|
throw Error('Final simulation changed released input closure');
|
|
107
124
|
const collection = await worker.request('bind', { inputsHash: release.inputsHash }) as DeliveryCollection;
|
|
108
125
|
check();
|
|
126
|
+
assertReleasedCalldataIntent(collection, qualification);
|
|
109
127
|
expectedDevelopmentAssertions(collection, release.operations);
|
|
110
|
-
if (collection.plan.executionId !== input.executionId || collection.plan.operator !== input.operator.toLowerCase())
|
|
128
|
+
if (collection.plan.executionId !== input.executionId || collection.plan.operator !== input.operator.toLowerCase() || collection.plan.derivedBindings.derivationSeed !== derivationSeed)
|
|
111
129
|
throw Error('Final simulation context mismatch');
|
|
112
130
|
await worker.request('close');
|
|
113
131
|
await source.request('close');
|
|
@@ -118,7 +136,8 @@ export async function prepareDeliveryProduction(raw: ProductionOptions) {
|
|
|
118
136
|
if (paths.file !== paths.fallbackFile && fileBytes(root, path.relative(root, paths.fallbackFile)) !== null)
|
|
119
137
|
throw Error('Noncanonical manifest fallback requires explicit migration');
|
|
120
138
|
const finalization = { compiler: checked.compiler, registration: { registry: JSON.parse(fileBytes(root, 'contractInfo.json') ?? '{}'), metadata: JSON.parse(fileBytes(root, 'deploymentInfo.json') ?? '{"version":1,"networks":{}}') }, manifests: [{ path: manifestPath, contents: fileBytes(root, manifestPath) }] };
|
|
121
|
-
const
|
|
139
|
+
const deliveryCalls=projectDeliveryCalls({plan:collection.plan,calldata:collection.calldata,operations:release.operations,preparations:collection.preparations});
|
|
140
|
+
const body = { schema: 'gate-delivery-production', version: 1, status: 'READY', generation: 'complete', releaseHash: current.releaseHash, collection, deliveryCalls, finalization }, record = { ...body, artifactHash: semanticHash(body) };
|
|
122
141
|
const prior = fileBytes(root, artifactPath(input.executionId));
|
|
123
142
|
if (prior !== null && prior !== encode(record))
|
|
124
143
|
throw Error('Execution ID already has immutable final simulation');
|
|
@@ -160,7 +179,7 @@ export async function runDeliveryProduction(command: Exclude<ProductionCommand,
|
|
|
160
179
|
throw Error('PRODUCTION_SIGNER_UNAVAILABLE: trusted host required');
|
|
161
180
|
if (command !== 'export-calldata')
|
|
162
181
|
throw Error('Trusted host observation provider required');
|
|
163
|
-
return renderProductionExport(ready);
|
|
182
|
+
return persistProductionExport(input,ready,renderProductionExport(ready));
|
|
164
183
|
}
|
|
165
184
|
const { guard, request, store, finality, signer, approval } = ports;
|
|
166
185
|
const check = () => { guard(); const current = inspectDeliveryProduction(input, command); if (current.status !== 'ready' || current.artifactHash !== ready.artifactHash)
|
|
@@ -175,7 +194,7 @@ export async function runDeliveryProduction(command: Exclude<ProductionCommand,
|
|
|
175
194
|
const result = command === 'execute' ? await kernel.execute(captured?.unsigned) : command === 'export-calldata' ? await kernel.export() : await kernel.reconcile(captured?.hashes ?? {});
|
|
176
195
|
check();
|
|
177
196
|
if (command === 'export-calldata')
|
|
178
|
-
return renderProductionExport(ready, result as Awaited<ReturnType<typeof kernel.export>>);
|
|
197
|
+
return persistProductionExport(input,ready,renderProductionExport(ready, result as Awaited<ReturnType<typeof kernel.export>>));
|
|
179
198
|
if (command === 'verify' && (result as any).status === 'complete') {
|
|
180
199
|
try {
|
|
181
200
|
const root = projectRoot(input.workspaceRoot, input.projectRoot), priorAnchor = retainedProductionFinalizationAnchor(root, ready);
|
|
@@ -193,14 +212,35 @@ export async function runDeliveryProduction(command: Exclude<ProductionCommand,
|
|
|
193
212
|
return result;
|
|
194
213
|
}
|
|
195
214
|
import { renderDeliveryExport } from './export';
|
|
215
|
+
async function persistProductionExport(input:ProductionOptions,ready:Extract<ReturnType<typeof inspectDeliveryProduction>,{status:'ready'}>,result:ReturnType<typeof renderProductionExport>){
|
|
216
|
+
const exported=result.status==='preparation-required'?result.export:result;
|
|
217
|
+
const deliveryFile=await archiveDeliveryCalls({...input,calls:JSON.parse(exported.json),provenance:{releaseRef:input.releaseRef,sources:[{planHash:ready.plan.planHash,chainId:ready.plan.chainId,artifactHash:ready.artifactHash,releaseHash:ready.releaseHash,executionId:ready.plan.executionId}]},assertCurrent(){const now=inspectDeliveryProduction(input,'export-calldata');if(now.status!=='ready'||now.artifactHash!==ready.artifactHash||now.releaseHash!==ready.releaseHash)throw Error('Production changed during calldata publication');}});
|
|
218
|
+
const artifact=deepFreeze({...exported,deliveryFile});
|
|
219
|
+
return result.status==='preparation-required'?deepFreeze({...result,export:artifact}):artifact;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Each network is validated independently before one combined immutable JSON is published. */
|
|
223
|
+
export async function exportProductionCalldata(productions:readonly ProductionOptions[]){
|
|
224
|
+
if(!productions.length)throw Error('Production targets required');
|
|
225
|
+
const inputs=productions.map(p=>deepFreeze(strictData(p))),first=inputs[0];
|
|
226
|
+
for(const p of inputs)if(canonicalJson([p.workspaceRoot,p.projectRoot,p.taskId,p.deploymentId,p.releaseRef])!==canonicalJson([first.workspaceRoot,first.projectRoot,first.taskId,first.deploymentId,first.releaseRef]))throw Error('Multi-network delivery must belong to one deployment release');
|
|
227
|
+
const ready=inputs.map(input=>{const r=inspectDeliveryProduction(input,'export-calldata');if(r.status!=='ready')throw Error('Production calldata generation is pending');return r;});
|
|
228
|
+
const calls=mergeDeliveryCalls(ready.map(r=>({plan:r.plan,calldata:r.collection.calldata,operations:r.operations,preparations:r.collection.preparations})));
|
|
229
|
+
const deliveryFile=await archiveDeliveryCalls({...first,calls,provenance:{releaseRef:first.releaseRef,sources:ready.map(r=>({planHash:r.plan.planHash,chainId:r.plan.chainId,artifactHash:r.artifactHash,releaseHash:r.releaseHash,executionId:r.plan.executionId}))},assertCurrent(){inputs.forEach((input,i)=>{const now=inspectDeliveryProduction(input,'export-calldata');if(now.status!=='ready'||now.artifactHash!==ready[i].artifactHash||now.releaseHash!==ready[i].releaseHash)throw Error('Multi-network production changed during publication');});}});
|
|
230
|
+
return deepFreeze({status:'artifact-delivered' as const,json:serializeDeliveryCalls(calls),deliveryFile});
|
|
231
|
+
}
|
|
196
232
|
export function renderProductionExport(ready: Extract<ReturnType<typeof inspectDeliveryProduction>, {
|
|
197
233
|
status: 'ready';
|
|
198
234
|
}>, exported = renderDeliveryExport(ready.plan)) {
|
|
235
|
+
if (canonicalJson(exported.model.plan) !== canonicalJson(ready.plan)) throw Error('Exported plan differs from frozen production plan');
|
|
236
|
+
const calldataReview = { version: 1, planHash: ready.plan.planHash, chainId: ready.plan.chainId,
|
|
237
|
+
transactions: parseCalldataReviews(ready.collection.calldata, ready.plan.transactions), check: 'matches-frozen-intent' };
|
|
199
238
|
const start = exported.model.completed.length, active = ready.plan.transactions[start], frozen = (exported.model as any).safeEnvelopes ?? [];
|
|
200
239
|
const needsPreparation = !!ready.safeTarget && active?.route === 'external' && !frozen.some((x: any) => x.envelope.start === start);
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
const
|
|
240
|
+
const deliveryContext = { operations: ready.operations, preparations: ready.collection.preparations };
|
|
241
|
+
const model = deepFreeze({ ...exported.model, calldataReview, deliveryContext, ...(ready.safeTarget ? { safePreparation: needsPreparation ? 'preparation-required' : active?.route === 'external' ? 'ready' : 'not-applicable', futureSafeEnvelopes: 'pending until their segment becomes executable' } : {}), releaseHash: ready.releaseHash, artifactHash: ready.artifactHash });
|
|
242
|
+
const json = serializeDeliveryCalls(projectDeliveryCalls({ plan: ready.plan, calldata: ready.collection.calldata, operations: ready.operations, preparations: ready.collection.preparations }));
|
|
243
|
+
const artifact = deepFreeze({ status: 'artifact-delivered' as const, model, json });
|
|
204
244
|
return needsPreparation ? deepFreeze({ status: 'preparation-required' as const, export: artifact }) : artifact;
|
|
205
245
|
}
|
|
206
246
|
import { parseConfigurationDefinition } from './configuration-definition';
|
|
@@ -34,25 +34,30 @@ export function readFile(root: string, relative: string): string | undefined {
|
|
|
34
34
|
const file = confinedFile(root, relative);
|
|
35
35
|
return stat(file) ? fs.readFileSync(file, 'utf8') : undefined;
|
|
36
36
|
}
|
|
37
|
-
export function
|
|
38
|
-
|
|
37
|
+
export function deliveryProjectMarker(root: string): void {
|
|
38
|
+
parseDeliveryProjectMarker(readFile(root, '.gate-template.json') ?? 'null');
|
|
39
39
|
}
|
|
40
|
-
/** Validate the same captured marker bytes whose digest is
|
|
41
|
-
export function
|
|
40
|
+
/** Validate the same captured marker bytes whose digest is bound to current inputs. */
|
|
41
|
+
export function parseDeliveryProjectMarker(bytes: string | Buffer): void {
|
|
42
42
|
const marker = JSON.parse(bytes.toString());
|
|
43
|
-
if (!marker || marker.
|
|
43
|
+
if (!marker || marker.profile !== 'full' || marker.templateVersion !== 1
|
|
44
|
+
|| marker.template !== `gateflow-evm-${marker.profile}@1`) throw new Error('Unsupported EVM template; no legacy migration is performed');
|
|
44
45
|
if (marker.generator?.name !== 'gate-evm-hardhat-foundry' || typeof marker.generator?.version !== 'string'
|
|
45
46
|
|| !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(marker.generator.version)
|
|
46
|
-
|| !marker.generation?.inputs || !marker.generation?.files || !Array.isArray(marker.generation?.preserved)) throw new Error('Invalid
|
|
47
|
-
if (marker.generation.state !== 'complete') throw new Error('
|
|
48
|
-
const inputs = object(marker.generation.inputs, '
|
|
49
|
-
const files = object(marker.generation.files, '
|
|
47
|
+
|| !marker.generation?.inputs || !marker.generation?.files || !Array.isArray(marker.generation?.preserved)) throw new Error('Invalid EVM generator provenance');
|
|
48
|
+
if (marker.generation.state !== 'complete') throw new Error('EVM generation is pending or invalid');
|
|
49
|
+
const inputs = object(marker.generation.inputs, 'EVM generation provenance inputs', ['projectName', 'networks', 'examples']);
|
|
50
|
+
const files = object(marker.generation.files, 'EVM generation provenance files');
|
|
50
51
|
const preserved = marker.generation.preserved as unknown[];
|
|
51
52
|
const optional = ['README.md', 'AGENTS.md', '.gitignore'];
|
|
52
|
-
const assets = ['package.json', 'hardhat.config.ts', 'tsconfig.json', ...optional, 'contracts/.gitkeep', 'test/.gitkeep'
|
|
53
|
+
const assets = ['package.json', 'package-lock.json', 'hardhat.config.ts', 'rpc.env', '.env.example', 'tsconfig.json', ...optional, 'contracts/.gitkeep', 'test/.gitkeep',
|
|
54
|
+
'foundry.toml', 'config.yaml',
|
|
55
|
+
...(Object.hasOwn(files, 'vendor/gate-evm-tools-1.2.0.tgz') ? ['vendor/gate-evm-tools-1.2.0.tgz'] : []),
|
|
56
|
+
...(inputs.examples === true ? ['contracts/Counter.sol', 'test/Counter.ts', 'test/Counter.t.sol'] : [])];
|
|
53
57
|
if (typeof inputs.projectName !== 'string' || !inputs.projectName || !Array.isArray(inputs.networks)
|
|
58
|
+
|| typeof inputs.examples !== 'boolean'
|
|
54
59
|
|| preserved.some(file => typeof file !== 'string' || !optional.includes(file))
|
|
55
60
|
|| new Set(preserved).size !== preserved.length
|
|
56
61
|
|| Object.keys(files).sort().join('\n') !== assets.filter(file => !preserved.includes(file)).sort().join('\n')
|
|
57
|
-
|| Object.values(files).some(hash => typeof hash !== 'string' || !/^[a-f0-9]{64}$/.test(hash))) throw new Error('Invalid
|
|
62
|
+
|| Object.values(files).some(hash => typeof hash !== 'string' || !/^[a-f0-9]{64}$/.test(hash))) throw new Error('Invalid EVM generation provenance');
|
|
58
63
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/** Fixed template RPC syntax. Inspection reads local env data without evaluating project code. */
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import { parseEnv } from 'node:util';
|
|
4
|
+
import { readFile } from './project-files';
|
|
5
|
+
|
|
6
|
+
export const PROJECT_ENV_BOOTSTRAP = `for (const file of ["rpc.env", ".env"]) {
|
|
7
|
+
const envFile = join(__dirname, file);
|
|
8
|
+
if (existsSync(envFile)) {
|
|
9
|
+
try {
|
|
10
|
+
process.loadEnvFile(envFile);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (file !== ".env" || !["ENOENT", "EPERM", "EACCES"].includes((error as NodeJS.ErrnoException).code ?? "")) throw error;
|
|
13
|
+
console.warn("Project credentials are unavailable; compilation and public checks can continue. Use the application host for credential-bearing delivery operations.");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}`;
|
|
17
|
+
export const PROJECT_RPC_HELPER = [
|
|
18
|
+
"function rpcUrl(options: { route: string; publicFallback: string | undefined; useBaseRpc: boolean }): string {",
|
|
19
|
+
" const fallback = options.publicFallback?.trim() || \"\";",
|
|
20
|
+
" const base = process.env.BASE_RPC_URI?.trim();",
|
|
21
|
+
" if (!options.useBaseRpc || !base) return fallback;",
|
|
22
|
+
" return `${base.replace(/\\/+$/, \"\")}/${options.route}/`;",
|
|
23
|
+
"}",
|
|
24
|
+
].join('\n');
|
|
25
|
+
const printer = ts.createPrinter({ removeComments: true });
|
|
26
|
+
function syntax(node: ts.Node, file: ts.SourceFile): string {
|
|
27
|
+
return printer.printNode(ts.EmitHint.Unspecified, node, file);
|
|
28
|
+
}
|
|
29
|
+
function statement(source: string): string {
|
|
30
|
+
const file = ts.createSourceFile('rpc.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
31
|
+
return syntax(file.statements[0], file);
|
|
32
|
+
}
|
|
33
|
+
const bootstrapSyntax = statement(PROJECT_ENV_BOOTSTRAP), helperSyntax = statement(PROJECT_RPC_HELPER);
|
|
34
|
+
|
|
35
|
+
export function projectRpcSyntax(file: ts.SourceFile): { statements: Set<ts.Statement>; bootstrap: boolean; helper: boolean } {
|
|
36
|
+
const statements = new Set<ts.Statement>();
|
|
37
|
+
let bootstrap = false, helper = false;
|
|
38
|
+
const imports = new Set<string>();
|
|
39
|
+
for (const node of file.statements) {
|
|
40
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)
|
|
41
|
+
&& ['node:fs', 'node:path'].includes(node.moduleSpecifier.text)) {
|
|
42
|
+
const name = node.moduleSpecifier.text, clause = node.importClause;
|
|
43
|
+
const wanted = name === 'node:fs' ? 'existsSync' : 'join';
|
|
44
|
+
if (imports.has(name) || node.attributes || !clause || clause.name || clause.isTypeOnly
|
|
45
|
+
|| !clause.namedBindings || !ts.isNamedImports(clause.namedBindings)
|
|
46
|
+
|| clause.namedBindings.elements.length !== 1 || clause.namedBindings.elements[0].name.text !== wanted
|
|
47
|
+
|| clause.namedBindings.elements[0].propertyName) throw Error('Unsupported project RPC import');
|
|
48
|
+
imports.add(name); statements.add(node);
|
|
49
|
+
} else if (ts.isForOfStatement(node) && syntax(node, file) === bootstrapSyntax) {
|
|
50
|
+
if (bootstrap) throw Error('Duplicate project RPC loader');
|
|
51
|
+
bootstrap = true; statements.add(node);
|
|
52
|
+
} else if (ts.isFunctionDeclaration(node) && node.name?.text === 'rpcUrl') {
|
|
53
|
+
if (helper || syntax(node, file) !== helperSyntax) throw Error('Unsupported project RPC helper');
|
|
54
|
+
helper = true; statements.add(node);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (bootstrap !== (imports.size === 2) || (!bootstrap && imports.size)) throw Error('Incomplete project RPC loader');
|
|
58
|
+
const config = file.statements.findIndex(node => ts.isVariableStatement(node) || ts.isExportAssignment(node));
|
|
59
|
+
if (bootstrap && config >= 0 && [...statements].some(node => ts.isForOfStatement(node) && file.statements.indexOf(node) > config))
|
|
60
|
+
throw Error('Project RPC loader must precede configuration');
|
|
61
|
+
return { statements, bootstrap, helper };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function projectRpcOptions(node: ts.CallExpression): { route: string; publicFallback: ts.Expression; useBaseRpc: boolean } {
|
|
65
|
+
if (!ts.isIdentifier(node.expression) || node.expression.text !== 'rpcUrl' || node.arguments.length !== 1 || node.typeArguments)
|
|
66
|
+
throw Error('Unsupported project RPC call');
|
|
67
|
+
const options = node.arguments[0];
|
|
68
|
+
if (!ts.isObjectLiteralExpression(options)) throw Error('Unsupported project RPC options');
|
|
69
|
+
const fields = new Map<string, ts.Expression>();
|
|
70
|
+
for (const property of options.properties) {
|
|
71
|
+
if (!ts.isPropertyAssignment(property) || (!ts.isIdentifier(property.name) && !ts.isStringLiteral(property.name)))
|
|
72
|
+
throw Error('Unsupported project RPC option');
|
|
73
|
+
const name = property.name.text;
|
|
74
|
+
if (!['route', 'publicFallback', 'useBaseRpc'].includes(name) || fields.has(name)) throw Error('Unsupported project RPC option');
|
|
75
|
+
fields.set(name, property.initializer);
|
|
76
|
+
}
|
|
77
|
+
const route = fields.get('route'), useBaseRpc = fields.get('useBaseRpc'), publicFallback = fields.get('publicFallback');
|
|
78
|
+
if (!route || !ts.isStringLiteral(route) || !/^[a-zA-Z0-9][a-zA-Z0-9-]*$/.test(route.text)) throw Error('Unsupported project RPC route');
|
|
79
|
+
if (!publicFallback || !useBaseRpc || ![ts.SyntaxKind.TrueKeyword, ts.SyntaxKind.FalseKeyword].includes(useBaseRpc.kind))
|
|
80
|
+
throw Error('Project RPC requires a literal useBaseRpc boolean and publicFallback');
|
|
81
|
+
return { route: route.text, publicFallback, useBaseRpc: useBaseRpc.kind === ts.SyntaxKind.TrueKeyword };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function resolveProjectRpc(route: string, publicFallback: string | undefined, useBaseRpc: boolean, env: NodeJS.ProcessEnv): string {
|
|
85
|
+
const base = env.BASE_RPC_URI?.trim();
|
|
86
|
+
return useBaseRpc && base ? `${base.replace(/\/+$/, '')}/${route}/` : publicFallback?.trim() || '';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function projectEnvironment(root: string, source: string, env: NodeJS.ProcessEnv, files: readonly string[]): NodeJS.ProcessEnv {
|
|
90
|
+
const file = ts.createSourceFile('hardhat.config.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
91
|
+
if (!projectRpcSyntax(file).bootstrap) return env;
|
|
92
|
+
const effective = { ...env };
|
|
93
|
+
for (const name of files) {
|
|
94
|
+
try {
|
|
95
|
+
const contents = readFile(root, name);
|
|
96
|
+
if (contents === undefined) continue;
|
|
97
|
+
for (const [key, value] of Object.entries(parseEnv(contents))) if (effective[key] === undefined) effective[key] = value;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (name === '.env' && ['EPERM', 'EACCES'].includes((error as NodeJS.ErrnoException).code ?? '')) {
|
|
100
|
+
throw Error('Project credential environment is protected; run credential-bearing delivery operations through the trusted application host. Configure public RPCs in rpc.env.');
|
|
101
|
+
}
|
|
102
|
+
throw Error(`Cannot read local project RPC environment (${name})`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return effective;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Static diagnostics never access the credential file, even when it is readable. */
|
|
109
|
+
export function projectPublicRpcEnvironment(root: string, source: string, env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
110
|
+
return projectEnvironment(root, source, env, ['rpc.env']);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Actual runtime observation must retain the same environment semantics as Hardhat. */
|
|
114
|
+
export function projectRpcEnvironment(root: string, source: string, env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
115
|
+
return projectEnvironment(root, source, env, ['rpc.env', '.env']);
|
|
116
|
+
}
|
|
@@ -3,8 +3,9 @@ import ts from 'typescript';
|
|
|
3
3
|
import { positiveInteger, type ProjectNetworkSnapshot } from './protocol';
|
|
4
4
|
import { validateNetworkAlias } from './policy';
|
|
5
5
|
|
|
6
|
-
export { stat, confinedFile, projectRoot, readFile,
|
|
6
|
+
export { stat, confinedFile, projectRoot, readFile, deliveryProjectMarker } from './project-files';
|
|
7
7
|
import { confinedFile, readFile } from './project-files';
|
|
8
|
+
import { projectRpcSyntax, projectRpcOptions, resolveProjectRpc, projectRpcEnvironment, projectPublicRpcEnvironment } from './project-rpc';
|
|
8
9
|
|
|
9
10
|
function unwrap(node: ts.Expression): ts.Expression {
|
|
10
11
|
while (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node)) node = node.expression;
|
|
@@ -63,7 +64,7 @@ export function projectDefaultNetwork(root: string): string {
|
|
|
63
64
|
if (!ts.isStringLiteral(network)) throw new Error('Project defaultNetwork must be a static string for delivery routing');
|
|
64
65
|
return validateNetworkAlias(network.text, 'defaultNetwork');
|
|
65
66
|
}
|
|
66
|
-
function rpcValue(node: ts.Expression, env: NodeJS.ProcessEnv): string | undefined {
|
|
67
|
+
function rpcValue(node: ts.Expression, env: NodeJS.ProcessEnv, helper: boolean): string | undefined {
|
|
67
68
|
node = unwrap(node);
|
|
68
69
|
if (ts.isStringLiteral(node)) return node.text;
|
|
69
70
|
if (ts.isPropertyAccessExpression(node) && ts.isPropertyAccessExpression(node.expression)
|
|
@@ -73,19 +74,30 @@ function rpcValue(node: ts.Expression, env: NodeJS.ProcessEnv): string | undefin
|
|
|
73
74
|
&& ts.isIdentifier(node.expression.expression) && node.expression.expression.text === 'process'
|
|
74
75
|
&& node.expression.name.text === 'env' && ts.isStringLiteral(node.argumentExpression)) return env[node.argumentExpression.text];
|
|
75
76
|
if (ts.isBinaryExpression(node) && [ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken].includes(node.operatorToken.kind)) {
|
|
76
|
-
const left = rpcValue(node.left, env);
|
|
77
|
+
const left = rpcValue(node.left, env, helper);
|
|
77
78
|
return node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken
|
|
78
|
-
? left ?? rpcValue(node.right, env) : left || rpcValue(node.right, env);
|
|
79
|
+
? left ?? rpcValue(node.right, env, helper) : left || rpcValue(node.right, env, helper);
|
|
80
|
+
}
|
|
81
|
+
if (helper && ts.isCallExpression(node)) {
|
|
82
|
+
const options = projectRpcOptions(node);
|
|
83
|
+
return resolveProjectRpc(options.route, rpcValue(options.publicFallback, env, false), options.useBaseRpc, env);
|
|
79
84
|
}
|
|
80
85
|
throw new Error('Selected network URL must be a literal or process.env reference');
|
|
81
86
|
}
|
|
82
87
|
/** Read the project-owned definitions without evaluating the config, plugins or HRE. */
|
|
83
88
|
export function readProjectNetworkSnapshots(root: string, aliases: string[], env: NodeJS.ProcessEnv = process.env): ProjectNetworkSnapshot[] {
|
|
84
|
-
|
|
89
|
+
const source = readFile(root, 'hardhat.config.ts') ?? '';
|
|
90
|
+
return parseProjectNetworkSnapshots(source, aliases, projectRpcEnvironment(root, source, env));
|
|
91
|
+
}
|
|
92
|
+
/** Public preflight only: RPCs must be in rpc.env or the supplied environment, never .env. */
|
|
93
|
+
export function readPublicProjectNetworkSnapshots(root: string, aliases: string[], env: NodeJS.ProcessEnv = process.env): ProjectNetworkSnapshot[] {
|
|
94
|
+
const source = readFile(root, 'hardhat.config.ts') ?? '';
|
|
95
|
+
return parseProjectNetworkSnapshots(source, aliases, projectPublicRpcEnvironment(root, source, env));
|
|
85
96
|
}
|
|
86
97
|
/** Parse one captured project definition without rereading files or evaluating plugins/HRE. */
|
|
87
98
|
export function parseProjectNetworkSnapshots(source: string, aliases: string[], env: NodeJS.ProcessEnv = process.env): ProjectNetworkSnapshot[] {
|
|
88
|
-
const { networks } = parseProjectConfig(source);
|
|
99
|
+
const { file, networks } = parseProjectConfig(source);
|
|
100
|
+
const { helper } = projectRpcSyntax(file);
|
|
89
101
|
return aliases.map(network => {
|
|
90
102
|
validateNetworkAlias(network, 'network', false);
|
|
91
103
|
const selected = networks.get(network);
|
|
@@ -96,7 +108,7 @@ export function parseProjectNetworkSnapshots(source: string, aliases: string[],
|
|
|
96
108
|
if (fields.has('zksync') && fields.get('zksync')!.kind !== ts.SyntaxKind.FalseKeyword) throw new Error('Delivery requires an EVM network');
|
|
97
109
|
const url = fields.get('url');
|
|
98
110
|
if (!url) throw new Error(`Selected network ${network} requires an RPC URL definition`);
|
|
99
|
-
return { network, chainId: positiveInteger(Number(chainId.text), 'network chainId'), url: rpcValue(url, env) ?? '' };
|
|
111
|
+
return { network, chainId: positiveInteger(Number(chainId.text), 'network chainId'), url: rpcValue(url, env, helper) ?? '' };
|
|
100
112
|
});
|
|
101
113
|
}
|
|
102
114
|
export function integratedConfig(source: string): string {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Public business projection. Internal provenance and runtime progress stay separate. */
|
|
2
|
+
import { immutableDeliveryPlan } from './export';
|
|
3
|
+
import { parseCalldataReviews } from './calldata-review';
|
|
4
|
+
import type { PreparationEntry } from './journal';
|
|
5
|
+
import { expandDeliveryInvocation, readableInvocationAbi, describeDeliveryInvocation, type ExpandedDetails } from './calldata-details';
|
|
6
|
+
import { nonempty, deepFreeze, canonicalJson, semanticHash } from '../workflow/lib/canonicalData';
|
|
7
|
+
import type { WorkflowStep } from '../workflow/lib/parameters';
|
|
8
|
+
|
|
9
|
+
export { archiveDeliveryCalls } from './calldata-archive';
|
|
10
|
+
export { serializeDeliveryCalls, deliveryCalldataPath } from './calldata-format';
|
|
11
|
+
|
|
12
|
+
export interface DeliveryCall {
|
|
13
|
+
chains: string[]; executor: string; to: string; name: string; description: string;
|
|
14
|
+
abi: string[]; calldata: string; value?: string;
|
|
15
|
+
details?: ExpandedDetails;
|
|
16
|
+
}
|
|
17
|
+
export interface PublicCalldataInput { plan: unknown; calldata: unknown; operations: readonly WorkflowStep[]; preparations?: unknown }
|
|
18
|
+
/** Frozen parameters independently re-encode and compare before any public projection. */
|
|
19
|
+
export function projectDeliveryCalls(input: PublicCalldataInput): readonly DeliveryCall[] {
|
|
20
|
+
const plan = immutableDeliveryPlan(input.plan);
|
|
21
|
+
const reviews = parseCalldataReviews(input.calldata, plan.transactions);
|
|
22
|
+
const journal=input.preparations??[];
|
|
23
|
+
const preparations: PreparationEntry[] = Array.isArray(journal)&&!journal.length&&!plan.deploymentBindings.length?[]
|
|
24
|
+
: (require('./journal') as typeof import('./journal')).parsePreparationJournal(journal, plan.transactions, plan.derivedBindings, {target:plan.target,chainId:plan.chainId,expectedStepIds:plan.deploymentBindings.map(d=>d.stepId)});
|
|
25
|
+
return deepFreeze(plan.transactions.map((tx, index) => {
|
|
26
|
+
if (tx.operation !== 0) throw Error('Public calldata supports ordinary CALL only');
|
|
27
|
+
const binding = plan.transactionBindings[index], operation = plan.operationBindings.find(o => o.stepId === binding.stepId);
|
|
28
|
+
if (!operation) throw Error('Public calldata operation binding missing');
|
|
29
|
+
const source = operation.operationId === null ? input.operations.find(s => s.id === operation.sourceStepId)
|
|
30
|
+
: plan.customBindings.find(s => s.sourceStepId === operation.sourceStepId)?.operations.find(s => s.id === operation.operationId);
|
|
31
|
+
if (!source) throw Error('Frozen operation intent missing');
|
|
32
|
+
const call = binding.callIndex === null ? undefined : (source.calls as any[] | undefined)?.[binding.callIndex];
|
|
33
|
+
const name = nonempty(call?.name ?? source.name, 'operation name');
|
|
34
|
+
const description = nonempty(call?.description ?? source.description, 'operation description');
|
|
35
|
+
const invocation = reviews[index].invocation;
|
|
36
|
+
if (invocation.kind !== 'function') throw Error('Outer public transaction requires function ABI');
|
|
37
|
+
const entry=preparations.find(p=>p.mappings.some(m=>m.transactionId===tx.transactionId));
|
|
38
|
+
const details=expandDeliveryInvocation(invocation,tx.to,{name,description},entry);
|
|
39
|
+
return { chains: [plan.chainId], executor: tx.executor, to: tx.to, name, description:describeDeliveryInvocation(description,invocation),
|
|
40
|
+
abi: readableInvocationAbi(invocation), calldata: tx.data, ...(BigInt(tx.value) === 0n ? {} : { value: tx.value }),...(details?{details}:{}) };
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Merge matching logical occurrences, never globally deduplicate calldata. */
|
|
45
|
+
export function mergeDeliveryCalls(inputs: readonly PublicCalldataInput[]): readonly DeliveryCall[] {
|
|
46
|
+
if(!inputs.length)throw Error('At least one independently frozen network is required');
|
|
47
|
+
const chains=new Set<string>(), sequences:Array<{chain:string;calls:readonly DeliveryCall[];keys:string[]}>=[];
|
|
48
|
+
const nodes=new Map<string,{call:DeliveryCall;next:Set<string>;incoming:number}>();
|
|
49
|
+
for(const input of inputs){
|
|
50
|
+
const plan=immutableDeliveryPlan(input.plan),chain=plan.chainId,calls=projectDeliveryCalls(input);
|
|
51
|
+
if(chains.has(chain))throw Error('Duplicate chain projection');chains.add(chain);
|
|
52
|
+
const keys=calls.map((call,index)=>{
|
|
53
|
+
const binding=plan.transactionBindings[index],operation=plan.operationBindings.find(o=>o.stepId===binding.stepId)!;
|
|
54
|
+
const {chains:_,...content}=call;
|
|
55
|
+
const key=semanticHash({logical:[operation.sourceStepId,operation.operationId,binding.role,binding.callIndex],content});
|
|
56
|
+
const existing=nodes.get(key);
|
|
57
|
+
if(existing){if(existing.call.chains.includes(chain))throw Error('Duplicate logical occurrence on one chain');existing.call.chains.push(chain);}
|
|
58
|
+
else nodes.set(key,{call:{...call,chains:[chain]},next:new Set(),incoming:0});
|
|
59
|
+
return key;
|
|
60
|
+
});
|
|
61
|
+
sequences.push({chain,calls,keys});
|
|
62
|
+
for(let i=1;i<keys.length;i++){const a=nodes.get(keys[i-1])!,b=nodes.get(keys[i])!;if(!a.next.has(keys[i])){a.next.add(keys[i]);b.incoming++;}}
|
|
63
|
+
}
|
|
64
|
+
const queue=[...nodes].filter(([,n])=>n.incoming===0).map(([key])=>key),ordered:DeliveryCall[]=[];
|
|
65
|
+
for(let at=0;at<queue.length;at++){
|
|
66
|
+
const node=nodes.get(queue[at])!;ordered.push(node.call);
|
|
67
|
+
for(const key of node.next){const next=nodes.get(key)!;if(--next.incoming===0)queue.push(key);}
|
|
68
|
+
}
|
|
69
|
+
// Conflicting cross-chain ordering cannot share occurrences; keep exact per-chain sequences.
|
|
70
|
+
const result=ordered.length===nodes.size?ordered:sequences.flatMap(s=>s.calls.map(call=>({...call,chains:[s.chain]})));
|
|
71
|
+
const content=(call:DeliveryCall)=>{const {chains:_,...rest}=call;return rest;};
|
|
72
|
+
for(const sequence of sequences)if(canonicalJson(result.filter(c=>c.chains.includes(sequence.chain)).map(content))!==canonicalJson(sequence.calls.map(content)))throw Error('Merged calldata changed per-chain order or coverage');
|
|
73
|
+
return deepFreeze(result);
|
|
74
|
+
}
|