@0xsequence/catapult 1.3.17 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +276 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +12 -0
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/provenance.d.ts +3 -0
- package/dist/commands/provenance.d.ts.map +1 -0
- package/dist/commands/provenance.js +138 -0
- package/dist/commands/provenance.js.map +1 -0
- package/dist/lib/__tests__/deployer.spec.js +118 -1
- package/dist/lib/__tests__/deployer.spec.js.map +1 -1
- package/dist/lib/__tests__/network-loader.spec.js.map +1 -1
- package/dist/lib/__tests__/provenance.spec.d.ts +2 -0
- package/dist/lib/__tests__/provenance.spec.d.ts.map +1 -0
- package/dist/lib/__tests__/provenance.spec.js +205 -0
- package/dist/lib/__tests__/provenance.spec.js.map +1 -0
- package/dist/lib/contracts/__tests__/repository.spec.js +243 -0
- package/dist/lib/contracts/__tests__/repository.spec.js.map +1 -1
- package/dist/lib/contracts/repository.d.ts +9 -1
- package/dist/lib/contracts/repository.d.ts.map +1 -1
- package/dist/lib/contracts/repository.js +93 -7
- package/dist/lib/contracts/repository.js.map +1 -1
- package/dist/lib/core/__tests__/assert-action.spec.d.ts +2 -0
- package/dist/lib/core/__tests__/assert-action.spec.d.ts.map +1 -0
- package/dist/lib/core/__tests__/assert-action.spec.js +377 -0
- package/dist/lib/core/__tests__/assert-action.spec.js.map +1 -0
- package/dist/lib/core/__tests__/engine.spec.js +80 -0
- package/dist/lib/core/__tests__/engine.spec.js.map +1 -1
- package/dist/lib/core/__tests__/loader.spec.js +29 -0
- package/dist/lib/core/__tests__/loader.spec.js.map +1 -1
- package/dist/lib/core/__tests__/resolver.spec.js +405 -0
- package/dist/lib/core/__tests__/resolver.spec.js.map +1 -1
- package/dist/lib/core/__tests__/sign-actions.spec.d.ts +2 -0
- package/dist/lib/core/__tests__/sign-actions.spec.d.ts.map +1 -0
- package/dist/lib/core/__tests__/sign-actions.spec.js +128 -0
- package/dist/lib/core/__tests__/sign-actions.spec.js.map +1 -0
- package/dist/lib/core/__tests__/signer.spec.d.ts +2 -0
- package/dist/lib/core/__tests__/signer.spec.d.ts.map +1 -0
- package/dist/lib/core/__tests__/signer.spec.js +40 -0
- package/dist/lib/core/__tests__/signer.spec.js.map +1 -0
- package/dist/lib/core/context.d.ts +3 -2
- package/dist/lib/core/context.d.ts.map +1 -1
- package/dist/lib/core/context.js +3 -2
- package/dist/lib/core/context.js.map +1 -1
- package/dist/lib/core/engine.d.ts +4 -0
- package/dist/lib/core/engine.d.ts.map +1 -1
- package/dist/lib/core/engine.js +206 -0
- package/dist/lib/core/engine.js.map +1 -1
- package/dist/lib/core/loader.d.ts +1 -0
- package/dist/lib/core/loader.d.ts.map +1 -1
- package/dist/lib/core/loader.js +6 -1
- package/dist/lib/core/loader.js.map +1 -1
- package/dist/lib/core/resolver.d.ts +2 -0
- package/dist/lib/core/resolver.d.ts.map +1 -1
- package/dist/lib/core/resolver.js +89 -0
- package/dist/lib/core/resolver.js.map +1 -1
- package/dist/lib/core/signer.d.ts +7 -0
- package/dist/lib/core/signer.d.ts.map +1 -0
- package/dist/lib/core/signer.js +60 -0
- package/dist/lib/core/signer.js.map +1 -0
- package/dist/lib/deployer.d.ts.map +1 -1
- package/dist/lib/deployer.js +21 -4
- package/dist/lib/deployer.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/parsers/__tests__/job.spec.js +77 -0
- package/dist/lib/parsers/__tests__/job.spec.js.map +1 -1
- package/dist/lib/parsers/__tests__/source.spec.d.ts +2 -0
- package/dist/lib/parsers/__tests__/source.spec.d.ts.map +1 -0
- package/dist/lib/parsers/__tests__/source.spec.js +158 -0
- package/dist/lib/parsers/__tests__/source.spec.js.map +1 -0
- package/dist/lib/parsers/index.d.ts +1 -0
- package/dist/lib/parsers/index.d.ts.map +1 -1
- package/dist/lib/parsers/index.js +1 -0
- package/dist/lib/parsers/index.js.map +1 -1
- package/dist/lib/parsers/job.d.ts.map +1 -1
- package/dist/lib/parsers/job.js +11 -0
- package/dist/lib/parsers/job.js.map +1 -1
- package/dist/lib/parsers/source.d.ts +4 -0
- package/dist/lib/parsers/source.d.ts.map +1 -0
- package/dist/lib/parsers/source.js +107 -0
- package/dist/lib/parsers/source.js.map +1 -0
- package/dist/lib/provenance.d.ts +34 -0
- package/dist/lib/provenance.d.ts.map +1 -0
- package/dist/lib/provenance.js +694 -0
- package/dist/lib/provenance.js.map +1 -0
- package/dist/lib/types/actions.d.ts +42 -2
- package/dist/lib/types/actions.d.ts.map +1 -1
- package/dist/lib/types/actions.js +4 -0
- package/dist/lib/types/actions.js.map +1 -1
- package/dist/lib/types/contracts.d.ts +3 -0
- package/dist/lib/types/contracts.d.ts.map +1 -1
- package/dist/lib/types/definitions.d.ts +1 -0
- package/dist/lib/types/definitions.d.ts.map +1 -1
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.d.ts.map +1 -1
- package/dist/lib/types/index.js +1 -0
- package/dist/lib/types/index.js.map +1 -1
- package/dist/lib/types/source.d.ts +26 -0
- package/dist/lib/types/source.d.ts.map +1 -0
- package/dist/lib/types/source.js +3 -0
- package/dist/lib/types/source.js.map +1 -0
- package/dist/lib/types/values.d.ts +33 -1
- package/dist/lib/types/values.d.ts.map +1 -1
- package/package.json +4 -1
- package/.eslintrc.json +0 -29
- package/.github/workflows/ci.yml +0 -181
- package/CONCEPT.md +0 -24
- package/contracts/checked-call.huff +0 -65
- package/eslint.config.js +0 -48
- package/examples/jobs/guards-v1.yaml +0 -17
- package/examples/jobs/sequence-seq-0001-patch.yaml +0 -59
- package/examples/jobs/sequence-v1.yaml +0 -59
- package/examples/templates/sequence-factory-v1.yaml +0 -56
- package/jest.config.js +0 -25
- package/src/cli.ts +0 -17
- package/src/commands/common.ts +0 -61
- package/src/commands/dry.ts +0 -209
- package/src/commands/etherscan.ts +0 -360
- package/src/commands/index.ts +0 -5
- package/src/commands/list.ts +0 -249
- package/src/commands/run.ts +0 -146
- package/src/commands/utils.ts +0 -215
- package/src/index.ts +0 -67
- package/src/lib/__tests__/deployer-events.spec.ts +0 -338
- package/src/lib/__tests__/deployer.spec.ts +0 -2093
- package/src/lib/__tests__/network-loader.spec.ts +0 -150
- package/src/lib/__tests__/network-selection.spec.ts +0 -41
- package/src/lib/__tests__/network-utils.spec.ts +0 -230
- package/src/lib/artifacts/__tests__/fixtures/contract1.json +0 -19
- package/src/lib/artifacts/__tests__/fixtures/contract2.json +0 -19
- package/src/lib/artifacts/__tests__/fixtures/duplicate-name.json +0 -19
- package/src/lib/artifacts/__tests__/fixtures/nested/nested-contract.json +0 -18
- package/src/lib/artifacts/__tests__/fixtures/not-an-artifact.json +0 -8
- package/src/lib/artifacts/__tests__/fixtures/readme.txt +0 -2
- package/src/lib/contracts/__tests__/repository.spec.ts +0 -344
- package/src/lib/contracts/repository.ts +0 -313
- package/src/lib/core/__tests__/context.spec.ts +0 -37
- package/src/lib/core/__tests__/engine.spec.ts +0 -1889
- package/src/lib/core/__tests__/graph.spec.ts +0 -125
- package/src/lib/core/__tests__/json-integration.spec.ts +0 -425
- package/src/lib/core/__tests__/loader.spec.ts +0 -334
- package/src/lib/core/__tests__/multi-platform-verification.spec.ts +0 -406
- package/src/lib/core/__tests__/resolver.spec.ts +0 -2053
- package/src/lib/core/__tests__/static-action.spec.ts +0 -172
- package/src/lib/core/context.ts +0 -127
- package/src/lib/core/engine.ts +0 -1782
- package/src/lib/core/graph.ts +0 -252
- package/src/lib/core/loader.ts +0 -247
- package/src/lib/core/resolver.ts +0 -757
- package/src/lib/deployer.ts +0 -981
- package/src/lib/events/__tests__/event-system.spec.ts +0 -392
- package/src/lib/events/cli-adapter.ts +0 -369
- package/src/lib/events/emitter.ts +0 -62
- package/src/lib/events/index.ts +0 -3
- package/src/lib/events/types.ts +0 -520
- package/src/lib/index.ts +0 -14
- package/src/lib/network-loader.ts +0 -90
- package/src/lib/network-selection.ts +0 -73
- package/src/lib/network-utils.ts +0 -64
- package/src/lib/parsers/__tests__/buildinfo.spec.ts +0 -122
- package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-bytecode-buildinfo.json +0 -62
- package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-json.txt +0 -2
- package/src/lib/parsers/__tests__/fixtures/buildinfo/multi-contract-buildinfo.json +0 -89
- package/src/lib/parsers/__tests__/fixtures/buildinfo/no-contracts-buildinfo.json +0 -17
- package/src/lib/parsers/__tests__/fixtures/buildinfo/simple-buildinfo.json +0 -63
- package/src/lib/parsers/__tests__/fixtures/buildinfo/wrong-format.json +0 -4
- package/src/lib/parsers/__tests__/job.spec.ts +0 -358
- package/src/lib/parsers/__tests__/template.spec.ts +0 -111
- package/src/lib/parsers/artifact/__tests__/artifact.spec.ts +0 -117
- package/src/lib/parsers/artifact/__tests__/fixtures/empty-bytecode.json +0 -5
- package/src/lib/parsers/artifact/__tests__/fixtures/hardhat-artifact.json +0 -67
- package/src/lib/parsers/artifact/__tests__/fixtures/invalid-bytecode.json +0 -5
- package/src/lib/parsers/artifact/__tests__/fixtures/invalid-json.txt +0 -11
- package/src/lib/parsers/artifact/__tests__/fixtures/minimal-artifact.json +0 -5
- package/src/lib/parsers/artifact/__tests__/fixtures/missing-abi.json +0 -4
- package/src/lib/parsers/artifact/__tests__/fixtures/missing-bytecode.json +0 -11
- package/src/lib/parsers/artifact/__tests__/fixtures/missing-contract-name.json +0 -11
- package/src/lib/parsers/artifact/__tests__/fixtures/simple-artifact.json +0 -40
- package/src/lib/parsers/artifact/__tests__/fixtures/wrong-types.json +0 -7
- package/src/lib/parsers/artifact/foundry-1.2.ts +0 -72
- package/src/lib/parsers/artifact/index.ts +0 -27
- package/src/lib/parsers/artifact/types.ts +0 -9
- package/src/lib/parsers/buildinfo.ts +0 -127
- package/src/lib/parsers/constants.ts +0 -56
- package/src/lib/parsers/index.ts +0 -5
- package/src/lib/parsers/job.ts +0 -148
- package/src/lib/parsers/template.ts +0 -135
- package/src/lib/std/templates/arachnid-deterministic-deployment-proxy.yaml +0 -68
- package/src/lib/std/templates/assured-deployment.yaml +0 -46
- package/src/lib/std/templates/era-evm-predeploy.yaml +0 -35
- package/src/lib/std/templates/erc-2470.yaml +0 -70
- package/src/lib/std/templates/min-balance.yaml +0 -35
- package/src/lib/std/templates/nano-universal-deployer.yaml +0 -61
- package/src/lib/std/templates/raw-erc-2470.yaml +0 -62
- package/src/lib/std/templates/raw-nano-universal-deployer.yaml +0 -54
- package/src/lib/std/templates/raw-sequence-universal-deployer-2.yaml +0 -52
- package/src/lib/std/templates/sequence-universal-deployer-2.yaml +0 -61
- package/src/lib/types/__tests__/json-request-action.spec.ts +0 -243
- package/src/lib/types/__tests__/read-json-value.spec.ts +0 -278
- package/src/lib/types/__tests__/resolve-json-value.spec.ts +0 -769
- package/src/lib/types/actions.ts +0 -127
- package/src/lib/types/artifacts.ts +0 -21
- package/src/lib/types/buildinfo.ts +0 -116
- package/src/lib/types/conditions.ts +0 -50
- package/src/lib/types/contracts.ts +0 -23
- package/src/lib/types/definitions.ts +0 -70
- package/src/lib/types/index.ts +0 -8
- package/src/lib/types/network.ts +0 -33
- package/src/lib/types/project.ts +0 -9
- package/src/lib/types/task.ts +0 -9
- package/src/lib/types/values.ts +0 -150
- package/src/lib/utils/assertion.ts +0 -24
- package/src/lib/utils/validation.ts +0 -116
- package/src/lib/validation/contract-references.ts +0 -210
- package/src/lib/validation/index.ts +0 -1
- package/src/lib/verification/__tests__/etherscan.spec.ts +0 -710
- package/src/lib/verification/__tests__/sourcify.spec.ts +0 -288
- package/src/lib/verification/etherscan.ts +0 -547
- package/src/lib/verification/sourcify.ts +0 -248
- package/test_validation/artifacts/TestContract.json +0 -9
- package/test_validation/jobs/test-missing.yaml +0 -16
- package/test_validation/networks.yaml +0 -3
- package/tsconfig.json +0 -36
package/src/lib/core/engine.ts
DELETED
|
@@ -1,1782 +0,0 @@
|
|
|
1
|
-
import { Job, Template, Action, JobAction, isPrimitiveActionType, Condition } from '../types'
|
|
2
|
-
import { Contract } from '../types/contracts'
|
|
3
|
-
import { ExecutionContext } from './context'
|
|
4
|
-
import { ValueResolver, ResolutionScope } from './resolver'
|
|
5
|
-
import { validateAddress, validateHexData, validateBigNumberish, validateRawTransaction } from '../utils/validation'
|
|
6
|
-
import { DeploymentEventEmitter, deploymentEvents } from '../events'
|
|
7
|
-
import { createDefaultVerificationRegistry, VerificationPlatformRegistry } from '../verification/etherscan'
|
|
8
|
-
import { BuildInfo } from '../types/buildinfo'
|
|
9
|
-
import { ethers } from 'ethers'
|
|
10
|
-
|
|
11
|
-
export type EngineOptions = {
|
|
12
|
-
eventEmitter?: DeploymentEventEmitter
|
|
13
|
-
verificationRegistry?: VerificationPlatformRegistry
|
|
14
|
-
noPostCheckConditions?: boolean
|
|
15
|
-
allowMultipleNicksMethodTests?: boolean
|
|
16
|
-
ignoreVerifyErrors?: boolean
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* The ExecutionEngine is the core component that runs jobs and their actions.
|
|
21
|
-
* It interprets the declarative YAML files, resolves values, interacts with the
|
|
22
|
-
* blockchain, and manages the overall execution flow.
|
|
23
|
-
*/
|
|
24
|
-
export class ExecutionEngine {
|
|
25
|
-
private readonly resolver: ValueResolver
|
|
26
|
-
private readonly templates: Map<string, Template>
|
|
27
|
-
private readonly events: DeploymentEventEmitter
|
|
28
|
-
private readonly verificationRegistry: VerificationPlatformRegistry
|
|
29
|
-
private readonly noPostCheckConditions: boolean
|
|
30
|
-
private readonly allowMultipleNicksMethodTests: boolean
|
|
31
|
-
private readonly ignoreVerifyErrors: boolean
|
|
32
|
-
private nicksMethodResult: boolean | undefined
|
|
33
|
-
private verificationWarnings: Array<{
|
|
34
|
-
actionName: string
|
|
35
|
-
address: string
|
|
36
|
-
contractName: string
|
|
37
|
-
platform: string
|
|
38
|
-
error: string
|
|
39
|
-
jobName?: string
|
|
40
|
-
networkName?: string
|
|
41
|
-
}> = []
|
|
42
|
-
|
|
43
|
-
constructor(templates: Map<string, Template>, options?: EngineOptions) {
|
|
44
|
-
this.resolver = new ValueResolver()
|
|
45
|
-
this.templates = templates
|
|
46
|
-
this.events = options?.eventEmitter || deploymentEvents
|
|
47
|
-
this.verificationRegistry = options?.verificationRegistry || createDefaultVerificationRegistry()
|
|
48
|
-
this.noPostCheckConditions = options?.noPostCheckConditions ?? false
|
|
49
|
-
this.allowMultipleNicksMethodTests = options?.allowMultipleNicksMethodTests ?? false
|
|
50
|
-
this.ignoreVerifyErrors = options?.ignoreVerifyErrors ?? false
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Computes retry configuration for post-execution checks, tuned for local vs public networks.
|
|
55
|
-
* Local (anvil/hardhat): 50ms delay for ~5s total (100 retries => 101 attempts)
|
|
56
|
-
* Public: 2000ms delay for ~30s total (15 retries => 16 attempts)
|
|
57
|
-
*/
|
|
58
|
-
private getPostCheckRetryConfig(context: ExecutionContext): { retries: number; delayMs: number } {
|
|
59
|
-
const network = context.getNetwork()
|
|
60
|
-
const isLocal =
|
|
61
|
-
network.chainId === 31337 ||
|
|
62
|
-
network.chainId === 1337 ||
|
|
63
|
-
/localhost|127\.0\.0\.1/i.test(network.rpcUrl)
|
|
64
|
-
|
|
65
|
-
if (isLocal) {
|
|
66
|
-
return { retries: 100, delayMs: 50 }
|
|
67
|
-
}
|
|
68
|
-
return { retries: 15, delayMs: 2000 }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Executes a single job against a given network context.
|
|
73
|
-
* @param job The Job object to execute.
|
|
74
|
-
* @param context The ExecutionContext for the target network.
|
|
75
|
-
*/
|
|
76
|
-
public async executeJob(job: Job, context: ExecutionContext): Promise<void> {
|
|
77
|
-
this.events.emitEvent({
|
|
78
|
-
type: 'job_started',
|
|
79
|
-
level: 'info',
|
|
80
|
-
data: {
|
|
81
|
-
jobName: job.name,
|
|
82
|
-
jobVersion: job.version,
|
|
83
|
-
networkName: context.getNetwork().name,
|
|
84
|
-
chainId: context.getNetwork().chainId
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
// Set context path for relative artifact resolution
|
|
89
|
-
const previousContextPath = context.getContextPath()
|
|
90
|
-
context.setContextPath(job._path)
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const executionOrder = this.topologicalSortActions(job)
|
|
94
|
-
|
|
95
|
-
for (const actionName of executionOrder) {
|
|
96
|
-
const action = job.actions.find(a => a.name === actionName)
|
|
97
|
-
if (!action) {
|
|
98
|
-
// This should be unreachable if topological sort is correct
|
|
99
|
-
throw new Error(`Internal error: Action "${actionName}" not found in job "${job.name}".`)
|
|
100
|
-
}
|
|
101
|
-
await this.executeAction(action, context, new Map())
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// If post-check conditions are enabled, re-evaluate job-level skip conditions with retry to handle RPC propagation lag
|
|
105
|
-
if (!this.noPostCheckConditions && job.skip_condition) {
|
|
106
|
-
const { retries, delayMs } = this.getPostCheckRetryConfig(context)
|
|
107
|
-
const shouldSkip = await this.retryBooleanCheck(
|
|
108
|
-
async () => this.evaluateSkipConditions(job.skip_condition!, context, new Map()),
|
|
109
|
-
retries,
|
|
110
|
-
delayMs
|
|
111
|
-
)
|
|
112
|
-
if (!shouldSkip) {
|
|
113
|
-
// If skip conditions don't evaluate to true after execution, the job failed
|
|
114
|
-
throw new Error(`Job "${job.name}" failed post-execution check: skip conditions did not evaluate to true`)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
} finally {
|
|
118
|
-
// Restore previous context path
|
|
119
|
-
context.setContextPath(previousContextPath)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
this.events.emitEvent({
|
|
123
|
-
type: 'job_completed',
|
|
124
|
-
level: 'info',
|
|
125
|
-
data: {
|
|
126
|
-
jobName: job.name,
|
|
127
|
-
networkName: context.getNetwork().name,
|
|
128
|
-
chainId: context.getNetwork().chainId
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* The central dispatcher for executing any action, whether it's a primitive
|
|
135
|
-
* or a call to another template.
|
|
136
|
-
* @param action The action to execute.
|
|
137
|
-
* @param context The global execution context.
|
|
138
|
-
* @param scope The local resolution scope, used for template arguments.
|
|
139
|
-
*/
|
|
140
|
-
private async executeAction(
|
|
141
|
-
action: JobAction | Action,
|
|
142
|
-
context: ExecutionContext,
|
|
143
|
-
scope: ResolutionScope,
|
|
144
|
-
): Promise<void> {
|
|
145
|
-
const actionName = 'name' in action ? action.name : action.type
|
|
146
|
-
// For JobAction, get template or type; for Action, get type
|
|
147
|
-
const templateName = 'template' in action
|
|
148
|
-
? (action.template || action.type)
|
|
149
|
-
: action.type
|
|
150
|
-
|
|
151
|
-
if (!templateName) {
|
|
152
|
-
throw new Error(`Action "${actionName}": missing both template and type fields`)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Emit action start with a guaranteed, meaningful name
|
|
156
|
-
const printableName =
|
|
157
|
-
(typeof actionName === 'string' && actionName.trim().length > 0)
|
|
158
|
-
? actionName
|
|
159
|
-
: (isPrimitiveActionType(templateName) ? templateName : `template:${templateName}`)
|
|
160
|
-
this.events.emitEvent({
|
|
161
|
-
type: 'action_started',
|
|
162
|
-
level: 'info',
|
|
163
|
-
data: {
|
|
164
|
-
actionName: printableName,
|
|
165
|
-
jobName: 'unknown' // We'll need to pass job context later
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
// 1. Evaluate skip conditions for the action itself.
|
|
170
|
-
const shouldSkip = await this.evaluateSkipConditions(action.skip_condition, context, scope)
|
|
171
|
-
if (shouldSkip) {
|
|
172
|
-
this.events.emitEvent({
|
|
173
|
-
type: 'action_skipped',
|
|
174
|
-
level: 'info',
|
|
175
|
-
data: {
|
|
176
|
-
actionName: actionName,
|
|
177
|
-
reason: 'condition met'
|
|
178
|
-
}
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
// Process static outputs even when action is skipped
|
|
182
|
-
// This is important for static outputs that don't depend on action execution
|
|
183
|
-
const hasCustomOutput = 'name' in action && action.name &&
|
|
184
|
-
(action as any).output &&
|
|
185
|
-
typeof (action as any).output === 'object' &&
|
|
186
|
-
!Array.isArray((action as any).output)
|
|
187
|
-
|
|
188
|
-
if (hasCustomOutput) {
|
|
189
|
-
const customOutput = (action as any).output
|
|
190
|
-
// Custom output map provided by job action: resolve each mapping within the current scope
|
|
191
|
-
for (const [key, value] of Object.entries(customOutput)) {
|
|
192
|
-
const resolvedOutput = await this.resolver.resolve(value as any, context, scope)
|
|
193
|
-
const outputKey = `${action.name}.${key}`
|
|
194
|
-
context.setOutput(outputKey, resolvedOutput)
|
|
195
|
-
this.events.emitEvent({
|
|
196
|
-
type: 'output_stored',
|
|
197
|
-
level: 'debug',
|
|
198
|
-
data: {
|
|
199
|
-
outputKey,
|
|
200
|
-
value: resolvedOutput
|
|
201
|
-
}
|
|
202
|
-
})
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// 2. Differentiate between a primitive action and a template call.
|
|
210
|
-
if (isPrimitiveActionType(templateName)) {
|
|
211
|
-
// Check if custom outputs are specified
|
|
212
|
-
const hasCustomOutput = 'name' in action && action.name &&
|
|
213
|
-
(action as any).output &&
|
|
214
|
-
typeof (action as any).output === 'object' &&
|
|
215
|
-
!Array.isArray((action as any).output)
|
|
216
|
-
|
|
217
|
-
// Convert JobAction to Action for primitive execution
|
|
218
|
-
const primitiveAction: Action = 'template' in action
|
|
219
|
-
? {
|
|
220
|
-
type: (action.type || action.template) as any,
|
|
221
|
-
name: action.name,
|
|
222
|
-
arguments: action.arguments,
|
|
223
|
-
skip_condition: action.skip_condition,
|
|
224
|
-
depends_on: action.depends_on
|
|
225
|
-
}
|
|
226
|
-
: action as Action
|
|
227
|
-
|
|
228
|
-
// Execute primitive with information about custom outputs
|
|
229
|
-
await this.executePrimitive(primitiveAction, context, scope, hasCustomOutput)
|
|
230
|
-
|
|
231
|
-
// Handle custom outputs for primitive actions (similar to template logic)
|
|
232
|
-
if (hasCustomOutput) {
|
|
233
|
-
const customOutput = (action as any).output
|
|
234
|
-
// Custom output map provided by job action: resolve each mapping within the current scope
|
|
235
|
-
for (const [key, value] of Object.entries(customOutput)) {
|
|
236
|
-
const resolvedOutput = await this.resolver.resolve(value as any, context, scope)
|
|
237
|
-
const outputKey = `${action.name}.${key}`
|
|
238
|
-
context.setOutput(outputKey, resolvedOutput)
|
|
239
|
-
this.events.emitEvent({
|
|
240
|
-
type: 'output_stored',
|
|
241
|
-
level: 'debug',
|
|
242
|
-
data: {
|
|
243
|
-
outputKey,
|
|
244
|
-
value: resolvedOutput
|
|
245
|
-
}
|
|
246
|
-
})
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
await this.executeTemplate(action, templateName, context, scope)
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Executes a template, including its setup, skip conditions, actions, and outputs.
|
|
256
|
-
* @param callingAction The action from the parent job/template that is calling this template.
|
|
257
|
-
* @param templateName The name of the template to execute.
|
|
258
|
-
* @param context The global execution context.
|
|
259
|
-
*/
|
|
260
|
-
private async executeTemplate(
|
|
261
|
-
callingAction: JobAction | Action,
|
|
262
|
-
templateName: string,
|
|
263
|
-
context: ExecutionContext,
|
|
264
|
-
parentScope: ResolutionScope = new Map(),
|
|
265
|
-
): Promise<void> {
|
|
266
|
-
const template = this.templates.get(templateName)
|
|
267
|
-
if (!template) {
|
|
268
|
-
const actionName = 'name' in callingAction ? callingAction.name : callingAction.type
|
|
269
|
-
throw new Error(`Template "${templateName}" not found for action "${actionName}".`)
|
|
270
|
-
}
|
|
271
|
-
this.events.emitEvent({
|
|
272
|
-
type: 'template_entered',
|
|
273
|
-
level: 'debug',
|
|
274
|
-
data: {
|
|
275
|
-
templateName: template.name
|
|
276
|
-
}
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
// 1. Create and populate a new resolution scope for this template call.
|
|
280
|
-
// NOTE: We resolve arguments in the CURRENT context (which should be the job's context)
|
|
281
|
-
// before changing to the template's context. This ensures artifact references in
|
|
282
|
-
// job arguments are resolved relative to the job, not the template.
|
|
283
|
-
const templateScope: ResolutionScope = new Map()
|
|
284
|
-
if ('arguments' in callingAction) {
|
|
285
|
-
for (const [key, value] of Object.entries(callingAction.arguments)) {
|
|
286
|
-
// Resolve the argument value in the parent's context, preserving the caller's local scope
|
|
287
|
-
// so that template arguments from the caller are available when invoking nested templates.
|
|
288
|
-
const resolvedValue = await this.resolver.resolve(value, context, parentScope)
|
|
289
|
-
templateScope.set(key, resolvedValue)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Set context path for relative artifact resolution within the template
|
|
294
|
-
const previousContextPath = context.getContextPath()
|
|
295
|
-
context.setContextPath(template._path)
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
|
|
299
|
-
// 2. Handle template-level setup block.
|
|
300
|
-
if (template.setup) {
|
|
301
|
-
// Check setup skip conditions before executing setup actions
|
|
302
|
-
if (template.setup.skip_condition && await this.evaluateSkipConditions(template.setup.skip_condition, context, templateScope)) {
|
|
303
|
-
this.events.emitEvent({
|
|
304
|
-
type: 'template_setup_skipped',
|
|
305
|
-
level: 'info',
|
|
306
|
-
data: {
|
|
307
|
-
templateName: template.name,
|
|
308
|
-
reason: 'setup skip condition met'
|
|
309
|
-
}
|
|
310
|
-
})
|
|
311
|
-
} else if (template.setup.actions) {
|
|
312
|
-
this.events.emitEvent({
|
|
313
|
-
type: 'template_setup_started',
|
|
314
|
-
level: 'debug',
|
|
315
|
-
data: {
|
|
316
|
-
templateName: template.name
|
|
317
|
-
}
|
|
318
|
-
})
|
|
319
|
-
for (const setupAction of template.setup.actions) {
|
|
320
|
-
// Setup actions are executed with the new template scope.
|
|
321
|
-
await this.executeAction(setupAction, context, templateScope)
|
|
322
|
-
}
|
|
323
|
-
this.events.emitEvent({
|
|
324
|
-
type: 'template_setup_completed',
|
|
325
|
-
level: 'debug',
|
|
326
|
-
data: {
|
|
327
|
-
templateName: template.name
|
|
328
|
-
}
|
|
329
|
-
})
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// 3. Evaluate template-level skip conditions.
|
|
334
|
-
const templateSkipConditions = template.skip_condition
|
|
335
|
-
const templateShouldSkip = await this.evaluateSkipConditions(templateSkipConditions, context, templateScope)
|
|
336
|
-
if (templateShouldSkip) {
|
|
337
|
-
this.events.emitEvent({
|
|
338
|
-
type: 'template_skipped',
|
|
339
|
-
level: 'info',
|
|
340
|
-
data: {
|
|
341
|
-
templateName: template.name,
|
|
342
|
-
reason: 'condition met'
|
|
343
|
-
}
|
|
344
|
-
})
|
|
345
|
-
// Even if we skip the main actions, we must still process the outputs,
|
|
346
|
-
// as they might be pre-computable (e.g., a CREATE2 address).
|
|
347
|
-
} else {
|
|
348
|
-
// 4. Execute the main actions within the template.
|
|
349
|
-
for (const templateAction of template.actions) {
|
|
350
|
-
await this.executeAction(templateAction, context, templateScope)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// If post-check conditions are enabled, re-evaluate template-level skip conditions with retry to handle RPC propagation lag
|
|
355
|
-
if (!this.noPostCheckConditions && template.skip_condition) {
|
|
356
|
-
const { retries, delayMs } = this.getPostCheckRetryConfig(context)
|
|
357
|
-
const shouldSkip = await this.retryBooleanCheck(
|
|
358
|
-
async () => this.evaluateSkipConditions(template.skip_condition!, context, templateScope),
|
|
359
|
-
retries,
|
|
360
|
-
delayMs
|
|
361
|
-
)
|
|
362
|
-
if (!shouldSkip) {
|
|
363
|
-
// If skip conditions don't evaluate to true after execution, the template failed
|
|
364
|
-
throw new Error(`Template "${template.name}" failed post-execution check: skip conditions did not evaluate to true`)
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// 5. Resolve and store the template's outputs into the global context.
|
|
369
|
-
// If the calling action (job action) specified a custom "output" map, that overrides the template outputs.
|
|
370
|
-
if ('name' in callingAction) {
|
|
371
|
-
const actionName = callingAction.name
|
|
372
|
-
const customOutput = (callingAction as any).output
|
|
373
|
-
if (customOutput && typeof customOutput === 'object' && !Array.isArray(customOutput)) {
|
|
374
|
-
// Custom output map provided by job action: resolve each mapping within the template scope
|
|
375
|
-
for (const [key, value] of Object.entries(customOutput)) {
|
|
376
|
-
const resolvedOutput = await this.resolver.resolve(value as any, context, templateScope)
|
|
377
|
-
const outputKey = `${actionName}.${key}`
|
|
378
|
-
context.setOutput(outputKey, resolvedOutput)
|
|
379
|
-
this.events.emitEvent({
|
|
380
|
-
type: 'output_stored',
|
|
381
|
-
level: 'debug',
|
|
382
|
-
data: {
|
|
383
|
-
outputKey,
|
|
384
|
-
value: resolvedOutput
|
|
385
|
-
}
|
|
386
|
-
})
|
|
387
|
-
}
|
|
388
|
-
} else if (template.outputs) {
|
|
389
|
-
// Default behavior: use template-defined outputs
|
|
390
|
-
for (const [key, value] of Object.entries(template.outputs)) {
|
|
391
|
-
const resolvedOutput = await this.resolver.resolve(value, context, templateScope)
|
|
392
|
-
const outputKey = `${actionName}.${key}`
|
|
393
|
-
context.setOutput(outputKey, resolvedOutput)
|
|
394
|
-
this.events.emitEvent({
|
|
395
|
-
type: 'output_stored',
|
|
396
|
-
level: 'debug',
|
|
397
|
-
data: {
|
|
398
|
-
outputKey,
|
|
399
|
-
value: resolvedOutput
|
|
400
|
-
}
|
|
401
|
-
})
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
this.events.emitEvent({
|
|
407
|
-
type: 'template_exited',
|
|
408
|
-
level: 'debug',
|
|
409
|
-
data: {
|
|
410
|
-
templateName: template.name
|
|
411
|
-
}
|
|
412
|
-
})
|
|
413
|
-
} finally {
|
|
414
|
-
// Restore previous context path
|
|
415
|
-
context.setContextPath(previousContextPath)
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Executes a primitive, built-in action.
|
|
421
|
-
* @param action The primitive action to execute.
|
|
422
|
-
* @param context The global execution context.
|
|
423
|
-
* @param scope The local resolution scope.
|
|
424
|
-
* @param hasCustomOutput Whether custom outputs are specified for this action.
|
|
425
|
-
*/
|
|
426
|
-
private async executePrimitive(
|
|
427
|
-
action: Action,
|
|
428
|
-
context: ExecutionContext,
|
|
429
|
-
scope: ResolutionScope,
|
|
430
|
-
hasCustomOutput: boolean = false,
|
|
431
|
-
): Promise<void> {
|
|
432
|
-
const actionName = action.name || action.type
|
|
433
|
-
this.events.emitEvent({
|
|
434
|
-
type: 'primitive_action',
|
|
435
|
-
level: 'debug',
|
|
436
|
-
data: {
|
|
437
|
-
actionType: action.type
|
|
438
|
-
}
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
switch (action.type) {
|
|
442
|
-
case 'send-transaction': {
|
|
443
|
-
const resolvedTo = await this.resolver.resolve(action.arguments.to, context, scope)
|
|
444
|
-
const resolvedData = action.arguments.data ? await this.resolver.resolve(action.arguments.data, context, scope) : '0x'
|
|
445
|
-
const resolvedValue = action.arguments.value ? await this.resolver.resolve(action.arguments.value, context, scope) : 0
|
|
446
|
-
const resolvedGasMultiplier = action.arguments.gasMultiplier !== undefined ? await this.resolver.resolve(action.arguments.gasMultiplier, context, scope) : undefined
|
|
447
|
-
|
|
448
|
-
// Validate and convert types
|
|
449
|
-
const to = validateAddress(resolvedTo, actionName)
|
|
450
|
-
const data = validateHexData(resolvedData, actionName, 'data')
|
|
451
|
-
const value = validateBigNumberish(resolvedValue, actionName, 'value')
|
|
452
|
-
|
|
453
|
-
// Validate gas multiplier if provided
|
|
454
|
-
let gasMultiplier: number | undefined
|
|
455
|
-
if (resolvedGasMultiplier !== undefined) {
|
|
456
|
-
if (typeof resolvedGasMultiplier !== 'number' || resolvedGasMultiplier <= 0) {
|
|
457
|
-
throw new Error(`Action "${actionName}": gasMultiplier must be a positive number, got: ${resolvedGasMultiplier}`)
|
|
458
|
-
}
|
|
459
|
-
gasMultiplier = resolvedGasMultiplier
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Prepare transaction parameters
|
|
463
|
-
const txParams: any = { to, data, value }
|
|
464
|
-
|
|
465
|
-
// Handle gas limit with optional multiplier
|
|
466
|
-
const network = context.getNetwork()
|
|
467
|
-
const signer = await context.getResolvedSigner()
|
|
468
|
-
if (network.gasLimit) {
|
|
469
|
-
const baseGasLimit = network.gasLimit
|
|
470
|
-
txParams.gasLimit = gasMultiplier ? Math.floor(baseGasLimit * gasMultiplier) : baseGasLimit
|
|
471
|
-
} else if (gasMultiplier) {
|
|
472
|
-
// If gasMultiplier is specified but no network gasLimit, estimate gas first
|
|
473
|
-
const estimatedGas = await signer.estimateGas({ to, data, value })
|
|
474
|
-
txParams.gasLimit = Math.floor(Number(estimatedGas) * gasMultiplier)
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
await this.checkFundsForTransaction(actionName, txParams, context, signer)
|
|
478
|
-
const tx = await signer.sendTransaction(txParams)
|
|
479
|
-
|
|
480
|
-
this.events.emitEvent({
|
|
481
|
-
type: 'transaction_sent',
|
|
482
|
-
level: 'info',
|
|
483
|
-
data: {
|
|
484
|
-
to,
|
|
485
|
-
value: value.toString(),
|
|
486
|
-
dataPreview: String(data).substring(0, 42),
|
|
487
|
-
txHash: tx.hash
|
|
488
|
-
}
|
|
489
|
-
})
|
|
490
|
-
|
|
491
|
-
const receipt = await tx.wait()
|
|
492
|
-
if (!receipt || receipt.status !== 1) {
|
|
493
|
-
throw new Error(`Transaction for action "${actionName}" failed (reverted). Hash: ${tx.hash}`)
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
this.events.emitEvent({
|
|
497
|
-
type: 'transaction_confirmed',
|
|
498
|
-
level: 'info',
|
|
499
|
-
data: {
|
|
500
|
-
txHash: tx.hash,
|
|
501
|
-
blockNumber: receipt.blockNumber
|
|
502
|
-
}
|
|
503
|
-
})
|
|
504
|
-
|
|
505
|
-
if (action.name && !hasCustomOutput) {
|
|
506
|
-
context.setOutput(`${action.name}.hash`, tx.hash)
|
|
507
|
-
context.setOutput(`${action.name}.receipt`, receipt)
|
|
508
|
-
}
|
|
509
|
-
break
|
|
510
|
-
}
|
|
511
|
-
case 'send-signed-transaction': {
|
|
512
|
-
const resolvedRawTx = await this.resolver.resolve(action.arguments.transaction, context, scope)
|
|
513
|
-
|
|
514
|
-
// Validate and convert type
|
|
515
|
-
const rawTx = validateRawTransaction(resolvedRawTx, actionName)
|
|
516
|
-
|
|
517
|
-
const tx = await context.provider.broadcastTransaction(rawTx)
|
|
518
|
-
|
|
519
|
-
this.events.emitEvent({
|
|
520
|
-
type: 'transaction_sent',
|
|
521
|
-
level: 'info',
|
|
522
|
-
data: {
|
|
523
|
-
to: '',
|
|
524
|
-
value: '0',
|
|
525
|
-
dataPreview: 'signed transaction',
|
|
526
|
-
txHash: tx.hash
|
|
527
|
-
}
|
|
528
|
-
})
|
|
529
|
-
|
|
530
|
-
const receipt = await tx.wait()
|
|
531
|
-
if (!receipt || receipt.status !== 1) {
|
|
532
|
-
throw new Error(`Signed transaction for action "${actionName}" failed (reverted). Hash: ${tx.hash}`)
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
this.events.emitEvent({
|
|
536
|
-
type: 'transaction_confirmed',
|
|
537
|
-
level: 'info',
|
|
538
|
-
data: {
|
|
539
|
-
txHash: tx.hash,
|
|
540
|
-
blockNumber: receipt.blockNumber
|
|
541
|
-
}
|
|
542
|
-
})
|
|
543
|
-
|
|
544
|
-
if (action.name && !hasCustomOutput) {
|
|
545
|
-
context.setOutput(`${action.name}.hash`, tx.hash)
|
|
546
|
-
context.setOutput(`${action.name}.receipt`, receipt)
|
|
547
|
-
}
|
|
548
|
-
break
|
|
549
|
-
}
|
|
550
|
-
case 'verify-contract': {
|
|
551
|
-
const actionName = action.name || action.type
|
|
552
|
-
|
|
553
|
-
// Resolve arguments
|
|
554
|
-
const resolvedAddress = await this.resolver.resolve(action.arguments.address, context, scope)
|
|
555
|
-
const resolvedContract = await this.resolver.resolve(action.arguments.contract, context, scope)
|
|
556
|
-
const resolvedConstructorArgs = action.arguments.constructorArguments
|
|
557
|
-
? await this.resolver.resolve(action.arguments.constructorArguments, context, scope)
|
|
558
|
-
: undefined
|
|
559
|
-
const resolvedPlatform = action.arguments.platform
|
|
560
|
-
? await this.resolver.resolve(action.arguments.platform, context, scope)
|
|
561
|
-
: 'all'
|
|
562
|
-
|
|
563
|
-
// Validate inputs
|
|
564
|
-
const address = validateAddress(resolvedAddress, actionName)
|
|
565
|
-
|
|
566
|
-
if (!resolvedContract || typeof resolvedContract !== 'object') {
|
|
567
|
-
throw new Error(`Action "${actionName}": contract must be a Contract object`)
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const contract = resolvedContract as Contract
|
|
571
|
-
|
|
572
|
-
// Handle platform validation - allow string, array of strings, or 'all'
|
|
573
|
-
let platformsToTry: string[]
|
|
574
|
-
if (resolvedPlatform === 'all') {
|
|
575
|
-
platformsToTry = ['all']
|
|
576
|
-
} else if (typeof resolvedPlatform === 'string') {
|
|
577
|
-
platformsToTry = [resolvedPlatform]
|
|
578
|
-
} else if (Array.isArray(resolvedPlatform)) {
|
|
579
|
-
// Validate that all array elements are strings
|
|
580
|
-
if (!resolvedPlatform.every(p => typeof p === 'string')) {
|
|
581
|
-
throw new Error(`Action "${actionName}": platform array must contain only strings`)
|
|
582
|
-
}
|
|
583
|
-
platformsToTry = resolvedPlatform
|
|
584
|
-
} else {
|
|
585
|
-
throw new Error(`Action "${actionName}": platform must be a string, array of strings, or 'all'`)
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// Validate that the contract has the necessary information for verification
|
|
589
|
-
if (!contract.sourceName) {
|
|
590
|
-
throw new Error(`Action "${actionName}": Contract is missing sourceName required for verification`)
|
|
591
|
-
}
|
|
592
|
-
if (!contract.contractName) {
|
|
593
|
-
throw new Error(`Action "${actionName}": Contract is missing contractName required for verification`)
|
|
594
|
-
}
|
|
595
|
-
if (!contract.compiler) {
|
|
596
|
-
throw new Error(`Action "${actionName}": Contract is missing compiler information required for verification`)
|
|
597
|
-
}
|
|
598
|
-
if (!contract.buildInfoId) {
|
|
599
|
-
throw new Error(`Action "${actionName}": Contract is missing buildInfoId required for verification`)
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// Validate constructor arguments if provided
|
|
603
|
-
let constructorArguments: string | undefined
|
|
604
|
-
if (resolvedConstructorArgs !== undefined) {
|
|
605
|
-
constructorArguments = validateHexData(resolvedConstructorArgs, actionName, 'constructorArguments')
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
const network = context.getNetwork()
|
|
609
|
-
const contractName = `${contract.sourceName}:${contract.contractName}`
|
|
610
|
-
|
|
611
|
-
// Handle platform verification
|
|
612
|
-
if (platformsToTry.includes('all')) {
|
|
613
|
-
// Handle "all" platform - try all configured platforms for this network
|
|
614
|
-
const configuredPlatforms = this.verificationRegistry.getConfiguredPlatforms(network)
|
|
615
|
-
|
|
616
|
-
if (configuredPlatforms.length === 0) {
|
|
617
|
-
this.events.emitEvent({
|
|
618
|
-
type: 'action_skipped',
|
|
619
|
-
level: 'warn',
|
|
620
|
-
data: {
|
|
621
|
-
actionName: actionName,
|
|
622
|
-
reason: `No configured verification platforms available for network ${network.name}`
|
|
623
|
-
}
|
|
624
|
-
})
|
|
625
|
-
return
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Try verification on all configured platforms
|
|
629
|
-
let anySuccess = false
|
|
630
|
-
for (const platform of configuredPlatforms) {
|
|
631
|
-
try {
|
|
632
|
-
await this.verifyOnSinglePlatform(
|
|
633
|
-
platform,
|
|
634
|
-
contract,
|
|
635
|
-
address,
|
|
636
|
-
constructorArguments,
|
|
637
|
-
network,
|
|
638
|
-
actionName,
|
|
639
|
-
contractName,
|
|
640
|
-
action,
|
|
641
|
-
context,
|
|
642
|
-
hasCustomOutput
|
|
643
|
-
)
|
|
644
|
-
anySuccess = true
|
|
645
|
-
} catch (error) {
|
|
646
|
-
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
647
|
-
|
|
648
|
-
// If ignoreVerifyErrors is enabled, add to warnings and continue
|
|
649
|
-
if (this.ignoreVerifyErrors) {
|
|
650
|
-
this.verificationWarnings.push({
|
|
651
|
-
actionName: actionName,
|
|
652
|
-
address,
|
|
653
|
-
contractName,
|
|
654
|
-
platform: platform.name,
|
|
655
|
-
error: errorMessage,
|
|
656
|
-
networkName: network.name
|
|
657
|
-
})
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// Log the error but continue with other platforms
|
|
661
|
-
this.events.emitEvent({
|
|
662
|
-
type: 'verification_failed',
|
|
663
|
-
level: 'warn',
|
|
664
|
-
data: {
|
|
665
|
-
actionName: actionName,
|
|
666
|
-
address,
|
|
667
|
-
contractName,
|
|
668
|
-
platform: platform.name,
|
|
669
|
-
error: errorMessage
|
|
670
|
-
}
|
|
671
|
-
})
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
if (!anySuccess) {
|
|
676
|
-
if (this.ignoreVerifyErrors) {
|
|
677
|
-
// Don't throw error if ignoreVerifyErrors is enabled - warnings already collected
|
|
678
|
-
this.events.emitEvent({
|
|
679
|
-
type: 'verification_skipped',
|
|
680
|
-
level: 'warn',
|
|
681
|
-
data: {
|
|
682
|
-
actionName: actionName,
|
|
683
|
-
reason: `Verification failed on all configured platforms for network ${network.name}, but continuing due to --ignore-verify-errors`
|
|
684
|
-
}
|
|
685
|
-
})
|
|
686
|
-
} else {
|
|
687
|
-
throw new Error(`Verification failed on all configured platforms for network ${network.name}`)
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
} else {
|
|
691
|
-
// Handle specific platform(s) verification
|
|
692
|
-
let anySuccess = false
|
|
693
|
-
for (const platformName of platformsToTry) {
|
|
694
|
-
const platform = this.verificationRegistry.get(platformName)
|
|
695
|
-
if (!platform) {
|
|
696
|
-
throw new Error(`Action "${actionName}": Unsupported verification platform "${platformName}"`)
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
try {
|
|
700
|
-
await this.verifyOnSinglePlatform(
|
|
701
|
-
platform,
|
|
702
|
-
contract,
|
|
703
|
-
address,
|
|
704
|
-
constructorArguments,
|
|
705
|
-
network,
|
|
706
|
-
actionName,
|
|
707
|
-
contractName,
|
|
708
|
-
action,
|
|
709
|
-
context,
|
|
710
|
-
hasCustomOutput
|
|
711
|
-
)
|
|
712
|
-
anySuccess = true
|
|
713
|
-
} catch (error) {
|
|
714
|
-
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
715
|
-
|
|
716
|
-
// If ignoreVerifyErrors is enabled, add to warnings
|
|
717
|
-
if (this.ignoreVerifyErrors) {
|
|
718
|
-
this.verificationWarnings.push({
|
|
719
|
-
actionName: actionName,
|
|
720
|
-
address,
|
|
721
|
-
contractName,
|
|
722
|
-
platform: platform.name,
|
|
723
|
-
error: errorMessage,
|
|
724
|
-
networkName: network.name
|
|
725
|
-
})
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// Log the error but continue with other platforms if multiple specified
|
|
729
|
-
this.events.emitEvent({
|
|
730
|
-
type: 'verification_failed',
|
|
731
|
-
level: platformsToTry.length > 1 ? 'warn' : 'error',
|
|
732
|
-
data: {
|
|
733
|
-
actionName: actionName,
|
|
734
|
-
address,
|
|
735
|
-
contractName,
|
|
736
|
-
platform: platform.name,
|
|
737
|
-
error: errorMessage
|
|
738
|
-
}
|
|
739
|
-
})
|
|
740
|
-
|
|
741
|
-
// If only one platform specified, re-throw the error unless ignoreVerifyErrors is enabled
|
|
742
|
-
if (platformsToTry.length === 1 && !this.ignoreVerifyErrors) {
|
|
743
|
-
throw error
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
if (!anySuccess && platformsToTry.length > 1) {
|
|
749
|
-
if (this.ignoreVerifyErrors) {
|
|
750
|
-
// Don't throw error if ignoreVerifyErrors is enabled - warnings already collected
|
|
751
|
-
this.events.emitEvent({
|
|
752
|
-
type: 'verification_skipped',
|
|
753
|
-
level: 'warn',
|
|
754
|
-
data: {
|
|
755
|
-
actionName: actionName,
|
|
756
|
-
reason: `Verification failed on all specified platforms: ${platformsToTry.join(', ')}, but continuing due to --ignore-verify-errors`
|
|
757
|
-
}
|
|
758
|
-
})
|
|
759
|
-
} else {
|
|
760
|
-
throw new Error(`Verification failed on all specified platforms: ${platformsToTry.join(', ')}`)
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
break
|
|
766
|
-
}
|
|
767
|
-
case 'static': {
|
|
768
|
-
const resolvedValue = await this.resolver.resolve(action.arguments.value, context, scope)
|
|
769
|
-
|
|
770
|
-
if (action.name && !hasCustomOutput) {
|
|
771
|
-
context.setOutput(`${action.name}.value`, resolvedValue)
|
|
772
|
-
}
|
|
773
|
-
break
|
|
774
|
-
}
|
|
775
|
-
case 'create-contract': {
|
|
776
|
-
const resolvedData = await this.resolver.resolve(action.arguments.data, context, scope)
|
|
777
|
-
const resolvedValue = action.arguments.value ? await this.resolver.resolve(action.arguments.value, context, scope) : 0
|
|
778
|
-
const resolvedGasMultiplier = action.arguments.gasMultiplier !== undefined ? await this.resolver.resolve(action.arguments.gasMultiplier, context, scope) : undefined
|
|
779
|
-
|
|
780
|
-
// Validate and convert types
|
|
781
|
-
const data = validateHexData(resolvedData, actionName, 'data')
|
|
782
|
-
const value = validateBigNumberish(resolvedValue, actionName, 'value')
|
|
783
|
-
|
|
784
|
-
// Validate gas multiplier if provided
|
|
785
|
-
let gasMultiplier: number | undefined
|
|
786
|
-
if (resolvedGasMultiplier !== undefined) {
|
|
787
|
-
if (typeof resolvedGasMultiplier !== 'number' || resolvedGasMultiplier <= 0) {
|
|
788
|
-
throw new Error(`Action "${actionName}": gasMultiplier must be a positive number, got: ${resolvedGasMultiplier}`)
|
|
789
|
-
}
|
|
790
|
-
gasMultiplier = resolvedGasMultiplier
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// Prepare transaction parameters for contract creation (to: null)
|
|
794
|
-
const txParams: any = { to: null, data, value }
|
|
795
|
-
|
|
796
|
-
// Handle gas limit with optional multiplier
|
|
797
|
-
const network = context.getNetwork()
|
|
798
|
-
const signer = await context.getResolvedSigner()
|
|
799
|
-
if (network.gasLimit) {
|
|
800
|
-
const baseGasLimit = network.gasLimit
|
|
801
|
-
txParams.gasLimit = gasMultiplier ? Math.floor(baseGasLimit * gasMultiplier) : baseGasLimit
|
|
802
|
-
} else if (gasMultiplier) {
|
|
803
|
-
// If gasMultiplier is specified but no network gasLimit, estimate gas first
|
|
804
|
-
const estimatedGas = await signer.estimateGas(txParams)
|
|
805
|
-
txParams.gasLimit = Math.floor(Number(estimatedGas) * gasMultiplier)
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
await this.checkFundsForTransaction(actionName, txParams, context, signer)
|
|
809
|
-
const tx = await signer.sendTransaction(txParams)
|
|
810
|
-
|
|
811
|
-
this.events.emitEvent({
|
|
812
|
-
type: 'transaction_sent',
|
|
813
|
-
level: 'info',
|
|
814
|
-
data: {
|
|
815
|
-
to: 'contract creation',
|
|
816
|
-
value: value.toString(),
|
|
817
|
-
dataPreview: String(data).substring(0, 42),
|
|
818
|
-
txHash: tx.hash
|
|
819
|
-
}
|
|
820
|
-
})
|
|
821
|
-
|
|
822
|
-
const receipt = await tx.wait()
|
|
823
|
-
if (!receipt || receipt.status !== 1) {
|
|
824
|
-
throw new Error(`Contract creation for action "${actionName}" failed (reverted). Hash: ${tx.hash}`)
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
if (!receipt.contractAddress) {
|
|
828
|
-
throw new Error(`Contract creation for action "${actionName}" did not return a contract address. Hash: ${tx.hash}`)
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
this.events.emitEvent({
|
|
832
|
-
type: 'transaction_confirmed',
|
|
833
|
-
level: 'info',
|
|
834
|
-
data: {
|
|
835
|
-
txHash: tx.hash,
|
|
836
|
-
blockNumber: receipt.blockNumber
|
|
837
|
-
}
|
|
838
|
-
})
|
|
839
|
-
|
|
840
|
-
this.events.emitEvent({
|
|
841
|
-
type: 'contract_created',
|
|
842
|
-
level: 'info',
|
|
843
|
-
data: {
|
|
844
|
-
contractAddress: receipt.contractAddress,
|
|
845
|
-
txHash: tx.hash,
|
|
846
|
-
blockNumber: receipt.blockNumber
|
|
847
|
-
}
|
|
848
|
-
})
|
|
849
|
-
|
|
850
|
-
if (action.name && !hasCustomOutput) {
|
|
851
|
-
context.setOutput(`${action.name}.hash`, tx.hash)
|
|
852
|
-
context.setOutput(`${action.name}.receipt`, receipt)
|
|
853
|
-
context.setOutput(`${action.name}.address`, receipt.contractAddress)
|
|
854
|
-
}
|
|
855
|
-
break
|
|
856
|
-
}
|
|
857
|
-
case 'test-nicks-method': {
|
|
858
|
-
if (this.nicksMethodResult !== undefined && !this.allowMultipleNicksMethodTests) {
|
|
859
|
-
if (this.nicksMethodResult === false) {
|
|
860
|
-
throw new Error(`Nick's method test already failed this run`)
|
|
861
|
-
}
|
|
862
|
-
this.events.emitEvent({
|
|
863
|
-
type: 'debug_info',
|
|
864
|
-
level: 'debug',
|
|
865
|
-
data: {
|
|
866
|
-
message: `Nick's method test already passed this run`,
|
|
867
|
-
},
|
|
868
|
-
})
|
|
869
|
-
break
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// Default bytecode if none provided
|
|
873
|
-
const defaultBytecode = '0x608060405234801561001057600080fd5b5061013d806100206000396000f3fe60806040526004361061001e5760003560e01c80639c4ae2d014610023575b600080fd5b6100cb6004803603604081101561003957600080fd5b81019060208101813564010000000081111561005457600080fd5b82018360208201111561006657600080fd5b8035906020019184600183028401116401000000008311171561008857600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955050913592506100cd915050565b005b60008183516020850134f56040805173ffffffffffffffffffffffffffffffffffffffff83168152905191925081900360200190a050505056fea264697066735822122033609f614f03931b92d88c309d698449bb77efcd517328d341fa4f923c5d8c7964736f6c63430007060033'
|
|
874
|
-
|
|
875
|
-
// Handle case where arguments is undefined (action takes no arguments)
|
|
876
|
-
const args = action.arguments || {}
|
|
877
|
-
const resolvedBytecode = args.bytecode ? await this.resolver.resolve(args.bytecode, context, scope) : defaultBytecode
|
|
878
|
-
const resolvedGasPrice = args.gasPrice ? await this.resolver.resolve(args.gasPrice, context, scope) : undefined
|
|
879
|
-
const resolvedGasLimit = args.gasLimit ? await this.resolver.resolve(args.gasLimit, context, scope) : undefined
|
|
880
|
-
const resolvedFundingAmount = args.fundingAmount ? await this.resolver.resolve(args.fundingAmount, context, scope) : undefined
|
|
881
|
-
|
|
882
|
-
// Validate inputs
|
|
883
|
-
const bytecode = validateHexData(resolvedBytecode, actionName, 'bytecode')
|
|
884
|
-
const gasPrice = resolvedGasPrice ? validateBigNumberish(resolvedGasPrice, actionName, 'gasPrice') : undefined
|
|
885
|
-
const gasLimit = resolvedGasLimit ? validateBigNumberish(resolvedGasLimit, actionName, 'gasLimit') : undefined
|
|
886
|
-
const fundingAmount = resolvedFundingAmount ? validateBigNumberish(resolvedFundingAmount, actionName, 'fundingAmount') : undefined
|
|
887
|
-
|
|
888
|
-
const success = await this.testNicksMethod(bytecode, context, gasPrice, gasLimit, fundingAmount)
|
|
889
|
-
this.nicksMethodResult = success
|
|
890
|
-
|
|
891
|
-
if (!success) {
|
|
892
|
-
throw new Error(`Nick's method test failed for action "${actionName}"`)
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
this.events.emitEvent({
|
|
896
|
-
type: 'action_completed',
|
|
897
|
-
level: 'info',
|
|
898
|
-
data: {
|
|
899
|
-
actionName: actionName,
|
|
900
|
-
result: 'Nick\'s method test passed'
|
|
901
|
-
}
|
|
902
|
-
})
|
|
903
|
-
|
|
904
|
-
if (action.name && !hasCustomOutput) {
|
|
905
|
-
context.setOutput(`${action.name}.success`, true)
|
|
906
|
-
}
|
|
907
|
-
break
|
|
908
|
-
}
|
|
909
|
-
case 'json-request': {
|
|
910
|
-
const resolvedUrl = await this.resolver.resolve(action.arguments.url, context, scope)
|
|
911
|
-
const resolvedMethod = action.arguments.method ? await this.resolver.resolve(action.arguments.method, context, scope) : 'GET'
|
|
912
|
-
const resolvedHeaders = action.arguments.headers ? await this.resolver.resolve(action.arguments.headers, context, scope) : {}
|
|
913
|
-
const resolvedBody = action.arguments.body ? await this.resolver.resolve(action.arguments.body, context, scope) : undefined
|
|
914
|
-
|
|
915
|
-
// Validate inputs
|
|
916
|
-
if (typeof resolvedUrl !== 'string') {
|
|
917
|
-
throw new Error(`Action "${actionName}": url must be a string, got: ${typeof resolvedUrl}`)
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
if (typeof resolvedMethod !== 'string') {
|
|
921
|
-
throw new Error(`Action "${actionName}": method must be a string, got: ${typeof resolvedMethod}`)
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
if (resolvedHeaders && typeof resolvedHeaders !== 'object') {
|
|
925
|
-
throw new Error(`Action "${actionName}": headers must be an object, got: ${typeof resolvedHeaders}`)
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
try {
|
|
929
|
-
// Prepare fetch options
|
|
930
|
-
const fetchOptions: RequestInit = {
|
|
931
|
-
method: resolvedMethod.toUpperCase(),
|
|
932
|
-
headers: {
|
|
933
|
-
'Content-Type': 'application/json',
|
|
934
|
-
...(resolvedHeaders as Record<string, string>)
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// Add body for non-GET requests
|
|
939
|
-
if (resolvedBody !== undefined && resolvedMethod.toUpperCase() !== 'GET') {
|
|
940
|
-
fetchOptions.body = JSON.stringify(resolvedBody)
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
this.events.emitEvent({
|
|
944
|
-
type: 'action_started',
|
|
945
|
-
level: 'info',
|
|
946
|
-
data: {
|
|
947
|
-
actionName: actionName,
|
|
948
|
-
message: `Making ${resolvedMethod.toUpperCase()} request to ${resolvedUrl}`
|
|
949
|
-
}
|
|
950
|
-
})
|
|
951
|
-
|
|
952
|
-
// Make the HTTP request
|
|
953
|
-
const response = await fetch(resolvedUrl, fetchOptions)
|
|
954
|
-
|
|
955
|
-
if (!response.ok) {
|
|
956
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
// Parse JSON response
|
|
960
|
-
const responseData = await response.json()
|
|
961
|
-
|
|
962
|
-
this.events.emitEvent({
|
|
963
|
-
type: 'action_completed',
|
|
964
|
-
level: 'info',
|
|
965
|
-
data: {
|
|
966
|
-
actionName: actionName,
|
|
967
|
-
message: `Request completed successfully (${response.status})`
|
|
968
|
-
}
|
|
969
|
-
})
|
|
970
|
-
|
|
971
|
-
// Store outputs
|
|
972
|
-
if (action.name && !hasCustomOutput) {
|
|
973
|
-
context.setOutput(`${action.name}.response`, responseData)
|
|
974
|
-
context.setOutput(`${action.name}.status`, response.status)
|
|
975
|
-
context.setOutput(`${action.name}.statusText`, response.statusText)
|
|
976
|
-
}
|
|
977
|
-
} catch (error) {
|
|
978
|
-
this.events.emitEvent({
|
|
979
|
-
type: 'action_failed',
|
|
980
|
-
level: 'error',
|
|
981
|
-
data: {
|
|
982
|
-
actionName: actionName,
|
|
983
|
-
error: error instanceof Error ? error.message : String(error)
|
|
984
|
-
}
|
|
985
|
-
})
|
|
986
|
-
throw new Error(`Action "${actionName}" failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
987
|
-
}
|
|
988
|
-
break
|
|
989
|
-
}
|
|
990
|
-
default:
|
|
991
|
-
throw new Error(`Unknown or unimplemented primitive action type: ${(action as any).type}`)
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
/**
|
|
996
|
-
* Helper method to verify a contract on a single platform
|
|
997
|
-
* @private
|
|
998
|
-
*/
|
|
999
|
-
private async verifyOnSinglePlatform(
|
|
1000
|
-
platform: any,
|
|
1001
|
-
contract: Contract,
|
|
1002
|
-
address: string,
|
|
1003
|
-
constructorArguments: string | undefined,
|
|
1004
|
-
network: any,
|
|
1005
|
-
actionName: string,
|
|
1006
|
-
contractName: string,
|
|
1007
|
-
action: Action,
|
|
1008
|
-
context: ExecutionContext,
|
|
1009
|
-
hasCustomOutput: boolean = false
|
|
1010
|
-
): Promise<void> {
|
|
1011
|
-
// Check if platform supports this network
|
|
1012
|
-
const supportsNetwork = platform.supportsNetwork(network)
|
|
1013
|
-
if (!supportsNetwork) {
|
|
1014
|
-
this.events.emitEvent({
|
|
1015
|
-
type: 'action_skipped',
|
|
1016
|
-
level: 'info',
|
|
1017
|
-
data: {
|
|
1018
|
-
actionName: actionName,
|
|
1019
|
-
reason: `Network ${network.name} does not support ${platform.name} verification`
|
|
1020
|
-
}
|
|
1021
|
-
})
|
|
1022
|
-
return
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
// Check if platform is properly configured
|
|
1026
|
-
const isConfigured = platform.isConfigured()
|
|
1027
|
-
if (!isConfigured) {
|
|
1028
|
-
this.events.emitEvent({
|
|
1029
|
-
type: 'action_skipped',
|
|
1030
|
-
level: 'warn',
|
|
1031
|
-
data: {
|
|
1032
|
-
actionName: actionName,
|
|
1033
|
-
reason: `Verification skipped: ${platform.getConfigurationRequirements()}`
|
|
1034
|
-
}
|
|
1035
|
-
})
|
|
1036
|
-
return
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
// Find and load build info
|
|
1040
|
-
let buildInfoPath: string | undefined
|
|
1041
|
-
for (const sourcePath of contract._sources) {
|
|
1042
|
-
if (sourcePath.includes('/build-info/') && sourcePath.endsWith('.json')) {
|
|
1043
|
-
buildInfoPath = sourcePath
|
|
1044
|
-
break
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
if (!buildInfoPath) {
|
|
1049
|
-
throw new Error(`Action "${actionName}": No build-info file found in contract sources`)
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
const fs = await import('fs/promises')
|
|
1053
|
-
let buildInfoContent: string
|
|
1054
|
-
try {
|
|
1055
|
-
buildInfoContent = await fs.readFile(buildInfoPath, 'utf-8')
|
|
1056
|
-
} catch (error) {
|
|
1057
|
-
throw new Error(`Action "${actionName}": Failed to read build info file at ${buildInfoPath}: ${error instanceof Error ? error.message : String(error)}`)
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
let buildInfo: BuildInfo
|
|
1061
|
-
try {
|
|
1062
|
-
buildInfo = JSON.parse(buildInfoContent)
|
|
1063
|
-
} catch (error) {
|
|
1064
|
-
throw new Error(`Action "${actionName}": Failed to parse build info JSON: ${error instanceof Error ? error.message : String(error)}`)
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
this.events.emitEvent({
|
|
1068
|
-
type: 'verification_started',
|
|
1069
|
-
level: 'info',
|
|
1070
|
-
data: {
|
|
1071
|
-
actionName: actionName,
|
|
1072
|
-
address,
|
|
1073
|
-
contractName,
|
|
1074
|
-
platform: platform.name,
|
|
1075
|
-
networkName: network.name
|
|
1076
|
-
}
|
|
1077
|
-
})
|
|
1078
|
-
|
|
1079
|
-
try {
|
|
1080
|
-
// Use the platform to verify the contract
|
|
1081
|
-
const verificationResult = await platform.verifyContract({
|
|
1082
|
-
contract,
|
|
1083
|
-
buildInfo,
|
|
1084
|
-
address,
|
|
1085
|
-
constructorArguments,
|
|
1086
|
-
network
|
|
1087
|
-
})
|
|
1088
|
-
|
|
1089
|
-
if (!verificationResult.success) {
|
|
1090
|
-
throw new Error(`Verification failed: ${verificationResult.message}`)
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// Emit appropriate events based on verification result
|
|
1094
|
-
if (verificationResult.isAlreadyVerified) {
|
|
1095
|
-
this.events.emitEvent({
|
|
1096
|
-
type: 'verification_completed',
|
|
1097
|
-
level: 'info',
|
|
1098
|
-
data: {
|
|
1099
|
-
actionName: actionName,
|
|
1100
|
-
address,
|
|
1101
|
-
contractName,
|
|
1102
|
-
platform: platform.name,
|
|
1103
|
-
message: verificationResult.message
|
|
1104
|
-
}
|
|
1105
|
-
})
|
|
1106
|
-
} else {
|
|
1107
|
-
this.events.emitEvent({
|
|
1108
|
-
type: 'verification_submitted',
|
|
1109
|
-
level: 'info',
|
|
1110
|
-
data: {
|
|
1111
|
-
actionName: actionName,
|
|
1112
|
-
platform: platform.name,
|
|
1113
|
-
guid: verificationResult.guid || 'N/A',
|
|
1114
|
-
message: verificationResult.message
|
|
1115
|
-
}
|
|
1116
|
-
})
|
|
1117
|
-
|
|
1118
|
-
this.events.emitEvent({
|
|
1119
|
-
type: 'verification_completed',
|
|
1120
|
-
level: 'info',
|
|
1121
|
-
data: {
|
|
1122
|
-
actionName: actionName,
|
|
1123
|
-
address,
|
|
1124
|
-
contractName,
|
|
1125
|
-
platform: platform.name,
|
|
1126
|
-
message: 'Contract verified successfully'
|
|
1127
|
-
}
|
|
1128
|
-
})
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
// Set outputs (only for successful verifications)
|
|
1132
|
-
if (action.name && !hasCustomOutput) {
|
|
1133
|
-
context.setOutput(`${action.name}.verified`, true)
|
|
1134
|
-
if (verificationResult.guid) {
|
|
1135
|
-
context.setOutput(`${action.name}.guid`, verificationResult.guid)
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
} catch (error) {
|
|
1140
|
-
this.events.emitEvent({
|
|
1141
|
-
type: 'verification_failed',
|
|
1142
|
-
level: 'error',
|
|
1143
|
-
data: {
|
|
1144
|
-
actionName: actionName,
|
|
1145
|
-
address,
|
|
1146
|
-
contractName,
|
|
1147
|
-
platform: platform.name,
|
|
1148
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1149
|
-
}
|
|
1150
|
-
})
|
|
1151
|
-
throw error
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
/**
|
|
1156
|
-
* Tests Nick's method for EOA deployment
|
|
1157
|
-
* Generates a valid ECDSA signature and tests if it can deploy the given bytecode
|
|
1158
|
-
* Returns any remaining funds to the original wallet after testing
|
|
1159
|
-
*/
|
|
1160
|
-
private async testNicksMethod(
|
|
1161
|
-
bytecode: string,
|
|
1162
|
-
context: ExecutionContext,
|
|
1163
|
-
gasPrice?: ethers.BigNumberish,
|
|
1164
|
-
gasLimit?: ethers.BigNumberish,
|
|
1165
|
-
fundingAmount?: ethers.BigNumberish
|
|
1166
|
-
): Promise<boolean> {
|
|
1167
|
-
let testResult = false
|
|
1168
|
-
let eoaAddress: string | undefined
|
|
1169
|
-
let wallet: ethers.HDNodeWallet | ethers.Wallet | undefined
|
|
1170
|
-
|
|
1171
|
-
try {
|
|
1172
|
-
// Default values
|
|
1173
|
-
const defaultGasPrice = gasPrice || ethers.parseUnits('100', 'gwei') // 100 gwei
|
|
1174
|
-
const defaultGasLimit = gasLimit || 250000n // Reasonable gas limit for deployment
|
|
1175
|
-
const calculatedCost = BigInt(defaultGasPrice.toString()) * BigInt(defaultGasLimit.toString())
|
|
1176
|
-
const defaultFundingAmount = fundingAmount || calculatedCost
|
|
1177
|
-
|
|
1178
|
-
// Check main signer balance first
|
|
1179
|
-
const signer = await context.getResolvedSigner()
|
|
1180
|
-
const signerAddress = await signer.getAddress()
|
|
1181
|
-
const signerBalance = await context.provider.getBalance(signerAddress)
|
|
1182
|
-
|
|
1183
|
-
if (signerBalance < BigInt(defaultFundingAmount.toString())) {
|
|
1184
|
-
this.events.emitEvent({
|
|
1185
|
-
type: 'action_failed',
|
|
1186
|
-
level: 'error',
|
|
1187
|
-
data: {
|
|
1188
|
-
message: `Insufficient funds: signer has ${ethers.formatEther(signerBalance)} ETH but needs ${ethers.formatEther(defaultFundingAmount)} ETH`
|
|
1189
|
-
}
|
|
1190
|
-
})
|
|
1191
|
-
return false
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
// Generate a valid ECDSA signature using Nick's method approach
|
|
1195
|
-
const result = await this.generateNicksMethodTransaction(bytecode, defaultGasPrice, defaultGasLimit)
|
|
1196
|
-
const {signedTx, unsignedTx} = result
|
|
1197
|
-
eoaAddress = result.eoaAddress
|
|
1198
|
-
wallet = result.wallet
|
|
1199
|
-
|
|
1200
|
-
// Simulate the contract creation transaction
|
|
1201
|
-
try {
|
|
1202
|
-
const simulationTx = {
|
|
1203
|
-
...unsignedTx,
|
|
1204
|
-
from: eoaAddress,
|
|
1205
|
-
};
|
|
1206
|
-
|
|
1207
|
-
if (unsignedTx.gasPrice) {
|
|
1208
|
-
// Check gas price
|
|
1209
|
-
const gasPrice = await context.provider.getFeeData().then(data => data.gasPrice)
|
|
1210
|
-
if (!gasPrice) {
|
|
1211
|
-
this.events.emitEvent({
|
|
1212
|
-
type: "debug_info",
|
|
1213
|
-
level: "debug",
|
|
1214
|
-
data: {
|
|
1215
|
-
message: `Legacy gas price not available.`,
|
|
1216
|
-
},
|
|
1217
|
-
});
|
|
1218
|
-
} else if (BigInt(unsignedTx.gasPrice.toString()) < gasPrice) {
|
|
1219
|
-
this.events.emitEvent({
|
|
1220
|
-
type: "debug_info",
|
|
1221
|
-
level: "warn",
|
|
1222
|
-
data: {
|
|
1223
|
-
message: `Gas price (${unsignedTx.gasPrice}) is lower than the current gas price (${gasPrice}). This may cause the transaction to not be mined.`,
|
|
1224
|
-
},
|
|
1225
|
-
});
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
if (simulationTx.gasLimit) {
|
|
1230
|
-
// Simulate the transaction expected gas usage
|
|
1231
|
-
const estimatedGas = await context.provider.estimateGas(simulationTx);
|
|
1232
|
-
const estimatedGasStr = estimatedGas.toString();
|
|
1233
|
-
const simulationTxGasLimitStr = simulationTx.gasLimit.toString();
|
|
1234
|
-
if (estimatedGas > BigInt(simulationTxGasLimitStr)) {
|
|
1235
|
-
this.events.emitEvent({
|
|
1236
|
-
type: "debug_info",
|
|
1237
|
-
level: "warn",
|
|
1238
|
-
data: {
|
|
1239
|
-
message: `Estimated gas (${estimatedGasStr}) is greater than gas provided in the transaction (${simulationTxGasLimitStr}). This may cause the transaction to revert.`,
|
|
1240
|
-
},
|
|
1241
|
-
});
|
|
1242
|
-
} else {
|
|
1243
|
-
this.events.emitEvent({
|
|
1244
|
-
type: "debug_info",
|
|
1245
|
-
level: "debug",
|
|
1246
|
-
data: {
|
|
1247
|
-
message: `Estimated gas: ${estimatedGasStr}, Gas provided: ${simulationTxGasLimitStr}`,
|
|
1248
|
-
},
|
|
1249
|
-
});
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
} catch (simulationError) {
|
|
1253
|
-
this.events.emitEvent({
|
|
1254
|
-
type: "debug_info",
|
|
1255
|
-
level: "warn",
|
|
1256
|
-
data: {
|
|
1257
|
-
message: `Simulation failed: ${
|
|
1258
|
-
simulationError instanceof Error
|
|
1259
|
-
? simulationError.message
|
|
1260
|
-
: String(simulationError)
|
|
1261
|
-
}`,
|
|
1262
|
-
},
|
|
1263
|
-
});
|
|
1264
|
-
// Continue with the test even if simulation fails
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
this.events.emitEvent({
|
|
1268
|
-
type: 'debug_info',
|
|
1269
|
-
level: 'debug',
|
|
1270
|
-
data: {
|
|
1271
|
-
message: `Testing Nick's method with EOA: ${eoaAddress}`
|
|
1272
|
-
}
|
|
1273
|
-
})
|
|
1274
|
-
|
|
1275
|
-
// Check if EOA already has sufficient balance
|
|
1276
|
-
const currentBalance = await context.provider.getBalance(eoaAddress)
|
|
1277
|
-
const neededFunding = BigInt(defaultFundingAmount.toString()) - currentBalance
|
|
1278
|
-
|
|
1279
|
-
if (neededFunding > 0) {
|
|
1280
|
-
// Fund the EOA
|
|
1281
|
-
this.events.emitEvent({
|
|
1282
|
-
type: 'transaction_sent',
|
|
1283
|
-
level: 'debug',
|
|
1284
|
-
data: {
|
|
1285
|
-
to: eoaAddress,
|
|
1286
|
-
value: neededFunding.toString(),
|
|
1287
|
-
dataPreview: 'funding EOA for Nick\'s method test',
|
|
1288
|
-
txHash: 'pending'
|
|
1289
|
-
}
|
|
1290
|
-
})
|
|
1291
|
-
|
|
1292
|
-
this.events.emitEvent({
|
|
1293
|
-
type: 'debug_info',
|
|
1294
|
-
level: 'debug',
|
|
1295
|
-
data: {
|
|
1296
|
-
message: `[NICK'S METHOD DEBUG] Sending funding transaction: ${ethers.formatEther(neededFunding)} ETH to ${eoaAddress}`
|
|
1297
|
-
}
|
|
1298
|
-
})
|
|
1299
|
-
|
|
1300
|
-
const signer = await context.getResolvedSigner()
|
|
1301
|
-
const fundingTx = await signer.sendTransaction({
|
|
1302
|
-
to: eoaAddress,
|
|
1303
|
-
value: neededFunding
|
|
1304
|
-
})
|
|
1305
|
-
|
|
1306
|
-
this.events.emitEvent({
|
|
1307
|
-
type: 'debug_info',
|
|
1308
|
-
level: 'debug',
|
|
1309
|
-
data: {
|
|
1310
|
-
message: `[NICK'S METHOD DEBUG] Funding transaction sent: ${fundingTx.hash}, waiting for confirmation...`
|
|
1311
|
-
}
|
|
1312
|
-
})
|
|
1313
|
-
|
|
1314
|
-
const fundingReceipt = await fundingTx.wait()
|
|
1315
|
-
|
|
1316
|
-
this.events.emitEvent({
|
|
1317
|
-
type: 'transaction_confirmed',
|
|
1318
|
-
level: 'debug',
|
|
1319
|
-
data: {
|
|
1320
|
-
txHash: fundingTx.hash,
|
|
1321
|
-
blockNumber: fundingReceipt?.blockNumber || 0
|
|
1322
|
-
}
|
|
1323
|
-
})
|
|
1324
|
-
|
|
1325
|
-
this.events.emitEvent({
|
|
1326
|
-
type: 'debug_info',
|
|
1327
|
-
level: 'debug',
|
|
1328
|
-
data: {
|
|
1329
|
-
message: `[NICK'S METHOD DEBUG] Funded EOA ${eoaAddress} with ${ethers.formatEther(neededFunding)} ETH, receipt status: ${fundingReceipt?.status}`
|
|
1330
|
-
}
|
|
1331
|
-
})
|
|
1332
|
-
|
|
1333
|
-
if (!fundingReceipt || fundingReceipt.status !== 1) {
|
|
1334
|
-
this.events.emitEvent({
|
|
1335
|
-
type: 'action_failed',
|
|
1336
|
-
level: 'error',
|
|
1337
|
-
data: {
|
|
1338
|
-
message: `[NICK'S METHOD DEBUG] Funding transaction failed! Hash: ${fundingTx.hash}, Status: ${fundingReceipt?.status}`
|
|
1339
|
-
}
|
|
1340
|
-
})
|
|
1341
|
-
return false
|
|
1342
|
-
}
|
|
1343
|
-
} else {
|
|
1344
|
-
this.events.emitEvent({
|
|
1345
|
-
type: 'debug_info',
|
|
1346
|
-
level: 'debug',
|
|
1347
|
-
data: {
|
|
1348
|
-
message: `[NICK'S METHOD DEBUG] EOA already has sufficient balance, skipping funding`
|
|
1349
|
-
}
|
|
1350
|
-
})
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
// Try to broadcast the raw transaction
|
|
1354
|
-
this.events.emitEvent({
|
|
1355
|
-
type: 'debug_info',
|
|
1356
|
-
level: 'debug',
|
|
1357
|
-
data: {
|
|
1358
|
-
message: `[NICK'S METHOD DEBUG] Broadcasting Nick's method transaction. RawTx: ${signedTx.substring(0, 100)}...`
|
|
1359
|
-
}
|
|
1360
|
-
})
|
|
1361
|
-
|
|
1362
|
-
const deployTx = await context.provider.broadcastTransaction(signedTx)
|
|
1363
|
-
|
|
1364
|
-
this.events.emitEvent({
|
|
1365
|
-
type: 'debug_info',
|
|
1366
|
-
level: 'debug',
|
|
1367
|
-
data: {
|
|
1368
|
-
message: `[NICK'S METHOD DEBUG] Transaction broadcasted successfully. Hash: ${deployTx.hash}, waiting for confirmation...`
|
|
1369
|
-
}
|
|
1370
|
-
})
|
|
1371
|
-
|
|
1372
|
-
const receipt = await deployTx.wait()
|
|
1373
|
-
|
|
1374
|
-
this.events.emitEvent({
|
|
1375
|
-
type: 'debug_info',
|
|
1376
|
-
level: 'debug',
|
|
1377
|
-
data: {
|
|
1378
|
-
message: `[NICK'S METHOD DEBUG] Transaction receipt received. Status: ${receipt?.status}, ContractAddress: ${receipt?.contractAddress}, BlockNumber: ${receipt?.blockNumber}`
|
|
1379
|
-
}
|
|
1380
|
-
})
|
|
1381
|
-
|
|
1382
|
-
if (receipt && receipt.status === 1) {
|
|
1383
|
-
this.events.emitEvent({
|
|
1384
|
-
type: 'transaction_confirmed',
|
|
1385
|
-
level: 'info',
|
|
1386
|
-
data: {
|
|
1387
|
-
txHash: deployTx.hash,
|
|
1388
|
-
blockNumber: receipt.blockNumber || 0
|
|
1389
|
-
}
|
|
1390
|
-
})
|
|
1391
|
-
|
|
1392
|
-
this.events.emitEvent({
|
|
1393
|
-
type: 'debug_info',
|
|
1394
|
-
level: 'debug',
|
|
1395
|
-
data: {
|
|
1396
|
-
message: `[NICK'S METHOD DEBUG] Nick's method test successful - contract deployed at ${receipt.contractAddress}`
|
|
1397
|
-
}
|
|
1398
|
-
})
|
|
1399
|
-
testResult = true
|
|
1400
|
-
} else {
|
|
1401
|
-
this.events.emitEvent({
|
|
1402
|
-
type: 'action_failed',
|
|
1403
|
-
level: 'error',
|
|
1404
|
-
data: {
|
|
1405
|
-
message: `[NICK'S METHOD DEBUG] Nick's method test failed - transaction reverted or failed. Hash: ${deployTx.hash}, Status: ${receipt?.status}`
|
|
1406
|
-
}
|
|
1407
|
-
})
|
|
1408
|
-
testResult = false
|
|
1409
|
-
}
|
|
1410
|
-
} catch (error) {
|
|
1411
|
-
this.events.emitEvent({
|
|
1412
|
-
type: 'action_failed',
|
|
1413
|
-
level: 'error',
|
|
1414
|
-
data: {
|
|
1415
|
-
message: `[NICK'S METHOD DEBUG] Nick's method test failed with error: ${error instanceof Error ? error.message : String(error)}`
|
|
1416
|
-
}
|
|
1417
|
-
})
|
|
1418
|
-
|
|
1419
|
-
// Log additional error details for debugging
|
|
1420
|
-
if (error instanceof Error && error.stack) {
|
|
1421
|
-
this.events.emitEvent({
|
|
1422
|
-
type: 'action_failed',
|
|
1423
|
-
level: 'debug',
|
|
1424
|
-
data: {
|
|
1425
|
-
message: `[NICK'S METHOD DEBUG] Error stack trace: ${error.stack}`
|
|
1426
|
-
}
|
|
1427
|
-
})
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
testResult = false
|
|
1431
|
-
} finally {
|
|
1432
|
-
// Always try to return remaining funds to the original wallet
|
|
1433
|
-
if (eoaAddress && wallet) {
|
|
1434
|
-
try {
|
|
1435
|
-
await this.returnRemainingFunds(eoaAddress, wallet, context)
|
|
1436
|
-
} catch (error) {
|
|
1437
|
-
// Log the error but don't fail the main test
|
|
1438
|
-
this.events.emitEvent({
|
|
1439
|
-
type: 'action_failed',
|
|
1440
|
-
level: 'warn',
|
|
1441
|
-
data: {
|
|
1442
|
-
message: `Failed to return remaining funds from EOA ${eoaAddress}: ${error instanceof Error ? error.message : String(error)}`
|
|
1443
|
-
}
|
|
1444
|
-
})
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
return testResult
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
/**
|
|
1453
|
-
* Generates a raw transaction and EOA address using Nick's method approach
|
|
1454
|
-
*/
|
|
1455
|
-
private async generateNicksMethodTransaction(
|
|
1456
|
-
bytecode: string,
|
|
1457
|
-
gasPrice: ethers.BigNumberish,
|
|
1458
|
-
gasLimit: ethers.BigNumberish
|
|
1459
|
-
): Promise<{ unsignedTx: ethers.TransactionRequest; signedTx: string; eoaAddress: string; wallet: ethers.HDNodeWallet }> {
|
|
1460
|
-
// Generate a random private key for the test
|
|
1461
|
-
const wallet = ethers.Wallet.createRandom()
|
|
1462
|
-
|
|
1463
|
-
// Create unsigned transaction
|
|
1464
|
-
const unsignedTx: ethers.TransactionRequest = {
|
|
1465
|
-
type: 0, // Legacy transaction
|
|
1466
|
-
chainId: 0, // Nick's method uses chainId 0
|
|
1467
|
-
nonce: 0,
|
|
1468
|
-
gasPrice: gasPrice,
|
|
1469
|
-
gasLimit: gasLimit,
|
|
1470
|
-
to: null, // Contract creation
|
|
1471
|
-
value: 0,
|
|
1472
|
-
data: bytecode
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
// Sign the transaction
|
|
1476
|
-
const signedTx = await wallet.signTransaction(unsignedTx)
|
|
1477
|
-
|
|
1478
|
-
// Parse the signed transaction to get the EOA address
|
|
1479
|
-
const parsedTx = ethers.Transaction.from(signedTx)
|
|
1480
|
-
const eoaAddress = parsedTx.from!
|
|
1481
|
-
|
|
1482
|
-
return {
|
|
1483
|
-
unsignedTx,
|
|
1484
|
-
signedTx,
|
|
1485
|
-
eoaAddress,
|
|
1486
|
-
wallet,
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
/**
|
|
1491
|
-
* Returns any remaining funds from the test EOA back to the original wallet
|
|
1492
|
-
*/
|
|
1493
|
-
private async returnRemainingFunds(
|
|
1494
|
-
eoaAddress: string,
|
|
1495
|
-
wallet: ethers.HDNodeWallet | ethers.Wallet,
|
|
1496
|
-
context: ExecutionContext
|
|
1497
|
-
): Promise<void> {
|
|
1498
|
-
// Check remaining balance in the test EOA
|
|
1499
|
-
const remainingBalance = await context.provider.getBalance(eoaAddress)
|
|
1500
|
-
|
|
1501
|
-
if (remainingBalance <= 0n) {
|
|
1502
|
-
// No funds to return
|
|
1503
|
-
return
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
// Connect the wallet to the provider to send transactions
|
|
1507
|
-
const connectedWallet = wallet.connect(context.provider)
|
|
1508
|
-
|
|
1509
|
-
// Estimate gas for a simple transfer
|
|
1510
|
-
const feeData = await context.provider.getFeeData()
|
|
1511
|
-
const txGas = feeData.maxFeePerGas ? {
|
|
1512
|
-
maxFeePerGas: feeData.maxFeePerGas,
|
|
1513
|
-
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas || ethers.parseUnits('20', 'gwei')
|
|
1514
|
-
} : {
|
|
1515
|
-
gasPrice: feeData.gasPrice || undefined,
|
|
1516
|
-
}
|
|
1517
|
-
const effectiveGasPrice = txGas.maxFeePerGas || txGas.gasPrice
|
|
1518
|
-
if (!effectiveGasPrice) {
|
|
1519
|
-
this.events.emitEvent({
|
|
1520
|
-
type: 'action_failed',
|
|
1521
|
-
level: 'error',
|
|
1522
|
-
data: {
|
|
1523
|
-
message: `No gas price available`
|
|
1524
|
-
}
|
|
1525
|
-
})
|
|
1526
|
-
return
|
|
1527
|
-
}
|
|
1528
|
-
const gasLimit = 21000n // Standard gas limit for ETH transfer
|
|
1529
|
-
const gasCost = effectiveGasPrice * gasLimit
|
|
1530
|
-
|
|
1531
|
-
// Check if we have enough balance to cover gas costs
|
|
1532
|
-
if (remainingBalance <= gasCost) {
|
|
1533
|
-
this.events.emitEvent({
|
|
1534
|
-
type: 'action_info',
|
|
1535
|
-
level: 'debug',
|
|
1536
|
-
data: {
|
|
1537
|
-
message: `Remaining balance ${ethers.formatEther(remainingBalance)} ETH is insufficient to cover gas costs for fund return`
|
|
1538
|
-
}
|
|
1539
|
-
})
|
|
1540
|
-
return
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
// Calculate amount to send (balance minus gas costs)
|
|
1544
|
-
const amountToSend = remainingBalance - gasCost
|
|
1545
|
-
|
|
1546
|
-
this.events.emitEvent({
|
|
1547
|
-
type: 'transaction_sent',
|
|
1548
|
-
level: 'debug',
|
|
1549
|
-
data: {
|
|
1550
|
-
to: await (await context.getResolvedSigner()).getAddress(),
|
|
1551
|
-
value: amountToSend.toString(),
|
|
1552
|
-
dataPreview: 'returning remaining funds from Nick\'s method test',
|
|
1553
|
-
txHash: 'pending'
|
|
1554
|
-
}
|
|
1555
|
-
})
|
|
1556
|
-
|
|
1557
|
-
// Send the remaining funds back to the original signer
|
|
1558
|
-
const returnTx = await connectedWallet.sendTransaction({
|
|
1559
|
-
to: await (await context.getResolvedSigner()).getAddress(),
|
|
1560
|
-
value: amountToSend,
|
|
1561
|
-
gasLimit: gasLimit,
|
|
1562
|
-
...txGas,
|
|
1563
|
-
})
|
|
1564
|
-
|
|
1565
|
-
await returnTx.wait()
|
|
1566
|
-
|
|
1567
|
-
this.events.emitEvent({
|
|
1568
|
-
type: 'transaction_confirmed',
|
|
1569
|
-
level: 'debug',
|
|
1570
|
-
data: {
|
|
1571
|
-
txHash: returnTx.hash,
|
|
1572
|
-
blockNumber: (await returnTx.wait())?.blockNumber || 0
|
|
1573
|
-
}
|
|
1574
|
-
})
|
|
1575
|
-
|
|
1576
|
-
this.events.emitEvent({
|
|
1577
|
-
type: 'debug_info',
|
|
1578
|
-
level: 'debug',
|
|
1579
|
-
data: {
|
|
1580
|
-
message: `Returned ${ethers.formatEther(amountToSend)} ETH from test EOA ${eoaAddress} to original wallet`
|
|
1581
|
-
}
|
|
1582
|
-
})
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
/**
|
|
1586
|
-
* Retries a boolean-producing async check to mitigate transient RPC state lag after transactions.
|
|
1587
|
-
* Returns true on first successful check; otherwise waits delayMs and retries up to retries times.
|
|
1588
|
-
*/
|
|
1589
|
-
private async retryBooleanCheck(checkFn: () => Promise<boolean>, retries: number = 3, delayMs: number = 2000): Promise<boolean> {
|
|
1590
|
-
// Throttle debug logging: log first, 25%, 50%, 75%, and final attempt
|
|
1591
|
-
const milestones = new Set<number>()
|
|
1592
|
-
const total = retries + 1
|
|
1593
|
-
milestones.add(1)
|
|
1594
|
-
milestones.add(Math.max(1, Math.floor(total * 0.25)))
|
|
1595
|
-
milestones.add(Math.max(1, Math.floor(total * 0.5)))
|
|
1596
|
-
milestones.add(Math.max(1, Math.floor(total * 0.75)))
|
|
1597
|
-
milestones.add(total)
|
|
1598
|
-
|
|
1599
|
-
for (let attempt = 0; attempt < total; attempt++) {
|
|
1600
|
-
try {
|
|
1601
|
-
const result = await checkFn()
|
|
1602
|
-
if (result) {
|
|
1603
|
-
return true
|
|
1604
|
-
}
|
|
1605
|
-
if (milestones.has(attempt + 1)) {
|
|
1606
|
-
this.events.emitEvent({
|
|
1607
|
-
type: 'debug_info',
|
|
1608
|
-
level: 'debug',
|
|
1609
|
-
data: {
|
|
1610
|
-
message: `Post-execution check returned false (attempt ${attempt + 1}/${total}).`
|
|
1611
|
-
}
|
|
1612
|
-
})
|
|
1613
|
-
}
|
|
1614
|
-
} catch (err) {
|
|
1615
|
-
if (milestones.has(attempt + 1)) {
|
|
1616
|
-
this.events.emitEvent({
|
|
1617
|
-
type: 'debug_info',
|
|
1618
|
-
level: 'debug',
|
|
1619
|
-
data: {
|
|
1620
|
-
message: `Post-execution check threw error (attempt ${attempt + 1}/${total}): ${err instanceof Error ? err.message : String(err)}`
|
|
1621
|
-
}
|
|
1622
|
-
})
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
if (attempt < retries) {
|
|
1626
|
-
await new Promise(res => setTimeout(res, delayMs))
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
return false
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
/**
|
|
1633
|
-
* Evaluates a list of conditions and returns true if any of them are met.
|
|
1634
|
-
*/
|
|
1635
|
-
public async evaluateSkipConditions(
|
|
1636
|
-
conditions: Condition[] | undefined,
|
|
1637
|
-
context: ExecutionContext,
|
|
1638
|
-
scope: ResolutionScope,
|
|
1639
|
-
): Promise<boolean> {
|
|
1640
|
-
if (!conditions || conditions.length === 0) {
|
|
1641
|
-
return false
|
|
1642
|
-
}
|
|
1643
|
-
for (const condition of conditions) {
|
|
1644
|
-
const shouldSkip = await this.resolver.resolve(condition, context, scope)
|
|
1645
|
-
if (shouldSkip) {
|
|
1646
|
-
return true
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
return false
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
/**
|
|
1653
|
-
* Creates a topological sort of actions within a job based on their `depends_on` fields.
|
|
1654
|
-
*/
|
|
1655
|
-
private topologicalSortActions(job: Job): string[] {
|
|
1656
|
-
const sorted: string[] = []
|
|
1657
|
-
const graph = new Map<string, Set<string>>()
|
|
1658
|
-
const inDegree = new Map<string, number>()
|
|
1659
|
-
const actionMap = new Map(job.actions.map(a => [a.name, a]))
|
|
1660
|
-
|
|
1661
|
-
// Initialize graph and in-degrees
|
|
1662
|
-
for (const action of job.actions) {
|
|
1663
|
-
graph.set(action.name, new Set(action.depends_on || []))
|
|
1664
|
-
inDegree.set(action.name, 0)
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
// Calculate in-degrees and validate dependencies
|
|
1668
|
-
for (const [actionName, dependencies] of graph.entries()) {
|
|
1669
|
-
for (const depName of dependencies) {
|
|
1670
|
-
if (!actionMap.has(depName)) {
|
|
1671
|
-
throw new Error(`Action "${actionName}" in job "${job.name}" has an invalid dependency on "${depName}", which does not exist.`)
|
|
1672
|
-
}
|
|
1673
|
-
inDegree.set(actionName, (inDegree.get(actionName) ?? 0) + 1)
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
// Initialize queue with actions having an in-degree of 0
|
|
1678
|
-
const queue = Array.from(inDegree.entries())
|
|
1679
|
-
.filter(([, degree]) => degree === 0)
|
|
1680
|
-
.map(([name]) => name)
|
|
1681
|
-
|
|
1682
|
-
// Process the queue
|
|
1683
|
-
while (queue.length > 0) {
|
|
1684
|
-
const currentName = queue.shift()!
|
|
1685
|
-
sorted.push(currentName)
|
|
1686
|
-
|
|
1687
|
-
// Find all actions that depend on the current one
|
|
1688
|
-
for (const [actionName, dependencies] of graph.entries()) {
|
|
1689
|
-
if (dependencies.has(currentName)) {
|
|
1690
|
-
const newDegree = (inDegree.get(actionName) ?? 1) - 1
|
|
1691
|
-
inDegree.set(actionName, newDegree)
|
|
1692
|
-
if (newDegree === 0) {
|
|
1693
|
-
queue.push(actionName)
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
if (sorted.length !== job.actions.length) {
|
|
1700
|
-
throw new Error(`Circular dependency detected among actions in job "${job.name}".`)
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
return sorted
|
|
1704
|
-
}
|
|
1705
|
-
|
|
1706
|
-
/**
|
|
1707
|
-
* Checks if the signer has enough funds to cover the estimated cost of the transaction.
|
|
1708
|
-
* Returns true if the signer has enough funds, false if the signer does not have enough funds, and null if no gas price is available.
|
|
1709
|
-
*/
|
|
1710
|
-
private async checkFundsForTransaction(actionName: string, txParams: ethers.TransactionRequest, context: ExecutionContext, signer: ethers.Signer): Promise<boolean | null> {
|
|
1711
|
-
try {
|
|
1712
|
-
const gasPrice = txParams.gasPrice || await context.provider.getFeeData().then(data => data.gasPrice)
|
|
1713
|
-
if (!gasPrice) {
|
|
1714
|
-
this.events.emitEvent({
|
|
1715
|
-
type: 'debug_info',
|
|
1716
|
-
level: 'warn',
|
|
1717
|
-
data: {
|
|
1718
|
-
actionName: actionName,
|
|
1719
|
-
message: `No gas price available`
|
|
1720
|
-
}
|
|
1721
|
-
})
|
|
1722
|
-
return null
|
|
1723
|
-
}
|
|
1724
|
-
const gasLimit = txParams.gasLimit || await signer.estimateGas(txParams)
|
|
1725
|
-
const requiredETH = BigInt(gasLimit) * BigInt(gasPrice)
|
|
1726
|
-
const signerBalance = await context.provider.getBalance(await signer.getAddress())
|
|
1727
|
-
this.events.emitEvent({
|
|
1728
|
-
type: 'debug_info',
|
|
1729
|
-
level: 'debug',
|
|
1730
|
-
data: {
|
|
1731
|
-
actionName: actionName,
|
|
1732
|
-
message: `Transaction ${txParams.gasLimit ? 'set' : 'estimated'} gas limit: ${gasLimit}, ${txParams.gasPrice ? 'set' : 'estimated'} gas price: ${ethers.formatUnits(gasPrice, 'gwei')} gwei, required ETH: ${ethers.formatEther(requiredETH)}`
|
|
1733
|
-
}
|
|
1734
|
-
})
|
|
1735
|
-
if (signerBalance < requiredETH) {
|
|
1736
|
-
this.events.emitEvent({
|
|
1737
|
-
type: 'debug_info',
|
|
1738
|
-
level: 'warn',
|
|
1739
|
-
data: {
|
|
1740
|
-
actionName: actionName,
|
|
1741
|
-
message: `Insufficient funds: signer has ${ethers.formatEther(signerBalance)} ETH but estimated cost is ${ethers.formatEther(requiredETH)} ETH`
|
|
1742
|
-
}
|
|
1743
|
-
})
|
|
1744
|
-
return false
|
|
1745
|
-
} else {
|
|
1746
|
-
return true
|
|
1747
|
-
}
|
|
1748
|
-
} catch (error) {
|
|
1749
|
-
this.events.emitEvent({
|
|
1750
|
-
type: 'debug_info',
|
|
1751
|
-
level: 'warn',
|
|
1752
|
-
data: {
|
|
1753
|
-
actionName: actionName,
|
|
1754
|
-
message: "Error checking signer balance: " + (error instanceof Error ? error.message : String(error))
|
|
1755
|
-
}
|
|
1756
|
-
})
|
|
1757
|
-
}
|
|
1758
|
-
return null
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
/**
|
|
1762
|
-
* Get all verification warnings that were collected when ignoreVerifyErrors is enabled
|
|
1763
|
-
*/
|
|
1764
|
-
public getVerificationWarnings(): Array<{
|
|
1765
|
-
actionName: string
|
|
1766
|
-
address: string
|
|
1767
|
-
contractName: string
|
|
1768
|
-
platform: string
|
|
1769
|
-
error: string
|
|
1770
|
-
jobName?: string
|
|
1771
|
-
networkName?: string
|
|
1772
|
-
}> {
|
|
1773
|
-
return [...this.verificationWarnings]
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
/**
|
|
1777
|
-
* Clear verification warnings (useful for testing)
|
|
1778
|
-
*/
|
|
1779
|
-
public clearVerificationWarnings(): void {
|
|
1780
|
-
this.verificationWarnings = []
|
|
1781
|
-
}
|
|
1782
|
-
}
|