@0xsequence/catapult 1.3.13 → 1.3.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xsequence/catapult",
3
- "version": "1.3.13",
3
+ "version": "1.3.15",
4
4
  "description": "Ethereum contract deployment CLI tool",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -460,6 +460,34 @@ describe('Deployer', () => {
460
460
  'deploy2.address': '0xdeploy2'
461
461
  })
462
462
  })
463
+
464
+ it('should keep outputs empty when only skipped opt-in actions produce no keys', async () => {
465
+ const jobWithSkippedOptIn: Job = {
466
+ name: 'job-skipped-opt-in',
467
+ version: '1.0.0',
468
+ description: 'Job where the only opted-in action produced no outputs',
469
+ actions: [
470
+ { name: 'deploy-action', template: 'template1', arguments: {}, output: false },
471
+ { name: 'skipped-action', template: 'template1', arguments: {}, output: true }
472
+ ]
473
+ }
474
+
475
+ mockLoader.jobs.clear()
476
+ mockLoader.jobs.set('job-skipped-opt-in', jobWithSkippedOptIn)
477
+ mockGraph.getExecutionOrder.mockReturnValue(['job-skipped-opt-in'])
478
+
479
+ mockContext.getOutputs.mockReturnValue(new Map<string, any>([
480
+ ['deploy-action.address', '0xdeployaddress']
481
+ ]))
482
+
483
+ const deployer = new Deployer(deployerOptions)
484
+ await deployer.run()
485
+
486
+ const outputCall = mockFs.writeFile.mock.calls[0]
487
+ const outputContent = JSON.parse(outputCall[1] as string)
488
+
489
+ expect(outputContent.networks[0].outputs).toEqual({})
490
+ })
463
491
  })
464
492
 
