@0xsequence/catapult 1.3.2 → 1.3.4

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.
Files changed (67) hide show
  1. package/CONCEPT.md +24 -0
  2. package/README.md +16 -0
  3. package/dist/commands/run.d.ts.map +1 -1
  4. package/dist/commands/run.js +4 -2
  5. package/dist/commands/run.js.map +1 -1
  6. package/dist/lib/__tests__/deployer.spec.js +71 -1
  7. package/dist/lib/__tests__/deployer.spec.js.map +1 -1
  8. package/dist/lib/core/__tests__/engine.spec.js +270 -0
  9. package/dist/lib/core/__tests__/engine.spec.js.map +1 -1
  10. package/dist/lib/core/__tests__/resolver.spec.js +132 -0
  11. package/dist/lib/core/__tests__/resolver.spec.js.map +1 -1
  12. package/dist/lib/core/engine.d.ts +13 -0
  13. package/dist/lib/core/engine.d.ts.map +1 -1
  14. package/dist/lib/core/engine.js +148 -15
  15. package/dist/lib/core/engine.js.map +1 -1
  16. package/dist/lib/core/resolver.d.ts +1 -0
  17. package/dist/lib/core/resolver.d.ts.map +1 -1
  18. package/dist/lib/core/resolver.js +24 -7
  19. package/dist/lib/core/resolver.js.map +1 -1
  20. package/dist/lib/deployer.d.ts +2 -0
  21. package/dist/lib/deployer.d.ts.map +1 -1
  22. package/dist/lib/deployer.js +18 -1
  23. package/dist/lib/deployer.js.map +1 -1
  24. package/dist/lib/events/__tests__/event-system.spec.js +30 -0
  25. package/dist/lib/events/__tests__/event-system.spec.js.map +1 -1
  26. package/dist/lib/events/cli-adapter.d.ts.map +1 -1
  27. package/dist/lib/events/cli-adapter.js +25 -1
  28. package/dist/lib/events/cli-adapter.js.map +1 -1
  29. package/dist/lib/events/types.d.ts +26 -2
  30. package/dist/lib/events/types.d.ts.map +1 -1
  31. package/dist/lib/std/templates/assured-deployment.yaml +4 -3
  32. package/dist/lib/std/templates/era-evm-predeploy.yaml +35 -0
  33. package/dist/lib/std/templates/erc-2470.yaml +3 -0
  34. package/dist/lib/std/templates/min-balance.yaml +3 -0
  35. package/dist/lib/std/templates/nano-universal-deployer.yaml +2 -0
  36. package/dist/lib/std/templates/raw-erc-2470.yaml +3 -0
  37. package/dist/lib/std/templates/raw-nano-universal-deployer.yaml +3 -0
  38. package/dist/lib/std/templates/raw-sequence-universal-deployer-2.yaml +4 -0
  39. package/dist/lib/std/templates/sequence-universal-deployer-2.yaml +4 -0
  40. package/dist/lib/types/values.d.ts +8 -1
  41. package/dist/lib/types/values.d.ts.map +1 -1
  42. package/dist/lib/utils/assertion.d.ts +4 -0
  43. package/dist/lib/utils/assertion.d.ts.map +1 -0
  44. package/dist/lib/utils/assertion.js +27 -0
  45. package/dist/lib/utils/assertion.js.map +1 -0
  46. package/package.json +12 -13
  47. package/src/commands/run.ts +4 -1
  48. package/src/lib/__tests__/deployer.spec.ts +108 -1
  49. package/src/lib/core/__tests__/engine.spec.ts +321 -0
  50. package/src/lib/core/__tests__/resolver.spec.ts +150 -1
  51. package/src/lib/core/engine.ts +188 -17
  52. package/src/lib/core/resolver.ts +29 -8
  53. package/src/lib/deployer.ts +28 -1
  54. package/src/lib/events/__tests__/event-system.spec.ts +51 -2
  55. package/src/lib/events/cli-adapter.ts +32 -4
  56. package/src/lib/events/types.ts +30 -2
  57. package/src/lib/std/templates/assured-deployment.yaml +4 -3
  58. package/src/lib/std/templates/era-evm-predeploy.yaml +35 -0
  59. package/src/lib/std/templates/erc-2470.yaml +3 -0
  60. package/src/lib/std/templates/min-balance.yaml +3 -0
  61. package/src/lib/std/templates/nano-universal-deployer.yaml +2 -0
  62. package/src/lib/std/templates/raw-erc-2470.yaml +3 -0
  63. package/src/lib/std/templates/raw-nano-universal-deployer.yaml +3 -0
  64. package/src/lib/std/templates/raw-sequence-universal-deployer-2.yaml +4 -0
  65. package/src/lib/std/templates/sequence-universal-deployer-2.yaml +4 -0
  66. package/src/lib/types/values.ts +10 -1
  67. package/src/lib/utils/assertion.ts +24 -0
