@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.
- package/README.md +27 -0
- package/dist/lib/__tests__/network-loader.spec.js.map +1 -1
- package/dist/lib/core/__tests__/resolver.spec.js +22 -0
- package/dist/lib/core/__tests__/resolver.spec.js.map +1 -1
- package/dist/lib/core/__tests__/sign-actions.spec.d.ts +2 -0
- package/dist/lib/core/__tests__/sign-actions.spec.d.ts.map +1 -0
- package/dist/lib/core/__tests__/sign-actions.spec.js +128 -0
- package/dist/lib/core/__tests__/sign-actions.spec.js.map +1 -0
- package/dist/lib/core/__tests__/signer.spec.d.ts +2 -0
- package/dist/lib/core/__tests__/signer.spec.d.ts.map +1 -0
- package/dist/lib/core/__tests__/signer.spec.js +40 -0
- package/dist/lib/core/__tests__/signer.spec.js.map +1 -0
- package/dist/lib/core/context.d.ts +3 -2
- package/dist/lib/core/context.d.ts.map +1 -1
- package/dist/lib/core/context.js +3 -2
- package/dist/lib/core/context.js.map +1 -1
- package/dist/lib/core/engine.d.ts +4 -0
- package/dist/lib/core/engine.d.ts.map +1 -1
- package/dist/lib/core/engine.js +173 -0
- package/dist/lib/core/engine.js.map +1 -1
- package/dist/lib/core/signer.d.ts +7 -0
- package/dist/lib/core/signer.d.ts.map +1 -0
- package/dist/lib/core/signer.js +60 -0
- package/dist/lib/core/signer.js.map +1 -0
- package/dist/lib/parsers/__tests__/source.spec.js +37 -0
- package/dist/lib/parsers/__tests__/source.spec.js.map +1 -1
- package/dist/lib/parsers/source.js +1 -1
- package/dist/lib/parsers/source.js.map +1 -1
- package/dist/lib/provenance.js +51 -2
- package/dist/lib/provenance.js.map +1 -1
- package/dist/lib/types/actions.d.ts +26 -2
- package/dist/lib/types/actions.d.ts.map +1 -1
- package/dist/lib/types/actions.js +3 -0
- package/dist/lib/types/actions.js.map +1 -1
- package/dist/lib/types/source.d.ts +2 -0
- package/dist/lib/types/source.d.ts.map +1 -1
- package/package.json +4 -1
- package/.eslintrc.json +0 -29
- package/.github/workflows/ci.yml +0 -181
- package/CONCEPT.md +0 -24
- package/contracts/checked-call.huff +0 -65
- package/eslint.config.js +0 -48
- package/examples/jobs/guards-v1.yaml +0 -17
- package/examples/jobs/sequence-seq-0001-patch.yaml +0 -59
- package/examples/jobs/sequence-v1.yaml +0 -59
- package/examples/templates/sequence-factory-v1.yaml +0 -56
- package/jest.config.js +0 -25
- package/src/cli.ts +0 -18
- package/src/commands/common.ts +0 -61
- package/src/commands/dry.ts +0 -209
- package/src/commands/etherscan.ts +0 -360
- package/src/commands/index.ts +0 -6
- package/src/commands/list.ts +0 -262
- package/src/commands/provenance.ts +0 -120
- package/src/commands/run.ts +0 -146
- package/src/commands/utils.ts +0 -215
- package/src/index.ts +0 -67
- package/src/lib/__tests__/deployer-events.spec.ts +0 -338
- package/src/lib/__tests__/deployer.spec.ts +0 -2269
- package/src/lib/__tests__/network-loader.spec.ts +0 -150
- package/src/lib/__tests__/network-selection.spec.ts +0 -41
- package/src/lib/__tests__/network-utils.spec.ts +0 -230
- package/src/lib/__tests__/provenance.spec.ts +0 -208
- package/src/lib/artifacts/__tests__/fixtures/contract1.json +0 -19
- package/src/lib/artifacts/__tests__/fixtures/contract2.json +0 -19
- package/src/lib/artifacts/__tests__/fixtures/duplicate-name.json +0 -19
- package/src/lib/artifacts/__tests__/fixtures/nested/nested-contract.json +0 -18
- package/src/lib/artifacts/__tests__/fixtures/not-an-artifact.json +0 -8
- package/src/lib/artifacts/__tests__/fixtures/readme.txt +0 -2
- package/src/lib/contracts/__tests__/repository.spec.ts +0 -612
- package/src/lib/contracts/repository.ts +0 -411
- package/src/lib/core/__tests__/assert-action.spec.ts +0 -474
- package/src/lib/core/__tests__/context.spec.ts +0 -37
- package/src/lib/core/__tests__/engine.spec.ts +0 -2005
- package/src/lib/core/__tests__/graph.spec.ts +0 -125
- package/src/lib/core/__tests__/json-integration.spec.ts +0 -425
- package/src/lib/core/__tests__/loader.spec.ts +0 -367
- package/src/lib/core/__tests__/multi-platform-verification.spec.ts +0 -406
- package/src/lib/core/__tests__/resolver.spec.ts +0 -2496
- package/src/lib/core/__tests__/static-action.spec.ts +0 -172
- package/src/lib/core/context.ts +0 -127
- package/src/lib/core/engine.ts +0 -1834
- package/src/lib/core/graph.ts +0 -252
- package/src/lib/core/loader.ts +0 -253
- package/src/lib/core/resolver.ts +0 -873
- package/src/lib/deployer.ts +0 -1005
- package/src/lib/events/__tests__/event-system.spec.ts +0 -392
- package/src/lib/events/cli-adapter.ts +0 -369
- package/src/lib/events/emitter.ts +0 -62
- package/src/lib/events/index.ts +0 -3
- package/src/lib/events/types.ts +0 -520
- package/src/lib/index.ts +0 -17
- package/src/lib/network-loader.ts +0 -90
- package/src/lib/network-selection.ts +0 -73
- package/src/lib/network-utils.ts +0 -64
- package/src/lib/parsers/__tests__/buildinfo.spec.ts +0 -122
- package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-bytecode-buildinfo.json +0 -62
- package/src/lib/parsers/__tests__/fixtures/buildinfo/invalid-json.txt +0 -2
- package/src/lib/parsers/__tests__/fixtures/buildinfo/multi-contract-buildinfo.json +0 -89
- package/src/lib/parsers/__tests__/fixtures/buildinfo/no-contracts-buildinfo.json +0 -17
- package/src/lib/parsers/__tests__/fixtures/buildinfo/simple-buildinfo.json +0 -63
- package/src/lib/parsers/__tests__/fixtures/buildinfo/wrong-format.json +0 -4
- package/src/lib/parsers/__tests__/job.spec.ts +0 -439
- package/src/lib/parsers/__tests__/source.spec.ts +0 -134
- package/src/lib/parsers/__tests__/template.spec.ts +0 -111
- package/src/lib/parsers/artifact/__tests__/artifact.spec.ts +0 -117
- package/src/lib/parsers/artifact/__tests__/fixtures/empty-bytecode.json +0 -5
- package/src/lib/parsers/artifact/__tests__/fixtures/hardhat-artifact.json +0 -67
- package/src/lib/parsers/artifact/__tests__/fixtures/invalid-bytecode.json +0 -5
- package/src/lib/parsers/artifact/__tests__/fixtures/invalid-json.txt +0 -11
- package/src/lib/parsers/artifact/__tests__/fixtures/minimal-artifact.json +0 -5
- package/src/lib/parsers/artifact/__tests__/fixtures/missing-abi.json +0 -4
- package/src/lib/parsers/artifact/__tests__/fixtures/missing-bytecode.json +0 -11
- package/src/lib/parsers/artifact/__tests__/fixtures/missing-contract-name.json +0 -11
- package/src/lib/parsers/artifact/__tests__/fixtures/simple-artifact.json +0 -40
- package/src/lib/parsers/artifact/__tests__/fixtures/wrong-types.json +0 -7
- package/src/lib/parsers/artifact/foundry-1.2.ts +0 -72
- package/src/lib/parsers/artifact/index.ts +0 -27
- package/src/lib/parsers/artifact/types.ts +0 -9
- package/src/lib/parsers/buildinfo.ts +0 -127
- package/src/lib/parsers/constants.ts +0 -56
- package/src/lib/parsers/index.ts +0 -6
- package/src/lib/parsers/job.ts +0 -160
- package/src/lib/parsers/source.ts +0 -129
- package/src/lib/parsers/template.ts +0 -135
- package/src/lib/provenance.ts +0 -785
- package/src/lib/std/templates/arachnid-deterministic-deployment-proxy.yaml +0 -68
- package/src/lib/std/templates/assured-deployment.yaml +0 -46
- package/src/lib/std/templates/era-evm-predeploy.yaml +0 -35
- package/src/lib/std/templates/erc-2470.yaml +0 -70
- package/src/lib/std/templates/min-balance.yaml +0 -35
- package/src/lib/std/templates/nano-universal-deployer.yaml +0 -61
- package/src/lib/std/templates/raw-erc-2470.yaml +0 -62
- package/src/lib/std/templates/raw-nano-universal-deployer.yaml +0 -54
- package/src/lib/std/templates/raw-sequence-universal-deployer-2.yaml +0 -52
- package/src/lib/std/templates/sequence-universal-deployer-2.yaml +0 -61
- package/src/lib/types/__tests__/json-request-action.spec.ts +0 -243
- package/src/lib/types/__tests__/read-json-value.spec.ts +0 -278
- package/src/lib/types/__tests__/resolve-json-value.spec.ts +0 -769
- package/src/lib/types/actions.ts +0 -148
- package/src/lib/types/artifacts.ts +0 -21
- package/src/lib/types/buildinfo.ts +0 -116
- package/src/lib/types/conditions.ts +0 -50
- package/src/lib/types/contracts.ts +0 -26
- package/src/lib/types/definitions.ts +0 -77
- package/src/lib/types/index.ts +0 -9
- package/src/lib/types/network.ts +0 -33
- package/src/lib/types/project.ts +0 -9
- package/src/lib/types/source.ts +0 -26
- package/src/lib/types/task.ts +0 -9
- package/src/lib/types/values.ts +0 -221
- package/src/lib/utils/assertion.ts +0 -24
- package/src/lib/utils/validation.ts +0 -116
- package/src/lib/validation/contract-references.ts +0 -210
- package/src/lib/validation/index.ts +0 -1
- package/src/lib/verification/__tests__/etherscan.spec.ts +0 -710
- package/src/lib/verification/__tests__/sourcify.spec.ts +0 -288
- package/src/lib/verification/etherscan.ts +0 -547
- package/src/lib/verification/sourcify.ts +0 -248
- package/test_validation/artifacts/TestContract.json +0 -9
- package/test_validation/jobs/test-missing.yaml +0 -16
- package/test_validation/networks.yaml +0 -3
- package/tsconfig.json +0 -36
package/src/lib/core/resolver.ts
DELETED
|
@@ -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
|
-
}
|