@layerzerolabs/lz-v2-stellar-sdk 0.2.50 → 0.2.52
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/.turbo/turbo-test.log +356 -421
- package/dist/generated/dvn.d.ts +9 -3
- package/dist/generated/dvn.js +4 -4
- package/dist/generated/executor.d.ts +9 -3
- package/dist/generated/executor.js +4 -4
- package/package.json +5 -5
- package/src/generated/dvn.ts +10 -4
- package/src/generated/executor.ts +10 -4
- package/test/oft-sml.test.ts +22 -41
- package/test/sac-manager.test.ts +23 -22
- package/test/secp256k1.ts +59 -0
- package/test/suites/constants.ts +5 -1
- package/test/suites/deploy.ts +14 -8
- package/test/suites/globalSetup.ts +144 -60
- package/test/suites/localnet.ts +20 -25
- package/test/utils.ts +1 -61
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
2
|
+
import * as secp from '@noble/secp256k1';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A secp256k1 key pair with private key and derived Ethereum-style address.
|
|
6
|
+
* Used for DVN multisig signing.
|
|
7
|
+
*/
|
|
8
|
+
export class Secp256k1KeyPair {
|
|
9
|
+
private privateKey: Uint8Array;
|
|
10
|
+
public readonly ethAddress: Buffer;
|
|
11
|
+
|
|
12
|
+
constructor(privateKey: Uint8Array | Buffer | string) {
|
|
13
|
+
if (typeof privateKey === 'string') {
|
|
14
|
+
// Remove 0x prefix if present
|
|
15
|
+
const hex = privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey;
|
|
16
|
+
this.privateKey = Buffer.from(hex, 'hex');
|
|
17
|
+
} else {
|
|
18
|
+
this.privateKey = new Uint8Array(privateKey);
|
|
19
|
+
}
|
|
20
|
+
this.ethAddress = this.deriveEthAddress();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate a random key pair.
|
|
25
|
+
*/
|
|
26
|
+
static generate(): Secp256k1KeyPair {
|
|
27
|
+
const privateKey = secp.utils.randomPrivateKey();
|
|
28
|
+
return new Secp256k1KeyPair(privateKey);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Derive Ethereum-style address from the public key.
|
|
33
|
+
* Address = last 20 bytes of keccak256(uncompressed_pubkey[1:65])
|
|
34
|
+
*/
|
|
35
|
+
private deriveEthAddress(): Buffer {
|
|
36
|
+
const publicKey = secp.getPublicKey(this.privateKey, false); // uncompressed
|
|
37
|
+
const pubkeyWithoutPrefix = publicKey.slice(1); // remove 0x04 prefix
|
|
38
|
+
const hash = keccak_256(pubkeyWithoutPrefix);
|
|
39
|
+
return Buffer.from(hash.slice(12)); // last 20 bytes
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Sign a 32-byte digest and return a 65-byte signature (r || s || v).
|
|
44
|
+
*/
|
|
45
|
+
async sign(digest: Uint8Array): Promise<Buffer> {
|
|
46
|
+
const [signature, recoveryId] = await secp.sign(digest, this.privateKey, {
|
|
47
|
+
canonical: true,
|
|
48
|
+
recovered: true,
|
|
49
|
+
der: false,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const v = 27 + recoveryId;
|
|
53
|
+
const result = Buffer.alloc(65);
|
|
54
|
+
result.set(signature, 0); // r (32 bytes) + s (32 bytes)
|
|
55
|
+
result[64] = v;
|
|
56
|
+
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
}
|
package/test/suites/constants.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Asset, Keypair, Networks } from '@stellar/stellar-sdk';
|
|
2
2
|
|
|
3
|
-
import { Secp256k1KeyPair } from '../
|
|
3
|
+
import { Secp256k1KeyPair } from '../secp256k1';
|
|
4
4
|
|
|
5
5
|
const CORE_URL = 'http://localhost:8086';
|
|
6
6
|
export const FRIENDBOT_URL = `${CORE_URL}/friendbot`;
|
|
@@ -17,6 +17,10 @@ export const ZRO_DISTRIBUTOR = Keypair.fromSecret(
|
|
|
17
17
|
export const EXECUTOR_ADMIN = Keypair.fromSecret(
|
|
18
18
|
'SACWJCNRT2AYRPBWW7IBRNI765EMZSWPXXAAHYN57UFQNOXMGET7HM5K',
|
|
19
19
|
);
|
|
20
|
+
// Separate deployer for Chain B to enable parallel contract deployment in globalSetup
|
|
21
|
+
export const CHAIN_B_DEPLOYER = Keypair.fromSecret(
|
|
22
|
+
'SDLIZSTG7W4C3FZYY52WIKF7FTWAXCWC5Z4OVVF3TDA3MBOR37LMIANJ',
|
|
23
|
+
);
|
|
20
24
|
|
|
21
25
|
// DVN secp256k1 signer for multisig (deterministic key for testing)
|
|
22
26
|
// Private key is keccak256("dvn_test_signer") truncated to 32 bytes
|
package/test/suites/deploy.ts
CHANGED
|
@@ -149,6 +149,7 @@ export async function deployContract<T extends { options: { contractId: string }
|
|
|
149
149
|
deployer: Keypair,
|
|
150
150
|
options: {
|
|
151
151
|
salt?: Buffer;
|
|
152
|
+
wasmHash?: string;
|
|
152
153
|
rpcUrl?: string;
|
|
153
154
|
networkPassphrase?: string;
|
|
154
155
|
allowHttp?: boolean;
|
|
@@ -165,14 +166,19 @@ export async function deployContract<T extends { options: { contractId: string }
|
|
|
165
166
|
allowHttp: allowHttp,
|
|
166
167
|
});
|
|
167
168
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
let wasmHash = options.wasmHash;
|
|
170
|
+
if (wasmHash) {
|
|
171
|
+
console.log('📦 Using pre-uploaded WASM hash:', wasmHash);
|
|
172
|
+
} else {
|
|
173
|
+
// Step 1: Read WASM file
|
|
174
|
+
console.log('📖 Reading WASM file from:', wasmFilePath);
|
|
175
|
+
const wasmBuffer = readFileSync(wasmFilePath);
|
|
176
|
+
|
|
177
|
+
// Step 2: Upload WASM and get hash
|
|
178
|
+
console.log('📤 Uploading WASM...');
|
|
179
|
+
wasmHash = await uploadWasm(wasmBuffer, deployer, server);
|
|
180
|
+
console.log('✅ WASM uploaded, hash:', wasmHash);
|
|
181
|
+
}
|
|
176
182
|
|
|
177
183
|
// Step 3: Deploy the contract
|
|
178
184
|
console.log('🚀 Deploying contract...');
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Keypair, rpc } from '@stellar/stellar-sdk';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
1
3
|
import path from 'path';
|
|
2
4
|
import type { GlobalSetupContext } from 'vitest/node';
|
|
3
5
|
|
|
@@ -13,7 +15,9 @@ import { Client as PriceFeedClient } from '../../src/generated/price_feed';
|
|
|
13
15
|
import { Client as SMLClient } from '../../src/generated/sml';
|
|
14
16
|
import { Client as TreasuryClient } from '../../src/generated/treasury';
|
|
15
17
|
import { Client as Uln302Client } from '../../src/generated/uln302';
|
|
18
|
+
import { createClient } from '../utils';
|
|
16
19
|
import {
|
|
20
|
+
CHAIN_B_DEPLOYER,
|
|
17
21
|
DEFAULT_DEPLOYER,
|
|
18
22
|
DVN_SIGNER,
|
|
19
23
|
DVN_VID,
|
|
@@ -21,9 +25,10 @@ import {
|
|
|
21
25
|
EID_B,
|
|
22
26
|
EXECUTOR_ADMIN,
|
|
23
27
|
NATIVE_TOKEN_ADDRESS,
|
|
28
|
+
RPC_URL,
|
|
24
29
|
ZRO_TOKEN_ADDRESS,
|
|
25
30
|
} from './constants';
|
|
26
|
-
import { deployContract } from './deploy';
|
|
31
|
+
import { deployContract, uploadWasm } from './deploy';
|
|
27
32
|
import { startStellarLocalnet, stopStellarLocalnet } from './localnet';
|
|
28
33
|
|
|
29
34
|
/**
|
|
@@ -75,21 +80,59 @@ declare module 'vitest' {
|
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
interface WasmHashes {
|
|
84
|
+
endpoint: string;
|
|
85
|
+
treasury: string;
|
|
86
|
+
uln302: string;
|
|
87
|
+
sml: string;
|
|
88
|
+
priceFeed: string;
|
|
89
|
+
executorFeeLib: string;
|
|
90
|
+
dvnFeeLib: string;
|
|
91
|
+
dvn: string;
|
|
92
|
+
executorHelper: string;
|
|
93
|
+
executor: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
/**
|
|
79
|
-
*
|
|
97
|
+
* Upload all protocol WASM files once and return their hashes.
|
|
80
98
|
*/
|
|
81
|
-
async function
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
-
|
|
99
|
+
async function uploadAllWasms(wasmDir: string): Promise<WasmHashes> {
|
|
100
|
+
const server = new rpc.Server(RPC_URL, { allowHttp: true });
|
|
101
|
+
|
|
102
|
+
const wasmFiles = {
|
|
103
|
+
endpoint: 'endpoint_v2.wasm',
|
|
104
|
+
treasury: 'treasury.wasm',
|
|
105
|
+
uln302: 'uln302.wasm',
|
|
106
|
+
sml: 'simple_message_lib.wasm',
|
|
107
|
+
priceFeed: 'price_feed.wasm',
|
|
108
|
+
executorFeeLib: 'executor_fee_lib.wasm',
|
|
109
|
+
dvnFeeLib: 'dvn_fee_lib.wasm',
|
|
110
|
+
dvn: 'dvn.wasm',
|
|
111
|
+
executorHelper: 'executor_helper.wasm',
|
|
112
|
+
executor: 'executor.wasm',
|
|
113
|
+
};
|
|
92
114
|
|
|
115
|
+
const hashes: Record<string, string> = {};
|
|
116
|
+
for (const [name, file] of Object.entries(wasmFiles)) {
|
|
117
|
+
const wasmBuffer = readFileSync(path.join(wasmDir, file));
|
|
118
|
+
console.log(`📤 Uploading ${name} WASM (${(wasmBuffer.length / 1024).toFixed(1)} KB)...`);
|
|
119
|
+
hashes[name] = await uploadWasm(wasmBuffer, DEFAULT_DEPLOYER, server);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return hashes as unknown as WasmHashes;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Deploy all protocol contracts for a single chain using pre-uploaded WASM hashes.
|
|
127
|
+
* The deployer pays gas; contract ownership is always DEFAULT_DEPLOYER.
|
|
128
|
+
*/
|
|
129
|
+
async function deployChainContracts(
|
|
130
|
+
eid: number,
|
|
131
|
+
chainLabel: string,
|
|
132
|
+
deployer: Keypair,
|
|
133
|
+
wasmDir: string,
|
|
134
|
+
wasmHashes: WasmHashes,
|
|
135
|
+
): Promise<ChainAddresses> {
|
|
93
136
|
const addresses: ChainAddresses = {
|
|
94
137
|
eid,
|
|
95
138
|
endpointV2: '',
|
|
@@ -106,7 +149,7 @@ async function deployChainContracts(eid: number, chainLabel: string): Promise<Ch
|
|
|
106
149
|
|
|
107
150
|
// 1. Deploy Endpoint
|
|
108
151
|
console.log(`🚀 [${chainLabel}] Deploying Endpoint (EID: ${eid})...`);
|
|
109
|
-
const
|
|
152
|
+
const deployedEndpoint = await deployContract<EndpointClient>(
|
|
110
153
|
EndpointClient,
|
|
111
154
|
path.join(wasmDir, 'endpoint_v2.wasm'),
|
|
112
155
|
{
|
|
@@ -114,25 +157,27 @@ async function deployChainContracts(eid: number, chainLabel: string): Promise<Ch
|
|
|
114
157
|
owner: DEFAULT_DEPLOYER.publicKey(),
|
|
115
158
|
native_token: NATIVE_TOKEN_ADDRESS,
|
|
116
159
|
},
|
|
117
|
-
|
|
160
|
+
deployer,
|
|
161
|
+
{ wasmHash: wasmHashes.endpoint },
|
|
118
162
|
);
|
|
119
|
-
addresses.endpointV2 =
|
|
163
|
+
addresses.endpointV2 = deployedEndpoint.options.contractId;
|
|
120
164
|
console.log(`✅ [${chainLabel}] Endpoint deployed:`, addresses.endpointV2);
|
|
121
165
|
|
|
122
166
|
// 2. Deploy Treasury
|
|
123
167
|
console.log(`🚀 [${chainLabel}] Deploying Treasury...`);
|
|
124
|
-
const
|
|
168
|
+
const deployedTreasury = await deployContract<TreasuryClient>(
|
|
125
169
|
TreasuryClient,
|
|
126
170
|
path.join(wasmDir, 'treasury.wasm'),
|
|
127
171
|
{ owner: DEFAULT_DEPLOYER.publicKey() },
|
|
128
|
-
|
|
172
|
+
deployer,
|
|
173
|
+
{ wasmHash: wasmHashes.treasury },
|
|
129
174
|
);
|
|
130
|
-
addresses.treasury =
|
|
175
|
+
addresses.treasury = deployedTreasury.options.contractId;
|
|
131
176
|
console.log(`✅ [${chainLabel}] Treasury deployed:`, addresses.treasury);
|
|
132
177
|
|
|
133
178
|
// 3. Deploy ULN302
|
|
134
179
|
console.log(`🚀 [${chainLabel}] Deploying ULN302...`);
|
|
135
|
-
const
|
|
180
|
+
const deployedUln302 = await deployContract<Uln302Client>(
|
|
136
181
|
Uln302Client,
|
|
137
182
|
path.join(wasmDir, 'uln302.wasm'),
|
|
138
183
|
{
|
|
@@ -140,14 +185,15 @@ async function deployChainContracts(eid: number, chainLabel: string): Promise<Ch
|
|
|
140
185
|
endpoint: addresses.endpointV2,
|
|
141
186
|
treasury: addresses.treasury,
|
|
142
187
|
},
|
|
143
|
-
|
|
188
|
+
deployer,
|
|
189
|
+
{ wasmHash: wasmHashes.uln302 },
|
|
144
190
|
);
|
|
145
|
-
addresses.uln302 =
|
|
191
|
+
addresses.uln302 = deployedUln302.options.contractId;
|
|
146
192
|
console.log(`✅ [${chainLabel}] ULN302 deployed:`, addresses.uln302);
|
|
147
193
|
|
|
148
194
|
// 4. Deploy SML (SimpleMessageLib)
|
|
149
195
|
console.log(`🚀 [${chainLabel}] Deploying SimpleMessageLib...`);
|
|
150
|
-
const
|
|
196
|
+
const deployedSml = await deployContract<SMLClient>(
|
|
151
197
|
SMLClient,
|
|
152
198
|
path.join(wasmDir, 'simple_message_lib.wasm'),
|
|
153
199
|
{
|
|
@@ -155,50 +201,54 @@ async function deployChainContracts(eid: number, chainLabel: string): Promise<Ch
|
|
|
155
201
|
endpoint: addresses.endpointV2,
|
|
156
202
|
fee_recipient: DEFAULT_DEPLOYER.publicKey(),
|
|
157
203
|
},
|
|
158
|
-
|
|
204
|
+
deployer,
|
|
205
|
+
{ wasmHash: wasmHashes.sml },
|
|
159
206
|
);
|
|
160
|
-
addresses.sml =
|
|
207
|
+
addresses.sml = deployedSml.options.contractId;
|
|
161
208
|
console.log(`✅ [${chainLabel}] SimpleMessageLib deployed:`, addresses.sml);
|
|
162
209
|
|
|
163
210
|
// 5. Deploy Price Feed
|
|
164
211
|
console.log(`🚀 [${chainLabel}] Deploying Price Feed...`);
|
|
165
|
-
const
|
|
212
|
+
const deployedPriceFeed = await deployContract<PriceFeedClient>(
|
|
166
213
|
PriceFeedClient,
|
|
167
214
|
path.join(wasmDir, 'price_feed.wasm'),
|
|
168
215
|
{
|
|
169
216
|
owner: DEFAULT_DEPLOYER.publicKey(),
|
|
170
217
|
price_updater: DEFAULT_DEPLOYER.publicKey(),
|
|
171
218
|
},
|
|
172
|
-
|
|
219
|
+
deployer,
|
|
220
|
+
{ wasmHash: wasmHashes.priceFeed },
|
|
173
221
|
);
|
|
174
|
-
addresses.priceFeed =
|
|
222
|
+
addresses.priceFeed = deployedPriceFeed.options.contractId;
|
|
175
223
|
console.log(`✅ [${chainLabel}] Price Feed deployed:`, addresses.priceFeed);
|
|
176
224
|
|
|
177
225
|
// 6. Deploy Executor Fee Lib
|
|
178
226
|
console.log(`🚀 [${chainLabel}] Deploying Executor Fee Lib...`);
|
|
179
|
-
const
|
|
227
|
+
const deployedExecutorFeeLib = await deployContract<ExecutorFeeLibClient>(
|
|
180
228
|
ExecutorFeeLibClient,
|
|
181
229
|
path.join(wasmDir, 'executor_fee_lib.wasm'),
|
|
182
230
|
{ owner: DEFAULT_DEPLOYER.publicKey() },
|
|
183
|
-
|
|
231
|
+
deployer,
|
|
232
|
+
{ wasmHash: wasmHashes.executorFeeLib },
|
|
184
233
|
);
|
|
185
|
-
addresses.executorFeeLib =
|
|
234
|
+
addresses.executorFeeLib = deployedExecutorFeeLib.options.contractId;
|
|
186
235
|
console.log(`✅ [${chainLabel}] Executor Fee Lib deployed:`, addresses.executorFeeLib);
|
|
187
236
|
|
|
188
237
|
// 7. Deploy DVN Fee Lib
|
|
189
238
|
console.log(`🚀 [${chainLabel}] Deploying DVN Fee Lib...`);
|
|
190
|
-
const
|
|
239
|
+
const deployedDvnFeeLib = await deployContract<DvnFeeLibClient>(
|
|
191
240
|
DvnFeeLibClient,
|
|
192
241
|
path.join(wasmDir, 'dvn_fee_lib.wasm'),
|
|
193
242
|
{ owner: DEFAULT_DEPLOYER.publicKey() },
|
|
194
|
-
|
|
243
|
+
deployer,
|
|
244
|
+
{ wasmHash: wasmHashes.dvnFeeLib },
|
|
195
245
|
);
|
|
196
|
-
addresses.dvnFeeLib =
|
|
246
|
+
addresses.dvnFeeLib = deployedDvnFeeLib.options.contractId;
|
|
197
247
|
console.log(`✅ [${chainLabel}] DVN Fee Lib deployed:`, addresses.dvnFeeLib);
|
|
198
248
|
|
|
199
249
|
// 8. Deploy DVN (same signer for both chains)
|
|
200
250
|
console.log(`🚀 [${chainLabel}] Deploying DVN...`);
|
|
201
|
-
const
|
|
251
|
+
const deployedDvn = await deployContract<DvnClient>(
|
|
202
252
|
DvnClient,
|
|
203
253
|
path.join(wasmDir, 'dvn.wasm'),
|
|
204
254
|
{
|
|
@@ -212,25 +262,27 @@ async function deployChainContracts(eid: number, chainLabel: string): Promise<Ch
|
|
|
212
262
|
worker_fee_lib: addresses.dvnFeeLib,
|
|
213
263
|
deposit_address: DEFAULT_DEPLOYER.publicKey(),
|
|
214
264
|
},
|
|
215
|
-
|
|
265
|
+
deployer,
|
|
266
|
+
{ wasmHash: wasmHashes.dvn },
|
|
216
267
|
);
|
|
217
|
-
addresses.dvn =
|
|
268
|
+
addresses.dvn = deployedDvn.options.contractId;
|
|
218
269
|
console.log(`✅ [${chainLabel}] DVN deployed:`, addresses.dvn);
|
|
219
270
|
|
|
220
271
|
// 9. Deploy Executor Helper
|
|
221
272
|
console.log(`🚀 [${chainLabel}] Deploying Executor Helper...`);
|
|
222
|
-
const
|
|
273
|
+
const deployedExecutorHelper = await deployContract<ExecutorHelperClient>(
|
|
223
274
|
ExecutorHelperClient,
|
|
224
275
|
path.join(wasmDir, 'executor_helper.wasm'),
|
|
225
276
|
undefined,
|
|
226
|
-
|
|
277
|
+
deployer,
|
|
278
|
+
{ wasmHash: wasmHashes.executorHelper },
|
|
227
279
|
);
|
|
228
|
-
addresses.executorHelper =
|
|
280
|
+
addresses.executorHelper = deployedExecutorHelper.options.contractId;
|
|
229
281
|
console.log(`✅ [${chainLabel}] Executor Helper deployed:`, addresses.executorHelper);
|
|
230
282
|
|
|
231
283
|
// 10. Deploy Executor (supports both ULN302 and SML)
|
|
232
284
|
console.log(`🚀 [${chainLabel}] Deploying Executor...`);
|
|
233
|
-
const
|
|
285
|
+
const deployedExecutor = await deployContract<ExecutorClient>(
|
|
234
286
|
ExecutorClient,
|
|
235
287
|
path.join(wasmDir, 'executor.wasm'),
|
|
236
288
|
{
|
|
@@ -243,13 +295,26 @@ async function deployChainContracts(eid: number, chainLabel: string): Promise<Ch
|
|
|
243
295
|
worker_fee_lib: addresses.executorFeeLib,
|
|
244
296
|
deposit_address: DEFAULT_DEPLOYER.publicKey(),
|
|
245
297
|
},
|
|
246
|
-
|
|
298
|
+
deployer,
|
|
299
|
+
{ wasmHash: wasmHashes.executor },
|
|
247
300
|
);
|
|
248
|
-
addresses.executor =
|
|
301
|
+
addresses.executor = deployedExecutor.options.contractId;
|
|
249
302
|
console.log(`✅ [${chainLabel}] Executor deployed:`, addresses.executor);
|
|
250
303
|
|
|
251
|
-
|
|
304
|
+
return addresses;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Register executor helper and create owner-signed clients for a chain.
|
|
309
|
+
* Must be called sequentially (uses DEFAULT_DEPLOYER for signing).
|
|
310
|
+
*/
|
|
311
|
+
async function initChainClients(
|
|
312
|
+
addresses: ChainAddresses,
|
|
313
|
+
chainLabel: string,
|
|
314
|
+
): Promise<ChainSetup> {
|
|
315
|
+
// Register Executor Helper with Executor (needs owner-signed client)
|
|
252
316
|
console.log(`🚀 [${chainLabel}] Registering Executor Helper with Executor...`);
|
|
317
|
+
const executorClient = createClient(ExecutorClient, addresses.executor);
|
|
253
318
|
await (
|
|
254
319
|
await executorClient.set_executor_helper({
|
|
255
320
|
admin: DEFAULT_DEPLOYER.publicKey(),
|
|
@@ -260,16 +325,16 @@ async function deployChainContracts(eid: number, chainLabel: string): Promise<Ch
|
|
|
260
325
|
console.log(`✅ [${chainLabel}] Executor Helper registered`);
|
|
261
326
|
|
|
262
327
|
const clients: ChainClients = {
|
|
263
|
-
endpointClient,
|
|
264
|
-
uln302Client,
|
|
265
|
-
smlClient,
|
|
266
|
-
treasuryClient,
|
|
328
|
+
endpointClient: createClient(EndpointClient, addresses.endpointV2),
|
|
329
|
+
uln302Client: createClient(Uln302Client, addresses.uln302),
|
|
330
|
+
smlClient: createClient(SMLClient, addresses.sml),
|
|
331
|
+
treasuryClient: createClient(TreasuryClient, addresses.treasury),
|
|
267
332
|
executorClient,
|
|
268
|
-
executorHelperClient,
|
|
269
|
-
executorFeeLibClient,
|
|
270
|
-
priceFeedClient,
|
|
271
|
-
dvnFeeLibClient,
|
|
272
|
-
dvnClient,
|
|
333
|
+
executorHelperClient: createClient(ExecutorHelperClient, addresses.executorHelper),
|
|
334
|
+
executorFeeLibClient: createClient(ExecutorFeeLibClient, addresses.executorFeeLib),
|
|
335
|
+
priceFeedClient: createClient(PriceFeedClient, addresses.priceFeed),
|
|
336
|
+
dvnFeeLibClient: createClient(DvnFeeLibClient, addresses.dvnFeeLib),
|
|
337
|
+
dvnClient: createClient(DvnClient, addresses.dvn),
|
|
273
338
|
};
|
|
274
339
|
|
|
275
340
|
return { addresses, clients };
|
|
@@ -477,17 +542,36 @@ export default async function globalSetup({
|
|
|
477
542
|
|
|
478
543
|
await startStellarLocalnet();
|
|
479
544
|
|
|
545
|
+
const repoRoot = await getFullyQualifiedRepoRootPath();
|
|
546
|
+
const wasmDir = path.join(
|
|
547
|
+
repoRoot,
|
|
548
|
+
'contracts',
|
|
549
|
+
'protocol',
|
|
550
|
+
'stellar',
|
|
551
|
+
'target',
|
|
552
|
+
'wasm32v1-none',
|
|
553
|
+
'release',
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
console.log('\n========================================');
|
|
557
|
+
console.log('📤 GLOBAL SETUP: Uploading WASM (once)');
|
|
558
|
+
console.log('========================================\n');
|
|
559
|
+
|
|
560
|
+
const wasmHashes = await uploadAllWasms(wasmDir);
|
|
561
|
+
|
|
480
562
|
console.log('\n========================================');
|
|
481
|
-
console.log('📦 GLOBAL SETUP: Deploying Protocol Contracts (Two Chains)');
|
|
563
|
+
console.log('📦 GLOBAL SETUP: Deploying Protocol Contracts (Two Chains in Parallel)');
|
|
482
564
|
console.log('========================================\n');
|
|
483
565
|
|
|
484
|
-
// Deploy
|
|
485
|
-
|
|
486
|
-
|
|
566
|
+
// Deploy both chains in parallel (each uses its own deployer key)
|
|
567
|
+
const [addressesA, addressesB] = await Promise.all([
|
|
568
|
+
deployChainContracts(EID_A, 'Chain A', DEFAULT_DEPLOYER, wasmDir, wasmHashes),
|
|
569
|
+
deployChainContracts(EID_B, 'Chain B', CHAIN_B_DEPLOYER, wasmDir, wasmHashes),
|
|
570
|
+
]);
|
|
487
571
|
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
const chainB = await
|
|
572
|
+
// Register executor helpers and create clients sequentially (uses DEFAULT_DEPLOYER)
|
|
573
|
+
const chainA = await initChainClients(addressesA, 'Chain A');
|
|
574
|
+
const chainB = await initChainClients(addressesB, 'Chain B');
|
|
491
575
|
|
|
492
576
|
console.log('\n========================================');
|
|
493
577
|
console.log('🔗 GLOBAL SETUP: Wiring Protocol Contracts (Cross-Chain)');
|
package/test/suites/localnet.ts
CHANGED
|
@@ -2,6 +2,7 @@ import axios from 'axios';
|
|
|
2
2
|
import { $, sleep } from 'zx';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
CHAIN_B_DEPLOYER,
|
|
5
6
|
DEFAULT_DEPLOYER,
|
|
6
7
|
EXECUTOR_ADMIN,
|
|
7
8
|
FRIENDBOT_URL,
|
|
@@ -74,33 +75,27 @@ async function waitForRpcHealth(startTime: number): Promise<void> {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
async function waitForFriendbotAndFundAccounts(startTime: number): Promise<void> {
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (!funded) {
|
|
98
|
-
throw new Error(
|
|
99
|
-
`Failed to fund account ${name} within ${STARTUP_TIMEOUT_MS / 1000} seconds`,
|
|
100
|
-
);
|
|
78
|
+
while (Date.now() - startTime < STARTUP_TIMEOUT_MS) {
|
|
79
|
+
try {
|
|
80
|
+
// Fund DEFAULT_DEPLOYER first (doubles as friendbot readiness check)
|
|
81
|
+
await fundAccount(DEFAULT_DEPLOYER.publicKey());
|
|
82
|
+
console.log('✅ Friendbot ready, DEFAULT_DEPLOYER funded');
|
|
83
|
+
|
|
84
|
+
// Fund remaining accounts in parallel
|
|
85
|
+
await Promise.all([
|
|
86
|
+
fundAccount(ZRO_DISTRIBUTOR.publicKey()),
|
|
87
|
+
fundAccount(EXECUTOR_ADMIN.publicKey()),
|
|
88
|
+
fundAccount(CHAIN_B_DEPLOYER.publicKey()),
|
|
89
|
+
]);
|
|
90
|
+
console.log('✅ All accounts funded');
|
|
91
|
+
return;
|
|
92
|
+
} catch {
|
|
93
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
94
|
+
console.log(`⏳ [${elapsed}s] Waiting for friendbot...`);
|
|
95
|
+
await sleep(RETRY_INTERVAL_MS);
|
|
101
96
|
}
|
|
102
97
|
}
|
|
103
|
-
|
|
98
|
+
throw new Error(`Friendbot not ready within ${STARTUP_TIMEOUT_MS / 1000} seconds`);
|
|
104
99
|
}
|
|
105
100
|
|
|
106
101
|
export async function fundAccount(publicKey: string): Promise<void> {
|
package/test/utils.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { keccak_256 } from '@noble/hashes/sha3';
|
|
2
|
-
import * as secp from '@noble/secp256k1';
|
|
3
2
|
import {
|
|
4
3
|
Account,
|
|
5
4
|
Address,
|
|
@@ -153,66 +152,7 @@ export async function getTokenAuthorized(
|
|
|
153
152
|
return false;
|
|
154
153
|
}
|
|
155
154
|
|
|
156
|
-
|
|
157
|
-
// Secp256k1 Key Pair for DVN Multisig
|
|
158
|
-
// ============================================================================
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* A secp256k1 key pair with private key and derived Ethereum-style address.
|
|
162
|
-
* Used for DVN multisig signing.
|
|
163
|
-
*/
|
|
164
|
-
export class Secp256k1KeyPair {
|
|
165
|
-
private privateKey: Uint8Array;
|
|
166
|
-
public readonly ethAddress: Buffer;
|
|
167
|
-
|
|
168
|
-
constructor(privateKey: Uint8Array | Buffer | string) {
|
|
169
|
-
if (typeof privateKey === 'string') {
|
|
170
|
-
// Remove 0x prefix if present
|
|
171
|
-
const hex = privateKey.startsWith('0x') ? privateKey.slice(2) : privateKey;
|
|
172
|
-
this.privateKey = Buffer.from(hex, 'hex');
|
|
173
|
-
} else {
|
|
174
|
-
this.privateKey = new Uint8Array(privateKey);
|
|
175
|
-
}
|
|
176
|
-
this.ethAddress = this.deriveEthAddress();
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Generate a random key pair.
|
|
181
|
-
*/
|
|
182
|
-
static generate(): Secp256k1KeyPair {
|
|
183
|
-
const privateKey = secp.utils.randomPrivateKey();
|
|
184
|
-
return new Secp256k1KeyPair(privateKey);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Derive Ethereum-style address from the public key.
|
|
189
|
-
* Address = last 20 bytes of keccak256(uncompressed_pubkey[1:65])
|
|
190
|
-
*/
|
|
191
|
-
private deriveEthAddress(): Buffer {
|
|
192
|
-
const publicKey = secp.getPublicKey(this.privateKey, false); // uncompressed
|
|
193
|
-
const pubkeyWithoutPrefix = publicKey.slice(1); // remove 0x04 prefix
|
|
194
|
-
const hash = keccak_256(pubkeyWithoutPrefix);
|
|
195
|
-
return Buffer.from(hash.slice(12)); // last 20 bytes
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Sign a 32-byte digest and return a 65-byte signature (r || s || v).
|
|
200
|
-
*/
|
|
201
|
-
async sign(digest: Uint8Array): Promise<Buffer> {
|
|
202
|
-
const [signature, recoveryId] = await secp.sign(digest, this.privateKey, {
|
|
203
|
-
canonical: true,
|
|
204
|
-
recovered: true,
|
|
205
|
-
der: false,
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const v = 27 + recoveryId;
|
|
209
|
-
const result = Buffer.alloc(65);
|
|
210
|
-
result.set(signature, 0); // r (32 bytes) + s (32 bytes)
|
|
211
|
-
result[64] = v;
|
|
212
|
-
|
|
213
|
-
return result;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
155
|
+
import { Secp256k1KeyPair } from './secp256k1';
|
|
216
156
|
|
|
217
157
|
// ============================================================================
|
|
218
158
|
// DVN Abstract Account Auth Signing
|