@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,873 +0,0 @@
1
- import { ethers } from 'ethers'
2
- import {
3
- Value,
4
- ValueResolver as ValueResolverObject,
5
- AbiEncodeValue,
6
- AbiPackValue,
7
- ConstructorEncodeValue,
8
- ComputeCreateValue,
9
- ComputeCreate2Value,
10
- ReadBalanceValue,
11
- GetStorageAtValue,
12
- ComputeSlotValue,
13
- BasicArithmeticValue,
14
- CallValue,
15
- ContractExistsValue,
16
- JobCompletedValue,
17
- ReadJsonValue,
18
- ValueEmptyValue,
19
- SliceBytesValue,
20
- } from '../types'
21
- import { ExecutionContext } from './context'
22
- import { isAddress, isBigNumberish, isBytesLike } from '../utils/assertion'
23
-
24
- /**
25
- * A scope for resolving local variables, such as template arguments.
26
- * This allows a template to use placeholders like `{{my_arg}}` which are
27
- * filled in by the job calling the template.
28
- */
29
- export type ResolutionScope = Map<string, any>
30
-
31
- /**
32
- * The ValueResolver is responsible for turning declarative `Value` types from
33
- * the YAML files into concrete, usable data at runtime. It handles placeholders,
34
- * on-chain data fetching, and dynamic computations like encoding and address calculation.
35
- */
36
- export class ValueResolver {
37
- /**
38
- * Resolves a `Value<any>` into its final, concrete form.
39
- * This is the main entry point for the resolver.
40
- *
41
- * @param value The value to resolve. It can be a literal, a `{{...}}` reference string,
42
- * or a `ValueResolver` object (e.g., `{ type: 'abi-encode', ... }`).
43
- * @param context The execution context, providing access to the provider, signer, and outputs.
44
- * @param scope The local resolution scope, used for template arguments.
45
- * @returns A promise that resolves to the final concrete value.
46
- */
47
- public async resolve<T>(value: Value<any>, context: ExecutionContext, scope: ResolutionScope = new Map()): Promise<T> {
48
- // 1. Handle literals (non-string, non-object) and null
49
- if (typeof value !== 'string' && (typeof value !== 'object' || value === null)) {
50
- return value as T
51
- }
52
-
53
- // 2. Handle string values
54
- if (typeof value === 'string') {
55
- const refMatch = value.match(/^{{(.*)}}$/)
56
- if (refMatch) {
57
- // It's a reference like `{{...}}`, resolve the expression inside
58
- const expression = refMatch[1].trim()
59
- return this.resolveExpression(expression, context, scope)
60
- }
61
- // It's a string literal
62
- return value as T
63
- }
64
-
65
- // 3. Handle arrays
66
- if (Array.isArray(value)) {
67
- return Promise.all(value.map(item => this.resolve(item, context, scope))) as Promise<T>
68
- }
69
-
70
- // 4. Handle ValueResolver objects
71
- if (typeof value === 'object' && 'type' in value) {
72
- return this.resolveValueResolverObject(value as ValueResolverObject, context, scope)
73
- }
74
-
75
- // 5. Handle plain objects as literals (for JSON data)
76
- if (typeof value === 'object') {
77
- return value as T
78
- }
79
-
80
- // 6. If we get here, something unexpected happened
81
- throw new Error(`Cannot resolve value: unexpected value type: ${typeof value}`)
82
- }
83
-
84
- /**
85
- * Resolves an expression from inside a `{{...}}` placeholder.
86
- * @private
87
- */
88
- private async resolveExpression(expression: string, context: ExecutionContext, scope: ResolutionScope): Promise<any> {
89
- // Check for Contract(...) syntax with optional property access
90
- const contractMatch = expression.match(/^Contract\((.*?)\)(\.\w+)?$/)
91
- if (contractMatch) {
92
- const [, reference, property] = contractMatch
93
- const contractRef = reference.trim()
94
-
95
- // Look up the contract with context path for relative artifact resolution
96
- const contract = context.contractRepository.lookup(contractRef, context.getContextPath())
97
- if (!contract) {
98
- // Provide extra diagnostics to help users understand where lookup occurred
99
- const ctx = context.getContextPath()
100
- throw new Error(
101
- `Artifact not found for reference: "${contractRef}" (resolved relative to: ${ctx ?? 'N/A'}). ` +
102
- `Ensure the path and contract name are correct and that the build-info/artifact is discoverable.`
103
- )
104
- }
105
-
106
- // If no property requested, return the entire Contract object
107
- if (!property) {
108
- return contract
109
- }
110
-
111
- // Extract the property name (remove the leading dot)
112
- const propName = property.substring(1)
113
-
114
- // Access the requested property
115
- const value = (contract as any)[propName]
116
- if (value === undefined) {
117
- throw new Error(`Property "${propName}" does not exist on contract found for reference "${contractRef}"`)
118
- }
119
-
120
- return value
121
- }
122
-
123
- // Check for Network() property access (single segment or dotted path)
124
- const networkMatch = expression.match(/^Network\(\)\.(.+)$/)
125
- if (networkMatch) {
126
- const path = networkMatch[1]
127
- const segments = path.split('.')
128
- const network = context.getNetwork()
129
-
130
- if (segments.length === 1) {
131
- const property = segments[0]
132
- if (property === 'testnet') {
133
- // Default to false if not set.
134
- return !!network.testnet
135
- }
136
- const value = (network as unknown as Record<string, unknown>)[property]
137
- if (value === undefined) {
138
- throw new Error(`Property "${property}" does not exist on network`)
139
- }
140
- return value
141
- }
142
-
143
- // Dotted paths (e.g. params.myParam): missing keys default to false.
144
- let current: unknown = network
145
- for (const segment of segments) {
146
- if (current === null || current === undefined || typeof current !== 'object') {
147
- return false
148
- }
149
- current = (current as Record<string, unknown>)[segment]
150
- if (current === undefined) {
151
- return false
152
- }
153
- }
154
- return current
155
- }
156
-
157
- // Check scope for local variables (template arguments) first
158
- if (scope.has(expression)) {
159
- return scope.get(expression)
160
- }
161
-
162
- // Check constants (job-level then top-level)
163
- const constantValue = (context as any).getConstant?.(expression)
164
- if (constantValue !== undefined) {
165
- return constantValue
166
- }
167
-
168
- // Check context for global outputs from other jobs/actions
169
- try {
170
- return context.getOutput(expression)
171
- } catch (e) {
172
- // Provide a more helpful error if an unresolved reference is found
173
- throw new Error(`Failed to resolve expression "{{${expression}}}". It is not a valid Contract(...) or Network() reference, local scope variable, constant, or a known output.`)
174
- }
175
- }
176
-
177
- /**
178
- * Dispatches a `ValueResolver` object to its specific handler.
179
- * @private
180
- */
181
- private async resolveValueResolverObject(
182
- obj: ValueResolverObject,
183
- context: ExecutionContext,
184
- scope: ResolutionScope,
185
- ): Promise<any> {
186
- // Recursively resolve all arguments before processing the object itself
187
- const resolvedArgs = await this.resolveArguments(obj.arguments, context, scope)
188
-
189
- switch (obj.type) {
190
- case 'abi-encode':
191
- return this.resolveAbiEncode(resolvedArgs as AbiEncodeValue['arguments'])
192
- case 'abi-pack':
193
- return this.resolveAbiPack(resolvedArgs as AbiPackValue['arguments'])
194
- case 'constructor-encode':
195
- return this.resolveConstructorEncode(resolvedArgs as ConstructorEncodeValue['arguments'])
196
- case 'compute-create':
197
- return this.resolveComputeCreate(resolvedArgs as ComputeCreateValue['arguments'])
198
- case 'compute-create2':
199
- return this.resolveComputeCreate2(resolvedArgs as ComputeCreate2Value['arguments'])
200
- case 'read-balance':
201
- return this.resolveReadBalance(resolvedArgs as ReadBalanceValue['arguments'], context)
202
- case 'get-storage-at':
203
- return this.resolveGetStorageAt(resolvedArgs as GetStorageAtValue['arguments'], context)
204
- case 'compute-slot':
205
- return this.resolveComputeSlot(resolvedArgs as ComputeSlotValue['arguments'])
206
- case 'basic-arithmetic':
207
- return this.resolveBasicArithmetic(resolvedArgs as BasicArithmeticValue['arguments'])
208
- case 'call':
209
- return this.resolveCall(resolvedArgs as CallValue['arguments'], context)
210
- case 'contract-exists':
211
- return this.resolveContractExists(resolvedArgs as ContractExistsValue['arguments'], context)
212
- case 'job-completed':
213
- return this.resolveJobCompleted(resolvedArgs as JobCompletedValue['arguments'], context)
214
- case 'read-json':
215
- return this.resolveReadJson(resolvedArgs as ReadJsonValue['arguments'])
216
- case 'resolve-json':
217
- return this.resolveJsonValue(resolvedArgs, context)
218
- case 'value-empty':
219
- return this.resolveValueEmpty(resolvedArgs as ValueEmptyValue['arguments'])
220
- case 'slice-bytes':
221
- return this.resolveSliceBytes(resolvedArgs as SliceBytesValue['arguments'])
222
- default:
223
- throw new Error(`Unknown value resolver type: ${(obj as any).type}`)
224
- }
225
- }
226
-
227
- // --- Specific Resolver Implementations ---
228
-
229
- private resolveAbiEncode(args: AbiEncodeValue['arguments']): string {
230
- const { signature, values } = args
231
-
232
- // Validate that signature is provided
233
- if (!signature) {
234
- throw new Error('abi-encode: signature is required')
235
- }
236
-
237
- // Validate that values array is provided
238
- if (!values) {
239
- throw new Error('abi-encode: values array is required')
240
- }
241
-
242
- // At this point, signature should be resolved to a string
243
- const signatureStr = signature as string
244
- if (typeof signatureStr !== 'string') {
245
- throw new Error('abi-encode: signature must be a string')
246
- }
247
-
248
- try {
249
- // Create a temporary interface with just this function to encode the data
250
- const iface = new ethers.Interface([`function ${signatureStr}`])
251
-
252
- // Get the function name from the signature (everything before the first '(')
253
- const functionName = signatureStr.split('(')[0]
254
-
255
- // Encode the function call data
256
- return iface.encodeFunctionData(functionName, values)
257
- } catch (error) {
258
- throw new Error(`abi-encode: Failed to encode function data: ${error instanceof Error ? error.message : String(error)}`)
259
- }
260
- }
261
-
262
- private resolveAbiPack(args: AbiPackValue['arguments']): string {
263
- const { types, values } = args
264
-
265
- // Validate that types array is provided
266
- if (!types) {
267
- throw new Error('abi-pack: types array is required')
268
- }
269
-
270
- // Validate that values array is provided
271
- if (!values) {
272
- throw new Error('abi-pack: values array is required')
273
- }
274
-
275
- // Validate that types and values arrays have the same length
276
- if (types.length !== values.length) {
277
- throw new Error(`abi-pack: types array length (${types.length}) must match values array length (${values.length})`)
278
- }
279
-
280
- // At this point, types should be resolved to strings
281
- const typesArray = types as string[]
282
- if (!typesArray.every(type => typeof type === 'string')) {
283
- throw new Error('abi-pack: all types must be strings')
284
- }
285
-
286
- try {
287
- // Use ethers.js solidityPacked for packed encoding (no padding)
288
- return ethers.solidityPacked(typesArray, values)
289
- } catch (error) {
290
- throw new Error(`abi-pack: Failed to pack values: ${error instanceof Error ? error.message : String(error)}`)
291
- }
292
- }
293
-
294
- private resolveConstructorEncode(args: ConstructorEncodeValue['arguments']): string {
295
- const { creationCode, types, values } = args
296
-
297
- // Validate that types and values arrays have the same length
298
- if (types && values && types.length !== values.length) {
299
- throw new Error(`constructor-encode: types array length (${types.length}) must match values array length (${values.length})`)
300
- }
301
-
302
- // If no creationCode is provided, just do ABI encoding of constructor arguments
303
- if (!creationCode) {
304
- // If no constructor arguments either, return empty string
305
- if (!types || !values || types.length === 0 || values.length === 0) {
306
- return '0x'
307
- }
308
-
309
- // ABI encode the constructor arguments using the explicit types
310
- return ethers.AbiCoder.defaultAbiCoder().encode(types as string[], values)
311
- }
312
-
313
- // Validate that creation code is valid bytecode
314
- if (!isBytesLike(creationCode)) {
315
- throw new Error(`Invalid creation code: ${creationCode}`)
316
- }
317
-
318
- // If no constructor arguments, return the creation code as-is
319
- if (!types || !values || types.length === 0 || values.length === 0) {
320
- return creationCode
321
- }
322
-
323
- // ABI encode the constructor arguments using the explicit types
324
- const encodedArgs = ethers.AbiCoder.defaultAbiCoder().encode(types as string[], values)
325
-
326
- // Concatenate creation code with encoded constructor arguments
327
- // Remove '0x' prefix from encoded args if present
328
- const cleanEncodedArgs = encodedArgs.startsWith('0x') ? encodedArgs.slice(2) : encodedArgs
329
- const cleanCreationCode = creationCode.startsWith('0x') ? creationCode.slice(2) : creationCode
330
-
331
- return '0x' + cleanCreationCode + cleanEncodedArgs
332
- }
333
-
334
- private resolveComputeCreate(args: ComputeCreateValue['arguments']): string {
335
- const { deployerAddress, nonce } = args
336
- // Check if the deployer address is a valid address
337
- if (!isAddress(deployerAddress)) {
338
- throw new Error(`Invalid deployer address: ${deployerAddress}`)
339
- }
340
- // Check if the nonce is a valid value
341
- if (!isBigNumberish(nonce)) {
342
- throw new Error(`Invalid nonce: ${nonce}`)
343
- }
344
- const bnNonce = ethers.toBigInt(nonce)
345
- // Create the create address
346
- return ethers.getCreateAddress({
347
- from: deployerAddress,
348
- nonce: bnNonce,
349
- })
350
- }
351
-
352
- private resolveComputeCreate2(args: ComputeCreate2Value['arguments']): string {
353
- const { deployerAddress, salt, initCode } = args
354
- // Check if the deployer address is a valid address
355
- if (!isAddress(deployerAddress)) {
356
- throw new Error(`Invalid deployer address: ${deployerAddress}`)
357
- }
358
- // Check if the salt is a valid bytes value
359
- if (!isBytesLike(salt)) {
360
- throw new Error(`Invalid salt: ${salt}`)
361
- }
362
- // Check if the init code is a valid bytes value
363
- if (!isBytesLike(initCode)) {
364
- throw new Error(`Invalid init code: ${initCode}`)
365
- }
366
- // Hash the init code using Keccak256
367
- const initCodeHash = ethers.keccak256(initCode)
368
- // Create the create2 address
369
- return ethers.getCreate2Address(deployerAddress, salt, initCodeHash)
370
- }
371
-
372
- private async resolveReadBalance(args: ReadBalanceValue['arguments'], context: ExecutionContext): Promise<string> {
373
- // Check if the address is a valid address
374
- const addressValue = args.address as any
375
-
376
- if (!isAddress(addressValue)) {
377
- throw new Error(`Invalid address: ${addressValue}`)
378
- }
379
-
380
- const balance = await context.provider.getBalance(addressValue)
381
- return balance.toString()
382
- }
383
-
384
- private async resolveGetStorageAt(args: GetStorageAtValue['arguments'], context: ExecutionContext): Promise<string> {
385
- const { address, slot } = args
386
-
387
- // Check if the address is a valid address
388
- if (!isAddress(address)) {
389
- throw new Error(`Invalid address: ${address}`)
390
- }
391
-
392
- // Normalize the slot to a BigInt
393
- // After resolution, slot should be a string or number
394
- const slotValue = ethers.toBigInt(slot as string | number)
395
-
396
- const storageValue = await context.provider.getStorage(address, slotValue)
397
- // getStorage returns a hex string, ensure it's 32 bytes (64 hex chars + 0x)
398
- return ethers.hexlify(storageValue)
399
- }
400
-
401
- /**
402
- * Computes EVM storage slots for the common Solidity storage layouts.
403
- * Always returns a 32-byte, 0x-prefixed lowercase hex string so the result can
404
- * be fed straight into `get-storage-at` or nested as the `slot` of another
405
- * `compute-slot` (e.g. nested mappings, structs inside mappings, ...).
406
- */
407
- private resolveComputeSlot(args: ComputeSlotValue['arguments']): string {
408
- if (!args || typeof args !== 'object' || typeof (args as any).kind !== 'string') {
409
- throw new Error('compute-slot: "kind" is required')
410
- }
411
-
412
- switch (args.kind) {
413
- case 'mapping': {
414
- const { slot, key, keyType } = args
415
- if (slot === undefined || slot === null) {
416
- throw new Error('compute-slot (mapping): "slot" is required')
417
- }
418
- if (key === undefined || key === null) {
419
- throw new Error('compute-slot (mapping): "key" is required')
420
- }
421
- const baseSlot = ethers.toBigInt(slot as string | number)
422
- const type = (keyType as string) || 'uint256'
423
- try {
424
- // mapping value slot = keccak256(h(key) . p)
425
- // Dynamic key types (string/bytes) are packed; value types are ABI-encoded (left-padded to 32 bytes).
426
- if (type === 'string' || type === 'bytes') {
427
- return ethers.solidityPackedKeccak256([type, 'uint256'], [key, baseSlot])
428
- }
429
- const encoded = ethers.AbiCoder.defaultAbiCoder().encode([type, 'uint256'], [key, baseSlot])
430
- return ethers.keccak256(encoded)
431
- } catch (error) {
432
- throw new Error(`compute-slot (mapping): failed to encode key as "${type}": ${error instanceof Error ? error.message : String(error)}`)
433
- }
434
- }
435
-
436
- case 'dynamic-array': {
437
- const { slot, index, elementSize } = args
438
- if (slot === undefined || slot === null) {
439
- throw new Error('compute-slot (dynamic-array): "slot" is required')
440
- }
441
- const baseSlot = ethers.toBigInt(slot as string | number)
442
- const idx = index === undefined || index === null ? 0n : ethers.toBigInt(index as string | number)
443
- const size = elementSize === undefined || elementSize === null ? 1n : ethers.toBigInt(elementSize as string | number)
444
- // element slot = keccak256(slot) + index * elementSize
445
- const dataStart = ethers.toBigInt(ethers.keccak256(ethers.toBeHex(baseSlot, 32)))
446
- return ethers.toBeHex(dataStart + idx * size, 32)
447
- }
448
-
449
- case 'struct-field': {
450
- const { slot, offset } = args
451
- if (slot === undefined || slot === null) {
452
- throw new Error('compute-slot (struct-field): "slot" is required')
453
- }
454
- if (offset === undefined || offset === null) {
455
- throw new Error('compute-slot (struct-field): "offset" is required')
456
- }
457
- const result = ethers.toBigInt(slot as string | number) + ethers.toBigInt(offset as string | number)
458
- return ethers.toBeHex(result, 32)
459
- }
460
-
461
- case 'erc7201': {
462
- const { id } = args
463
- if (typeof id !== 'string' || id.length === 0) {
464
- throw new Error('compute-slot (erc7201): "id" must be a non-empty string')
465
- }
466
- // keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
467
- const idHash = ethers.toBigInt(ethers.keccak256(ethers.toUtf8Bytes(id)))
468
- const inner = ethers.keccak256(ethers.toBeHex(idHash - 1n, 32))
469
- const mask = ((1n << 256n) - 1n) ^ 0xffn
470
- return ethers.toBeHex(ethers.toBigInt(inner) & mask, 32)
471
- }
472
-
473
- case 'eip1967': {
474
- const labels: Record<string, string> = {
475
- implementation: 'eip1967.proxy.implementation',
476
- admin: 'eip1967.proxy.admin',
477
- beacon: 'eip1967.proxy.beacon',
478
- }
479
- const name = args.name as string
480
- const label = labels[name]
481
- if (!label) {
482
- throw new Error(`compute-slot (eip1967): "name" must be one of implementation, admin, beacon (got "${name}")`)
483
- }
484
- // slot = keccak256("eip1967.proxy.<name>") - 1
485
- const slot = ethers.toBigInt(ethers.keccak256(ethers.toUtf8Bytes(label))) - 1n
486
- return ethers.toBeHex(slot, 32)
487
- }
488
-
489
- default:
490
- throw new Error(`compute-slot: unknown kind "${(args as any).kind}"`)
491
- }
492
- }
493
-
494
- private resolveBasicArithmetic(args: BasicArithmeticValue['arguments']): string | boolean {
495
- if (!args.values || args.values.length < 2) {
496
- throw new Error(`basic-arithmetic requires at least 2 values, got ${args.values?.length ?? 0}`)
497
- }
498
-
499
- switch (args.operation) {
500
- // Equality (return boolean, tolerate nullish / non-numeric values)
501
- case 'eq': {
502
- const [a, b] = args.values
503
- return this.valuesEqual(a, b)
504
- }
505
- case 'neq': {
506
- const [a, b] = args.values
507
- return !this.valuesEqual(a, b)
508
- }
509
-
510
- // Arithmetic (return string)
511
- case 'add':
512
- case 'sub':
513
- case 'mul':
514
- case 'div':
515
- case 'gt':
516
- case 'lt':
517
- case 'gte':
518
- case 'lte':
519
- break
520
-
521
- default:
522
- throw new Error(`Unsupported basic-arithmetic operation: ${args.operation}`)
523
- }
524
-
525
- const numbers = args.values.map(v => ethers.toBigInt(v))
526
- const [a, b] = numbers
527
-
528
- switch (args.operation) {
529
- case 'add': return numbers.reduce((sum, current) => sum + current).toString()
530
- case 'sub': return (a - b).toString()
531
- case 'mul': return (a * b).toString()
532
- case 'div': return (a / b).toString()
533
-
534
- // Comparison (return boolean)
535
- case 'gt': return a > b
536
- case 'lt': return a < b
537
- case 'gte': return a >= b
538
- case 'lte': return a <= b
539
-
540
- default:
541
- throw new Error(`Unsupported basic-arithmetic operation: ${args.operation}`)
542
- }
543
- }
544
-
545
- private valuesEqual(a: any, b: any): boolean {
546
- if (a == null || b == null) {
547
- return a == null && b == null
548
- }
549
-
550
- if (isBigNumberish(a) && isBigNumberish(b)) {
551
- return ethers.toBigInt(a) === ethers.toBigInt(b)
552
- }
553
-
554
- if (typeof a === 'string' && typeof b === 'string') {
555
- return a === b
556
- }
557
-
558
- if (typeof a === 'boolean' && typeof b === 'boolean') {
559
- return a === b
560
- }
561
-
562
- if ((typeof a === 'object' && a !== null) || (typeof b === 'object' && b !== null)) {
563
- return JSON.stringify(a) === JSON.stringify(b)
564
- }
565
-
566
- return a === b
567
- }
568
-
569
- private async resolveCall(args: CallValue['arguments'], context: ExecutionContext): Promise<any> {
570
- const { to, signature, values } = args
571
-
572
- // Validate that we have a target address
573
- if (!to) {
574
- throw new Error('call: target address (to) is required')
575
- }
576
-
577
- // Validate that the target address is a valid Ethereum address
578
- if (!isAddress(to)) {
579
- throw new Error(`call: invalid target address: ${to}`)
580
- }
581
-
582
- // Validate that signature is provided
583
- if (!signature) {
584
- throw new Error('call: function signature is required')
585
- }
586
-
587
- // Validate that values array is provided
588
- if (!values) {
589
- throw new Error('call: values array is required')
590
- }
591
-
592
- const signatureStr = signature as string
593
- if (typeof signatureStr !== 'string') {
594
- throw new Error('call: signature must be a string')
595
- }
596
-
597
- try {
598
- // Create a temporary interface with just this function to encode the call data
599
- const iface = new ethers.Interface([`function ${signatureStr}`])
600
-
601
- // Get the function name from the signature (everything before the first '(')
602
- const functionName = signatureStr.split('(')[0]
603
-
604
- // Encode the function call data
605
- const callData = iface.encodeFunctionData(functionName, values)
606
-
607
- // Make the call using the provider
608
- const result = await context.provider.call({
609
- to: to,
610
- data: callData
611
- })
612
-
613
- // If the result is '0x', it means the function doesn't return anything
614
- if (result === '0x') {
615
- return null
616
- }
617
-
618
- // Decode the result using the function's return type
619
- const decodedResult = iface.decodeFunctionResult(functionName, result)
620
-
621
- // If there's only one return value, return it directly
622
- // Otherwise, return the array of values
623
- if (decodedResult.length === 1) {
624
- return decodedResult[0]
625
- }
626
-
627
- return decodedResult
628
- } catch (error) {
629
- throw new Error(`call: Failed to execute contract call: ${error instanceof Error ? error.message : String(error)}`)
630
- }
631
- }
632
-
633
- private async resolveContractExists(args: ContractExistsValue['arguments'], context: ExecutionContext): Promise<boolean> {
634
- const { address } = args
635
-
636
- if (!isAddress(address)) {
637
- throw new Error(`contract-exists: invalid address: ${address}`)
638
- }
639
-
640
- try {
641
- const code = await context.provider.getCode(address)
642
- // getCode returns '0x' if no contract exists at the address
643
- return code !== '0x'
644
- } catch (error) {
645
- throw new Error(`contract-exists: Failed to check contract existence: ${error instanceof Error ? error.message : String(error)}`)
646
- }
647
- }
648
-
649
- private async resolveJobCompleted(args: JobCompletedValue['arguments'], context: ExecutionContext): Promise<boolean> {
650
- const { job: jobName } = args
651
-
652
- // For now, we'll assume that if the job is being referenced, it has been completed.
653
- // This is a simplification - in a more complete implementation, we might check
654
- // the job's completion status from the deployer's results.
655
- //
656
- // Since the dependency graph already ensures jobs run in the correct order,
657
- // and this condition is used in setup blocks to wait for dependencies,
658
- // we can simply return true here.
659
- return true
660
- }
661
-
662
- private resolveReadJson(args: ReadJsonValue['arguments']): any {
663
- const { json, path } = args
664
-
665
- if (json === undefined || json === null) {
666
- throw new Error('read-json: json argument is required')
667
- }
668
-
669
- if (typeof path !== 'string' && typeof path !== 'number') {
670
- throw new Error('read-json: path must be a string or number')
671
- }
672
-
673
- const normalizedPath = String(path)
674
-
675
- // If path is empty, return the entire JSON object
676
- if (normalizedPath === '') {
677
- return json
678
- }
679
-
680
- try {
681
- // Split the path by dots to handle nested access
682
- const pathParts = normalizedPath.split('.')
683
- let current = json
684
-
685
- for (const part of pathParts) {
686
- if (current === null || current === undefined) {
687
- throw new Error(`Cannot access property "${part}" of ${current}`)
688
- }
689
-
690
- // Check if the part is a number (array index)
691
- const index = parseInt(part, 10)
692
- if (!isNaN(index) && Array.isArray(current)) {
693
- current = current[index]
694
- } else if (typeof current === 'object') {
695
- current = current[part]
696
- } else {
697
- throw new Error(`Cannot access property "${part}" of non-object value`)
698
- }
699
- }
700
-
701
- return current
702
- } catch (error) {
703
- throw new Error(`read-json: Failed to access path "${normalizedPath}": ${error instanceof Error ? error.message : String(error)}`)
704
- }
705
- }
706
-
707
- private async resolveJsonValue(args: any, context: ExecutionContext): Promise<any> {
708
- if (Array.isArray(args)) {
709
- return Promise.all(args.map(v => this.resolveJsonValue(v, context)))
710
- } else if (typeof args === 'object' && args !== null) {
711
- const resolved: Record<string, any> = {}
712
- for (const [k, v] of Object.entries(args)) {
713
- resolved[k] = await this.resolveJsonValue(v, context)
714
- }
715
- return resolved
716
- } else {
717
- // For primitive values, resolve them using the main resolve method
718
- return this.resolve(args, context)
719
- }
720
- }
721
-
722
- private resolveValueEmpty(args: ValueEmptyValue['arguments']): boolean {
723
- const { value } = args
724
-
725
- if (value === undefined || value === null) {
726
- return true
727
- }
728
-
729
- if (typeof value === 'string') {
730
- return value === '' || value === '0x'
731
- }
732
-
733
- if (Array.isArray(value)) {
734
- return value.length === 0
735
- }
736
-
737
- if (typeof value === 'object') {
738
- return Object.keys(value).length === 0
739
- }
740
-
741
- return false
742
- }
743
-
744
- private resolveSliceBytes(args: SliceBytesValue['arguments']): string {
745
- const { value, start, end, range } = args
746
-
747
- if (!isBytesLike(value)) {
748
- throw new Error('slice-bytes: value must be bytes-like (hex string, Uint8Array, etc.)')
749
- }
750
-
751
- if (range !== undefined && (start !== undefined || end !== undefined)) {
752
- throw new Error('slice-bytes: provide either range or start/end, not both')
753
- }
754
-
755
- if (range !== undefined && typeof range !== 'string') {
756
- throw new Error('slice-bytes: range must be a string in "start:end" format')
757
- }
758
-
759
- const normalizedHex = ethers.hexlify(value as ethers.BytesLike)
760
- const hexBody = normalizedHex.slice(2)
761
-
762
- if (hexBody.length % 2 !== 0) {
763
- throw new Error('slice-bytes: value must have an even-length hex string')
764
- }
765
-
766
- const totalBytes = hexBody.length / 2
767
- const { startIndex, endIndex } = this.computeSliceBounds(totalBytes, { start, end, range })
768
-
769
- if (startIndex >= endIndex) {
770
- return '0x'
771
- }
772
-
773
- const sliced = hexBody.slice(startIndex * 2, endIndex * 2)
774
- return sliced.length === 0 ? '0x' : `0x${sliced}`
775
- }
776
-
777
- private computeSliceBounds(
778
- totalBytes: number,
779
- params: { start?: any; end?: any; range?: any },
780
- ): { startIndex: number; endIndex: number } {
781
- let startValue: number | undefined
782
- let endValue: number | undefined
783
-
784
- if (params.range !== undefined) {
785
- const trimmedRange = (params.range as string).trim()
786
- const rangeMatch = trimmedRange.match(/^\[?\s*(-?\d+)?\s*:\s*(-?\d+)?\s*\]?$/)
787
-
788
- if (!rangeMatch) {
789
- throw new Error('slice-bytes: range must follow the "start:end" format (e.g., "0:4" or ":-1")')
790
- }
791
-
792
- const [, rawStart, rawEnd] = rangeMatch
793
- if (rawStart !== undefined) {
794
- startValue = this.parseSliceIndex(rawStart, 'range start')
795
- }
796
- if (rawEnd !== undefined) {
797
- endValue = this.parseSliceIndex(rawEnd, 'range end')
798
- }
799
- } else {
800
- if (params.start !== undefined) {
801
- startValue = this.parseSliceIndex(params.start, 'start')
802
- }
803
- if (params.end !== undefined) {
804
- endValue = this.parseSliceIndex(params.end, 'end')
805
- }
806
- }
807
-
808
- const startIndex = this.normalizeSliceIndex(startValue ?? 0, totalBytes)
809
- const endIndex = this.normalizeSliceIndex(endValue ?? totalBytes, totalBytes)
810
-
811
- return { startIndex, endIndex }
812
- }
813
-
814
- private parseSliceIndex(value: any, label: string): number {
815
- if (value === undefined || value === null) {
816
- throw new Error(`slice-bytes: ${label} cannot be null or undefined`)
817
- }
818
-
819
- const normalizedValue = typeof value === 'string' ? value.trim() : value
820
-
821
- try {
822
- const bigIntValue = ethers.toBigInt(normalizedValue)
823
- const maxSafe = BigInt(Number.MAX_SAFE_INTEGER)
824
- if (bigIntValue > maxSafe || bigIntValue < -maxSafe) {
825
- throw new Error(`${label} is outside the supported numeric range`)
826
- }
827
- return Number(bigIntValue)
828
- } catch (_error) {
829
- throw new Error(`slice-bytes: ${label} must be an integer or integer-like string`)
830
- }
831
- }
832
-
833
- private normalizeSliceIndex(index: number, totalBytes: number): number {
834
- if (!Number.isFinite(index) || !Number.isInteger(index)) {
835
- throw new Error('slice-bytes: slice positions must be finite integers')
836
- }
837
-
838
- let normalized = index
839
- if (normalized < 0) {
840
- normalized = totalBytes + normalized
841
- }
842
-
843
- if (normalized < 0) {
844
- normalized = 0
845
- }
846
-
847
- if (normalized > totalBytes) {
848
- normalized = totalBytes
849
- }
850
-
851
- return normalized
852
- }
853
-
854
- /**
855
- * Helper to recursively resolve the `arguments` field of any `ValueResolver` object.
856
- * @private
857
- */
858
- private async resolveArguments(args: any, context: ExecutionContext, scope: ResolutionScope): Promise<any> {
859
- if (Array.isArray(args)) {
860
- return Promise.all(args.map(arg => this.resolve(arg, context, scope)))
861
- }
862
- if (typeof args === 'object' && args !== null) {
863
- const resolvedObject: { [key: string]: any } = {}
864
- for (const key in args) {
865
- if (Object.prototype.hasOwnProperty.call(args, key)) {
866
- resolvedObject[key] = await this.resolve(args[key], context, scope)
867
- }
868
- }
869
- return resolvedObject
870
- }
871
- return this.resolve(args, context, scope)
872
- }
873
- }