@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.
@@ -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
- // Chain B's DVN verifies the packet on Chain B's ULN302
264
- const verifyTx = await uln302ClientB.verify({
265
- dvn: chainB.dvn,
266
- packet_header: packetHeader,
267
- payload_hash: payloadHash,
268
- confirmations: 1n,
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
- // Chain A's DVN verifies the packet on Chain A's ULN302
473
- const verifyTx = await uln302ClientA.verify({
474
- dvn: chainA.dvn,
475
- packet_header: returnPacketHeader,
476
- payload_hash: returnPayloadHash,
477
- confirmations: 1n,
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,
@@ -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. Compute the multisig hash from raw invocation XDR
238
- // hash = keccak256(vid || expiration || invocation_xdr)
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 invocationXdr = Buffer.from(rootInvocation.toXDR());
241
- const callHash = computeCallHash(vid, expiration, invocationXdr);
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, sender, preimage }
265
- const preimageXdr = Buffer.from(preimage.toXDR());
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) || invocation_xdr)
416
+ * hash = keccak256(vid.to_be_bytes(4) || expiration.to_be_bytes(8) || calls_xdr)
331
417
  */
332
- function computeCallHash(vid: number, expiration: bigint, invocationXdr: Buffer): Uint8Array {
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
- const data = Buffer.concat([vidBytes, expirationBytes, invocationXdr]);
427
+ // Concatenate and hash
428
+ const data = Buffer.concat([vidBytes, expirationBytes, callsXdr]);
342
429
  return keccak_256(data);
343
430
  }
344
431