465
493
  describe('error handling', () => {
@@ -0,0 +1,37 @@
1
+ import { ethers } from 'ethers'
2
+ import { ExecutionContext } from '../context'
3
+ import { Network } from '../../types'
4
+
5
+ describe('ExecutionContext', () => {
6
+ const network: Network = {
7
+ name: 'test',
8
+ chainId: 31337,
9
+ rpcUrl: 'http://127.0.0.1:8545'
10
+ }
11
+
12
+ const contractRepository = {} as any
13
+
14
+ it('wraps private-key signers in a NonceManager', async () => {
15
+ const context = new ExecutionContext(
16
+ network,
17
+ '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
18
+ contractRepository
19
+ )
20
+
21
+ const signer = await context.getResolvedSigner()
22
+ expect(signer).toBeInstanceOf(ethers.NonceManager)
23
+ })
24
+
25
+ it('wraps promised signers in a NonceManager when first resolved', async () => {
26
+ const getSignerSpy = jest.spyOn(ethers.JsonRpcProvider.prototype, 'getSigner')
27
+ getSignerSpy.mockResolvedValue(
28
+ ethers.Wallet.createRandom().connect(new ethers.JsonRpcProvider(network.rpcUrl)) as any
29
+ )
30
+
31
+ const context = new ExecutionContext(network, undefined, contractRepository)
32
+
33
+ await expect(context.getResolvedSigner()).resolves.toBeInstanceOf(ethers.NonceManager)
34
+
35
+ getSignerSpy.mockRestore()
36
+ })
37
+ })
@@ -102,6 +102,28 @@ describe('ValueResolver', () => {
102
102
  expect(await resolver.resolve(valueFalse, context)).toBe(false)
103
103
  })
104
104
 
105
+ it('should treat nullish nested values as non-equal without throwing', async () => {
106
+ const value: BasicArithmeticValue = {
107
+ type: 'basic-arithmetic',
108
+ arguments: {
109
+ operation: 'eq',
110
+ values: [
111
+ {
112
+ type: 'call',
113
+ arguments: {
114
+ to: '0x1234567890123456789012345678901234567890',
115
+ signature: 'imageHash() returns (bytes32)',
116
+ values: [],
117
+ },
118
+ },
119
+ '0xfd72d46fd0d0a98780315f81790f051356f2f3101af4b5c3fb1c6da3237fb765',
120
+ ],
121
+ },
122
+ }
123
+
124
+ await expect(resolver.resolve(value, context)).resolves.toBe(false)
125
+ })
126
+
105
127
  it('should correctly evaluate "gt" (greater than)', async () => {
106
128
  const valueTrue: BasicArithmeticValue = { type: 'basic-arithmetic', arguments: { operation: 'gt', values: [10, 5] } }
107
129
  const valueFalse: BasicArithmeticValue = { type: 'basic-arithmetic', arguments: { operation: 'gt', values: [10, 10] } }
@@ -33,11 +33,11 @@ export class ExecutionContext {
33
33
 
34
34
  // Determine the signer
35
35
  if (privateKey) {
36
- this.signer = new ethers.Wallet(privateKey, this.provider)
36
+ this.signer = new ethers.NonceManager(new ethers.Wallet(privateKey, this.provider))
37
37
  } else if (network.rpcUrl) {
38
38
  // If no private key, but RPC URL is provided, get a signer from the provider.
39
39
  // This returns a Promise that we need to resolve on first use.
40
- this.signer = this.provider.getSigner() // Keep as Promise
40
+ this.signer = this.provider.getSigner().then(signer => new ethers.NonceManager(signer)) // Keep as Promise
41
41
  } else {
42
42
  throw new Error('A private key must be provided or an RPC URL must be configured to obtain a signer for the network.')
43
43
  }
@@ -124,4 +124,4 @@ export class ExecutionContext {
124
124
  // Ignore errors during cleanup
125
125
  }
126
126
  }
127
- }
127
+ }
@@ -362,19 +362,42 @@ export class ValueResolver {
362
362
  throw new Error(`basic-arithmetic requires at least 2 values, got ${args.values?.length ?? 0}`)
363
363
  }
364
364
 
365
+ switch (args.operation) {
366
+ // Equality (return boolean, tolerate nullish / non-numeric values)
367
+ case 'eq': {
368
+ const [a, b] = args.values
369
+ return this.valuesEqual(a, b)
370
+ }
371
+ case 'neq': {
372
+ const [a, b] = args.values
373
+ return !this.valuesEqual(a, b)
374
+ }
375
+
376
+ // Arithmetic (return string)
377
+ case 'add':
378
+ case 'sub':
379
+ case 'mul':
380
+ case 'div':
381
+ case 'gt':
382
+ case 'lt':
383
+ case 'gte':
384
+ case 'lte':
385
+ break
386
+
387
+ default:
388
+ throw new Error(`Unsupported basic-arithmetic operation: ${args.operation}`)
389
+ }
390
+
365
391
  const numbers = args.values.map(v => ethers.toBigInt(v))
366
392
  const [a, b] = numbers
367
393
 
368
394
  switch (args.operation) {
369
- // Arithmetic (return string)
370
395
  case 'add': return numbers.reduce((sum, current) => sum + current).toString()
371
396
  case 'sub': return (a - b).toString()
372
397
  case 'mul': return (a * b).toString()
373
398
  case 'div': return (a / b).toString()
374
399
 
375
400
  // Comparison (return boolean)
376
- case 'eq': return a === b
377
- case 'neq': return a !== b
378
401
  case 'gt': return a > b
379
402
  case 'lt': return a < b
380
403
  case 'gte': return a >= b
@@ -385,6 +408,30 @@ export class ValueResolver {
385
408
  }
386
409
  }
387
410
 
411
+ private valuesEqual(a: any, b: any): boolean {
412
+ if (a == null || b == null) {
413
+ return a == null && b == null
414
+ }
415
+
416
+ if (isBigNumberish(a) && isBigNumberish(b)) {
417
+ return ethers.toBigInt(a) === ethers.toBigInt(b)
418
+ }
419
+
420
+ if (typeof a === 'string' && typeof b === 'string') {
421
+ return a === b
422
+ }
423
+
424
+ if (typeof a === 'boolean' && typeof b === 'boolean') {
425
+ return a === b
426
+ }
427
+
428
+ if ((typeof a === 'object' && a !== null) || (typeof b === 'object' && b !== null)) {
429
+ return JSON.stringify(a) === JSON.stringify(b)
430
+ }
431
+
432
+ return a === b
433
+ }
434
+
388
435
  private async resolveCall(args: CallValue['arguments'], context: ExecutionContext): Promise<any> {
389
436
  const { to, signature, values } = args
390
437
 
@@ -879,8 +879,10 @@ export class Deployer {
879
879
 
880
880
  // 3) Exclude any actions explicitly marked false (they won't be included by rules above anyway)
881
881
 
882
- // If we have any inclusions (custom maps or trues), return them
883
- if (result.size > 0) {
882
+ const hasExplicitOutputSelection = actionsWithCustomMap.length > 0 || actionsWithTrue.length > 0
883
+
884
+ // If the job has any explicit output selection, return the selected outputs even when empty.
885
+ if (hasExplicitOutputSelection) {
884
886
  // Additionally, filter out any accidentally included outputs from actions marked false
885
887
  for (const falseActionName of actionsWithFalse) {
886
888
  for (const key of Array.from(result.keys())) {
@@ -976,4 +978,4 @@ export class Deployer {
976
978
  // Return all entries: successes first, then errors
977
979
  return [...successEntries, ...errorEntries]
978
980
  }
979
- }
981
+ }