@layerzerolabs/lz-v2-stellar-sdk 0.2.59 → 0.2.61
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 +303 -269
- package/dist/generated/dvn.d.ts +59 -11
- package/dist/generated/dvn.js +21 -10
- package/package.json +5 -5
- package/src/generated/dvn.ts +64 -18
- package/test/counter-uln.test.ts +34 -15
- package/test/upgrader.test.ts +21 -0
- package/test/utils.ts +101 -14
package/test/counter-uln.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StrKey } from '@stellar/stellar-sdk';
|
|
1
|
+
import { Address, nativeToScVal, StrKey, xdr } from '@stellar/stellar-sdk';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { beforeAll, describe, expect, inject, it } from 'vitest';
|
|
4
4
|
|
|
@@ -6,6 +6,7 @@ import { getFullyQualifiedRepoRootPath } from '@layerzerolabs/common-node-utils'
|
|
|
6
6
|
import { Options, PacketSerializer, PacketV1Codec } from '@layerzerolabs/lz-v2-utilities';
|
|
7
7
|
|
|
8
8
|
import { Client as CounterClient } from '../src/generated/counter';
|
|
9
|
+
import { Client as DvnClient } from '../src/generated/dvn';
|
|
9
10
|
import { Client as ExecutorHelperClient } from '../src/generated/executor_helper';
|
|
10
11
|
import { Client as Uln302Client } from '../src/generated/uln302';
|
|
11
12
|
import {
|
|
@@ -42,11 +43,13 @@ let counterBAddress = '';
|
|
|
42
43
|
let uln302ClientA: Uln302Client;
|
|
43
44
|
let counterClientA: CounterClient;
|
|
44
45
|
let executorHelperClientA: ExecutorHelperClient;
|
|
46
|
+
let dvnClientA: DvnClient;
|
|
45
47
|
|
|
46
48
|
// Chain B Clients
|
|
47
49
|
let uln302ClientB: Uln302Client;
|
|
48
50
|
let counterClientB: CounterClient;
|
|
49
51
|
let executorHelperClientB: ExecutorHelperClient;
|
|
52
|
+
let dvnClientB: DvnClient;
|
|
50
53
|
|
|
51
54
|
describe('Counter Cross-Chain Testing (ULN302)', async () => {
|
|
52
55
|
const repoRoot = await getFullyQualifiedRepoRootPath();
|
|
@@ -82,10 +85,12 @@ describe('Counter Cross-Chain Testing (ULN302)', async () => {
|
|
|
82
85
|
// Create clients for Chain A protocol contracts
|
|
83
86
|
uln302ClientA = createClient(Uln302Client, chainA.uln302);
|
|
84
87
|
executorHelperClientA = createClient(ExecutorHelperClient, chainA.executorHelper);
|
|
88
|
+
dvnClientA = createClient(DvnClient, chainA.dvn);
|
|
85
89
|
|
|
86
90
|
// Create clients for Chain B protocol contracts
|
|
87
91
|
uln302ClientB = createClient(Uln302Client, chainB.uln302);
|
|
88
92
|
executorHelperClientB = createClient(ExecutorHelperClient, chainB.executorHelper);
|
|
93
|
+
dvnClientB = createClient(DvnClient, chainB.dvn);
|
|
89
94
|
});
|
|
90
95
|
|
|
91
96
|
describe('Deploy Counters on Both Chains', () => {
|
|
@@ -260,15 +265,22 @@ describe('Counter Cross-Chain Testing (ULN302)', async () => {
|
|
|
260
265
|
});
|
|
261
266
|
|
|
262
267
|
it('DVN B Verifies Message on Chain B (from Chain A)', async () => {
|
|
263
|
-
//
|
|
264
|
-
const verifyTx = await
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
268
|
+
// DVN calls execute_transaction to invoke uln302.verify externally
|
|
269
|
+
const verifyTx = await dvnClientB.execute_transaction({
|
|
270
|
+
calls: [
|
|
271
|
+
{
|
|
272
|
+
to: chainB.uln302,
|
|
273
|
+
func: 'verify',
|
|
274
|
+
args: [
|
|
275
|
+
nativeToScVal(Address.fromString(chainB.dvn), { type: 'address' }),
|
|
276
|
+
nativeToScVal(packetHeader, { type: 'bytes' }),
|
|
277
|
+
nativeToScVal(payloadHash, { type: 'bytes' }),
|
|
278
|
+
xdr.ScVal.scvU64(new xdr.Uint64(BigInt(1))),
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
],
|
|
269
282
|
});
|
|
270
283
|
|
|
271
|
-
// Sign with DVN_SIGNER (shared between both chains)
|
|
272
284
|
await signDvnAuthEntries(
|
|
273
285
|
chainB.dvn,
|
|
274
286
|
DVN_VID,
|
|
@@ -469,15 +481,22 @@ describe('Counter Cross-Chain Testing (ULN302)', async () => {
|
|
|
469
481
|
});
|
|
470
482
|
|
|
471
483
|
it('DVN A Verifies Return Message on Chain A (from Chain B)', async () => {
|
|
472
|
-
//
|
|
473
|
-
const verifyTx = await
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
484
|
+
// DVN calls execute_transaction to invoke uln302.verify externally
|
|
485
|
+
const verifyTx = await dvnClientA.execute_transaction({
|
|
486
|
+
calls: [
|
|
487
|
+
{
|
|
488
|
+
to: chainA.uln302,
|
|
489
|
+
func: 'verify',
|
|
490
|
+
args: [
|
|
491
|
+
nativeToScVal(Address.fromString(chainA.dvn), { type: 'address' }),
|
|
492
|
+
nativeToScVal(returnPacketHeader, { type: 'bytes' }),
|
|
493
|
+
nativeToScVal(returnPayloadHash, { type: 'bytes' }),
|
|
494
|
+
xdr.ScVal.scvU64(new xdr.Uint64(BigInt(1))),
|
|
495
|
+
],
|
|
496
|
+
},
|
|
497
|
+
],
|
|
478
498
|
});
|
|
479
499
|
|
|
480
|
-
// Sign with DVN_SIGNER (shared between both chains)
|
|
481
500
|
await signDvnAuthEntries(
|
|
482
501
|
chainA.dvn,
|
|
483
502
|
DVN_VID,
|
package/test/upgrader.test.ts
CHANGED
|
@@ -111,6 +111,27 @@ describe('Upgrader Contract Testing', async () => {
|
|
|
111
111
|
expect(newWasmHash).toBeDefined();
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
+
it('Set Upgrader on DVN', async () => {
|
|
115
|
+
const setUpgraderTx = await dvnClient.set_upgrader({
|
|
116
|
+
upgrader: upgraderClient.options.contractId,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await signDvnAuthEntries(
|
|
120
|
+
testContractAddress,
|
|
121
|
+
DVN_VID,
|
|
122
|
+
DEFAULT_DEPLOYER,
|
|
123
|
+
[DVN_SIGNER],
|
|
124
|
+
setUpgraderTx,
|
|
125
|
+
NETWORK_PASSPHRASE,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
await setUpgraderTx.signAndSend();
|
|
129
|
+
console.log('✅ Upgrader set on DVN to:', upgraderClient.options.contractId);
|
|
130
|
+
|
|
131
|
+
const { result: upgrader } = await dvnClient.upgrader();
|
|
132
|
+
expect(upgrader).toBe(upgraderClient.options.contractId);
|
|
133
|
+
});
|
|
134
|
+
|
|
114
135
|
it('Test Upgrader Contract Can Upgrade DVN with Multisig Auth', async () => {
|
|
115
136
|
console.log('🔄 Testing upgrader contract call...');
|
|
116
137
|
console.log(' Contract address:', testContractAddress);
|
package/test/utils.ts
CHANGED
|
@@ -167,7 +167,6 @@ import { Secp256k1KeyPair } from './secp256k1';
|
|
|
167
167
|
* - expiration: u64 - Ledger timestamp for when auth expires
|
|
168
168
|
* - signatures: Vec<BytesN<65>> - Secp256k1 signatures from multisig signers
|
|
169
169
|
* - sender: Sender - Either None or Admin(public_key, ed25519_signature)
|
|
170
|
-
* - preimage: Bytes - XDR-encoded HashIdPreimage::SorobanAuthorization (invocation is sliced from byte 48)
|
|
171
170
|
*/
|
|
172
171
|
export async function signDvnAuthEntries<T>(
|
|
173
172
|
dvnAddress: string,
|
|
@@ -234,11 +233,15 @@ export async function signDvnAuthEntries<T>(
|
|
|
234
233
|
adminSignature.toString('hex').slice(0, 32) + '...',
|
|
235
234
|
);
|
|
236
235
|
|
|
237
|
-
// 3.
|
|
238
|
-
//
|
|
236
|
+
// 3. Extract calls from the auth entry and compute the multisig hash
|
|
237
|
+
// The expiration is the ledger timestamp when the auth expires
|
|
238
|
+
// We use validUntilLedgerSeq * 5 as a rough approximation (5 seconds per ledger)
|
|
239
239
|
const expiration = BigInt(validUntilLedgerSeq) * 5n + BigInt(Math.floor(Date.now() / 1000));
|
|
240
|
-
const
|
|
241
|
-
const
|
|
240
|
+
const rootCall = getRootCall(rootInvocation);
|
|
241
|
+
const isSelfCall = rootCall.to === dvnAddr.toString();
|
|
242
|
+
const calls = isSelfCall ? [rootCall] : collectCallsFromInvocation(rootInvocation);
|
|
243
|
+
const callsXdr = serializeCallsToXdr(calls);
|
|
244
|
+
const callHash = computeCallHash(vid, expiration, callsXdr);
|
|
242
245
|
console.log('📝 Call hash for multisig:', Buffer.from(callHash).toString('hex'));
|
|
243
246
|
|
|
244
247
|
// 4. Sign the call hash with multisig signers
|
|
@@ -261,19 +264,16 @@ export async function signDvnAuthEntries<T>(
|
|
|
261
264
|
const sortedSignatures = signaturesWithAddresses.map((s) => s.sig);
|
|
262
265
|
|
|
263
266
|
// 6. Build TransactionAuthData as ScVal
|
|
264
|
-
// struct TransactionAuthData { vid, expiration, signatures
|
|
265
|
-
|
|
267
|
+
// struct TransactionAuthData { vid: u32, expiration: u64, signatures: Vec<BytesN<65>>, sender: Sender }
|
|
268
|
+
// enum Sender { None, Admin(BytesN<32>, BytesN<64>) }
|
|
266
269
|
const transactionAuthDataScVal = xdr.ScVal.scvMap([
|
|
267
270
|
new xdr.ScMapEntry({
|
|
268
271
|
key: xdr.ScVal.scvSymbol('expiration'),
|
|
269
272
|
val: xdr.ScVal.scvU64(new xdr.Uint64(expiration)),
|
|
270
273
|
}),
|
|
271
|
-
new xdr.ScMapEntry({
|
|
272
|
-
key: xdr.ScVal.scvSymbol('preimage'),
|
|
273
|
-
val: xdr.ScVal.scvBytes(preimageXdr),
|
|
274
|
-
}),
|
|
275
274
|
new xdr.ScMapEntry({
|
|
276
275
|
key: xdr.ScVal.scvSymbol('sender'),
|
|
276
|
+
// Sender::Admin(public_key, signature)
|
|
277
277
|
val: xdr.ScVal.scvVec([
|
|
278
278
|
xdr.ScVal.scvSymbol('Admin'),
|
|
279
279
|
xdr.ScVal.scvBytes(adminKeypair.rawPublicKey()),
|
|
@@ -325,11 +325,97 @@ export async function signDvnAuthEntries<T>(
|
|
|
325
325
|
console.log('✅ Final simulation complete');
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Represents a contract call for multisig authorization.
|
|
330
|
+
*/
|
|
331
|
+
interface Call {
|
|
332
|
+
to: string; // Contract address
|
|
333
|
+
func: string; // Function name
|
|
334
|
+
args: xdr.ScVal[]; // Function arguments
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Extracts only the root call from an invocation (no recursion into sub-invocations).
|
|
339
|
+
*/
|
|
340
|
+
function getRootCall(invocation: xdr.SorobanAuthorizedInvocation): Call {
|
|
341
|
+
const fn = invocation.function();
|
|
342
|
+
if (
|
|
343
|
+
fn.switch() !== xdr.SorobanAuthorizedFunctionType.sorobanAuthorizedFunctionTypeContractFn()
|
|
344
|
+
) {
|
|
345
|
+
throw new Error('Root invocation is not a contract function');
|
|
346
|
+
}
|
|
347
|
+
const contractFn = fn.contractFn();
|
|
348
|
+
return {
|
|
349
|
+
to: Address.fromScAddress(contractFn.contractAddress()).toString(),
|
|
350
|
+
func: contractFn.functionName().toString(),
|
|
351
|
+
args: contractFn.args(),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Collects all contract calls from an invocation tree.
|
|
357
|
+
*/
|
|
358
|
+
function collectCallsFromInvocation(invocation: xdr.SorobanAuthorizedInvocation): Call[] {
|
|
359
|
+
const calls: Call[] = [];
|
|
360
|
+
collectCallsRecursive(invocation, calls);
|
|
361
|
+
return calls;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function collectCallsRecursive(invocation: xdr.SorobanAuthorizedInvocation, calls: Call[]): void {
|
|
365
|
+
const fn = invocation.function();
|
|
366
|
+
|
|
367
|
+
if (
|
|
368
|
+
fn.switch() === xdr.SorobanAuthorizedFunctionType.sorobanAuthorizedFunctionTypeContractFn()
|
|
369
|
+
) {
|
|
370
|
+
const contractFn = fn.contractFn();
|
|
371
|
+
const contractAddr = Address.fromScAddress(contractFn.contractAddress());
|
|
372
|
+
|
|
373
|
+
calls.push({
|
|
374
|
+
to: contractAddr.toString(),
|
|
375
|
+
func: contractFn.functionName().toString(),
|
|
376
|
+
args: contractFn.args(),
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Process sub-invocations
|
|
381
|
+
for (const sub of invocation.subInvocations()) {
|
|
382
|
+
collectCallsRecursive(sub, calls);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Serializes calls to XDR format matching Soroban's Vec<Call> serialization.
|
|
388
|
+
*
|
|
389
|
+
* Call struct: { args: Vec<Val>, func: Symbol, to: Address }
|
|
390
|
+
* Serialized as ScMap with keys in alphabetical order.
|
|
391
|
+
*/
|
|
392
|
+
function serializeCallsToXdr(calls: Call[]): Buffer {
|
|
393
|
+
const callScVals = calls.map((call) =>
|
|
394
|
+
xdr.ScVal.scvMap([
|
|
395
|
+
new xdr.ScMapEntry({
|
|
396
|
+
key: xdr.ScVal.scvSymbol('args'),
|
|
397
|
+
val: xdr.ScVal.scvVec(call.args),
|
|
398
|
+
}),
|
|
399
|
+
new xdr.ScMapEntry({
|
|
400
|
+
key: xdr.ScVal.scvSymbol('func'),
|
|
401
|
+
val: xdr.ScVal.scvSymbol(call.func),
|
|
402
|
+
}),
|
|
403
|
+
new xdr.ScMapEntry({
|
|
404
|
+
key: xdr.ScVal.scvSymbol('to'),
|
|
405
|
+
val: Address.fromString(call.to).toScVal(),
|
|
406
|
+
}),
|
|
407
|
+
]),
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
const vecScVal = xdr.ScVal.scvVec(callScVals);
|
|
411
|
+
return Buffer.from(vecScVal.toXDR());
|
|
412
|
+
}
|
|
413
|
+
|
|
328
414
|
/**
|
|
329
415
|
* Computes the call hash for multisig signing.
|
|
330
|
-
* hash = keccak256(vid.to_be_bytes(4) || expiration.to_be_bytes(8) ||
|
|
416
|
+
* hash = keccak256(vid.to_be_bytes(4) || expiration.to_be_bytes(8) || calls_xdr)
|
|
331
417
|
*/
|
|
332
|
-
function computeCallHash(vid: number, expiration: bigint,
|
|
418
|
+
function computeCallHash(vid: number, expiration: bigint, callsXdr: Buffer): Uint8Array {
|
|
333
419
|
// vid as 4-byte big-endian
|
|
334
420
|
const vidBytes = Buffer.alloc(4);
|
|
335
421
|
vidBytes.writeUInt32BE(vid, 0);
|
|
@@ -338,7 +424,8 @@ function computeCallHash(vid: number, expiration: bigint, invocationXdr: Buffer)
|
|
|
338
424
|
const expirationBytes = Buffer.alloc(8);
|
|
339
425
|
expirationBytes.writeBigUInt64BE(expiration, 0);
|
|
340
426
|
|
|
341
|
-
|
|
427
|
+
// Concatenate and hash
|
|
428
|
+
const data = Buffer.concat([vidBytes, expirationBytes, callsXdr]);
|
|
342
429
|
return keccak_256(data);
|
|
343
430
|
}
|
|
344
431
|
|