@0xsequence/catapult 1.3.14 → 1.3.16

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 (33) hide show
  1. package/dist/lib/__tests__/deployer.spec.js +22 -0
  2. package/dist/lib/__tests__/deployer.spec.js.map +1 -1
  3. package/dist/lib/__tests__/network-loader.spec.js +44 -0
  4. package/dist/lib/__tests__/network-loader.spec.js.map +1 -1
  5. package/dist/lib/core/__tests__/context.spec.d.ts +2 -0
  6. package/dist/lib/core/__tests__/context.spec.d.ts.map +1 -0
  7. package/dist/lib/core/__tests__/context.spec.js +25 -0
  8. package/dist/lib/core/__tests__/context.spec.js.map +1 -0
  9. package/dist/lib/core/__tests__/resolver.spec.js +40 -0
  10. package/dist/lib/core/__tests__/resolver.spec.js.map +1 -1
  11. package/dist/lib/core/context.js +2 -2
  12. package/dist/lib/core/context.js.map +1 -1
  13. package/dist/lib/core/resolver.d.ts.map +1 -1
  14. package/dist/lib/core/resolver.js +23 -8
  15. package/dist/lib/core/resolver.js.map +1 -1
  16. package/dist/lib/deployer.d.ts.map +1 -1
  17. package/dist/lib/deployer.js +2 -1
  18. package/dist/lib/deployer.js.map +1 -1
  19. package/dist/lib/network-loader.d.ts.map +1 -1
  20. package/dist/lib/network-loader.js +4 -1
  21. package/dist/lib/network-loader.js.map +1 -1
  22. package/dist/lib/types/network.d.ts +1 -0
  23. package/dist/lib/types/network.d.ts.map +1 -1
  24. package/package.json +1 -1
  25. package/src/lib/__tests__/deployer.spec.ts +28 -0
  26. package/src/lib/__tests__/network-loader.spec.ts +50 -0
  27. package/src/lib/core/__tests__/context.spec.ts +37 -0
  28. package/src/lib/core/__tests__/resolver.spec.ts +42 -0
  29. package/src/lib/core/context.ts +3 -3
  30. package/src/lib/core/resolver.ts +28 -10
  31. package/src/lib/deployer.ts +5 -3
  32. package/src/lib/network-loader.ts +7 -1
  33. package/src/lib/types/network.ts +5 -0
