@0xsequence/catapult 1.3.6 → 1.3.8
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/dist/index.js +0 -0
- package/dist/lib/__tests__/deployer.spec.js +572 -0
- package/dist/lib/__tests__/deployer.spec.js.map +1 -1
- package/dist/lib/core/__tests__/engine.spec.js +1 -1
- package/dist/lib/core/__tests__/engine.spec.js.map +1 -1
- package/dist/lib/core/engine.d.ts +5 -3
- package/dist/lib/core/engine.d.ts.map +1 -1
- package/dist/lib/core/engine.js +69 -15
- package/dist/lib/core/engine.js.map +1 -1
- package/dist/lib/deployer.d.ts.map +1 -1
- package/dist/lib/deployer.js +42 -8
- package/dist/lib/deployer.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/__tests__/deployer.spec.ts +849 -93
- package/src/lib/core/__tests__/engine.spec.ts +1 -1
- package/src/lib/core/engine.ts +75 -17
- package/src/lib/deployer.ts +106 -65
- package/dist/lib/network-match.d.ts +0 -3
- package/dist/lib/network-match.d.ts.map +0 -1
- package/dist/lib/network-match.js +0 -62
- package/dist/lib/network-match.js.map +0 -1
|
@@ -1492,7 +1492,7 @@ describe('ExecutionEngine', () => {
|
|
|
1492
1492
|
}
|
|
1493
1493
|
|
|
1494
1494
|
await expect((engine as any).executeAction(action, context, new Map())).rejects.toThrow(new Error(`Nick's method test failed for action "nick-test-fail-twice"`))
|
|
1495
|
-
await expect((engine as any).executeAction(action, context, new Map())).rejects.toThrow(new Error('Nick\'s method test already
|
|
1495
|
+
await expect((engine as any).executeAction(action, context, new Map())).rejects.toThrow(new Error('Nick\'s method test already failed this run'))
|
|
1496
1496
|
})
|
|
1497
1497
|
|
|
1498
1498
|
it('should prevent passing Nick\'s method from being tested twice', async () => {
|
package/src/lib/core/engine.ts
CHANGED
|
@@ -29,7 +29,7 @@ export class ExecutionEngine {
|
|
|
29
29
|
private readonly noPostCheckConditions: boolean
|
|
30
30
|
private readonly allowMultipleNicksMethodTests: boolean
|
|
31
31
|
private readonly ignoreVerifyErrors: boolean
|
|
32
|
-
private
|
|
32
|
+
private nicksMethodResult: boolean | undefined
|
|
33
33
|
private verificationWarnings: Array<{
|
|
34
34
|
actionName: string
|
|
35
35
|
address: string
|
|
@@ -464,17 +464,17 @@ export class ExecutionEngine {
|
|
|
464
464
|
|
|
465
465
|
// Handle gas limit with optional multiplier
|
|
466
466
|
const network = context.getNetwork()
|
|
467
|
+
const signer = await context.getResolvedSigner()
|
|
467
468
|
if (network.gasLimit) {
|
|
468
469
|
const baseGasLimit = network.gasLimit
|
|
469
470
|
txParams.gasLimit = gasMultiplier ? Math.floor(baseGasLimit * gasMultiplier) : baseGasLimit
|
|
470
471
|
} else if (gasMultiplier) {
|
|
471
472
|
// If gasMultiplier is specified but no network gasLimit, estimate gas first
|
|
472
|
-
const signer = await context.getResolvedSigner()
|
|
473
473
|
const estimatedGas = await signer.estimateGas({ to, data, value })
|
|
474
474
|
txParams.gasLimit = Math.floor(Number(estimatedGas) * gasMultiplier)
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
-
|
|
477
|
+
await this.checkFundsForTransaction(actionName, txParams, context, signer)
|
|
478
478
|
const tx = await signer.sendTransaction(txParams)
|
|
479
479
|
|
|
480
480
|
this.events.emitEvent({
|
|
@@ -795,17 +795,17 @@ export class ExecutionEngine {
|
|
|
795
795
|
|
|
796
796
|
// Handle gas limit with optional multiplier
|
|
797
797
|
const network = context.getNetwork()
|
|
798
|
+
const signer = await context.getResolvedSigner()
|
|
798
799
|
if (network.gasLimit) {
|
|
799
800
|
const baseGasLimit = network.gasLimit
|
|
800
801
|
txParams.gasLimit = gasMultiplier ? Math.floor(baseGasLimit * gasMultiplier) : baseGasLimit
|
|
801
802
|
} else if (gasMultiplier) {
|
|
802
803
|
// If gasMultiplier is specified but no network gasLimit, estimate gas first
|
|
803
|
-
const
|
|
804
|
-
const estimatedGas = await signer.estimateGas({ to: null, data, value })
|
|
804
|
+
const estimatedGas = await signer.estimateGas(txParams)
|
|
805
805
|
txParams.gasLimit = Math.floor(Number(estimatedGas) * gasMultiplier)
|
|
806
806
|
}
|
|
807
|
-
|
|
808
|
-
|
|
807
|
+
|
|
808
|
+
await this.checkFundsForTransaction(actionName, txParams, context, signer)
|
|
809
809
|
const tx = await signer.sendTransaction(txParams)
|
|
810
810
|
|
|
811
811
|
this.events.emitEvent({
|
|
@@ -855,17 +855,19 @@ export class ExecutionEngine {
|
|
|
855
855
|
break
|
|
856
856
|
}
|
|
857
857
|
case 'test-nicks-method': {
|
|
858
|
-
if (this.
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
// Return previous result
|
|
862
|
-
break
|
|
863
|
-
}
|
|
864
|
-
} catch (e) {
|
|
865
|
-
throw new Error(`Nick's method test already performed this run`)
|
|
858
|
+
if (this.nicksMethodResult !== undefined && !this.allowMultipleNicksMethodTests) {
|
|
859
|
+
if (this.nicksMethodResult === false) {
|
|
860
|
+
throw new Error(`Nick's method test already failed this run`)
|
|
866
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
|
|
867
870
|
}
|
|
868
|
-
this.nicksMethodTested = true
|
|
869
871
|
|
|
870
872
|
// Default bytecode if none provided
|
|
871
873
|
const defaultBytecode = '0x608060405234801561001057600080fd5b5061013d806100206000396000f3fe60806040526004361061001e5760003560e01c80639c4ae2d014610023575b600080fd5b6100cb6004803603604081101561003957600080fd5b81019060208101813564010000000081111561005457600080fd5b82018360208201111561006657600080fd5b8035906020019184600183028401116401000000008311171561008857600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955050913592506100cd915050565b005b60008183516020850134f56040805173ffffffffffffffffffffffffffffffffffffffff83168152905191925081900360200190a050505056fea264697066735822122033609f614f03931b92d88c309d698449bb77efcd517328d341fa4f923c5d8c7964736f6c63430007060033'
|
|
@@ -884,6 +886,7 @@ export class ExecutionEngine {
|
|
|
884
886
|
const fundingAmount = resolvedFundingAmount ? validateBigNumberish(resolvedFundingAmount, actionName, 'fundingAmount') : undefined
|
|
885
887
|
|
|
886
888
|
const success = await this.testNicksMethod(bytecode, context, gasPrice, gasLimit, fundingAmount)
|
|
889
|
+
this.nicksMethodResult = success
|
|
887
890
|
|
|
888
891
|
if (!success) {
|
|
889
892
|
throw new Error(`Nick's method test failed for action "${actionName}"`)
|
|
@@ -1629,7 +1632,7 @@ export class ExecutionEngine {
|
|
|
1629
1632
|
/**
|
|
1630
1633
|
* Evaluates a list of conditions and returns true if any of them are met.
|
|
1631
1634
|
*/
|
|
1632
|
-
|
|
1635
|
+
public async evaluateSkipConditions(
|
|
1633
1636
|
conditions: Condition[] | undefined,
|
|
1634
1637
|
context: ExecutionContext,
|
|
1635
1638
|
scope: ResolutionScope,
|
|
@@ -1700,6 +1703,61 @@ export class ExecutionEngine {
|
|
|
1700
1703
|
return sorted
|
|
1701
1704
|
}
|
|
1702
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
|
+
|
|
1703
1761
|
/**
|
|
1704
1762
|
* Get all verification warnings that were collected when ignoreVerifyErrors is enabled
|
|
1705
1763
|
*/
|
package/src/lib/deployer.ts
CHANGED
|
@@ -16,31 +16,31 @@ import type { RunSummaryEvent } from './events'
|
|
|
16
16
|
export interface DeployerOptions {
|
|
17
17
|
/** The root directory of the deployment project. */
|
|
18
18
|
projectRoot: string
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
/** The private key of the EOA to be used as the signer/relayer. Optional if an implicit sender from RPC is desired. */
|
|
21
21
|
privateKey?: string
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
/** An array of network configurations to use for deployment. */
|
|
24
24
|
networks: Network[]
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
/** Optional: An array of job names to execute. If not provided, all jobs are considered. */
|
|
27
27
|
runJobs?: string[]
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
/** Optional: An array of chain IDs to run on. If not provided, all configured networks are used. */
|
|
30
30
|
runOnNetworks?: number[]
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
/** Optional: Custom event emitter instance. If not provided, uses the global singleton. */
|
|
33
33
|
eventEmitter?: DeploymentEventEmitter
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
/** Optional: Project loader options (e.g., whether to load standard templates). */
|
|
36
36
|
loaderOptions?: ProjectLoaderOptions
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
/** Optional Etherscan API key for contract verification. */
|
|
39
39
|
etherscanApiKey?: string
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
/** Optional: Stop execution as soon as any job fails. Defaults to false. */
|
|
42
42
|
failEarly?: boolean
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
/** Optional: Skip post-execution check of skip conditions. Defaults to false (post-check enabled). */
|
|
45
45
|
noPostCheckConditions?: boolean
|
|
46
46
|
|
|
@@ -68,11 +68,11 @@ export class Deployer {
|
|
|
68
68
|
private readonly loader: ProjectLoader
|
|
69
69
|
private readonly noPostCheckConditions: boolean
|
|
70
70
|
private readonly showSummary: boolean
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
// Store both successful and failed execution results
|
|
73
73
|
private readonly results = new Map<string, {
|
|
74
74
|
job: Job;
|
|
75
|
-
outputs: Map<number, { status: 'success' | 'error'; data: Map<string, unknown> | string }>
|
|
75
|
+
outputs: Map<number, { status: 'success' | 'error' | 'skipped'; data: Map<string, unknown> | string }>
|
|
76
76
|
}>()
|
|
77
77
|
private graph?: DependencyGraph
|
|
78
78
|
|
|
@@ -97,7 +97,7 @@ export class Deployer {
|
|
|
97
97
|
projectRoot: this.options.projectRoot
|
|
98
98
|
}
|
|
99
99
|
})
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
try {
|
|
102
102
|
// 1. Load all project artifacts, templates, and jobs.
|
|
103
103
|
this.events.emitEvent({
|
|
@@ -107,9 +107,9 @@ export class Deployer {
|
|
|
107
107
|
projectRoot: this.options.projectRoot
|
|
108
108
|
}
|
|
109
109
|
})
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
await this.loader.load()
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
this.events.emitEvent({
|
|
114
114
|
type: 'project_loaded',
|
|
115
115
|
level: 'info',
|
|
@@ -118,7 +118,7 @@ export class Deployer {
|
|
|
118
118
|
templateCount: this.loader.templates.size
|
|
119
119
|
}
|
|
120
120
|
})
|
|
121
|
-
|
|
121
|
+
|
|
122
122
|
// 2. Build the dependency graph and determine execution order.
|
|
123
123
|
const graph = new DependencyGraph(this.loader.jobs, this.loader.templates)
|
|
124
124
|
this.graph = graph
|
|
@@ -142,7 +142,7 @@ export class Deployer {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
const targetNetworks = this.getTargetNetworks()
|
|
145
|
-
|
|
145
|
+
|
|
146
146
|
this.events.emitEvent({
|
|
147
147
|
type: 'execution_plan',
|
|
148
148
|
level: 'info',
|
|
@@ -163,12 +163,12 @@ export class Deployer {
|
|
|
163
163
|
noPostCheckConditions: this.noPostCheckConditions,
|
|
164
164
|
ignoreVerifyErrors: this.options.ignoreVerifyErrors ?? false
|
|
165
165
|
})
|
|
166
|
-
|
|
166
|
+
|
|
167
167
|
// Track if any jobs have failed
|
|
168
168
|
let hasFailures = false
|
|
169
169
|
// Emit signer info once per network (chainId)
|
|
170
170
|
const signerInfoPrintedForChain = new Set<number>()
|
|
171
|
-
|
|
171
|
+
|
|
172
172
|
for (const network of targetNetworks) {
|
|
173
173
|
this.events.emitEvent({
|
|
174
174
|
type: 'network_started',
|
|
@@ -180,7 +180,12 @@ export class Deployer {
|
|
|
180
180
|
})
|
|
181
181
|
for (const jobName of jobsToRun) {
|
|
182
182
|
const job = this.loader.jobs.get(jobName)!
|
|
183
|
-
|
|
183
|
+
|
|
184
|
+
// Initialize results storage for this job if not exists
|
|
185
|
+
if (!this.results.has(job.name)) {
|
|
186
|
+
this.results.set(job.name, { job, outputs: new Map() })
|
|
187
|
+
}
|
|
188
|
+
|
|
184
189
|
if (this.shouldSkipJobOnNetwork(job, network)) {
|
|
185
190
|
this.events.emitEvent({
|
|
186
191
|
type: 'job_skipped',
|
|
@@ -191,14 +196,16 @@ export class Deployer {
|
|
|
191
196
|
reason: 'configuration'
|
|
192
197
|
}
|
|
193
198
|
})
|
|
199
|
+
|
|
200
|
+
// Store skipped result
|
|
201
|
+
this.results.get(job.name)!.outputs.set(network.chainId, {
|
|
202
|
+
status: 'skipped',
|
|
203
|
+
data: 'Job skipped due to network configuration'
|
|
204
|
+
})
|
|
205
|
+
|
|
194
206
|
continue
|
|
195
207
|
}
|
|
196
|
-
|
|
197
|
-
// Initialize results storage for this job if not exists
|
|
198
|
-
if (!this.results.has(job.name)) {
|
|
199
|
-
this.results.set(job.name, { job, outputs: new Map() })
|
|
200
|
-
}
|
|
201
|
-
|
|
208
|
+
|
|
202
209
|
let context: ExecutionContext | undefined
|
|
203
210
|
try {
|
|
204
211
|
context = new ExecutionContext(
|
|
@@ -212,7 +219,7 @@ export class Deployer {
|
|
|
212
219
|
if (typeof (context as unknown as { setJobConstants?: (constants: unknown) => void }).setJobConstants === 'function') {
|
|
213
220
|
(context as unknown as { setJobConstants: (constants: unknown) => void }).setJobConstants(job.constants)
|
|
214
221
|
}
|
|
215
|
-
|
|
222
|
+
|
|
216
223
|
// Emit signer info once per network using the first job's context
|
|
217
224
|
if (!signerInfoPrintedForChain.has(network.chainId)) {
|
|
218
225
|
try {
|
|
@@ -253,11 +260,35 @@ export class Deployer {
|
|
|
253
260
|
}
|
|
254
261
|
}
|
|
255
262
|
|
|
263
|
+
// Check job-level skip conditions before execution
|
|
264
|
+
if (job.skip_condition) {
|
|
265
|
+
const shouldSkip = await engine.evaluateSkipConditions(job.skip_condition, context, new Map())
|
|
266
|
+
if (shouldSkip) {
|
|
267
|
+
// Store skipped result
|
|
268
|
+
this.results.get(job.name)!.outputs.set(network.chainId, {
|
|
269
|
+
status: 'skipped',
|
|
270
|
+
data: `Job "${job.name}" skipped due to skip condition`
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
this.events.emitEvent({
|
|
274
|
+
type: 'job_skipped',
|
|
275
|
+
level: 'warn',
|
|
276
|
+
data: {
|
|
277
|
+
jobName: job.name,
|
|
278
|
+
networkName: network.name,
|
|
279
|
+
reason: 'skip_condition'
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
continue // Skip to next job
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
256
287
|
// Populate context with outputs from previously executed dependent jobs
|
|
257
288
|
this.populateContextWithDependentJobOutputs(job, context, network)
|
|
258
|
-
|
|
289
|
+
|
|
259
290
|
await engine.executeJob(job, context)
|
|
260
|
-
|
|
291
|
+
|
|
261
292
|
// Store successful results
|
|
262
293
|
this.results.get(job.name)!.outputs.set(network.chainId, {
|
|
263
294
|
status: 'success',
|
|
@@ -270,7 +301,7 @@ export class Deployer {
|
|
|
270
301
|
status: 'error',
|
|
271
302
|
data: errorMessage
|
|
272
303
|
})
|
|
273
|
-
|
|
304
|
+
|
|
274
305
|
this.events.emitEvent({
|
|
275
306
|
type: 'job_execution_failed',
|
|
276
307
|
level: 'error',
|
|
@@ -281,15 +312,15 @@ export class Deployer {
|
|
|
281
312
|
error: errorMessage
|
|
282
313
|
}
|
|
283
314
|
})
|
|
284
|
-
|
|
315
|
+
|
|
285
316
|
// Mark that we have failures
|
|
286
317
|
hasFailures = true
|
|
287
|
-
|
|
318
|
+
|
|
288
319
|
// If fail-early is enabled, throw the error immediately
|
|
289
320
|
if (this.options.failEarly) {
|
|
290
321
|
throw error
|
|
291
322
|
}
|
|
292
|
-
|
|
323
|
+
|
|
293
324
|
// Otherwise, continue to next job/network
|
|
294
325
|
} finally {
|
|
295
326
|
// Clean up the context to prevent hanging connections
|
|
@@ -312,7 +343,7 @@ export class Deployer {
|
|
|
312
343
|
}
|
|
313
344
|
}
|
|
314
345
|
}
|
|
315
|
-
|
|
346
|
+
|
|
316
347
|
// 5. Write results to output files.
|
|
317
348
|
await this.writeOutputFiles()
|
|
318
349
|
|
|
@@ -386,18 +417,13 @@ export class Deployer {
|
|
|
386
417
|
const jobCount = this.results.size
|
|
387
418
|
let successCount = 0
|
|
388
419
|
let failedCount = 0
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
// Detect skipped by comparing planned jobs across networks vs executed entries
|
|
392
|
-
// Here we approximate: an entry exists per job and per network outcome. We count
|
|
393
|
-
// successes/errors; skips were emitted as events during execution and are not persisted
|
|
394
|
-
// in results. We cannot perfectly reconstruct skipped count without tracking, so we
|
|
395
|
-
// expose it as 0 for now; could be improved by tracking per-network skip events.
|
|
420
|
+
let skippedCount = 0
|
|
396
421
|
|
|
397
422
|
for (const [, result] of this.results) {
|
|
398
423
|
for (const [, netResult] of result.outputs) {
|
|
399
424
|
if (netResult.status === 'success') successCount++
|
|
400
|
-
else
|
|
425
|
+
else if (netResult.status === 'skipped') skippedCount++
|
|
426
|
+
else if (netResult.status === 'error') failedCount++
|
|
401
427
|
}
|
|
402
428
|
}
|
|
403
429
|
|
|
@@ -437,7 +463,7 @@ export class Deployer {
|
|
|
437
463
|
*/
|
|
438
464
|
private emitVerificationWarningsReport(engine: ExecutionEngine): void {
|
|
439
465
|
const warnings = engine.getVerificationWarnings()
|
|
440
|
-
|
|
466
|
+
|
|
441
467
|
if (warnings.length > 0) {
|
|
442
468
|
this.events.emitEvent({
|
|
443
469
|
type: 'verification_warnings_report',
|
|
@@ -530,7 +556,7 @@ export class Deployer {
|
|
|
530
556
|
const allowed = new Set<string>([...nonDeprecatedJobs, ...requiredDeprecated])
|
|
531
557
|
return fullOrder.filter(name => allowed.has(name))
|
|
532
558
|
}
|
|
533
|
-
|
|
559
|
+
|
|
534
560
|
// Expand patterns to concrete names
|
|
535
561
|
const expandedRunJobs = expandRunJobs(this.options.runJobs)
|
|
536
562
|
const explicitlyRequested = new Set<string>(expandedRunJobs)
|
|
@@ -557,7 +583,7 @@ export class Deployer {
|
|
|
557
583
|
return this.options.runDeprecated === true
|
|
558
584
|
})
|
|
559
585
|
const allowedSet = new Set(filtered)
|
|
560
|
-
|
|
586
|
+
|
|
561
587
|
// Filter the original execution order to only include the required jobs, preserving the correct sequence.
|
|
562
588
|
return fullOrder.filter(jobName => allowedSet.has(jobName))
|
|
563
589
|
}
|
|
@@ -569,10 +595,10 @@ export class Deployer {
|
|
|
569
595
|
if (!this.options.runOnNetworks || this.options.runOnNetworks.length === 0) {
|
|
570
596
|
return this.options.networks // Run on all configured networks
|
|
571
597
|
}
|
|
572
|
-
|
|
598
|
+
|
|
573
599
|
const targetChainIds = new Set(this.options.runOnNetworks)
|
|
574
600
|
const filteredNetworks = this.options.networks.filter(n => targetChainIds.has(n.chainId))
|
|
575
|
-
|
|
601
|
+
|
|
576
602
|
if (filteredNetworks.length !== this.options.runOnNetworks.length) {
|
|
577
603
|
const foundIds = new Set(filteredNetworks.map(n => n.chainId))
|
|
578
604
|
const missingIds = this.options.runOnNetworks.filter(id => !foundIds.has(id))
|
|
@@ -584,7 +610,7 @@ export class Deployer {
|
|
|
584
610
|
}
|
|
585
611
|
})
|
|
586
612
|
}
|
|
587
|
-
|
|
613
|
+
|
|
588
614
|
return filteredNetworks
|
|
589
615
|
}
|
|
590
616
|
|
|
@@ -611,7 +637,7 @@ export class Deployer {
|
|
|
611
637
|
}
|
|
612
638
|
}
|
|
613
639
|
}
|
|
614
|
-
|
|
640
|
+
|
|
615
641
|
// Check minimal EVM hardfork requirement if present on job and network declares an EVM version
|
|
616
642
|
if (jobWithNetworkFilters.min_evm_version) {
|
|
617
643
|
const jobMin = this.normalizeEvmVersion(jobWithNetworkFilters.min_evm_version)
|
|
@@ -622,7 +648,7 @@ export class Deployer {
|
|
|
622
648
|
}
|
|
623
649
|
// If network has no evmVersion declared, do not skip (assume compatible)
|
|
624
650
|
}
|
|
625
|
-
|
|
651
|
+
|
|
626
652
|
return false // Run by default
|
|
627
653
|
}
|
|
628
654
|
|
|
@@ -688,16 +714,31 @@ export class Deployer {
|
|
|
688
714
|
|
|
689
715
|
/**
|
|
690
716
|
* Populates the execution context with outputs from previously executed dependent jobs.
|
|
717
|
+
* Throws an error if any dependency failed.
|
|
691
718
|
*/
|
|
692
719
|
private populateContextWithDependentJobOutputs(job: Job, context: ExecutionContext, network: Network): void {
|
|
693
720
|
if (!job.depends_on) return
|
|
694
721
|
|
|
695
722
|
for (const dependentJobName of job.depends_on) {
|
|
696
723
|
const dependentJobResults = this.results.get(dependentJobName)
|
|
697
|
-
if (!dependentJobResults)
|
|
724
|
+
if (!dependentJobResults) {
|
|
725
|
+
throw new Error(`Job "${job.name}" depends on "${dependentJobName}", but "${dependentJobName}" has not been executed yet.`)
|
|
726
|
+
}
|
|
698
727
|
|
|
699
728
|
const networkResult = dependentJobResults.outputs.get(network.chainId)
|
|
700
|
-
if (!networkResult
|
|
729
|
+
if (!networkResult) {
|
|
730
|
+
throw new Error(`Job "${job.name}" depends on "${dependentJobName}", but "${dependentJobName}" has not been executed on network ${network.name} (chainId: ${network.chainId}).`)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Skip jobs don't provide outputs, but they don't prevent dependent jobs from running
|
|
734
|
+
if (networkResult.status === 'skipped') {
|
|
735
|
+
continue
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (networkResult.status !== 'success') {
|
|
739
|
+
const errorMessage = typeof networkResult.data === 'string' ? networkResult.data : 'Unknown error'
|
|
740
|
+
throw new Error(`Job "${job.name}" depends on "${dependentJobName}", but "${dependentJobName}" failed: ${errorMessage}`)
|
|
741
|
+
}
|
|
701
742
|
|
|
702
743
|
// Add outputs with job name prefixes for cross-job access
|
|
703
744
|
const outputs = networkResult.data as Map<string, unknown>
|
|
@@ -726,7 +767,7 @@ export class Deployer {
|
|
|
726
767
|
|
|
727
768
|
const outputRoot = path.join(this.options.projectRoot, 'output')
|
|
728
769
|
await fs.mkdir(outputRoot, { recursive: true })
|
|
729
|
-
|
|
770
|
+
|
|
730
771
|
this.events.emitEvent({
|
|
731
772
|
type: 'output_writing_started',
|
|
732
773
|
level: 'info'
|
|
@@ -755,7 +796,7 @@ export class Deployer {
|
|
|
755
796
|
const outputFilePath = path.join(outputRoot, relativeJobSubpath)
|
|
756
797
|
const outputFileDir = path.dirname(outputFilePath)
|
|
757
798
|
await fs.mkdir(outputFileDir, { recursive: true })
|
|
758
|
-
|
|
799
|
+
|
|
759
800
|
// Group networks by identical status and outputs
|
|
760
801
|
const groupedResults = this.groupNetworkResults(resultData.outputs, resultData.job)
|
|
761
802
|
|
|
@@ -765,7 +806,7 @@ export class Deployer {
|
|
|
765
806
|
lastRun: new Date().toISOString(),
|
|
766
807
|
networks: groupedResults
|
|
767
808
|
}
|
|
768
|
-
|
|
809
|
+
|
|
769
810
|
await fs.writeFile(outputFilePath, JSON.stringify(fileContent, null, 2))
|
|
770
811
|
this.events.emitEvent({
|
|
771
812
|
type: 'output_file_written',
|
|
@@ -865,20 +906,20 @@ export class Deployer {
|
|
|
865
906
|
*/
|
|
866
907
|
private filterOutDependencyOutputs(outputs: Map<string, unknown>, job: Job): Record<string, unknown> {
|
|
867
908
|
const filtered = new Map<string, unknown>()
|
|
868
|
-
|
|
909
|
+
|
|
869
910
|
// Get list of dependency job names
|
|
870
911
|
const dependencyNames = job.depends_on || []
|
|
871
|
-
|
|
912
|
+
|
|
872
913
|
for (const [key, value] of outputs) {
|
|
873
914
|
// Check if this output key starts with any dependency job name prefix
|
|
874
915
|
const isDependencyOutput = dependencyNames.some(depName => key.startsWith(`${depName}.`))
|
|
875
|
-
|
|
916
|
+
|
|
876
917
|
// Only include outputs that are NOT from dependencies
|
|
877
918
|
if (!isDependencyOutput) {
|
|
878
919
|
filtered.set(key, value)
|
|
879
920
|
}
|
|
880
921
|
}
|
|
881
|
-
|
|
922
|
+
|
|
882
923
|
return Object.fromEntries(filtered)
|
|
883
924
|
}
|
|
884
925
|
|
|
@@ -887,8 +928,8 @@ export class Deployer {
|
|
|
887
928
|
* - Success states with identical outputs are grouped together with chainIds array
|
|
888
929
|
* - Error states are kept separate (one entry per network)
|
|
889
930
|
*/
|
|
890
|
-
private groupNetworkResults(outputs: Map<number, { status: 'success' | 'error'; data: Map<string, unknown> | string }>, job: Job): Array<{
|
|
891
|
-
status: 'success' | 'error';
|
|
931
|
+
private groupNetworkResults(outputs: Map<number, { status: 'success' | 'error' | 'skipped'; data: Map<string, unknown> | string }>, job: Job): Array<{
|
|
932
|
+
status: 'success' | 'error' | 'skipped';
|
|
892
933
|
chainIds?: string[];
|
|
893
934
|
chainId?: string;
|
|
894
935
|
outputs?: Record<string, unknown>;
|
|
@@ -900,20 +941,20 @@ export class Deployer {
|
|
|
900
941
|
chainId: string;
|
|
901
942
|
error: string;
|
|
902
943
|
}> = []
|
|
903
|
-
|
|
944
|
+
|
|
904
945
|
for (const [chainId, result] of outputs.entries()) {
|
|
905
946
|
if (result.status === 'success') {
|
|
906
947
|
// Group successful results by identical outputs, filtered by action output flags
|
|
907
948
|
const outputsObj = result.data instanceof Map ? this.filterOutputsByActionFlags(result.data, job) : {}
|
|
908
949
|
const key = JSON.stringify(outputsObj)
|
|
909
|
-
|
|
950
|
+
|
|
910
951
|
if (!successGroups.has(key)) {
|
|
911
952
|
successGroups.set(key, {
|
|
912
953
|
chainIds: [],
|
|
913
954
|
outputs: outputsObj
|
|
914
955
|
})
|
|
915
956
|
}
|
|
916
|
-
|
|
957
|
+
|
|
917
958
|
successGroups.get(key)!.chainIds.push(chainId.toString())
|
|
918
959
|
} else {
|
|
919
960
|
// Keep error results separate - one entry per network
|
|
@@ -924,14 +965,14 @@ export class Deployer {
|
|
|
924
965
|
})
|
|
925
966
|
}
|
|
926
967
|
}
|
|
927
|
-
|
|
968
|
+
|
|
928
969
|
// Convert success groups to array format
|
|
929
970
|
const successEntries = Array.from(successGroups.values()).map(group => ({
|
|
930
971
|
status: 'success' as const,
|
|
931
972
|
chainIds: group.chainIds.sort(), // Sort for consistent output
|
|
932
973
|
outputs: group.outputs
|
|
933
974
|
}))
|
|
934
|
-
|
|
975
|
+
|
|
935
976
|
// Return all entries: successes first, then errors
|
|
936
977
|
return [...successEntries, ...errorEntries]
|
|
937
978
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"network-match.d.ts","sourceRoot":"","sources":["../../src/lib/network-match.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AA8BjC,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAyC1F"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveRequestedNetworks = resolveRequestedNetworks;
|
|
4
|
-
function normalize(input) {
|
|
5
|
-
return input
|
|
6
|
-
.toLowerCase()
|
|
7
|
-
.replace(/[^a-z0-9]+/g, ' ')
|
|
8
|
-
.replace(/\s+/g, ' ')
|
|
9
|
-
.trim();
|
|
10
|
-
}
|
|
11
|
-
function scoreNameMatch(candidateName, query) {
|
|
12
|
-
const c = normalize(candidateName);
|
|
13
|
-
const q = normalize(query);
|
|
14
|
-
if (!q || !c)
|
|
15
|
-
return 0;
|
|
16
|
-
if (c === q)
|
|
17
|
-
return 100;
|
|
18
|
-
if (c.startsWith(q))
|
|
19
|
-
return 90;
|
|
20
|
-
if (c.includes(q))
|
|
21
|
-
return 75;
|
|
22
|
-
const qTokens = q.split(' ');
|
|
23
|
-
const allTokensPresent = qTokens.every(t => c.includes(t));
|
|
24
|
-
if (allTokensPresent)
|
|
25
|
-
return 70;
|
|
26
|
-
return 0;
|
|
27
|
-
}
|
|
28
|
-
function resolveRequestedNetworks(requests, networks) {
|
|
29
|
-
const chainIds = [];
|
|
30
|
-
for (const req of requests) {
|
|
31
|
-
const asNumber = Number(req);
|
|
32
|
-
if (!Number.isNaN(asNumber)) {
|
|
33
|
-
const found = networks.find(n => n.chainId === asNumber);
|
|
34
|
-
if (found) {
|
|
35
|
-
chainIds.push(found.chainId);
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
let bestScore = -1;
|
|
40
|
-
let bestMatches = [];
|
|
41
|
-
for (const n of networks) {
|
|
42
|
-
const s = scoreNameMatch(n.name, req);
|
|
43
|
-
if (s > bestScore) {
|
|
44
|
-
bestScore = s;
|
|
45
|
-
bestMatches = [n];
|
|
46
|
-
}
|
|
47
|
-
else if (s === bestScore && s > 0) {
|
|
48
|
-
bestMatches.push(n);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (bestScore < 60 || bestMatches.length === 0) {
|
|
52
|
-
throw new Error(`No network matched "${req}". Available: ${networks.map(n => `${n.name} (${n.chainId})`).join(', ')}`);
|
|
53
|
-
}
|
|
54
|
-
if (bestMatches.length > 1) {
|
|
55
|
-
const candidates = bestMatches.map(n => `${n.name} (${n.chainId})`).join(', ');
|
|
56
|
-
throw new Error(`Ambiguous network "${req}". Possible matches: ${candidates}`);
|
|
57
|
-
}
|
|
58
|
-
chainIds.push(bestMatches[0].chainId);
|
|
59
|
-
}
|
|
60
|
-
return chainIds;
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=network-match.js.map
|