@@ -13,6 +13,7 @@ export type EngineOptions = {
13
13
  verificationRegistry?: VerificationPlatformRegistry
14
14
  noPostCheckConditions?: boolean
15
15
  allowMultipleNicksMethodTests?: boolean
16
+ ignoreVerifyErrors?: boolean
16
17
  }
17
18
 
18
19
  /**
@@ -27,7 +28,17 @@ export class ExecutionEngine {
27
28
  private readonly verificationRegistry: VerificationPlatformRegistry
28
29
  private readonly noPostCheckConditions: boolean
29
30
  private readonly allowMultipleNicksMethodTests: boolean
31
+ private readonly ignoreVerifyErrors: boolean
30
32
  private nicksMethodTested: boolean = false
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
+ }> = []
31
42
 
32
43
  constructor(templates: Map<string, Template>, options?: EngineOptions) {
33
44
  this.resolver = new ValueResolver()
@@ -36,6 +47,7 @@ export class ExecutionEngine {
36
47
  this.verificationRegistry = options?.verificationRegistry || createDefaultVerificationRegistry()
37
48
  this.noPostCheckConditions = options?.noPostCheckConditions ?? false
38
49
  this.allowMultipleNicksMethodTests = options?.allowMultipleNicksMethodTests ?? false
50
+ this.ignoreVerifyErrors = options?.ignoreVerifyErrors ?? false
39
51
  }
40
52
 
41
53
  /**
@@ -631,6 +643,20 @@ export class ExecutionEngine {
631
643
  )
632
644
  anySuccess = true
633
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
+
634
660
  // Log the error but continue with other platforms
635
661
  this.events.emitEvent({
636
662
  type: 'verification_failed',
@@ -640,14 +666,26 @@ export class ExecutionEngine {
640
666
  address,
641
667
  contractName,
642
668
  platform: platform.name,
643
- error: error instanceof Error ? error.message : String(error)
669
+ error: errorMessage
644
670
  }
645
671
  })
646
672
  }
647
673
  }
648
674
 
649
675
  if (!anySuccess) {
650
- throw new Error(`Verification failed on all configured platforms for network ${network.name}`)
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
+ }
651
689
  }
652
690
  } else {
653
691
  // Handle specific platform(s) verification
@@ -673,6 +711,20 @@ export class ExecutionEngine {
673
711
  )
674
712
  anySuccess = true
675
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
+
676
728
  // Log the error but continue with other platforms if multiple specified
677
729
  this.events.emitEvent({
678
730
  type: 'verification_failed',
@@ -682,19 +734,31 @@ export class ExecutionEngine {
682
734
  address,
683
735
  contractName,
684
736
  platform: platform.name,
685
- error: error instanceof Error ? error.message : String(error)
737
+ error: errorMessage
686
738
  }
687
739
  })
688
740
 
689
- // If only one platform specified, re-throw the error
690
- if (platformsToTry.length === 1) {
741
+ // If only one platform specified, re-throw the error unless ignoreVerifyErrors is enabled
742
+ if (platformsToTry.length === 1 && !this.ignoreVerifyErrors) {
691
743
  throw error
692
744
  }
693
745
  }
694
746
  }
695
747
 
696
748
  if (!anySuccess && platformsToTry.length > 1) {
697
- throw new Error(`Verification failed on all specified platforms: ${platformsToTry.join(', ')}`)
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
+ }
698
762
  }
699
763
  }
700
764
 
@@ -1126,9 +1190,76 @@ export class ExecutionEngine {
1126
1190
 
1127
1191
  // Generate a valid ECDSA signature using Nick's method approach
1128
1192
  const result = await this.generateNicksMethodTransaction(bytecode, defaultGasPrice, defaultGasLimit)
1129
- const rawTx = result.rawTx
1193
+ const {signedTx, unsignedTx} = result
1130
1194
  eoaAddress = result.eoaAddress
1131
1195
  wallet = result.wallet
1196
+
1197
+ // Simulate the contract creation transaction
1198
+ try {
1199
+ const simulationTx = {
1200
+ ...unsignedTx,
1201
+ from: eoaAddress,
1202
+ };
1203
+
1204
+ if (unsignedTx.gasPrice) {
1205
+ // Check gas price
1206
+ const gasPrice = await context.provider.getFeeData().then(data => data.gasPrice)
1207
+ if (!gasPrice) {
1208
+ this.events.emitEvent({
1209
+ type: "debug_info",
1210
+ level: "debug",
1211
+ data: {
1212
+ message: `Legacy gas price not available.`,
1213
+ },
1214
+ });
1215
+ } else if (BigInt(unsignedTx.gasPrice.toString()) < gasPrice) {
1216
+ this.events.emitEvent({
1217
+ type: "debug_info",
1218
+ level: "warn",
1219
+ data: {
1220
+ message: `Gas price (${unsignedTx.gasPrice}) is lower than the current gas price (${gasPrice}). This may cause the transaction to not be mined.`,
1221
+ },
1222
+ });
1223
+ }
1224
+ }
1225
+
1226
+ if (simulationTx.gasLimit) {
1227
+ // Simulate the transaction expected gas usage
1228
+ const estimatedGas = await context.provider.estimateGas(simulationTx);
1229
+ const estimatedGasStr = estimatedGas.toString();
1230
+ const simulationTxGasLimitStr = simulationTx.gasLimit.toString();
1231
+ if (estimatedGas > BigInt(simulationTxGasLimitStr)) {
1232
+ this.events.emitEvent({
1233
+ type: "debug_info",
1234
+ level: "warn",
1235
+ data: {
1236
+ message: `Estimated gas (${estimatedGasStr}) is greater than gas provided in the transaction (${simulationTxGasLimitStr}). This may cause the transaction to revert.`,
1237
+ },
1238
+ });
1239
+ } else {
1240
+ this.events.emitEvent({
1241
+ type: "debug_info",
1242
+ level: "debug",
1243
+ data: {
1244
+ message: `Estimated gas: ${estimatedGasStr}, Gas provided: ${simulationTxGasLimitStr}`,
1245
+ },
1246
+ });
1247
+ }
1248
+ }
1249
+ } catch (simulationError) {
1250
+ this.events.emitEvent({
1251
+ type: "debug_info",
1252
+ level: "warn",
1253
+ data: {
1254
+ message: `Simulation failed: ${
1255
+ simulationError instanceof Error
1256
+ ? simulationError.message
1257
+ : String(simulationError)
1258
+ }`,
1259
+ },
1260
+ });
1261
+ // Continue with the test even if simulation fails
1262
+ }
1132
1263
 
1133
1264
  this.events.emitEvent({
1134
1265
  type: 'debug_info',
@@ -1221,11 +1352,11 @@ export class ExecutionEngine {
1221
1352
  type: 'debug_info',
1222
1353
  level: 'debug',
1223
1354
  data: {
1224
- message: `[NICK'S METHOD DEBUG] Broadcasting Nick's method transaction. RawTx: ${rawTx.substring(0, 100)}...`
1355
+ message: `[NICK'S METHOD DEBUG] Broadcasting Nick's method transaction. RawTx: ${signedTx.substring(0, 100)}...`
1225
1356
  }
1226
1357
  })
1227
1358
 
1228
- const deployTx = await context.provider.broadcastTransaction(rawTx)
1359
+ const deployTx = await context.provider.broadcastTransaction(signedTx)
1229
1360
 
1230
1361
  this.events.emitEvent({
1231
1362
  type: 'debug_info',
@@ -1322,7 +1453,7 @@ export class ExecutionEngine {
1322
1453
  bytecode: string,
1323
1454
  gasPrice: ethers.BigNumberish,
1324
1455
  gasLimit: ethers.BigNumberish
1325
- ): Promise<{ rawTx: string; eoaAddress: string; wallet: ethers.HDNodeWallet }> {
1456
+ ): Promise<{ unsignedTx: ethers.TransactionRequest; signedTx: string; eoaAddress: string; wallet: ethers.HDNodeWallet }> {
1326
1457
  // Generate a random private key for the test
1327
1458
  const wallet = ethers.Wallet.createRandom()
1328
1459
 
@@ -1346,9 +1477,10 @@ export class ExecutionEngine {
1346
1477
  const eoaAddress = parsedTx.from!
1347
1478
 
1348
1479
  return {
1349
- rawTx: signedTx,
1350
- eoaAddress: eoaAddress,
1351
- wallet: wallet
1480
+ unsignedTx,
1481
+ signedTx,
1482
+ eoaAddress,
1483
+ wallet,
1352
1484
  }
1353
1485
  }
1354
1486
 
@@ -1372,9 +1504,26 @@ export class ExecutionEngine {
1372
1504
  const connectedWallet = wallet.connect(context.provider)
1373
1505
 
1374
1506
  // Estimate gas for a simple transfer
1375
- const gasPrice = await context.provider.getFeeData().then(data => data.gasPrice || ethers.parseUnits('20', 'gwei'))
1507
+ const feeData = await context.provider.getFeeData()
1508
+ const txGas = feeData.maxFeePerGas ? {
1509
+ maxFeePerGas: feeData.maxFeePerGas,
1510
+ maxPriorityFeePerGas: feeData.maxPriorityFeePerGas || ethers.parseUnits('20', 'gwei')
1511
+ } : {
1512
+ gasPrice: feeData.gasPrice || undefined,
1513
+ }
1514
+ const effectiveGasPrice = txGas.maxFeePerGas || txGas.gasPrice
1515
+ if (!effectiveGasPrice) {
1516
+ this.events.emitEvent({
1517
+ type: 'action_failed',
1518
+ level: 'error',
1519
+ data: {
1520
+ message: `No gas price available`
1521
+ }
1522
+ })
1523
+ return
1524
+ }
1376
1525
  const gasLimit = 21000n // Standard gas limit for ETH transfer
1377
- const gasCost = BigInt(gasPrice.toString()) * gasLimit
1526
+ const gasCost = effectiveGasPrice * gasLimit
1378
1527
 
1379
1528
  // Check if we have enough balance to cover gas costs
1380
1529
  if (remainingBalance <= gasCost) {
@@ -1406,8 +1555,8 @@ export class ExecutionEngine {
1406
1555
  const returnTx = await connectedWallet.sendTransaction({
1407
1556
  to: await (await context.getResolvedSigner()).getAddress(),
1408
1557
  value: amountToSend,
1409
- gasPrice: gasPrice,
1410
- gasLimit: gasLimit
1558
+ gasLimit: gasLimit,
1559
+ ...txGas,
1411
1560
  })
1412
1561
 
1413
1562
  await returnTx.wait()
@@ -1550,4 +1699,26 @@ export class ExecutionEngine {
1550
1699
 
1551
1700
  return sorted
1552
1701
  }
1702
+
1703
+ /**
1704
+ * Get all verification warnings that were collected when ignoreVerifyErrors is enabled
1705
+ */
1706
+ public getVerificationWarnings(): Array<{
1707
+ actionName: string
1708
+ address: string
1709
+ contractName: string
1710
+ platform: string
1711
+ error: string
1712
+ jobName?: string
1713
+ networkName?: string
1714
+ }> {
1715
+ return [...this.verificationWarnings]
1716
+ }
1717
+
1718
+ /**
1719
+ * Clear verification warnings (useful for testing)
1720
+ */
1721
+ public clearVerificationWarnings(): void {
1722
+ this.verificationWarnings = []
1723
+ }
1553
1724
  }
