@0xsequence/catapult 1.4.0 → 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.
Files changed (163) hide show
  1. package/README.md +27 -0
  2. package/dist/lib/__tests__/network-loader.spec.js.map +1 -1
  3. package/dist/lib/core/__tests__/resolver.spec.js +22 -0
  4. package/dist/lib/core/__tests__/resolver.spec.js.map +1 -1
  5. package/dist/lib/core/__tests__/sign-actions.spec.d.ts +2 -0
  6. package/dist/lib/core/__tests__/sign-actions.spec.d.ts.map +1 -0
  7. package/dist/lib/core/__tests__/sign-actions.spec.js +128 -0
  8. package/dist/lib/core/__tests__/sign-actions.spec.js.map +1 -0
  9. package/dist/lib/core/__tests__/signer.spec.d.ts +2 -0
  10. package/dist/lib/core/__tests__/signer.spec.d.ts.map +1 -0
  11. package/dist/lib/core/__tests__/signer.spec.js +40 -0
  12. package/dist/lib/core/__tests__/signer.spec.js.map +1 -0
  13. package/dist/lib/core/context.d.ts +3 -2
  14. package/dist/lib/core/context.d.ts.map +1 -1
  15. package/dist/lib/core/context.js +3 -2
  16. package/dist/lib/core/context.js.map +1 -1
  17. package/dist/lib/core/engine.d.ts +4 -0
  18. package/dist/lib/core/engine.d.ts.map +1 -1
  19. package/dist/lib/core/engine.js +173 -0
  20. package/dist/lib/core/engine.js.map +1 -1
  21. package/dist/lib/core/signer.d.ts +7 -0
  22. package/dist/lib/core/signer.d.ts.map +1 -0
  23. package/dist/lib/core/signer.js +60 -0
  24. package/dist/lib/core/signer.js.map +1 -0
  25. package/dist/lib/parsers/__tests__/source.spec.js +37 -0
  26. package/dist/lib/parsers/__tests__/source.spec.js.map +1 -1
  27. package/dist/lib/parsers/source.js +1 -1
  28. package/dist/lib/parsers/source.js.map +1 -1
  29. package/dist/lib/provenance.js +51 -2
  30. package/dist/lib/provenance.js.map +1 -1
  31. package/dist/lib/types/actions.d.ts +26 -2
  32. package/dist/lib/types/actions.d.ts.map +1 -1
  33. package/dist/lib/types/actions.js +3 -0
  34. package/dist/lib/types/actions.js.map +1 -1
  35. package/dist/lib/types/source.d.ts +2 -0
  36. package/dist/lib/types/source.d.ts.map +1 -1
  37. package/package.json +4 -1
  38. package/.eslintrc.json +0 -29
  39. package/.github/workflows/ci.yml +0 -181
  40. package/CONCEPT.md +0 -24
  41. package/contracts/checked-call.huff +0 -65
  42. package/eslint.config.js +0 -48
  43. package/examples/jobs/guards-v1.yaml +0 -17
  44. package/examples/jobs/sequence-seq-0001-patch.yaml +0 -59
  45. package/examples/jobs/sequence-v1.yaml +0 -59
  46. package/examples/templates/sequence-factory-v1.yaml +0 -56
  47. package/jest.config.js +0 -25
  48. package/src/cli.ts +0 -18
  49. package/src/commands/common.ts +0 -61
  50. package/src/commands/dry.ts +0 -209
  51. package/src/commands/etherscan.ts +0 -360
  52. package/src/commands/index.ts +0 -6
  53. package/src/commands/list.ts +0 -262
  54. package/src/commands/provenance.ts +0 -120
  55. package/src/commands/run.ts +0 -146
  56. package/src/commands/utils.ts +0 -215
  57. package/src/index.ts +0 -67
  58. package/src/lib/__tests__/deployer-events.spec.ts +0 -338
  59. package/src/lib/__tests__/deployer.spec.ts +0 -2269
  60. package/src/lib/__tests__/network-loader.spec.ts +0 -150
  61. package/src/lib/__tests__/network-selection.spec.ts +0 -41
  62. package/src/lib/__tests__/network-utils.spec.ts +0 -230
  63. package/src/lib/__tests__/provenance.spec.ts +0 -208
  64. package/src/lib/artifacts/__tests__/fixtures/contract1.json +0 -19
  65. package/src/lib/artifacts/__tests__/fixtures/contract2.json +0 -19
  66. package/src/lib/artifacts/__tests__/fixtures/duplicate-name.json +0 -19
  67. package/src/lib/artifacts/__tests__/fixtures/nested/nested-contract.json +0 -18
  68. package/src/lib/artifacts/__tests__/fixtures/not-an-artifact.json +0 -8
  69. package/src/lib/artifacts/__tests__/fixtures/readme.txt +0 -2
  70. package/src/lib/contracts/__tests__/repository.spec.ts +0 -612
  71. package/src/lib/contracts/repository.ts +0 -411
  72. package/src/lib/core/__tests__/assert-action.spec.ts +0 -474
  73. package/src/lib/core/__tests__/context.spec.ts +0 -37
  74. package/src/lib/core/__tests__/engine.spec.ts +0 -2005
  75. package/src/lib/core/__tests__/graph.spec.ts +0 -125
  76. package/src/lib/core/__tests__/json-integration.spec.ts +0 -425
  77. package/src/lib/core/__tests__/loader.spec.ts +0 -367
  78. package/src/lib/core/__tests__/multi-platform-verification.spec.ts +0 -406
  79. package/src/lib/core/__tests__/resolver.spec.ts +0 -2496
  80. package/src/lib/core/__tests__/static-action.spec.ts +0 -172
  81. package/src/lib/core/context.ts +0 -127
  82. package/src/lib/core/engine.ts +0 -1834
  83. package/src/lib/core/graph.ts +0 -252
  84. package/src/lib/core/loader.ts +0 -253
  85. package/src/lib/core/resolver.ts +0 -873
  86. package/src/lib/deployer.ts +0 -1005
  87. package/src/lib/events/__tests__/event-system.spec.ts +0 -392
  88. package/src/lib/events/cli-adapter.ts +0 -369
  89. package/src/lib/events/emitter.ts +0 -62
  90. package/src/lib/events/index.ts +0 -3
  91. package/src/lib/events/types.ts +0 -520
  92. package/src/lib/index.ts +0 -17
  93. package/src/lib/network-loader.ts +0 -90
  94. package/src/lib/network-selection.ts +0 -73
  95. package/src/lib/network-utils.ts +0 -64
  96. package/src/lib/parsers/__tests__/buildinfo.spec.ts +0 -122
  97. package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-bytecode-buildinfo.json +0 -62
  98. package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-json.txt +0 -2
  99. package/src/lib/parsers/__tests__/fixtures/buildinfo/multi-contract-buildinfo.json +0 -89
  100. package/src/lib/parsers/__tests__/fixtures/buildinfo/no-contracts-buildinfo.json +0 -17
  101. package/src/lib/parsers/__tests__/fixtures/buildinfo/simple-buildinfo.json +0 -63
  102. package/src/lib/parsers/__tests__/fixtures/buildinfo/wrong-format.json +0 -4
  103. package/src/lib/parsers/__tests__/job.spec.ts +0 -439
  104. package/src/lib/parsers/__tests__/source.spec.ts +0 -134
  105. package/src/lib/parsers/__tests__/template.spec.ts +0 -111
  106. package/src/lib/parsers/artifact/__tests__/artifact.spec.ts +0 -117
  107. package/src/lib/parsers/artifact/__tests__/fixtures/empty-bytecode.json +0 -5
  108. package/src/lib/parsers/artifact/__tests__/fixtures/hardhat-artifact.json +0 -67
  109. package/src/lib/parsers/artifact/__tests__/fixtures/invalid-bytecode.json +0 -5
  110. package/src/lib/parsers/artifact/__tests__/fixtures/invalid-json.txt +0 -11
  111. package/src/lib/parsers/artifact/__tests__/fixtures/minimal-artifact.json +0 -5
  112. package/src/lib/parsers/artifact/__tests__/fixtures/missing-abi.json +0 -4
  113. package/src/lib/parsers/artifact/__tests__/fixtures/missing-bytecode.json +0 -11
  114. package/src/lib/parsers/artifact/__tests__/fixtures/missing-contract-name.json +0 -11
  115. package/src/lib/parsers/artifact/__tests__/fixtures/simple-artifact.json +0 -40
  116. package/src/lib/parsers/artifact/__tests__/fixtures/wrong-types.json +0 -7
  117. package/src/lib/parsers/artifact/foundry-1.2.ts +0 -72
  118. package/src/lib/parsers/artifact/index.ts +0 -27
  119. package/src/lib/parsers/artifact/types.ts +0 -9
  120. package/src/lib/parsers/buildinfo.ts +0 -127
  121. package/src/lib/parsers/constants.ts +0 -56
  122. package/src/lib/parsers/index.ts +0 -6
  123. package/src/lib/parsers/job.ts +0 -160
  124. package/src/lib/parsers/source.ts +0 -129
  125. package/src/lib/parsers/template.ts +0 -135
  126. package/src/lib/provenance.ts +0 -785
  127. package/src/lib/std/templates/arachnid-deterministic-deployment-proxy.yaml +0 -68
  128. package/src/lib/std/templates/assured-deployment.yaml +0 -46
  129. package/src/lib/std/templates/era-evm-predeploy.yaml +0 -35
  130. package/src/lib/std/templates/erc-2470.yaml +0 -70
  131. package/src/lib/std/templates/min-balance.yaml +0 -35
  132. package/src/lib/std/templates/nano-universal-deployer.yaml +0 -61
  133. package/src/lib/std/templates/raw-erc-2470.yaml +0 -62
  134. package/src/lib/std/templates/raw-nano-universal-deployer.yaml +0 -54
  135. package/src/lib/std/templates/raw-sequence-universal-deployer-2.yaml +0 -52
  136. package/src/lib/std/templates/sequence-universal-deployer-2.yaml +0 -61
  137. package/src/lib/types/__tests__/json-request-action.spec.ts +0 -243
  138. package/src/lib/types/__tests__/read-json-value.spec.ts +0 -278
  139. package/src/lib/types/__tests__/resolve-json-value.spec.ts +0 -769
  140. package/src/lib/types/actions.ts +0 -148
  141. package/src/lib/types/artifacts.ts +0 -21
  142. package/src/lib/types/buildinfo.ts +0 -116
  143. package/src/lib/types/conditions.ts +0 -50
  144. package/src/lib/types/contracts.ts +0 -26
  145. package/src/lib/types/definitions.ts +0 -77
  146. package/src/lib/types/index.ts +0 -9
  147. package/src/lib/types/network.ts +0 -33
  148. package/src/lib/types/project.ts +0 -9
  149. package/src/lib/types/source.ts +0 -26
  150. package/src/lib/types/task.ts +0 -9
  151. package/src/lib/types/values.ts +0 -221
  152. package/src/lib/utils/assertion.ts +0 -24
  153. package/src/lib/utils/validation.ts +0 -116
  154. package/src/lib/validation/contract-references.ts +0 -210
  155. package/src/lib/validation/index.ts +0 -1
  156. package/src/lib/verification/__tests__/etherscan.spec.ts +0 -710
  157. package/src/lib/verification/__tests__/sourcify.spec.ts +0 -288
  158. package/src/lib/verification/etherscan.ts +0 -547
  159. package/src/lib/verification/sourcify.ts +0 -248
  160. package/test_validation/artifacts/TestContract.json +0 -9
  161. package/test_validation/jobs/test-missing.yaml +0 -16
  162. package/test_validation/networks.yaml +0 -3
  163. package/tsconfig.json +0 -36
