@0xsequence/catapult 1.3.9 → 1.3.11
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 +18 -0
- package/dist/lib/core/__tests__/json-integration.spec.js +22 -0
- package/dist/lib/core/__tests__/json-integration.spec.js.map +1 -1
- package/dist/lib/core/__tests__/resolver.spec.js +58 -0
- package/dist/lib/core/__tests__/resolver.spec.js.map +1 -1
- package/dist/lib/core/loader.d.ts.map +1 -1
- package/dist/lib/core/loader.js +0 -14
- package/dist/lib/core/loader.js.map +1 -1
- package/dist/lib/core/resolver.d.ts +4 -0
- package/dist/lib/core/resolver.d.ts.map +1 -1
- package/dist/lib/core/resolver.js +88 -0
- package/dist/lib/core/resolver.js.map +1 -1
- package/dist/lib/parsers/__tests__/job.spec.js +25 -2
- package/dist/lib/parsers/__tests__/job.spec.js.map +1 -1
- package/dist/lib/parsers/job.d.ts.map +1 -1
- package/dist/lib/parsers/job.js +31 -1
- package/dist/lib/parsers/job.js.map +1 -1
- package/dist/lib/std/templates/create4.yaml +172 -0
- package/dist/lib/types/values.d.ts +10 -1
- package/dist/lib/types/values.d.ts.map +1 -1
- package/dist/lib/utils/create4.d.ts +78 -0
- package/dist/lib/utils/create4.d.ts.map +1 -0
- package/dist/lib/utils/create4.js +237 -0
- package/dist/lib/utils/create4.js.map +1 -0
- package/package.json +13 -12
- package/src/lib/core/__tests__/json-integration.spec.ts +27 -2
- package/src/lib/core/__tests__/resolver.spec.ts +70 -2
- package/src/lib/core/loader.ts +0 -16
- package/src/lib/core/resolver.ts +114 -1
- package/src/lib/parsers/__tests__/job.spec.ts +27 -4
- package/src/lib/parsers/job.ts +44 -2
- package/src/lib/types/values.ts +12 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeCreate4Plan = computeCreate4Plan;
|
|
4
|
+
const ethers_1 = require("ethers");
|
|
5
|
+
const create4_1 = require("@0xsequence/create4");
|
|
6
|
+
function computeCreate4Plan(args) {
|
|
7
|
+
const factoryAddress = normalizeAddress(args.deployerAddress);
|
|
8
|
+
const targetChainId = normalizeChainIdInput(args.chainId, 'chain id');
|
|
9
|
+
const planSalt = normalizePlanSalt(args.salt);
|
|
10
|
+
const planSpec = buildPlanSpec(args.bytecodes, planSalt);
|
|
11
|
+
const plan = (0, create4_1.buildPlanFromSpec)(planSpec);
|
|
12
|
+
const deploymentSalt = (0, create4_1.deriveDeploymentSalt)(plan.root, planSalt);
|
|
13
|
+
const address = (0, create4_1.computeCreate3Address)(factoryAddress, deploymentSalt);
|
|
14
|
+
const targetLeaf = plan.leaves.find(leaf => BigInt(leaf.chainId) === targetChainId);
|
|
15
|
+
const target = targetLeaf
|
|
16
|
+
? buildChainTarget(targetLeaf)
|
|
17
|
+
: buildFallbackTarget(plan.leaves, plan.fallback, targetChainId);
|
|
18
|
+
return {
|
|
19
|
+
factoryAddress,
|
|
20
|
+
chainId: targetChainId.toString(),
|
|
21
|
+
planRoot: plan.root,
|
|
22
|
+
salt: plan.salt,
|
|
23
|
+
deploymentSalt,
|
|
24
|
+
address,
|
|
25
|
+
target,
|
|
26
|
+
plan: {
|
|
27
|
+
root: plan.root,
|
|
28
|
+
leaves: plan.leaves,
|
|
29
|
+
fallback: plan.fallback,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function buildPlanSpec(bytecodes, salt) {
|
|
34
|
+
if (!bytecodes || typeof bytecodes !== 'object') {
|
|
35
|
+
throw new Error('create4-plan: bytecodes must be an object with chain entries and a fallback');
|
|
36
|
+
}
|
|
37
|
+
const sharedConstructor = normalizeConstructor(bytecodes.sharedConstructor, 'bytecodes.sharedConstructor');
|
|
38
|
+
const fallbackEntry = bytecodes.fallback ?? bytecodes['fallbackInitCode'];
|
|
39
|
+
if (!fallbackEntry) {
|
|
40
|
+
throw new Error('create4-plan: bytecodes.fallback is required');
|
|
41
|
+
}
|
|
42
|
+
const chains = [];
|
|
43
|
+
const chainSource = (bytecodes.chains && typeof bytecodes.chains === 'object') ? bytecodes.chains : bytecodes;
|
|
44
|
+
for (const [key, value] of Object.entries(chainSource)) {
|
|
45
|
+
if (key === 'fallback' || key === 'fallbackInitCode' || key === 'sharedConstructor' || key === 'chains') {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (value === undefined || value === null) {
|
|
49
|
+
throw new Error(`create4-plan: bytecodes entry "${key}" is missing a value`);
|
|
50
|
+
}
|
|
51
|
+
chains.push(normalizeChainEntry(key, value, sharedConstructor));
|
|
52
|
+
}
|
|
53
|
+
if (chains.length === 0) {
|
|
54
|
+
throw new Error('create4-plan: at least one chain entry is required');
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
salt,
|
|
58
|
+
fallbackInitCode: normalizeBytecodeEntry(fallbackEntry, undefined, 'bytecodes.fallback'),
|
|
59
|
+
chains: chains.map(entry => ({
|
|
60
|
+
chainId: entry.chainId.toString(),
|
|
61
|
+
initCode: entry.initCode,
|
|
62
|
+
label: entry.label,
|
|
63
|
+
})),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function buildChainTarget(leaf) {
|
|
67
|
+
return {
|
|
68
|
+
mode: 'chain',
|
|
69
|
+
modeFlag: '1',
|
|
70
|
+
chainId: leaf.chainId,
|
|
71
|
+
nextChainId: leaf.nextChainId,
|
|
72
|
+
initCode: leaf.initCode,
|
|
73
|
+
initCodeHash: leaf.initCodeHash,
|
|
74
|
+
proof: leaf.proof,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function buildFallbackTarget(leaves, fallback, targetChainId) {
|
|
78
|
+
const gapLeaf = findGapLeaf(leaves, targetChainId);
|
|
79
|
+
return {
|
|
80
|
+
mode: 'fallback',
|
|
81
|
+
modeFlag: '0',
|
|
82
|
+
gapChainId: gapLeaf.chainId,
|
|
83
|
+
gapNextChainId: gapLeaf.nextChainId,
|
|
84
|
+
gapLeafPrefix: gapLeaf.prefix,
|
|
85
|
+
gapLeafHash: gapLeaf.initCodeHash,
|
|
86
|
+
gapProof: gapLeaf.proof,
|
|
87
|
+
initCode: fallback.initCode,
|
|
88
|
+
initCodeHash: fallback.initCodeHash,
|
|
89
|
+
proof: fallback.proof,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function findGapLeaf(leaves, targetChainId) {
|
|
93
|
+
for (const leaf of leaves) {
|
|
94
|
+
if ((0, create4_1.isChainIdInGap)(leaf.chainId, leaf.nextChainId, targetChainId)) {
|
|
95
|
+
return leaf;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
throw new Error(`create4-plan: no fallback gap covers chain id ${targetChainId.toString()}`);
|
|
99
|
+
}
|
|
100
|
+
function normalizePlanSalt(value) {
|
|
101
|
+
if (value === undefined || value === null || value === '') {
|
|
102
|
+
return create4_1.ZERO_SALT;
|
|
103
|
+
}
|
|
104
|
+
return (0, create4_1.normalizeSaltHex)(value);
|
|
105
|
+
}
|
|
106
|
+
function normalizeChainEntry(key, entry, sharedConstructor) {
|
|
107
|
+
const trimmedKey = key.trim();
|
|
108
|
+
if (!trimmedKey) {
|
|
109
|
+
throw new Error('create4-plan: chain entry keys cannot be empty');
|
|
110
|
+
}
|
|
111
|
+
let entryObj;
|
|
112
|
+
if (typeof entry === 'string') {
|
|
113
|
+
entryObj = { initCode: entry };
|
|
114
|
+
}
|
|
115
|
+
else if (entry && typeof entry === 'object') {
|
|
116
|
+
entryObj = entry;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
throw new Error(`create4-plan: chain entry "${key}" must be a hex string or an object`);
|
|
120
|
+
}
|
|
121
|
+
const keyChainId = normalizeChainIdInput(trimmedKey, `chain id key "${key}"`);
|
|
122
|
+
if (entryObj.chainId !== undefined) {
|
|
123
|
+
const providedChainId = normalizeChainIdInput(entryObj.chainId, `chain id for "${key}"`);
|
|
124
|
+
if (providedChainId !== keyChainId) {
|
|
125
|
+
throw new Error(`create4-plan: chain id mismatch for entry "${key}"`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
key,
|
|
130
|
+
chainId: keyChainId,
|
|
131
|
+
label: typeof entryObj.label === 'string' ? entryObj.label : undefined,
|
|
132
|
+
initCode: normalizeBytecodeEntry(entryObj, sharedConstructor, `bytecodes[${key}]`),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function normalizeBytecodeEntry(entry, sharedConstructor, context) {
|
|
136
|
+
if (typeof entry === 'string') {
|
|
137
|
+
return normalizeHex(entry, `${context}.initCode`);
|
|
138
|
+
}
|
|
139
|
+
if (!entry || typeof entry !== 'object') {
|
|
140
|
+
throw new Error(`create4-plan: ${context} must be a hex string or an object`);
|
|
141
|
+
}
|
|
142
|
+
if (entry.initCode !== undefined) {
|
|
143
|
+
return normalizeHex(entry.initCode, `${context}.initCode`);
|
|
144
|
+
}
|
|
145
|
+
if (!entry.creationCode) {
|
|
146
|
+
throw new Error(`create4-plan: ${context} must include initCode or creationCode`);
|
|
147
|
+
}
|
|
148
|
+
const creationCode = normalizeHex(entry.creationCode, `${context}.creationCode`);
|
|
149
|
+
const hasOwnConstructor = Object.prototype.hasOwnProperty.call(entry, 'constructor');
|
|
150
|
+
const constructorOverride = hasOwnConstructor ? entry.constructor : undefined;
|
|
151
|
+
const constructorSpec = normalizeConstructor(constructorOverride ?? sharedConstructor, `${context}.constructor`);
|
|
152
|
+
if (!constructorSpec) {
|
|
153
|
+
return creationCode;
|
|
154
|
+
}
|
|
155
|
+
const encodedArgs = encodeConstructorArgs(constructorSpec, context);
|
|
156
|
+
return ethers_1.ethers.hexlify(ethers_1.ethers.concat([ethers_1.ethers.getBytes(creationCode), ethers_1.ethers.getBytes(encodedArgs)]));
|
|
157
|
+
}
|
|
158
|
+
function normalizeConstructor(value, context) {
|
|
159
|
+
if (value === undefined || value === null) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
if (typeof value !== 'object') {
|
|
163
|
+
throw new Error(`create4-plan: ${context} must be an object with types and values arrays`);
|
|
164
|
+
}
|
|
165
|
+
const types = Array.isArray(value.types) ? value.types.map(t => String(t)) : undefined;
|
|
166
|
+
const values = Array.isArray(value.values) ? [...value.values] : undefined;
|
|
167
|
+
if (!types || !values) {
|
|
168
|
+
throw new Error(`create4-plan: ${context} must include types[] and values[]`);
|
|
169
|
+
}
|
|
170
|
+
if (types.length !== values.length) {
|
|
171
|
+
throw new Error(`create4-plan: ${context} types length (${types.length}) must match values length (${values.length})`);
|
|
172
|
+
}
|
|
173
|
+
return { types, values };
|
|
174
|
+
}
|
|
175
|
+
function encodeConstructorArgs(constructorSpec, context) {
|
|
176
|
+
try {
|
|
177
|
+
return ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(constructorSpec.types, constructorSpec.values);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
181
|
+
throw new Error(`create4-plan: failed to encode constructor for ${context}: ${reason}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function normalizeHex(value, label) {
|
|
185
|
+
if (typeof value !== 'string') {
|
|
186
|
+
throw new Error(`create4-plan: ${label} must be provided as a hex string`);
|
|
187
|
+
}
|
|
188
|
+
let hex = value.trim();
|
|
189
|
+
if (!hex.startsWith('0x')) {
|
|
190
|
+
hex = '0x' + hex;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
return ethers_1.ethers.hexlify(ethers_1.ethers.getBytes(hex));
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
throw new Error(`create4-plan: ${label} must be a valid hex string`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function normalizeChainIdInput(value, label) {
|
|
200
|
+
if (typeof value === 'bigint') {
|
|
201
|
+
return assertChainIdRange(value, label);
|
|
202
|
+
}
|
|
203
|
+
if (typeof value === 'number') {
|
|
204
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) {
|
|
205
|
+
throw new Error(`create4-plan: ${label} must be a finite integer`);
|
|
206
|
+
}
|
|
207
|
+
return assertChainIdRange(BigInt(value), label);
|
|
208
|
+
}
|
|
209
|
+
if (typeof value === 'string') {
|
|
210
|
+
const trimmed = value.trim();
|
|
211
|
+
if (!trimmed) {
|
|
212
|
+
throw new Error(`create4-plan: ${label} cannot be empty`);
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
return assertChainIdRange(BigInt(trimmed), label);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
throw new Error(`create4-plan: invalid ${label}: ${value}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
throw new Error(`create4-plan: ${label} must be a string, number, or bigint`);
|
|
222
|
+
}
|
|
223
|
+
function assertChainIdRange(value, label) {
|
|
224
|
+
if (value < 0n || value > ((1n << 64n) - 1n)) {
|
|
225
|
+
throw new Error(`create4-plan: ${label} must fit within uint64 range`);
|
|
226
|
+
}
|
|
227
|
+
return value;
|
|
228
|
+
}
|
|
229
|
+
function normalizeAddress(value) {
|
|
230
|
+
try {
|
|
231
|
+
return ethers_1.ethers.getAddress(value);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
throw new Error(`create4-plan: invalid deployer address: ${value}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=create4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create4.js","sourceRoot":"","sources":["../../../src/lib/utils/create4.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xsequence/catapult",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.11",
|
|
4
4
|
"description": "Ethereum contract deployment CLI tool",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"catapult": "dist/index.js"
|
|
8
8
|
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc && cp -r src/lib/std dist/lib/",
|
|
11
|
+
"dev": "ts-node src/index.ts",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"start:dev": "npm run build && npm run start",
|
|
14
|
+
"watch": "tsc --watch",
|
|
15
|
+
"clean": "rm -rf dist",
|
|
16
|
+
"prepare": "npm run build",
|
|
17
|
+
"test": "jest --runInBand --detectOpenHandles",
|
|
18
|
+
"lint": "eslint src/**/*.ts",
|
|
19
|
+
"lint:fix": "eslint src/**/*.ts --fix"
|
|
20
|
+
},
|
|
9
21
|
"keywords": [
|
|
10
22
|
"ethereum",
|
|
11
23
|
"deployment",
|
|
@@ -52,16 +64,5 @@
|
|
|
52
64
|
},
|
|
53
65
|
"engines": {
|
|
54
66
|
"node": ">=16.0.0"
|
|
55
|
-
},
|
|
56
|
-
"scripts": {
|
|
57
|
-
"build": "tsc && cp -r src/lib/std dist/lib/",
|
|
58
|
-
"dev": "ts-node src/index.ts",
|
|
59
|
-
"start": "node dist/index.js",
|
|
60
|
-
"start:dev": "npm run build && npm run start",
|
|
61
|
-
"watch": "tsc --watch",
|
|
62
|
-
"clean": "rm -rf dist",
|
|
63
|
-
"test": "jest --runInBand --detectOpenHandles",
|
|
64
|
-
"lint": "eslint src/**/*.ts",
|
|
65
|
-
"lint:fix": "eslint src/**/*.ts --fix"
|
|
66
67
|
}
|
|
67
68
|
}
|
|
@@ -2,7 +2,7 @@ import { ExecutionEngine } from '../engine'
|
|
|
2
2
|
import { ExecutionContext } from '../context'
|
|
3
3
|
import { ValueResolver } from '../resolver'
|
|
4
4
|
import { ContractRepository } from '../../contracts/repository'
|
|
5
|
-
import { Action, Network, ReadJsonValue } from '../../types'
|
|
5
|
+
import { Action, Network, ReadJsonValue, SliceBytesValue } from '../../types'
|
|
6
6
|
import { VerificationPlatformRegistry } from '../../verification/etherscan'
|
|
7
7
|
|
|
8
8
|
describe('JSON Integration Tests', () => {
|
|
@@ -292,6 +292,31 @@ describe('JSON Integration Tests', () => {
|
|
|
292
292
|
expect(extractedTo).toBe('0x596aF90CecdBF9A768886E771178fd5561dD27Ab')
|
|
293
293
|
})
|
|
294
294
|
|
|
295
|
+
it('should allow piping read-json output into slice-bytes', async () => {
|
|
296
|
+
const response = {
|
|
297
|
+
txs: {
|
|
298
|
+
data: '0xaabbccddff'
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const value: SliceBytesValue = {
|
|
303
|
+
type: 'slice-bytes',
|
|
304
|
+
arguments: {
|
|
305
|
+
value: {
|
|
306
|
+
type: 'read-json',
|
|
307
|
+
arguments: {
|
|
308
|
+
json: response,
|
|
309
|
+
path: 'txs.data'
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
range: ':-1'
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const trimmed = await resolver.resolve(value, context)
|
|
317
|
+
expect(trimmed).toBe('0xaabbccdd')
|
|
318
|
+
})
|
|
319
|
+
|
|
295
320
|
it('should handle complex nested API responses', async () => {
|
|
296
321
|
const complexApiResponse = {
|
|
297
322
|
status: 'success',
|
|
@@ -357,4 +382,4 @@ describe('JSON Integration Tests', () => {
|
|
|
357
382
|
expect(await resolver.resolve(timestamp, context)).toBe(1234567890)
|
|
358
383
|
})
|
|
359
384
|
})
|
|
360
|
-
})
|
|
385
|
+
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ethers } from 'ethers'
|
|
2
2
|
import { ValueResolver } from '../resolver'
|
|
3
3
|
import { ExecutionContext } from '../context'
|
|
4
|
-
import { BasicArithmeticValue, Network, ReadBalanceValue, ComputeCreate2Value, ConstructorEncodeValue, AbiEncodeValue, AbiPackValue, CallValue, ContractExistsValue, ComputeCreateValue } from '../../types'
|
|
4
|
+
import { BasicArithmeticValue, Network, ReadBalanceValue, ComputeCreate2Value, ConstructorEncodeValue, AbiEncodeValue, AbiPackValue, CallValue, ContractExistsValue, ComputeCreateValue, SliceBytesValue } from '../../types'
|
|
5
5
|
import { ContractRepository } from '../../contracts/repository'
|
|
6
6
|
|
|
7
7
|
describe('ValueResolver', () => {
|
|
@@ -1918,4 +1918,72 @@ describe('ValueResolver', () => {
|
|
|
1918
1918
|
await expect(resolver.resolve(value, context)).rejects.toThrow('contract-exists: invalid address: invalid-address')
|
|
1919
1919
|
})
|
|
1920
1920
|
})
|
|
1921
|
-
|
|
1921
|
+
|
|
1922
|
+
describe('slice-bytes', () => {
|
|
1923
|
+
it('should slice bytes with explicit start and end positions', async () => {
|
|
1924
|
+
const value: SliceBytesValue = {
|
|
1925
|
+
type: 'slice-bytes',
|
|
1926
|
+
arguments: {
|
|
1927
|
+
value: '0x112233445566',
|
|
1928
|
+
start: 1,
|
|
1929
|
+
end: 4,
|
|
1930
|
+
},
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
const result = await resolver.resolve(value, context)
|
|
1934
|
+
expect(result).toBe('0x223344')
|
|
1935
|
+
})
|
|
1936
|
+
|
|
1937
|
+
it('should support negative indexes to drop trailing bytes', async () => {
|
|
1938
|
+
const value: SliceBytesValue = {
|
|
1939
|
+
type: 'slice-bytes',
|
|
1940
|
+
arguments: {
|
|
1941
|
+
value: '0xdeadbeefcafebabe',
|
|
1942
|
+
end: -1,
|
|
1943
|
+
},
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
const result = await resolver.resolve(value, context)
|
|
1947
|
+
expect(result).toBe('0xdeadbeefcafeba')
|
|
1948
|
+
})
|
|
1949
|
+
|
|
1950
|
+
it('should accept range syntax', async () => {
|
|
1951
|
+
const value: SliceBytesValue = {
|
|
1952
|
+
type: 'slice-bytes',
|
|
1953
|
+
arguments: {
|
|
1954
|
+
value: '0xaabbccddeeff',
|
|
1955
|
+
range: '0:2',
|
|
1956
|
+
},
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
const result = await resolver.resolve(value, context)
|
|
1960
|
+
expect(result).toBe('0xaabb')
|
|
1961
|
+
})
|
|
1962
|
+
|
|
1963
|
+
it('should handle open range syntax with missing start', async () => {
|
|
1964
|
+
const value: SliceBytesValue = {
|
|
1965
|
+
type: 'slice-bytes',
|
|
1966
|
+
arguments: {
|
|
1967
|
+
value: '0xaabbccddeeff',
|
|
1968
|
+
range: ':-1',
|
|
1969
|
+
},
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
const result = await resolver.resolve(value, context)
|
|
1973
|
+
expect(result).toBe('0xaabbccddee')
|
|
1974
|
+
})
|
|
1975
|
+
|
|
1976
|
+
it('should accept bracketed range syntax', async () => {
|
|
1977
|
+
const value: SliceBytesValue = {
|
|
1978
|
+
type: 'slice-bytes',
|
|
1979
|
+
arguments: {
|
|
1980
|
+
value: '0x0102030405',
|
|
1981
|
+
range: '[:3]',
|
|
1982
|
+
},
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
const result = await resolver.resolve(value, context)
|
|
1986
|
+
expect(result).toBe('0x010203')
|
|
1987
|
+
})
|
|
1988
|
+
})
|
|
1989
|
+
})
|
package/src/lib/core/loader.ts
CHANGED
|
@@ -121,22 +121,6 @@ export class ProjectLoader {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
const job = parseJob(content)
|
|
124
|
-
// Capture optional job-level constants by peeking the raw YAML for "constants"
|
|
125
|
-
try {
|
|
126
|
-
const raw = JSON.parse(JSON.stringify(require('yaml').parse(content)))
|
|
127
|
-
if (raw && typeof raw === 'object' && raw.constants !== undefined) {
|
|
128
|
-
if (typeof raw.constants !== 'object' || Array.isArray(raw.constants)) {
|
|
129
|
-
throw new Error(`Invalid job "${job.name}": "constants" field must be an object if provided.`)
|
|
130
|
-
}
|
|
131
|
-
(job as any).constants = raw.constants
|
|
132
|
-
}
|
|
133
|
-
} catch (peekErr) {
|
|
134
|
-
// parseJob already validated YAML; if this peek fails due to YAML parsing, surface it
|
|
135
|
-
if (peekErr instanceof Error && peekErr.message.includes('Failed to parse')) {
|
|
136
|
-
throw new Error(`Job constants peek failed for ${filePath}: ${peekErr.message}`)
|
|
137
|
-
}
|
|
138
|
-
// Otherwise ignore non-YAML related errors here
|
|
139
|
-
}
|
|
140
124
|
job._path = filePath
|
|
141
125
|
this.jobs.set(job.name, job)
|
|
142
126
|
} catch (error) {
|
package/src/lib/core/resolver.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ContractExistsValue,
|
|
14
14
|
JobCompletedValue,
|
|
15
15
|
ReadJsonValue,
|
|
16
|
+
SliceBytesValue,
|
|
16
17
|
} from '../types'
|
|
17
18
|
import { ExecutionContext } from './context'
|
|
18
19
|
import { isAddress, isBigNumberish, isBytesLike } from '../utils/assertion'
|
|
@@ -189,6 +190,8 @@ export class ValueResolver {
|
|
|
189
190
|
return this.resolveReadJson(resolvedArgs as ReadJsonValue['arguments'])
|
|
190
191
|
case 'resolve-json':
|
|
191
192
|
return this.resolveJsonValue(resolvedArgs, context)
|
|
193
|
+
case 'slice-bytes':
|
|
194
|
+
return this.resolveSliceBytes(resolvedArgs as SliceBytesValue['arguments'])
|
|
192
195
|
default:
|
|
193
196
|
throw new Error(`Unknown value resolver type: ${(obj as any).type}`)
|
|
194
197
|
}
|
|
@@ -530,6 +533,116 @@ export class ValueResolver {
|
|
|
530
533
|
}
|
|
531
534
|
}
|
|
532
535
|
|
|
536
|
+
private resolveSliceBytes(args: SliceBytesValue['arguments']): string {
|
|
537
|
+
const { value, start, end, range } = args
|
|
538
|
+
|
|
539
|
+
if (!isBytesLike(value)) {
|
|
540
|
+
throw new Error('slice-bytes: value must be bytes-like (hex string, Uint8Array, etc.)')
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (range !== undefined && (start !== undefined || end !== undefined)) {
|
|
544
|
+
throw new Error('slice-bytes: provide either range or start/end, not both')
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (range !== undefined && typeof range !== 'string') {
|
|
548
|
+
throw new Error('slice-bytes: range must be a string in "start:end" format')
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const normalizedHex = ethers.hexlify(value as ethers.BytesLike)
|
|
552
|
+
const hexBody = normalizedHex.slice(2)
|
|
553
|
+
|
|
554
|
+
if (hexBody.length % 2 !== 0) {
|
|
555
|
+
throw new Error('slice-bytes: value must have an even-length hex string')
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const totalBytes = hexBody.length / 2
|
|
559
|
+
const { startIndex, endIndex } = this.computeSliceBounds(totalBytes, { start, end, range })
|
|
560
|
+
|
|
561
|
+
if (startIndex >= endIndex) {
|
|
562
|
+
return '0x'
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const sliced = hexBody.slice(startIndex * 2, endIndex * 2)
|
|
566
|
+
return sliced.length === 0 ? '0x' : `0x${sliced}`
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private computeSliceBounds(
|
|
570
|
+
totalBytes: number,
|
|
571
|
+
params: { start?: any; end?: any; range?: any },
|
|
572
|
+
): { startIndex: number; endIndex: number } {
|
|
573
|
+
let startValue: number | undefined
|
|
574
|
+
let endValue: number | undefined
|
|
575
|
+
|
|
576
|
+
if (params.range !== undefined) {
|
|
577
|
+
const trimmedRange = (params.range as string).trim()
|
|
578
|
+
const rangeMatch = trimmedRange.match(/^\[?\s*(-?\d+)?\s*:\s*(-?\d+)?\s*\]?$/)
|
|
579
|
+
|
|
580
|
+
if (!rangeMatch) {
|
|
581
|
+
throw new Error('slice-bytes: range must follow the "start:end" format (e.g., "0:4" or ":-1")')
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const [, rawStart, rawEnd] = rangeMatch
|
|
585
|
+
if (rawStart !== undefined) {
|
|
586
|
+
startValue = this.parseSliceIndex(rawStart, 'range start')
|
|
587
|
+
}
|
|
588
|
+
if (rawEnd !== undefined) {
|
|
589
|
+
endValue = this.parseSliceIndex(rawEnd, 'range end')
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
if (params.start !== undefined) {
|
|
593
|
+
startValue = this.parseSliceIndex(params.start, 'start')
|
|
594
|
+
}
|
|
595
|
+
if (params.end !== undefined) {
|
|
596
|
+
endValue = this.parseSliceIndex(params.end, 'end')
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const startIndex = this.normalizeSliceIndex(startValue ?? 0, totalBytes)
|
|
601
|
+
const endIndex = this.normalizeSliceIndex(endValue ?? totalBytes, totalBytes)
|
|
602
|
+
|
|
603
|
+
return { startIndex, endIndex }
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
private parseSliceIndex(value: any, label: string): number {
|
|
607
|
+
if (value === undefined || value === null) {
|
|
608
|
+
throw new Error(`slice-bytes: ${label} cannot be null or undefined`)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const normalizedValue = typeof value === 'string' ? value.trim() : value
|
|
612
|
+
|
|
613
|
+
try {
|
|
614
|
+
const bigIntValue = ethers.toBigInt(normalizedValue)
|
|
615
|
+
const maxSafe = BigInt(Number.MAX_SAFE_INTEGER)
|
|
616
|
+
if (bigIntValue > maxSafe || bigIntValue < -maxSafe) {
|
|
617
|
+
throw new Error(`${label} is outside the supported numeric range`)
|
|
618
|
+
}
|
|
619
|
+
return Number(bigIntValue)
|
|
620
|
+
} catch (_error) {
|
|
621
|
+
throw new Error(`slice-bytes: ${label} must be an integer or integer-like string`)
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
private normalizeSliceIndex(index: number, totalBytes: number): number {
|
|
626
|
+
if (!Number.isFinite(index) || !Number.isInteger(index)) {
|
|
627
|
+
throw new Error('slice-bytes: slice positions must be finite integers')
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
let normalized = index
|
|
631
|
+
if (normalized < 0) {
|
|
632
|
+
normalized = totalBytes + normalized
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (normalized < 0) {
|
|
636
|
+
normalized = 0
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (normalized > totalBytes) {
|
|
640
|
+
normalized = totalBytes
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return normalized
|
|
644
|
+
}
|
|
645
|
+
|
|
533
646
|
/**
|
|
534
647
|
* Helper to recursively resolve the `arguments` field of any `ValueResolver` object.
|
|
535
648
|
* @private
|
|
@@ -549,4 +662,4 @@ export class ValueResolver {
|
|
|
549
662
|
}
|
|
550
663
|
return this.resolve(args, context, scope)
|
|
551
664
|
}
|
|
552
|
-
}
|
|
665
|
+
}
|
|
@@ -315,7 +315,7 @@ actions:
|
|
|
315
315
|
|
|
316
316
|
// --- New: Job-level constants field parsing ---
|
|
317
317
|
|
|
318
|
-
it('should
|
|
318
|
+
it('should attach an optional job-level constants block as an object', () => {
|
|
319
319
|
const yamlContent = `
|
|
320
320
|
name: "job-with-constants"
|
|
321
321
|
version: "1"
|
|
@@ -328,8 +328,31 @@ actions:
|
|
|
328
328
|
arguments:
|
|
329
329
|
x: "{{FEE}}"
|
|
330
330
|
`
|
|
331
|
-
const job = parseJob(yamlContent)
|
|
332
|
-
|
|
333
|
-
|
|
331
|
+
const job = parseJob(yamlContent)
|
|
332
|
+
expect(job.constants).toEqual({
|
|
333
|
+
FEE: '1000',
|
|
334
|
+
ADMIN: '0x0000000000000000000000000000000000000001'
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it('should parse job-level skip conditions', () => {
|
|
339
|
+
const yamlContent = `
|
|
340
|
+
name: "job-with-skip"
|
|
341
|
+
version: "1"
|
|
342
|
+
skip_condition:
|
|
343
|
+
- type: "contract-exists"
|
|
344
|
+
arguments:
|
|
345
|
+
address: "0xabc"
|
|
346
|
+
actions:
|
|
347
|
+
- name: "a1"
|
|
348
|
+
template: "t1"
|
|
349
|
+
arguments: {}
|
|
350
|
+
`
|
|
351
|
+
const job = parseJob(yamlContent)
|
|
352
|
+
expect(job.skip_condition).toHaveLength(1)
|
|
353
|
+
expect(job.skip_condition?.[0]).toEqual({
|
|
354
|
+
type: 'contract-exists',
|
|
355
|
+
arguments: { address: '0xabc' }
|
|
356
|
+
})
|
|
334
357
|
})
|
|
335
358
|
})
|