@@ -5,16 +5,17 @@ import {
5
5
  AbiEncodeValue,
6
6
  AbiPackValue,
7
7
  ConstructorEncodeValue,
8
+ ComputeCreateValue,
8
9
  ComputeCreate2Value,
9
10
  ReadBalanceValue,
10
11
  BasicArithmeticValue,
11
12
  CallValue,
12
- ContractExistsCondition,
13
13
  ContractExistsValue,
14
14
  JobCompletedValue,
15
15
  ReadJsonValue,
16
16
  } from '../types'
17
17
  import { ExecutionContext } from './context'
18
+ import { isAddress, isBigNumberish, isBytesLike } from '../utils/assertion'
18
19
 
19
20
  /**
20
21
  * A scope for resolving local variables, such as template arguments.
@@ -154,6 +155,8 @@ export class ValueResolver {
154
155
  return this.resolveAbiPack(resolvedArgs as AbiPackValue['arguments'])
155
156
  case 'constructor-encode':
156
157
  return this.resolveConstructorEncode(resolvedArgs as ConstructorEncodeValue['arguments'])
158
+ case 'compute-create':
159
+ return this.resolveComputeCreate(resolvedArgs as ComputeCreateValue['arguments'])
157
160
  case 'compute-create2':
158
161
  return this.resolveComputeCreate2(resolvedArgs as ComputeCreate2Value['arguments'])
159
162
  case 'read-balance':
@@ -260,7 +263,7 @@ export class ValueResolver {
260
263
  }
261
264
 
262
265
  // Validate that creation code is valid bytecode
263
- if (!ethers.isBytesLike(creationCode)) {
266
+ if (!isBytesLike(creationCode)) {
264
267
  throw new Error(`Invalid creation code: ${creationCode}`)
265
268
  }
266
269
 
@@ -280,18 +283,36 @@ export class ValueResolver {
280
283
  return '0x' + cleanCreationCode + cleanEncodedArgs
281
284
  }
282
285
 
286
+ private resolveComputeCreate(args: ComputeCreateValue['arguments']): string {
287
+ const { deployerAddress, nonce } = args
288
+ // Check if the deployer address is a valid address
289
+ if (!isAddress(deployerAddress)) {
290
+ throw new Error(`Invalid deployer address: ${deployerAddress}`)
291
+ }
292
+ // Check if the nonce is a valid value
293
+ if (!isBigNumberish(nonce)) {
294
+ throw new Error(`Invalid nonce: ${nonce}`)
295
+ }
296
+ const bnNonce = ethers.toBigInt(nonce)
297
+ // Create the create address
298
+ return ethers.getCreateAddress({
299
+ from: deployerAddress,
300
+ nonce: bnNonce,
301
+ })
302
+ }
303
+
283
304
  private resolveComputeCreate2(args: ComputeCreate2Value['arguments']): string {
284
305
  const { deployerAddress, salt, initCode } = args
285
306
  // Check if the deployer address is a valid address
286
- if (!ethers.isAddress(deployerAddress)) {
307
+ if (!isAddress(deployerAddress)) {
287
308
  throw new Error(`Invalid deployer address: ${deployerAddress}`)
288
309
  }
289
310
  // Check if the salt is a valid bytes value
290
- if (!ethers.isBytesLike(salt)) {
311
+ if (!isBytesLike(salt)) {
291
312
  throw new Error(`Invalid salt: ${salt}`)
292
313
  }
293
314
  // Check if the init code is a valid bytes value
294
- if (!ethers.isBytesLike(initCode)) {
315
+ if (!isBytesLike(initCode)) {
295
316
  throw new Error(`Invalid init code: ${initCode}`)
296
317
  }
297
318
  // Hash the init code using Keccak256
@@ -304,7 +325,7 @@ export class ValueResolver {
304
325
  // Check if the address is a valid address
305
326
  const addressValue = args.address as any
306
327
 
307
- if (!ethers.isAddress(addressValue)) {
328
+ if (!isAddress(addressValue)) {
308
329
  throw new Error(`Invalid address: ${addressValue}`)
309
330
  }
310
331
 
@@ -349,7 +370,7 @@ export class ValueResolver {
349
370
  }
350
371
 
351
372
  // Validate that the target address is a valid Ethereum address
352
- if (!ethers.isAddress(to)) {
373
+ if (!isAddress(to)) {
353
374
  throw new Error(`call: invalid target address: ${to}`)
354
375
  }
355
376
 
@@ -407,7 +428,7 @@ export class ValueResolver {
407
428
  private async resolveContractExists(args: ContractExistsValue['arguments'], context: ExecutionContext): Promise<boolean> {
408
429
  const { address } = args
409
430
 
410
- if (!ethers.isAddress(address)) {
431
+ if (!isAddress(address)) {
411
432
  throw new Error(`contract-exists: invalid address: ${address}`)
412
433
  }
413
434
 
@@ -52,6 +52,9 @@ export interface DeployerOptions {
52
52
 
53
53
  /** Optional: Show end-of-run summary (default: true). */