@@ -1,150 +0,0 @@
1
- import * as fs from 'fs/promises'
2
- import * as path from 'path'
3
- import { loadNetworks } from '../network-loader'
4
-
5
- const tmpDir = path.join(process.cwd(), '.tmp-network-loader-tests')
6
-
7
- async function writeNetworksYaml(projectRoot: string, yamlContent: string): Promise<void> {
8
- await fs.mkdir(projectRoot, { recursive: true })
9
- await fs.writeFile(path.join(projectRoot, 'networks.yaml'), yamlContent, 'utf-8')
10
- }
11
-
12
- describe('network-loader rpcUrl token replacement', () => {
13
- const originalEnv = { ...process.env }
14
-
15
- beforeAll(async () => {
16
- await fs.mkdir(tmpDir, { recursive: true })
17
- })
18
-
19
- afterEach(() => {
20
- process.env = { ...originalEnv }
21
- })
22
-
23
- afterAll(async () => {
24
- try {
25
- await fs.rm(tmpDir, { recursive: true, force: true })
26
- } catch {}
27
- })
28
-
29
- test('replaces {{RPC_*}} tokens with env values', async () => {
30
- const projectRoot = path.join(tmpDir, 'case1')
31
- process.env.RPC_URL_TOKEN = 'abc123'
32
- const yaml = `
33
- - name: "TestNet"
34
- chainId: 123
35
- rpcUrl: "https://node.example.com/{{RPC_URL_TOKEN}}"
36
- `
37
- await writeNetworksYaml(projectRoot, yaml)
38
-
39
- const networks = await loadNetworks(projectRoot)
40
- expect(networks).toHaveLength(1)
41
- expect(networks[0].rpcUrl).toBe('https://node.example.com/abc123')
42
- })
43
-
44
- test('leaves non-RPC tokens intact', async () => {
45
- const projectRoot = path.join(tmpDir, 'case2')
46
- process.env.SOME_TOKEN = 'should_not_be_used'
47
- const yaml = `
48
- - name: "TestNet"
49
- chainId: 123
50
- rpcUrl: "https://node.example.com/{{SOME_TOKEN}}"
51
- `
52
- await writeNetworksYaml(projectRoot, yaml)
53
-
54
- const networks = await loadNetworks(projectRoot)
55
- expect(networks[0].rpcUrl).toBe('https://node.example.com/{{SOME_TOKEN}}')
56
- })
57
-
58
- test('supports multiple RPC tokens in a single url', async () => {
59
- const projectRoot = path.join(tmpDir, 'case3')
60
- process.env.RPC_A = 'A'
61
- process.env.RPC_B = 'B'
62
- const yaml = `
63
- - name: "TestNet"
64
- chainId: 123
65
- rpcUrl: "https://node.example.com/{{RPC_A}}/path/{{RPC_B}}"
66
- `
67
- await writeNetworksYaml(projectRoot, yaml)
68
-
69
- const networks = await loadNetworks(projectRoot)
70
- expect(networks[0].rpcUrl).toBe('https://node.example.com/A/path/B')
71
- })
72
-
73
- test('trims whitespace within tokens', async () => {
74
- const projectRoot = path.join(tmpDir, 'case4')
75
- process.env.RPC_TOKEN = 'XYZ'
76
- const yaml = `
77
- - name: "TestNet"
78
- chainId: 123
79
- rpcUrl: "https://node.example.com/{{ RPC_TOKEN }}"
80
- `
81
- await writeNetworksYaml(projectRoot, yaml)
82
-
83
- const networks = await loadNetworks(projectRoot)
84
- expect(networks[0].rpcUrl).toBe('https://node.example.com/XYZ')
85
- })
86
-
87
- test('defaults to empty string when RPC token has no matching env var', async () => {
88
- const projectRoot = path.join(tmpDir, 'case5')
89
- delete process.env.RPC_MISSING
90
- const yaml = `
91
- - name: "TestNet"
92
- chainId: 123
93
- rpcUrl: "https://node.example.com/{{RPC_MISSING}}"
94
- `
95
- await writeNetworksYaml(projectRoot, yaml)
96
-
97
- const networks = await loadNetworks(projectRoot)
98
- expect(networks[0].rpcUrl).toBe('https://node.example.com/')
99
- })
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
- })
@@ -1,41 +0,0 @@
1
- import { resolveSelectedChainIds, resolveSingleChainId } from '../../lib/network-selection'
2
- import { Network } from '../../lib/types'
3
-
4
- describe('network-selection', () => {
5
- const networks: Network[] = [
6
- { name: 'Mainnet', chainId: 1, rpcUrl: 'https://mainnet' },
7
- { name: 'Arbitrum One', chainId: 42161, rpcUrl: 'https://arb' },
8
- { name: 'Polygon', chainId: 137, rpcUrl: 'https://polygon' },
9
- { name: 'Mainnet', chainId: 10_000, rpcUrl: 'https://fork' },
10
- ]
11
-
12
- it('returns undefined for empty/undefined input', () => {
13
- expect(resolveSelectedChainIds(undefined, networks)).toBeUndefined()
14
- expect(resolveSelectedChainIds('', networks)).toBeUndefined()
15
- expect(resolveSelectedChainIds(' ', networks)).toBeUndefined()
16
- })
17
-
18
- it('parses numeric IDs and removes duplicates', () => {
19
- expect(resolveSelectedChainIds('1,1,42161', networks)).toEqual([1, 42161])
20
- })
21
-
22
- it('matches names case-insensitively and includes all with same name', () => {
23
- expect(resolveSelectedChainIds('mainnet', networks)).toEqual([1, 10000])
24
- expect(resolveSelectedChainIds('MAINNET,polygon', networks)).toEqual([1, 10000, 137])
25
- })
26
-
27
- it('preserves token order and then network order for name matches', () => {
28
- expect(resolveSelectedChainIds('polygon,mainnet', networks)).toEqual([137, 1, 10000])
29
- })
30
-
31
- it('throws on unknown name', () => {
32
- expect(() => resolveSelectedChainIds('unknown', networks)).toThrow(/Unknown network selector/)
33
- })
34
-
35
- it('resolveSingleChainId returns first match for name and first token for multi', () => {
36
- expect(resolveSingleChainId('mainnet', networks)).toBe(1)
37
- expect(resolveSingleChainId('137,1', networks)).toBe(137)
38
- })
39
- })
40
-
41
-
@@ -1,230 +0,0 @@
1
- import { detectNetworkFromRpc, isValidRpcUrl } from '../network-utils'
2
- import { Network } from '../types'
3
-
4
- // Mock ethers.JsonRpcProvider
5
- jest.mock('ethers', () => ({
6
- ...jest.requireActual('ethers'),
7
- ethers: {
8
- ...jest.requireActual('ethers').ethers,
9
- JsonRpcProvider: jest.fn().mockImplementation(() => ({
10
- getNetwork: jest.fn()
11
- }))
12
- }
13
- }))
14
-
15
- describe('Network Utils', () => {
16
- describe('isValidRpcUrl', () => {
17
- it('should validate valid HTTP RPC URLs', () => {
18
- expect(isValidRpcUrl('http://localhost:8545')).toBe(true)
19
- expect(isValidRpcUrl('https://mainnet.infura.io/v3/abc123')).toBe(true)
20
- })
21
-
22
- it('should validate valid WebSocket RPC URLs', () => {
23
- expect(isValidRpcUrl('ws://localhost:8545')).toBe(true)
24
- expect(isValidRpcUrl('wss://mainnet.infura.io/v3/abc123')).toBe(true)
25
- })
26
-
27
- it('should reject invalid URLs', () => {
28
- expect(isValidRpcUrl('invalid-url')).toBe(false)
29
- expect(isValidRpcUrl('ftp://example.com')).toBe(false)
30
- expect(isValidRpcUrl('')).toBe(false)
31
- })
32
-
33
- it('should reject URLs without hostname', () => {
34
- expect(isValidRpcUrl('http://')).toBe(false)
35
- })
36
- })
37
-
38
- describe('detectNetworkFromRpc', () => {
39
- let mockGetNetwork: jest.Mock
40
-
41
- beforeEach(() => {
42
- // Reset mocks
43
- jest.clearAllMocks()
44
-
45
- // Mock the JsonRpcProvider constructor and getNetwork method
46
- const { ethers } = require('ethers')
47
- mockGetNetwork = jest.fn()
48
-
49
- // Mock the provider's getNetwork method
50
- ethers.JsonRpcProvider.mockImplementation(() => ({
51
- getNetwork: mockGetNetwork
52
- }))
53
- })
54
-
55
- it('should detect network successfully', async () => {
56
- const mockNetwork = {
57
- name: 'mainnet',
58
- chainId: 1
59
- }
60
-
61
- mockGetNetwork.mockResolvedValue(mockNetwork)
62
-
63
- const result = await detectNetworkFromRpc('https://mainnet.infura.io/v3/abc123')
64
-
65
- expect(result).toEqual({
66
- name: 'mainnet',
67
- chainId: 1,
68
- rpcUrl: 'https://mainnet.infura.io/v3/abc123'
69
- })
70
-
71
- // Verify provider was created with correct URL
72
- const { ethers } = require('ethers')
73
- expect(ethers.JsonRpcProvider).toHaveBeenCalledWith('https://mainnet.infura.io/v3/abc123')
74
- })
75
-
76
- it('should handle network with unknown name', async () => {
77
- const mockNetwork = {
78
- name: 'unknown',
79
- chainId: 31337
80
- }
81
-
82
- mockGetNetwork.mockResolvedValue(mockNetwork)
83
-
84
- const result = await detectNetworkFromRpc('http://localhost:8545')
85
-
86
- expect(result).toEqual({
87
- name: 'unknown',
88
- chainId: 31337,
89
- rpcUrl: 'http://localhost:8545'
90
- })
91
- })
92
-
93
- it('should handle connection errors', async () => {
94
- mockGetNetwork.mockRejectedValue(new Error('Connection failed'))
95
-
96
- await expect(detectNetworkFromRpc('http://localhost:8545'))
97
- .rejects.toThrow('Failed to detect network from RPC URL "http://localhost:8545": Connection failed')
98
- })
99
-
100
- it('should handle network detection errors', async () => {
101
- mockGetNetwork.mockRejectedValue(new Error('Network not supported'))
102
-
103
- await expect(detectNetworkFromRpc('http://invalid-rpc.com'))
104
- .rejects.toThrow('Failed to detect network from RPC URL "http://invalid-rpc.com": Network not supported')
105
- })
106
- })
107
-
108
- describe('Integration with Run Command', () => {
109
- it('should create a complete Network object from detected information', async () => {
110
- const { ethers } = require('ethers')
111
-
112
- const mockNetwork = {
113
- name: 'sepolia',
114
- chainId: 11155111,
115
- }
116
-
117
- const getNetworkMock = jest.fn().mockResolvedValue(mockNetwork)
118
- ethers.JsonRpcProvider.mockImplementation(() => ({
119
- getNetwork: getNetworkMock,
120
- }))
121
-
122
- const detectedInfo = await detectNetworkFromRpc('https://sepolia.infura.io/v3/abc123')
123
-
124
- // Simulate what the run command does
125
- const customNetwork: Network = {
126
- name: detectedInfo.name || `custom-${detectedInfo.chainId}`,
127
- chainId: detectedInfo.chainId!,
128
- rpcUrl: 'https://sepolia.infura.io/v3/abc123',
129
- supports: detectedInfo.supports || [],
130
- gasLimit: detectedInfo.gasLimit,
131
- testnet: detectedInfo.testnet,
132
- evmVersion: detectedInfo.evmVersion,
133
- params: detectedInfo.params,
134
- }
135
-
136
- expect(customNetwork).toEqual({
137
- name: 'sepolia',
138
- chainId: 11155111,
139
- rpcUrl: 'https://sepolia.infura.io/v3/abc123',
140
- supports: [],
141
- gasLimit: undefined,
142
- testnet: undefined,
143
- evmVersion: undefined,
144
- params: undefined,
145
- })
146
- })
147
-
148
- it('should merge params and other yaml fields when chainId matches networks.yaml', async () => {
149
- const { ethers } = require('ethers')
150
-
151
- const getNetworkMock = jest.fn().mockResolvedValue({ name: 'unknown', chainId: 137 })
152
- ethers.JsonRpcProvider.mockImplementation(() => ({
153
- getNetwork: getNetworkMock,
154
- }))
155
-
156
- const detectedInfo = await detectNetworkFromRpc('http://127.0.0.1:8545')
157
- const knownNetwork: Network = {
158
- name: 'Polygon',
159
- chainId: 137,
160
- rpcUrl: 'https://nodes.sequence.app/polygon/token',
161
- supports: ['etherscan_v2', 'sourcify'],
162
- testnet: false,
163
- evmVersion: 'paris',
164
- params: { trailsOnly: false },
165
- }
166
-
167
- // Simulate what the run command does
168
- const customNetwork: Network = {
169
- name: detectedInfo.name || knownNetwork.name || `custom-${detectedInfo.chainId}`,
170
- chainId: detectedInfo.chainId!,
171
- rpcUrl: 'http://127.0.0.1:8545',
172
- supports: detectedInfo.supports || knownNetwork.supports || [],
173
- gasLimit: detectedInfo.gasLimit || knownNetwork.gasLimit,
174
- testnet: detectedInfo.testnet !== undefined ? detectedInfo.testnet : knownNetwork.testnet,
175
- evmVersion: detectedInfo.evmVersion || knownNetwork.evmVersion,
176
- params: detectedInfo.params || knownNetwork.params,
177
- }
178
-
179
- expect(customNetwork).toEqual({
180
- name: 'unknown',
181
- chainId: 137,
182
- rpcUrl: 'http://127.0.0.1:8545',
183
- supports: ['etherscan_v2', 'sourcify'],
184
- gasLimit: undefined,
185
- testnet: false,
186
- evmVersion: 'paris',
187
- params: { trailsOnly: false },
188
- })
189
- })
190
-
191
- it('should handle partial network information gracefully', async () => {
192
- const { ethers } = require('ethers')
193
-
194
- const mockNetwork = {
195
- name: 'unknown',
196
- chainId: 42,
197
- }
198
-
199
- const getNetworkMock = jest.fn().mockResolvedValue(mockNetwork)
200
- ethers.JsonRpcProvider.mockImplementation(() => ({
201
- getNetwork: getNetworkMock,
202
- }))
203
-
204
- const detectedInfo = await detectNetworkFromRpc('http://custom-network:8545')
205
-
206
- // Simulate what the run command does
207
- const customNetwork: Network = {
208
- name: detectedInfo.name || `custom-${detectedInfo.chainId}`,
209
- chainId: detectedInfo.chainId!,
210
- rpcUrl: 'http://custom-network:8545',
211
- supports: detectedInfo.supports || [],
212
- gasLimit: detectedInfo.gasLimit,
213
- testnet: detectedInfo.testnet,
214
- evmVersion: detectedInfo.evmVersion,
215
- params: detectedInfo.params,
216
- }
217
-
218
- expect(customNetwork).toEqual({
219
- name: 'unknown',
220
- chainId: 42,
221
- rpcUrl: 'http://custom-network:8545',
222
- supports: [],
223
- gasLimit: undefined,
224
- testnet: undefined,
225
- evmVersion: undefined,
226
- params: undefined,
227
- })
228
- })
229
- })
230
- })
@@ -1,208 +0,0 @@
1
- import * as fs from 'fs/promises'
2
- import * as os from 'os'
3
- import * as path from 'path'
4
- import { execFile } from 'child_process'
5
- import { promisify } from 'util'
6
- import {
7
- collectSourceProvenanceEntries,
8
- generateBuildInfoFromSourceProvenance,
9
- verifySourceProvenance
10
- } from '../provenance'
11
-
12
- const execFileAsync = promisify(execFile)
13
-
14
- async function git(cwd: string, args: string[]): Promise<string> {
15
- const result = await execFileAsync('git', args, { cwd })
16
- return String(result.stdout).trim()
17
- }
18
-
19
- async function writeFile(filePath: string, content: string): Promise<void> {
20
- await fs.mkdir(path.dirname(filePath), { recursive: true })
21
- await fs.writeFile(filePath, content)
22
- }
23
-
24
- function sourceYaml(repo: string, commit: string, build: string): string {
25
- return `
26
- type: source
27
- build_info:
28
- "./stage1.json":
29
- repo: ${JSON.stringify(repo)}
30
- commit: ${JSON.stringify(commit)}
31
- build: ${JSON.stringify(build)}
32
- `
33
- }
34
-
35
- function jobYaml(name: string, dependsOn: string[] = []): string {
36
- const depends = dependsOn.length > 0 ? `depends_on: ${JSON.stringify(dependsOn)}\n` : ''
37
- return `
38
- name: ${JSON.stringify(name)}
39
- version: "1.0.0"
40
- ${depends}actions:
41
- - name: "noop"
42
- type: "static"
43
- arguments:
44
- value: true
45
- `
46
- }
47
-
48
- describe('source provenance operations', () => {
49
- let tempDir: string
50
-
51
- beforeEach(async () => {
52
- tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'catapult-provenance-test-'))
53
- })
54
-
55
- afterEach(async () => {
56
- await fs.rm(tempDir, { recursive: true, force: true })
57
- })
58
-
59
- it('collects source provenance entries even when build-info is missing', async () => {
60
- const projectRoot = path.join(tempDir, 'project')
61
- const sourcePath = path.join(projectRoot, 'jobs', 'demo', 'build-info', 'source.yaml')
62
- await writeFile(sourcePath, sourceYaml('https://github.com/example/repo', 'abc123', 'forge build --build-info'))
63
-
64
- const result = await collectSourceProvenanceEntries(projectRoot)
65
-
66
- expect(result.warnings).toEqual([])
67
- expect(result.entries).toHaveLength(1)
68
- expect(result.entries[0]).toMatchObject({
69
- sourceDocumentPath: sourcePath,
70
- buildInfoRef: './stage1.json',
71
- buildInfoPath: path.join(projectRoot, 'jobs', 'demo', 'build-info', 'stage1.json')
72
- })
73
- })
74
-
75
- it('can scope provenance entries to a job and its dependencies', async () => {
76
- const projectRoot = path.join(tempDir, 'project')
77
- await writeFile(path.join(projectRoot, 'jobs', 'base.yaml'), jobYaml('base'))
78
- await writeFile(path.join(projectRoot, 'jobs', 'child.yaml'), jobYaml('child', ['base']))
79
- await writeFile(path.join(projectRoot, 'jobs', 'base', 'build-info', 'source.yaml'), sourceYaml('https://github.com/example/base', 'abc123', 'build-base'))
80
- await writeFile(path.join(projectRoot, 'jobs', 'child', 'build-info', 'source.yaml'), sourceYaml('https://github.com/example/child', 'def456', 'build-child'))
81
-
82
- const childOnly = await collectSourceProvenanceEntries(projectRoot, {
83
- jobs: ['child'],
84
- loadStdTemplates: false
85
- })
86
- const withDependencies = await collectSourceProvenanceEntries(projectRoot, {
87
- jobs: ['child'],
88
- includeDependencies: true,
89
- loadStdTemplates: false
90
- })
91
-
92
- expect(childOnly.entries.map(entry => entry.provenance.repo)).toEqual(['https://github.com/example/child'])
93
- expect(withDependencies.entries.map(entry => entry.provenance.repo).sort()).toEqual([
94
- 'https://github.com/example/base',
95
- 'https://github.com/example/child'
96
- ])
97
- })
98
-
99
- it('generates missing build-info from a local Git provenance repo', async () => {
100
- const { projectRoot, expectedBuildInfo } = await createProjectWithLocalProvenanceRepo()
101
-
102
- const result = await generateBuildInfoFromSourceProvenance(projectRoot)
103
- const generatedPath = path.join(projectRoot, 'jobs', 'demo', 'build-info', 'stage1.json')
104
- const generatedJson = JSON.parse(await fs.readFile(generatedPath, 'utf-8'))
105
-
106
- expect(result.results).toHaveLength(1)
107
- expect(result.results[0].status).toBe('generated')
108
- expect(generatedJson).toEqual(expectedBuildInfo)
109
- })
110
-
111
- it('verifies generated build-info and reports mismatches', async () => {
112
- const { projectRoot } = await createProjectWithLocalProvenanceRepo()
113
- const targetPath = path.join(projectRoot, 'jobs', 'demo', 'build-info', 'stage1.json')
114
-
115
- await generateBuildInfoFromSourceProvenance(projectRoot)
116
- const verified = await verifySourceProvenance(projectRoot)
117
- expect(verified.results[0].status).toBe('verified')
118
-
119
- const changed = JSON.parse(await fs.readFile(targetPath, 'utf-8'))
120
- changed.solcVersion = '0.8.1'
121
- await fs.writeFile(targetPath, JSON.stringify(changed, null, 2))
122
-
123
- const mismatch = await verifySourceProvenance(projectRoot)
124
- expect(mismatch.results[0].status).toBe('failed')
125
- expect(mismatch.results[0].message).toContain('does not match')
126
- expect(mismatch.results[0].message).toContain('$.solcVersion')
127
- })
128
-
129
- it('normalizes checkout-local build-info paths and top-level ids while verifying', async () => {
130
- const { projectRoot } = await createProjectWithLocalProvenanceRepo({ checkoutSensitiveBuildInfo: true })
131
- const targetPath = path.join(projectRoot, 'jobs', 'demo', 'build-info', 'stage1.json')
132
-
133
- await generateBuildInfoFromSourceProvenance(projectRoot)
134
- const generatedJson = JSON.parse(await fs.readFile(targetPath, 'utf-8'))
135
- expect(generatedJson.id).toContain('catapult-provenance-')
136
- expect(generatedJson.input.basePath).toContain('catapult-provenance-')
137
-
138
- const verified = await verifySourceProvenance(projectRoot)
139
- expect(verified.results[0].status).toBe('verified')
140
- })
141
-
142
- async function createProjectWithLocalProvenanceRepo(
143
- options: { checkoutSensitiveBuildInfo?: boolean } = {}
144
- ): Promise<{ projectRoot: string; expectedBuildInfo: Record<string, unknown> }> {
145
- const sourceRepo = path.join(tempDir, 'source-repo')
146
- const projectRoot = path.join(tempDir, 'project')
147
- await fs.mkdir(sourceRepo, { recursive: true })
148
-
149
- const expectedBuildInfo = {
150
- _format: 'hh-sol-build-info-1',
151
- id: 'stage1',
152
- solcVersion: '0.8.0',
153
- input: {
154
- language: 'Solidity',
155
- sources: {}
156
- },
157
- output: {
158
- contracts: {}
159
- }
160
- }
161
-
162
- const buildInfoScript = options.checkoutSensitiveBuildInfo
163
- ? `
164
- const fs = require('fs')
165
- const path = require('path')
166
- const buildInfo = {
167
- _format: 'hh-sol-build-info-1',
168
- id: path.basename(path.dirname(__dirname)),
169
- solcVersion: '0.8.0',
170
- input: {
171
- language: 'Solidity',
172
- basePath: __dirname,
173
- allowPaths: [__dirname, path.join(__dirname, 'lib')],
174
- includePaths: [__dirname],
175
- sources: {}
176
- },
177
- output: {
178
- contracts: {}
179
- }
180
- }
181
- fs.mkdirSync(path.join(__dirname, 'out', 'build-info'), { recursive: true })
182
- fs.writeFileSync(path.join(__dirname, 'out', 'build-info', 'stage1.json'), JSON.stringify(buildInfo, null, 2))
183
- `
184
- : `
185
- const fs = require('fs')
186
- const path = require('path')
187
- fs.mkdirSync(path.join(__dirname, 'out', 'build-info'), { recursive: true })
188
- fs.writeFileSync(path.join(__dirname, 'out', 'build-info', 'stage1.json'), JSON.stringify(${JSON.stringify(expectedBuildInfo)}, null, 2))
189
- `
190
-
191
- await writeFile(path.join(sourceRepo, 'build-info.js'), `
192
- ${buildInfoScript.trim()}
193
- `)
194
- await git(sourceRepo, ['init'])
195
- await git(sourceRepo, ['config', 'user.email', 'catapult@example.com'])
196
- await git(sourceRepo, ['config', 'user.name', 'Catapult Test'])
197
- await git(sourceRepo, ['add', 'build-info.js'])
198
- await git(sourceRepo, ['commit', '-m', 'add build script'])
199
- const commit = await git(sourceRepo, ['rev-parse', 'HEAD'])
200
-
201
- await writeFile(
202
- path.join(projectRoot, 'jobs', 'demo', 'build-info', 'source.yaml'),
203
- sourceYaml(sourceRepo, commit, 'node build-info.js')
204
- )
205
-
206
- return { projectRoot, expectedBuildInfo }
207
- }
208
- })
@@ -1,19 +0,0 @@
1
- {
2
- "contractName": "TestContract1",
3
- "sourceName": "contracts/TestContract1.sol",
4
- "abi": [
5
- {
6
- "type": "function",
7
- "name": "getValue",
8
- "inputs": [],
9
- "outputs": [
10
- {
11
- "name": "",
12
- "type": "uint256"
13
- }
14
- ]
15
- }
16
- ],
17
- "bytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806320965255146100365780636d4ce63c14610040575b600080fd5b610040610058565b005b610048610062565b6040516100579190610078565b60405180910390f35b0000005b6000806000fefe",
18
- "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806320965255146100365780636d4ce63c14610040575b600080fd5b610040610058565b005b610048610062565b6040516100579190610078565b60405180910390f35b6000fefe"
19
- }
@@ -1,19 +0,0 @@
1
- {
2
- "contractName": "TestContract2",
3
- "sourceName": "contracts/TestContract2.sol",
4
- "abi": [
5
- {
6
- "type": "function",
7
- "name": "setValue",
8
- "inputs": [
9
- {
10
- "name": "value",
11
- "type": "uint256"
12
- }
13
- ],
14
- "outputs": []
15
- }
16
- ],
17
- "bytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806355241077146100365780636d4ce63c14610040575b600080fd5b610040610058565b005b610048610062565b6040516100579190610078565b60405180910390f35b6000fefe",
18
- "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806355241077146100365780636d4ce63c14610040575b600080fd5b610040610058565b005b610048610062565b6040516100579190610078565b60405180910390f35b6000fefe"
19
- }
@@ -1,19 +0,0 @@
1
- {
2
- "contractName": "TestContract1",
3
- "sourceName": "contracts/DuplicateTestContract1.sol",
4
- "abi": [
5
- {
6
- "type": "function",
7
- "name": "differentFunction",
8
- "inputs": [],
9
- "outputs": [
10
- {
11
- "name": "",
12
- "type": "string"
13
- }
14
- ]
15
- }
16
- ],
17
- "bytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806320965255146100365780636d4ce63c14610040575b600080fd5b610040610058565b005b610048610062565b6040516100579190610078565b60405180910390f35b0000005b6000806000fefe",
18
- "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806320965255146100365780636d4ce63c14610040575b600080fd5b610040610058565b005b610048610062565b6040516100579190610078565b60405180910390f35b6000fefe"
19
- }