@@ -1 +1 @@
1
- {"version":3,"file":"network-loader.js","sourceRoot":"","sources":["../../src/lib/network-loader.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,oCA+BC;AAnFD,gDAAiC;AACjC,2CAA4B;AAC5B,+BAAyC;AAGzC,SAAS,cAAc,CAAC,GAAY;IAClC,OAAO,CACL,OAAO,GAAG,KAAK,QAAQ;QACvB,GAAG,KAAK,IAAI;QACZ,MAAM,IAAI,GAAG;QACb,SAAS,IAAI,GAAG;QAChB,QAAQ,IAAI,GAAG;QACf,OAAQ,GAA+B,CAAC,IAAI,KAAK,QAAQ;QACzD,OAAQ,GAA+B,CAAC,OAAO,KAAK,QAAQ;QAC5D,OAAQ,GAA+B,CAAC,MAAM,KAAK,QAAQ;QAE3D,CAAC,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC;YACpB,CAAC,KAAK,CAAC,OAAO,CAAE,GAA+B,CAAC,QAAQ,CAAC;gBACtD,GAA+B,CAAC,QAAsB,CAAC,KAAK,CAAC,CAAC,IAAa,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC;QAE/G,CAAC,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC,IAAI,OAAQ,GAA+B,CAAC,QAAQ,KAAK,QAAQ,CAAC;QAEvF,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,IAAI,OAAQ,GAA+B,CAAC,OAAO,KAAK,SAAS,CAAC;QAEtF,CAAC,CAAC,CAAC,YAAY,IAAI,GAAG,CAAC,IAAI,OAAQ,GAA+B,CAAC,UAAU,KAAK,QAAQ,CAAC,CAC5F,CAAA;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,MAAc;IAGzC,MAAM,WAAW,GAAG,gCAAgC,CAAA;IACpD,OAAO,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAa,EAAE,OAAe,EAAE,EAAE;QACpE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YAE/B,OAAO,KAAK,CAAA;QACd,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAClC,IAAI,OAAO,KAAK,KAAK,WAAW,EAAE,CAAC;YAEjC,OAAO,EAAE,CAAA;QACX,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC,CAAC,CAAA;AACJ,CAAC;AAQM,KAAK,UAAU,YAAY,CAAC,WAAmB;IACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,CAAA;IACxD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QACpD,MAAM,MAAM,GAAG,IAAA,YAAS,EAAC,OAAO,CAAC,CAAA;QAEjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAA;QACnF,CAAC;QAED,MAAM,QAAQ,GAAc,EAAE,CAAA;QAC9B,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,KAAK,CAAC,yDAAyD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YAClG,CAAC;YAED,MAAM,cAAc,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACvD,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAA;QACpD,CAAC;QACD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;YAC1D,MAAM,WAAW,GAAG,KAA2B,CAAA;YAC/C,IAAI,WAAW,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAElC,OAAO,EAAE,CAAA;YACX,CAAC;QACH,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACtE,MAAM,IAAI,KAAK,CAAC,0CAA0C,OAAO,EAAE,CAAC,CAAA;IACtE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"network-loader.js","sourceRoot":"","sources":["../../src/lib/network-loader.ts"],"names":[],"mappings":"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"}
@@ -6,5 +6,6 @@ export interface Network {
6
6
  gasLimit?: number;
7
7
  testnet?: boolean;
8
8
  evmVersion?: string;
9
+ params?: Record<string, unknown>;
9
10
  }
10
11
  //# sourceMappingURL=network.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../../src/lib/types/network.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,OAAO;IAEtB,IAAI,EAAE,MAAM,CAAA;IAGZ,OAAO,EAAE,MAAM,CAAA;IAGf,MAAM,EAAE,MAAM,CAAA;IAGd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IAGnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IAGjB,OAAO,CAAC,EAAE,OAAO,CAAA;IAMjB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB"}
1
+ {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../../src/lib/types/network.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,OAAO;IAEtB,IAAI,EAAE,MAAM,CAAA;IAGZ,OAAO,EAAE,MAAM,CAAA;IAGf,MAAM,EAAE,MAAM,CAAA;IAGd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IAGnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IAGjB,OAAO,CAAC,EAAE,OAAO,CAAA;IAMjB,UAAU,CAAC,EAAE,MAAM,CAAA;IAKnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xsequence/catapult",
3
- "version": "1.3.14",
3
+ "version": "1.3.16",
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', () => {
@@ -98,3 +98,53 @@ describe('network-loader rpcUrl token replacement', () => {
98
98
  expect(networks[0].rpcUrl).toBe('https://node.example.com/')
99
99
  })
100
100
  })
101
+
102
+ describe('network-loader params', () => {
103
+ afterAll(async () => {
104
+ try {
105
+ await fs.rm(tmpDir, { recursive: true, force: true })
106
+ } catch {}
107
+ })
108
+
109
+ test('passes through valid params object', async () => {
110
+ const projectRoot = path.join(tmpDir, 'params-valid')
111
+ const yaml = `
112
+ - name: "MyNet"
113
+ chainId: 4217
114
+ rpcUrl: "http://127.0.0.1:8545"
115
+ params:
116
+ myParam: true
117
+ `
118
+ await writeNetworksYaml(projectRoot, yaml)
119
+
120
+ const networks = await loadNetworks(projectRoot)
121
+ expect(networks).toHaveLength(1)
122
+ expect(networks[0].params).toEqual({ myParam: true })
123
+ })
124
+
125
+ test('rejects params when not a plain object', async () => {
126
+ const projectRoot = path.join(tmpDir, 'params-invalid-array')
127
+ const yaml = `
128
+ - name: "BadNet"
129
+ chainId: 1
130
+ rpcUrl: "http://127.0.0.1:8545"
131
+ params: [1, 2, 3]
132
+ `
133
+ await writeNetworksYaml(projectRoot, yaml)
134
+
135
+ await expect(loadNetworks(projectRoot)).rejects.toThrow(/Failed to load or parse networks.yaml/)
136
+ })
137
+
138
+ test('rejects params when null', async () => {
139
+ const projectRoot = path.join(tmpDir, 'params-invalid-null')
140
+ const yaml = `
141
+ - name: "BadNet"
142
+ chainId: 1
143
+ rpcUrl: "http://127.0.0.1:8545"
144
+ params: null
145
+ `
146
+ await writeNetworksYaml(projectRoot, yaml)
147
+
148
+ await expect(loadNetworks(projectRoot)).rejects.toThrow(/Failed to load or parse networks.yaml/)
149
+ })
150
+ })
@@ -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
+ })
@@ -1573,6 +1573,48 @@ describe('ValueResolver', () => {
1573
1573
  })
1574
1574
  })
1575
1575
 