54
54
  showSummary?: boolean
55
+
56
+ /** Optional: Convert verification errors to warnings instead of failing (default: false). */
57
+ ignoreVerifyErrors?: boolean
55
58
  }
56
59
 
57
60
  /**
@@ -157,7 +160,8 @@ export class Deployer {
157
160
  const engine = new ExecutionEngine(this.loader.templates, {
158
161
  eventEmitter: this.events,
159
162
  verificationRegistry,
160
- noPostCheckConditions: this.noPostCheckConditions
163
+ noPostCheckConditions: this.noPostCheckConditions,
164
+ ignoreVerifyErrors: this.options.ignoreVerifyErrors ?? false
161
165
  })
162
166
 
163
167
  // Track if any jobs have failed
@@ -312,6 +316,11 @@ export class Deployer {
312
316
  // 5. Write results to output files.
313
317
  await this.writeOutputFiles()
314
318
 
319
+ // Show verification warnings report if ignoreVerifyErrors is enabled
320
+ if (this.options.ignoreVerifyErrors) {
321
+ this.emitVerificationWarningsReport(engine)
322
+ }
323
+
315
324
  // Emit end-of-run summary before final status
316
325
  if (this.showSummary) {
317
326
  this.emitRunSummary(hasFailures)
@@ -423,6 +432,24 @@ export class Deployer {
423
432
  this.events.emitEvent(summaryEvent)
424
433
  }
425
434
 
435
+ /**
436
+ * Emit verification warnings report when ignoreVerifyErrors is enabled
437
+ */
438
+ private emitVerificationWarningsReport(engine: ExecutionEngine): void {
439
+ const warnings = engine.getVerificationWarnings()
440
+
441
+ if (warnings.length > 0) {
442
+ this.events.emitEvent({
443
+ type: 'verification_warnings_report',
444
+ level: 'warn',
445
+ data: {
446
+ totalWarnings: warnings.length,
447
+ warnings: warnings
448
+ }
449
+ })
450
+ }
451
+ }
452
+
426
453
  /**
427
454
  * Determines the final, ordered list of jobs to execute based on user input.
428
455
  * If a user requests specific jobs, this ensures all their dependencies are also included.
@@ -11,7 +11,7 @@ describe('Event System', () => {
11
11
  beforeEach(() => {
12
12
  eventEmitter = new DeploymentEventEmitter()
13
13
  cliAdapter = new CLIEventAdapter(eventEmitter, 3)
14
-
14
+
15
15
  // Mock console methods
16
16
  consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
17
17
  consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
@@ -339,5 +339,54 @@ describe('Event System', () => {
339
339
 
340
340
  expect(handler).not.toHaveBeenCalled()
341
341
  })
342
+
343
+ it('should respect debug_info event level for verbosity filtering', () => {
344
+ // Test with verbosity 0 (default) - should NOT show any debug_info events
345
+ cliAdapter.setVerbosity(0)
346
+
347
+ eventEmitter.emitEvent({
348
+ type: 'debug_info',
349
+ level: 'warn',
350
+ data: {
351
+ message: 'This warning should NOT be shown at verbosity 0'
352
+ }
353
+ })
354
+
355
+ expect(consoleLogSpy).not.toHaveBeenCalled()
356
+
357
+ // Clear previous calls
358
+ consoleLogSpy.mockClear()
359
+
360
+ // Test with verbosity 3 - should show warn level with correct formatting
361
+ cliAdapter.setVerbosity(3)
362
+
363
+ eventEmitter.emitEvent({
364
+ type: 'debug_info',
365
+ level: 'warn',
366
+ data: {
367
+ message: 'This warning should be shown at verbosity 3'
368
+ }
369
+ })
370
+
371
+ expect(consoleLogSpy).toHaveBeenCalledWith(
372
+ expect.stringContaining('[WARN] This warning should be shown at verbosity 3')
373
+ )
374
+
375
+ // Clear previous calls
376
+ consoleLogSpy.mockClear()
377
+
378
+ // Test with verbosity 3 - should show debug level with correct formatting
379
+ eventEmitter.emitEvent({
380
+ type: 'debug_info',
381
+ level: 'debug',
382
+ data: {
383
+ message: 'This debug message should be shown at verbosity 3'
384
+ }
385
+ })
386
+
387
+ expect(consoleLogSpy).toHaveBeenCalledWith(
388
+ expect.stringContaining('[DEBUG] This debug message should be shown at verbosity 3')
389
+ )
390
+ })
342
391
  })
343
- })
392
+ })
@@ -5,7 +5,7 @@ import { DeploymentEventEmitter } from './emitter'
5
5
  /**
6
6
  * Verbosity levels for filtering console output:
7
7
  * 0 (default): Critical info only - errors, warnings, main deployment steps
8
- * 1 (-v): Add transaction details and verification steps
8
+ * 1 (-v): Add transaction details and verification steps
9
9
  * 2 (-vv): Add action details and file operations
10
10
  * 3 (-vvv): Full debug - show everything including template transitions
11
11
  */