1576
+ describe('Network().params dotted paths', () => {
1577
+ const mockPrivateKey = '0x0000000000000000000000000000000000000000000000000000000000000001'
1578
+
1579
+ it('should default params.myParam to false when params is absent', async () => {
1580
+ const result = await resolver.resolve('{{Network().params.myParam}}', context)
1581
+ expect(result).toBe(false)
1582
+ })
1583
+
1584
+ it('should default params.myParam to false when myParam is absent', async () => {
1585
+ const networkWithParams: Network = { ...mockNetwork, params: {} }
1586
+ const paramsContext = new ExecutionContext(networkWithParams, mockPrivateKey, mockRegistry)
1587
+ try {
1588
+ const result = await resolver.resolve('{{Network().params.myParam}}', paramsContext)
1589
+ expect(result).toBe(false)
1590
+ } finally {
1591
+ await paramsContext.dispose()
1592
+ }
1593
+ })
1594
+
1595
+ it('should return params.myParam when set to true', async () => {
1596
+ const networkWithParams: Network = { ...mockNetwork, params: { myParam: true } }
1597
+ const paramsContext = new ExecutionContext(networkWithParams, mockPrivateKey, mockRegistry)
1598
+ try {
1599
+ const result = await resolver.resolve('{{Network().params.myParam}}', paramsContext)
1600
+ expect(result).toBe(true)
1601
+ } finally {
1602
+ await paramsContext.dispose()
1603
+ }
1604
+ })
1605
+
1606
+ it('should return params.myParam when set to false', async () => {
1607
+ const networkWithParams: Network = { ...mockNetwork, params: { myParam: false } }
1608
+ const paramsContext = new ExecutionContext(networkWithParams, mockPrivateKey, mockRegistry)
1609
+ try {
1610
+ const result = await resolver.resolve('{{Network().params.myParam}}', paramsContext)
1611
+ expect(result).toBe(false)
1612
+ } finally {
1613
+ await paramsContext.dispose()
1614
+ }
1615
+ })
1616
+ })
1617
+
1576
1618
  describe('invalid Network expressions', () => {
1577
1619
  it('should fail for invalid property', async () => {
1578
1620
  await expect(resolver.resolve('{{Network().invalid}}', context))
@@ -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
+ }
@@ -118,20 +118,38 @@ export class ValueResolver {
118
118
  return value
119
119
  }
120
120
 
121
- // Check for Network(...) property access
122
- const networkMatch = expression.match(/^Network\(\)\.(\w+)$/)
121
+ // Check for Network() property access (single segment or dotted path)
122
+ const networkMatch = expression.match(/^Network\(\)\.(.+)$/)
123
123
  if (networkMatch) {
124
- const [, property] = networkMatch
124
+ const path = networkMatch[1]
125
+ const segments = path.split('.')
125
126
  const network = context.getNetwork()
126
- if (property === 'testnet') {
127
- // Special case for testnet property. Default to false if not set.
128
- return !!network.testnet
127
+
128
+ if (segments.length === 1) {
129
+ const property = segments[0]
130
+ if (property === 'testnet') {
131
+ // Default to false if not set.
132
+ return !!network.testnet
133
+ }
134
+ const value = (network as unknown as Record<string, unknown>)[property]
135
+ if (value === undefined) {
136
+ throw new Error(`Property "${property}" does not exist on network`)
137
+ }
138
+ return value
129
139
  }
130
- const value = (network as any)[property]
131
- if (value === undefined) {
132
- throw new Error(`Property "${property}" does not exist on network`)
140
+
141
+ // Dotted paths (e.g. params.myParam): missing keys default to false.
142
+ let current: unknown = network
143
+ for (const segment of segments) {
144
+ if (current === null || current === undefined || typeof current !== 'object') {
145
+ return false
146
+ }
147
+ current = (current as Record<string, unknown>)[segment]
148
+ if (current === undefined) {
149
+ return false
150
+ }
133
151
  }
134
- return value
152
+ return current
135
153
  }
136
154
 
137
155
  // Check scope for local variables (template arguments) first
@@ -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
+ }
@@ -22,7 +22,13 @@ function isValidNetwork(obj: unknown): obj is Network {
22
22
  // testnet field is optional and should be a boolean if present
23
23
  (!('testnet' in obj) || typeof (obj as Record<string, unknown>).testnet === 'boolean') &&
24
24
  // evmVersion field is optional and should be a string if present
25
- (!('evmVersion' in obj) || typeof (obj as Record<string, unknown>).evmVersion === 'string')
25
+ (!('evmVersion' in obj) || typeof (obj as Record<string, unknown>).evmVersion === 'string') &&
26
+ // params field is optional and must be a plain object if present
27
+ (!('params' in obj) || (
28
+ typeof (obj as Record<string, unknown>).params === 'object' &&
29
+ (obj as Record<string, unknown>).params !== null &&
30
+ !Array.isArray((obj as Record<string, unknown>).params)
31
+ ))
26
32
  )
27
33
  }
28
34
 
@@ -25,4 +25,9 @@ export interface Network {
25
25
  * "paris", "shanghai", "cancun". Used to filter jobs that require a minimum EVM version.
26
26
  */
27
27
  evmVersion?: string
28
+
29
+ /**
30
+ * Integrator-owned metadata bag. Keys are not validated by Catapult.
31
+ */
32
+ params?: Record<string, unknown>
28
33
  }