@@ -81,7 +81,7 @@ export class CLIEventAdapter {
81
81
  if (level1Events.has(eventType)) return 1
82
82
  if (level2Events.has(eventType)) return 2
83
83
  if (level3Events.has(eventType)) return 3
84
-
84
+
85
85
  // Default to level 3 for any new events we haven't categorized
86
86
  return 3
87
87
  }
@@ -266,6 +266,30 @@ export class CLIEventAdapter {
266
266
  // Keep short to reduce noise
267
267
  console.log(chalk.gray(` Verification retry ${event.data.attempt}/${event.data.maxRetries}: ${event.data.error}`))
268
268
  break
269
+
270
+ case 'verification_skipped':
271
+ console.log(chalk.yellow(` ⚠️ ${event.data.reason}`))
272
+ break
273
+
274
+ case 'verification_warnings_report':
275
+ // Display detailed verification warnings report
276
+ console.log(chalk.yellow('\n📋 Verification Warnings Report'))
277
+ console.log(chalk.yellow(` Total warnings: ${event.data.totalWarnings}`))
278
+ console.log('')
279
+
280
+ if (event.data.warnings && event.data.warnings.length > 0) {
281
+ for (const warning of event.data.warnings) {
282
+ console.log(chalk.red(` ❌ ${warning.actionName} (${warning.contractName})`))
283
+ console.log(chalk.gray(` Address: ${warning.address}`))
284
+ console.log(chalk.gray(` Platform: ${warning.platform}`))
285
+ if (warning.networkName) {
286
+ console.log(chalk.gray(` Network: ${warning.networkName}`))
287
+ }
288
+ console.log(chalk.gray(` Error: ${warning.error}`))
289
+ console.log('')
290
+ }
291
+ }
292
+ break
269
293
  case 'contract_created':
270
294
  console.log(chalk.gray(` contract: ${event.data.contractAddress}`))
271
295
  break
@@ -314,7 +338,11 @@ export class CLIEventAdapter {
314
338
  break
315
339
 
316
340
  case 'debug_info':
317
- console.log(chalk.gray(` [DEBUG] ${event.data.message}`))
341
+ const levelPrefix = event.level.toUpperCase()
342
+ const levelColor = event.level === 'warn' ? chalk.yellow :
343
+ event.level === 'info' ? chalk.blue :
344
+ chalk.gray
345
+ console.log(levelColor(` [${levelPrefix}] ${event.data.message}`))
318
346
  break
319
347
 
320
348
  default:
@@ -338,4 +366,4 @@ export class CLIEventAdapter {
338
366
  public destroy(): void {
339
367
  this.emitter.removeAllListeners()
340
368
  }
341
- }
369
+ }
@@ -147,7 +147,7 @@ export interface ActionInfoEvent extends BaseEvent {
147
147
 
148
148
  export interface DebugInfoEvent extends BaseEvent {
149
149
  type: 'debug_info'
150
- level: 'debug'
150
+ level: 'debug' | 'info' | 'warn'
151
151
  data: {
152
152
  message: string
153
153
  }
@@ -429,6 +429,32 @@ export interface VerificationRetryEvent extends BaseEvent {
429
429
  }
430
430
  }
431
431
 
432
+ export interface VerificationSkippedEvent extends BaseEvent {
433
+ type: 'verification_skipped'
434
+ level: 'warn'
435
+ data: {
436
+ actionName: string
437
+ reason: string
438
+ }
439
+ }
440
+
441
+ export interface VerificationWarningsReportEvent extends BaseEvent {
442
+ type: 'verification_warnings_report'
443
+ level: 'warn'
444
+ data: {
445
+ totalWarnings: number
446
+ warnings: Array<{
447
+ actionName: string
448
+ address: string
449
+ contractName: string
450
+ platform: string
451
+ error: string
452
+ jobName?: string
453
+ networkName?: string
454
+ }>
455
+ }
456
+ }
457
+
432
458
  // End-of-run summary
433
459
  export interface RunSummaryEvent extends BaseEvent {
434
460
  type: 'run_summary'
@@ -488,5 +514,7 @@ export type DeploymentEvent =
488
514
  | VerificationSubmittedEvent
489
515
  | VerificationCompletedEvent
490
516
  | VerificationFailedEvent
491
- | VerificationRetryEvent
517
+ | VerificationRetryEvent
518
+ | VerificationSkippedEvent
519
+ | VerificationWarningsReportEvent
492
520
  | RunSummaryEvent
@@ -13,17 +13,18 @@
13
13
  # code before the call data, hence the wrapping pattern.
14
14
  name: "assured-deployment"
15
15
  type: "template"
16
+ description: "Wraps a factory call with a CREATE that runs a minimal assurance program"
16
17
 
17
18
  arguments:
18
- # The address that is expected to exist after the factory call (e.g., CREATE2 result)
19
19
  targetAddress:
20
20
  type: "address"
21
- # The factory that will perform the deployment
21
+ description: "The address that is expected to exist after the factory call (e.g., CREATE2 result)"
22
22
  factoryAddress:
23
23
  type: "address"
24
- # Encoded call to the factory (e.g., abi-encoded deploy(...))
24
+ description: "The factory that will perform the deployment"
25
25
  callData:
26
26
  type: "bytes"
27
+ description: "The encoded call to the factory (e.g., abi-encoded deploy(...))"
27
28
 
28
29
  actions:
29
30
  - type: "create-contract"