@across-protocol/contracts 3.0.17 → 3.0.18

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.
Files changed (52) hide show
  1. package/dist/scripts/svm/addressToPublicKey.d.ts +1 -0
  2. package/dist/scripts/svm/addressToPublicKey.js +20 -0
  3. package/dist/scripts/svm/bridgeLiabilityToHubPool.d.ts +28 -0
  4. package/dist/scripts/svm/bridgeLiabilityToHubPool.js +231 -0
  5. package/dist/scripts/svm/closeRelayerPdas.js +6 -7
  6. package/dist/scripts/svm/enableRoute.js +7 -1
  7. package/dist/scripts/svm/executeRebalanceToHubPool.d.ts +32 -0
  8. package/dist/scripts/svm/executeRebalanceToHubPool.js +355 -0
  9. package/dist/scripts/svm/executeRebalanceToSpokePool.d.ts +1 -0
  10. package/dist/scripts/svm/executeRebalanceToSpokePool.js +253 -0
  11. package/dist/scripts/svm/proposeRebalanceToHubPool.d.ts +27 -0
  12. package/dist/scripts/svm/proposeRebalanceToHubPool.js +117 -0
  13. package/dist/scripts/svm/proposeRebalanceToSpokePool.d.ts +1 -0
  14. package/dist/scripts/svm/proposeRebalanceToSpokePool.js +101 -0
  15. package/dist/scripts/svm/publicKeyToAddress.d.ts +1 -0
  16. package/dist/scripts/svm/publicKeyToAddress.js +20 -0
  17. package/dist/scripts/svm/queryDeposits.js +1 -0
  18. package/dist/scripts/svm/queryFills.js +11 -12
  19. package/dist/scripts/svm/queryRoute.js +7 -1
  20. package/dist/scripts/svm/queryState.js +1 -0
  21. package/dist/scripts/svm/remoteHubPoolPauseDeposits.d.ts +1 -0
  22. package/dist/scripts/svm/remoteHubPoolPauseDeposits.js +205 -0
  23. package/dist/scripts/svm/simpleDeposit.js +7 -1
  24. package/dist/scripts/svm/simpleFill.js +5 -4
  25. package/dist/scripts/svm/utils/constants.d.ts +4 -0
  26. package/dist/scripts/svm/utils/constants.js +8 -1
  27. package/dist/scripts/svm/utils/helpers.d.ts +31 -0
  28. package/dist/scripts/svm/utils/helpers.js +50 -1
  29. package/dist/scripts/svm/utils/poolRebalanceTree.d.ts +22 -0
  30. package/dist/scripts/svm/utils/poolRebalanceTree.js +20 -0
  31. package/dist/src/SvmUtils.d.ts +24 -2
  32. package/dist/src/SvmUtils.js +107 -26
  33. package/dist/target/types/svm_spoke.d.ts +47 -42
  34. package/dist/tasks/enableL1TokenAcrossEcosystem.js +3 -33
  35. package/dist/tasks/types.d.ts +2 -0
  36. package/dist/tasks/types.js +2 -0
  37. package/dist/tasks/utils.d.ts +12 -0
  38. package/dist/tasks/utils.js +34 -0
  39. package/dist/test/svm/SvmSpoke.Bundle.js +15 -16
  40. package/dist/test/svm/SvmSpoke.Deposit.js +18 -26
  41. package/dist/test/svm/SvmSpoke.Fill.AcrossPlus.js +1 -1
  42. package/dist/test/svm/SvmSpoke.Fill.js +13 -14
  43. package/dist/test/svm/SvmSpoke.Ownership.js +10 -16
  44. package/dist/test/svm/SvmSpoke.RefundClaims.js +9 -8
  45. package/dist/test/svm/SvmSpoke.Routes.js +10 -6
  46. package/dist/test/svm/SvmSpoke.SlowFill.AcrossPlus.js +59 -2
  47. package/dist/test/svm/SvmSpoke.SlowFill.js +23 -18
  48. package/dist/test/svm/SvmSpoke.TokenBridge.js +3 -5
  49. package/dist/test/svm/SvmSpoke.common.js +2 -1
  50. package/dist/test/svm/utils.d.ts +4 -5
  51. package/dist/test/svm/utils.js +24 -18
  52. package/package.json +2 -2
@@ -144,11 +144,9 @@ describe("svm_spoke.bundle", () => {
144
144
  .relayRootBundle(relayerRefundRootArray, slowRelayRootArray)
145
145
  .accounts(relayRootBundleAccounts)
146
146
  .rpc();
147
- // Wait for event processing
148
- await new Promise((resolve) => setTimeout(resolve, 1000));
149
147
  // Check for the emitted event
150
- const events = await (0, utils_1.readEvents)(connection, tx, [program]);
151
- const event = events.find((event) => event.name === "relayedRootBundle").data;
148
+ let events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
149
+ const event = events.find((event) => event.name === "relayedRootBundle")?.data;
152
150
  chai_1.assert.isTrue(event.rootBundleId.toString() === rootBundleId.toString(), "Root bundle ID should match");
153
151
  chai_1.assert.isTrue(event.relayerRefundRoot.toString() === relayerRefundRootArray.toString(), "Relayer refund root should match");
154
152
  chai_1.assert.isTrue(event.slowRelayRoot.toString() === slowRelayRootArray.toString(), "Slow relay root should match");
@@ -200,15 +198,14 @@ describe("svm_spoke.bundle", () => {
200
198
  };
201
199
  const proofAsNumbers = proof.map((p) => Array.from(p));
202
200
  await (0, utils_1.loadExecuteRelayerRefundLeafParams)(program, owner, stateAccountData.rootBundleId, leaf, proofAsNumbers);
203
- await program.methods
201
+ const tx = await program.methods
204
202
  .executeRelayerRefundLeaf()
205
203
  .accounts(executeRelayerRefundLeafAccounts)
206
204
  .remainingAccounts(remainingAccounts)
207
205
  .rpc();
208
206
  // Verify the ExecutedRelayerRefundRoot event
209
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for event processing
210
- let events = await (0, utils_1.readProgramEvents)(connection, program);
211
- let event = events.find((event) => event.name === "executedRelayerRefundRoot").data;
207
+ let events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
208
+ let event = events.find((event) => event.name === "executedRelayerRefundRoot")?.data;
212
209
  // Remove the expectedValues object and use direct assertions
213
210
  assertSE(event.amountToReturn, relayerRefundLeaves[0].amountToReturn, "amountToReturn should match");
214
211
  assertSE(event.chainId, chainId, "chainId should match");
@@ -221,6 +218,12 @@ describe("svm_spoke.bundle", () => {
221
218
  assertSE(event.refundAddresses[1], relayerB.publicKey, "Relayer B address should match");
222
219
  chai_1.assert.isFalse(event.deferredRefunds, "deferredRefunds should be false");
223
220
  assertSE(event.caller, owner, "caller should match");
221
+ event = events.find((event) => event.name === "tokensBridged")?.data;
222
+ assertSE(event.amountToReturn, relayerRefundLeaves[0].amountToReturn, "amountToReturn should match");
223
+ assertSE(event.chainId, chainId, "chainId should match");
224
+ assertSE(event.leafId, leaf.leafId, "leafId should match");
225
+ assertSE(event.l2TokenAddress, mint, "l2TokenAddress should match");
226
+ assertSE(event.caller, owner, "caller should match");
224
227
  const fVaultBal = (await connection.getTokenAccountBalance(vault)).value.amount;
225
228
  const fRelayerABal = (await connection.getTokenAccountBalance(relayerTA)).value.amount;
226
229
  const fRelayerBBal = (await connection.getTokenAccountBalance(relayerTB)).value.amount;
@@ -1180,16 +1183,14 @@ describe("svm_spoke.bundle", () => {
1180
1183
  };
1181
1184
  it("No deferred refunds in all Token Accounts", async () => {
1182
1185
  const tx = await executeRelayerRefundLeaf({ deferredRefunds: false });
1183
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for event processing
1184
- const events = await (0, utils_1.readEvents)(connection, tx, [program]);
1185
- const event = events.find((event) => event.name === "executedRelayerRefundRoot").data;
1186
+ const events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
1187
+ const event = events.find((event) => event.name === "executedRelayerRefundRoot")?.data;
1186
1188
  chai_1.assert.isFalse(event.deferredRefunds, "deferredRefunds should be false");
1187
1189
  });
1188
1190
  it("Deferred refunds in all Claim Accounts", async () => {
1189
1191
  const tx = await executeRelayerRefundLeaf({ deferredRefunds: true });
1190
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for event processing
1191
- const events = await (0, utils_1.readEvents)(connection, tx, [program]);
1192
- const event = events.find((event) => event.name === "executedRelayerRefundRoot").data;
1192
+ const events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
1193
+ const event = events.find((event) => event.name === "executedRelayerRefundRoot")?.data;
1193
1194
  chai_1.assert.isTrue(event.deferredRefunds, "deferredRefunds should be true");
1194
1195
  });
1195
1196
  });
@@ -1321,7 +1322,6 @@ describe("svm_spoke.bundle", () => {
1321
1322
  chai_1.assert.fail("Leaf verification should fail without leading 64 bytes");
1322
1323
  }
1323
1324
  catch (err) {
1324
- console.log("err", err);
1325
1325
  chai_1.assert.include(err.toString(), "Invalid Merkle proof", "Expected merkle verification to fail");
1326
1326
  }
1327
1327
  });
@@ -1399,7 +1399,6 @@ describe("svm_spoke.bundle", () => {
1399
1399
  chai_1.assert.fail("Leaf verification should fail without leading 64 bytes");
1400
1400
  }
1401
1401
  catch (err) {
1402
- console.log("err", err);
1403
1402
  chai_1.assert.include(err.toString(), "Invalid Merkle proof", "Expected merkle verification to fail");
1404
1403
  }
1405
1404
  });
@@ -85,7 +85,8 @@ describe("svm_spoke.deposit", () => {
85
85
  .accounts(calledDepositAccounts)
86
86
  .instruction();
87
87
  const depositTx = new web3_js_1.Transaction().add(approveIx, depositIx);
88
- await (0, web3_js_1.sendAndConfirmTransaction)(connection, depositTx, [payer, depositor]);
88
+ const tx = await (0, web3_js_1.sendAndConfirmTransaction)(connection, depositTx, [payer, depositor]);
89
+ return tx;
89
90
  };
90
91
  beforeEach(async () => {
91
92
  ({ state, seed } = await initializeState());
@@ -122,9 +123,8 @@ describe("svm_spoke.deposit", () => {
122
123
  depositData.inputAmount = depositData.inputAmount.add(new anchor_1.BN(69));
123
124
  // Execute the first deposit_v3 call
124
125
  let depositDataValues = Object.values(depositData);
125
- await approvedDepositV3(depositDataValues);
126
- await new Promise((resolve) => setTimeout(resolve, 500));
127
- let events = await (0, utils_1.readProgramEvents)(connection, program);
126
+ const tx = await approvedDepositV3(depositDataValues);
127
+ let events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
128
128
  let event = events[0].data; // 0th event is the latest event
129
129
  const expectedValues1 = { ...depositData, depositId: (0, utils_1.intToU8Array32)(1) }; // Verify the event props emitted match the depositData.
130
130
  for (let [key, value] of Object.entries(expectedValues1)) {
@@ -133,9 +133,8 @@ describe("svm_spoke.deposit", () => {
133
133
  assertSE(event[key], value, `${key} should match`);
134
134
  }
135
135
  // Execute the second deposit_v3 call
136
- await approvedDepositV3(depositDataValues);
137
- await new Promise((resolve) => setTimeout(resolve, 500));
138
- events = await (0, utils_1.readProgramEvents)(connection, program);
136
+ const tx2 = await approvedDepositV3(depositDataValues);
137
+ events = await (0, utils_1.readEventsUntilFound)(connection, tx2, [program]);
139
138
  event = events[0].data; // 0th event is the latest event.
140
139
  const expectedValues2 = { ...expectedValues1, depositId: (0, utils_1.intToU8Array32)(2) }; // Verify the event props emitted match the depositData.
141
140
  for (let [key, value] of Object.entries(expectedValues2)) {
@@ -286,7 +285,6 @@ describe("svm_spoke.deposit", () => {
286
285
  program: program.programId,
287
286
  };
288
287
  await program.methods.setEnableRoute(inputToken, fakeRouteChainId, true).accounts(fakeSetEnableRouteAccounts).rpc();
289
- await new Promise((resolve) => setTimeout(resolve, 2000));
290
288
  const fakeDepositAccounts = {
291
289
  state: fakeState.state,
292
290
  route: fakeRoutePda,
@@ -302,9 +300,8 @@ describe("svm_spoke.deposit", () => {
302
300
  ...depositData,
303
301
  destinationChainId: fakeRouteChainId,
304
302
  });
305
- await approvedDepositV3(depositDataValues, fakeDepositAccounts);
306
- await new Promise((resolve) => setTimeout(resolve, 500));
307
- let events = await (0, utils_1.readProgramEvents)(connection, program);
303
+ const tx = await approvedDepositV3(depositDataValues, fakeDepositAccounts);
304
+ let events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
308
305
  let event = events[0].data; // 0th event is the latest event.
309
306
  const expectedValues = {
310
307
  ...{ ...depositData, destinationChainId: fakeRouteChainId },
@@ -346,9 +343,8 @@ describe("svm_spoke.deposit", () => {
346
343
  .accounts(depositAccounts)
347
344
  .instruction();
348
345
  const depositTx = new web3_js_1.Transaction().add(approveIx, depositIx);
349
- await (0, web3_js_1.sendAndConfirmTransaction)(connection, depositTx, [payer, depositor]);
350
- await new Promise((resolve) => setTimeout(resolve, 500));
351
- const events = await (0, utils_1.readProgramEvents)(connection, program);
346
+ const tx = await (0, web3_js_1.sendAndConfirmTransaction)(connection, depositTx, [payer, depositor]);
347
+ const events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
352
348
  const event = events[0].data; // 0th event is the latest event.
353
349
  // Verify the event props emitted match the expected values
354
350
  const currentTime = await getCurrentTime(program, state);
@@ -407,9 +403,8 @@ describe("svm_spoke.deposit", () => {
407
403
  depositData.exclusiveRelayer = depositor.publicKey;
408
404
  depositData.exclusivityParameter = maxExclusivityOffsetSeconds;
409
405
  const depositDataValues = Object.values(depositData);
410
- await approvedDepositV3(depositDataValues);
411
- await new Promise((resolve) => setTimeout(resolve, 500));
412
- const events = await (0, utils_1.readProgramEvents)(connection, program);
406
+ const tx = await approvedDepositV3(depositDataValues);
407
+ const events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
413
408
  const event = events[0].data; // 0th event is the latest event
414
409
  assertSE(event.exclusivityDeadline, currentTime.add(maxExclusivityOffsetSeconds), "exclusivityDeadline should be current time + offset");
415
410
  });
@@ -420,9 +415,8 @@ describe("svm_spoke.deposit", () => {
420
415
  depositData.exclusiveRelayer = depositor.publicKey;
421
416
  depositData.exclusivityParameter = exclusivityDeadlineTimestamp;
422
417
  const depositDataValues = Object.values(depositData);
423
- await approvedDepositV3(depositDataValues);
424
- await new Promise((resolve) => setTimeout(resolve, 500));
425
- const events = await (0, utils_1.readProgramEvents)(connection, program);
418
+ const tx = await approvedDepositV3(depositDataValues);
419
+ const events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
426
420
  const event = events[0].data; // 0th event is the latest event;
427
421
  assertSE(event.exclusivityDeadline, exclusivityDeadlineTimestamp, "exclusivityDeadline should be passed in time");
428
422
  });
@@ -433,9 +427,8 @@ describe("svm_spoke.deposit", () => {
433
427
  depositData.exclusiveRelayer = depositor.publicKey;
434
428
  depositData.exclusivityParameter = zeroExclusivity;
435
429
  const depositDataValues = Object.values(depositData);
436
- await approvedDepositV3(depositDataValues);
437
- await new Promise((resolve) => setTimeout(resolve, 500));
438
- const events = await (0, utils_1.readProgramEvents)(connection, program);
430
+ const tx = await approvedDepositV3(depositDataValues);
431
+ const events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
439
432
  const event = events[0].data; // 0th event is the latest event;
440
433
  assertSE(event.exclusivityDeadline, zeroExclusivity, "Exclusivity deadline should always be 0");
441
434
  });
@@ -462,11 +455,10 @@ describe("svm_spoke.deposit", () => {
462
455
  .accounts(depositAccounts) // Assuming depositAccounts is already set up correctly
463
456
  .instruction();
464
457
  const unsafeDepositTx = new web3_js_1.Transaction().add(approveIx, unsafeDepositIx);
465
- await (0, web3_js_1.sendAndConfirmTransaction)(connection, unsafeDepositTx, [payer, depositor]);
458
+ const tx = await (0, web3_js_1.sendAndConfirmTransaction)(connection, unsafeDepositTx, [payer, depositor]);
466
459
  // Wait for a short period to ensure the event is emitted
467
- await new Promise((resolve) => setTimeout(resolve, 500));
468
460
  // Read and verify the event
469
- const events = await (0, utils_1.readProgramEvents)(connection, program);
461
+ const events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
470
462
  const event = events[0].data; // Assuming the latest event is the one we want
471
463
  const expectedValues = { ...depositData, depositId: expectedDepositIdArray };
472
464
  for (let [key, value] of Object.entries(expectedValues)) {
@@ -176,7 +176,7 @@ describe("svm_spoke.fill.across_plus", () => {
176
176
  const computeBudgetIx = web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 });
177
177
  await (0, SvmUtils_1.sendTransactionWithLookupTable)(connection, [computeBudgetIx, approveIx, fillIx], relayer);
178
178
  // Verify relayer's balance after the fill
179
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Make sure token transfers get processed.
179
+ await new Promise((resolve) => setTimeout(resolve, 500)); // Make sure token transfers get processed.
180
180
  const fRelayerBal = (await (0, spl_token_1.getAccount)(connection, relayerATA)).amount;
181
181
  assertSE(fRelayerBal, iRelayerBal - BigInt(distributionAmount * numberOfDistributions), "Relayer's balance should be reduced by the relay amount");
182
182
  // Verify all recipient account balances after the fill.
@@ -71,7 +71,8 @@ describe("svm_spoke.fill", () => {
71
71
  .remainingAccounts(fillRemainingAccounts)
72
72
  .instruction();
73
73
  const fillTx = new web3_js_1.Transaction().add(approveIx, fillIx);
74
- await (0, web3_js_1.sendAndConfirmTransaction)(connection, fillTx, [payer, callingRelayer]);
74
+ const tx = await (0, web3_js_1.sendAndConfirmTransaction)(connection, fillTx, [payer, callingRelayer]);
75
+ return tx;
75
76
  };
76
77
  before("Funds relayer wallets", async () => {
77
78
  await connection.requestAirdrop(relayer.publicKey, 10_000_000_000); // 10 SOL
@@ -124,23 +125,22 @@ describe("svm_spoke.fill", () => {
124
125
  });
125
126
  it("Verifies FilledV3Relay event after filling a relay", async () => {
126
127
  const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
127
- await approvedFillV3Relay([relayHash, relayData, new anchor_1.BN(420), otherRelayer.publicKey]);
128
+ const tx = await approvedFillV3Relay([relayHash, relayData, new anchor_1.BN(420), otherRelayer.publicKey]);
128
129
  // Fetch and verify the FilledV3Relay event
129
- await new Promise((resolve) => setTimeout(resolve, 500));
130
- const events = await (0, SvmUtils_1.readProgramEvents)(connection, program);
131
- const event = events.find((event) => event.name === "filledV3Relay").data;
130
+ const events = await (0, SvmUtils_1.readEventsUntilFound)(connection, tx, [program]);
131
+ const event = events.find((event) => event.name === "filledV3Relay")?.data;
132
132
  assert.isNotNull(event, "FilledV3Relay event should be emitted");
133
133
  // Verify that the event data matches the relay data.
134
134
  Object.entries(relayData).forEach(([key, value]) => {
135
135
  if (key === "message") {
136
- assertSE(event.messageHash, (0, utils_2.hashNonEmptyMessage)(value), `MessageHash should match`);
136
+ assertSE(event.messageHash, (0, SvmUtils_1.hashNonEmptyMessage)(value), `MessageHash should match`);
137
137
  }
138
138
  else
139
139
  assertSE(event[key], value, `${key.charAt(0).toUpperCase() + key.slice(1)} should match`);
140
140
  });
141
141
  // RelayExecutionInfo should match.
142
142
  assertSE(event.relayExecutionInfo.updatedRecipient, relayData.recipient, "UpdatedRecipient should match");
143
- assertSE(event.relayExecutionInfo.updatedMessageHash, (0, utils_2.hashNonEmptyMessage)(relayData.message), "UpdatedMessageHash should match");
143
+ assertSE(event.relayExecutionInfo.updatedMessageHash, (0, SvmUtils_1.hashNonEmptyMessage)(relayData.message), "UpdatedMessageHash should match");
144
144
  assertSE(event.relayExecutionInfo.updatedOutputAmount, relayData.outputAmount, "UpdatedOutputAmount should match");
145
145
  assert.equal(JSON.stringify(event.relayExecutionInfo.fillType), `{"fastFill":{}}`, "FillType should be FastFill");
146
146
  // These props below are not part of relayData.
@@ -213,7 +213,7 @@ describe("svm_spoke.fill", () => {
213
213
  assert.isNotNull(fillStatusAccountBefore, "Fill PDA should exist before closing");
214
214
  // Attempt to close the fill PDA before the fill deadline should fail.
215
215
  try {
216
- await program.methods.closeFillPda(relayHash, relayData).accounts(closeFillPdaAccounts).signers([relayer]).rpc();
216
+ await program.methods.closeFillPda().accounts(closeFillPdaAccounts).signers([relayer]).rpc();
217
217
  assert.fail("Closing fill PDA should have failed before fill deadline");
218
218
  }
219
219
  catch (err) {
@@ -222,7 +222,7 @@ describe("svm_spoke.fill", () => {
222
222
  // Set the current time to past the fill deadline
223
223
  await setCurrentTime(program, state, relayer, new anchor_1.BN(relayData.fillDeadline + 1));
224
224
  // Close the fill PDA
225
- await program.methods.closeFillPda(relayHash, relayData).accounts(closeFillPdaAccounts).signers([relayer]).rpc();
225
+ await program.methods.closeFillPda().accounts(closeFillPdaAccounts).signers([relayer]).rpc();
226
226
  // Verify the fill PDA is closed
227
227
  const fillStatusAccountAfter = await connection.getAccountInfo(accounts.fillStatus);
228
228
  assert.isNull(fillStatusAccountAfter, "Fill PDA should be closed after closing");
@@ -426,7 +426,7 @@ describe("svm_spoke.fill", () => {
426
426
  // Fill using the ALT.
427
427
  await (0, SvmUtils_1.sendTransactionWithLookupTable)(connection, [createTokenAccountsInstruction, approveInstruction, ...fillInstructions], relayer);
428
428
  // Verify balances after the fill
429
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for tx processing
429
+ await new Promise((resolve) => setTimeout(resolve, 500)); // Wait for tx processing
430
430
  const fRelayerBal = (await (0, spl_token_1.getAccount)(connection, relayerTA)).amount;
431
431
  assertSE(fRelayerBal, iRelayerBal - BigInt(relayAmount * numberOfFills), "Relayer's balance should be reduced by total relay amount");
432
432
  recipientAssociatedTokens.forEach(async (recipientAssociatedToken) => {
@@ -466,11 +466,10 @@ describe("svm_spoke.fill", () => {
466
466
  it("Emits zeroed hash for empty message", async () => {
467
467
  updateRelayData({ ...relayData, message: Buffer.alloc(0) });
468
468
  const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
469
- await approvedFillV3Relay([relayHash, relayData, new anchor_1.BN(420), otherRelayer.publicKey]);
469
+ const tx = await approvedFillV3Relay([relayHash, relayData, new anchor_1.BN(420), otherRelayer.publicKey]);
470
470
  // Fetch and verify the FilledV3Relay event
471
- await new Promise((resolve) => setTimeout(resolve, 500));
472
- const events = await (0, SvmUtils_1.readProgramEvents)(connection, program);
473
- const event = events.find((event) => event.name === "filledV3Relay").data;
471
+ const events = await (0, SvmUtils_1.readEventsUntilFound)(connection, tx, [program]);
472
+ const event = events.find((event) => event.name === "filledV3Relay")?.data;
474
473
  assert.isNotNull(event, "FilledV3Relay event should be emitted");
475
474
  // Verify that the event data has zeroed message hash.
476
475
  assertSE(event.messageHash, new Uint8Array(32), `MessageHash should be zeroed`);
@@ -62,23 +62,21 @@ describe("svm_spoke.ownership", () => {
62
62
  chai_1.assert.isFalse((await program.account.state.fetch(state)).pausedDeposits, "Deposits should not be paused");
63
63
  // Pause deposits as owner
64
64
  const pauseDepositsAccounts = { state, signer: owner, program: program.programId };
65
- await program.methods.pauseDeposits(true).accounts(pauseDepositsAccounts).rpc();
66
- await new Promise((resolve) => setTimeout(resolve, 500));
65
+ const tx = await program.methods.pauseDeposits(true).accounts(pauseDepositsAccounts).rpc();
67
66
  // Fetch the updated state
68
67
  let stateAccountData = await program.account.state.fetch(state);
69
68
  chai_1.assert.isTrue(stateAccountData.pausedDeposits, "Deposits should be paused");
70
69
  // Verify the PausedDeposits event
71
- let events = await (0, utils_1.readProgramEvents)(provider.connection, program);
70
+ let events = await (0, utils_1.readEventsUntilFound)(provider.connection, tx, [program]);
72
71
  let pausedDepositEvents = events.filter((event) => event.name === "pausedDeposits");
73
72
  chai_1.assert.isTrue(pausedDepositEvents[0].data.isPaused, "PausedDeposits event should indicate deposits are paused");
74
73
  // Unpause deposits as owner
75
- await program.methods.pauseDeposits(false).accounts(pauseDepositsAccounts).rpc();
76
- await new Promise((resolve) => setTimeout(resolve, 500));
74
+ const tx2 = await program.methods.pauseDeposits(false).accounts(pauseDepositsAccounts).rpc();
77
75
  // Fetch the updated state
78
76
  stateAccountData = await program.account.state.fetch(state);
79
77
  chai_1.assert.isFalse(stateAccountData.pausedDeposits, "Deposits should not be paused");
80
78
  // Verify the PausedDeposits event
81
- events = await (0, utils_1.readProgramEvents)(provider.connection, program);
79
+ events = await (0, utils_1.readEventsUntilFound)(provider.connection, tx2, [program]);
82
80
  pausedDepositEvents = events.filter((event) => event.name === "pausedDeposits");
83
81
  chai_1.assert.isFalse(pausedDepositEvents[0].data.isPaused, "PausedDeposits event should indicate deposits are unpaused");
84
82
  // Try to pause deposits as non-owner
@@ -95,23 +93,21 @@ describe("svm_spoke.ownership", () => {
95
93
  chai_1.assert.isFalse((await program.account.state.fetch(state)).pausedFills, "Fills should not be paused");
96
94
  // Pause fills as owner
97
95
  const pauseFillsAccounts = { state, signer: owner, program: program.programId };
98
- await program.methods.pauseFills(true).accounts(pauseFillsAccounts).rpc();
99
- await new Promise((resolve) => setTimeout(resolve, 500));
96
+ const tx = await program.methods.pauseFills(true).accounts(pauseFillsAccounts).rpc();
100
97
  // Fetch the updated state
101
98
  let stateAccountData = await program.account.state.fetch(state);
102
99
  chai_1.assert.isTrue(stateAccountData.pausedFills, "Fills should be paused");
103
100
  // Verify the PausedFills event
104
- let events = await (0, utils_1.readProgramEvents)(provider.connection, program);
101
+ let events = await (0, utils_1.readEventsUntilFound)(provider.connection, tx, [program]);
105
102
  let pausedFillEvents = events.filter((event) => event.name === "pausedFills");
106
103
  chai_1.assert.isTrue(pausedFillEvents[0].data.isPaused, "PausedFills event should indicate fills are paused");
107
104
  // Unpause fills as owner
108
- await program.methods.pauseFills(false).accounts(pauseFillsAccounts).rpc();
109
- await new Promise((resolve) => setTimeout(resolve, 500));
105
+ const tx2 = await program.methods.pauseFills(false).accounts(pauseFillsAccounts).rpc();
110
106
  // Fetch the updated state
111
107
  stateAccountData = await program.account.state.fetch(state);
112
108
  chai_1.assert.isFalse(stateAccountData.pausedFills, "Fills should not be paused");
113
109
  // Verify the PausedFills event
114
- events = await (0, utils_1.readProgramEvents)(provider.connection, program);
110
+ events = await (0, utils_1.readEventsUntilFound)(provider.connection, tx2, [program]);
115
111
  pausedFillEvents = events.filter((event) => event.name === "pausedFills");
116
112
  chai_1.assert.isFalse(pausedFillEvents[0].data.isPaused, "PausedFills event should indicate fills are unpaused");
117
113
  // Try to pause fills as non-owner
@@ -128,7 +124,6 @@ describe("svm_spoke.ownership", () => {
128
124
  // Transfer ownership to newOwner
129
125
  const transferOwnershipAccounts = { state, signer: owner };
130
126
  await program.methods.transferOwnership(newOwner.publicKey).accounts(transferOwnershipAccounts).rpc();
131
- await new Promise((resolve) => setTimeout(resolve, 500));
132
127
  // Verify the new owner
133
128
  let stateAccountData = await program.account.state.fetch(state);
134
129
  chai_1.assert.equal(stateAccountData.owner.toString(), newOwner.publicKey.toString(), "Ownership should be transferred");
@@ -149,16 +144,15 @@ describe("svm_spoke.ownership", () => {
149
144
  it("Sets cross-domain admin", async () => {
150
145
  // Set cross-domain admin as owner
151
146
  const setCrossDomainAdminAccounts = { state, signer: owner, program: program.programId };
152
- await program.methods
147
+ const tx = await program.methods
153
148
  .setCrossDomainAdmin(newCrossDomainAdmin.publicKey)
154
149
  .accounts(setCrossDomainAdminAccounts)
155
150
  .rpc();
156
- await new Promise((resolve) => setTimeout(resolve, 500));
157
151
  // Verify the new cross-domain admin
158
152
  let stateAccountData = await program.account.state.fetch(state);
159
153
  chai_1.assert.equal(stateAccountData.crossDomainAdmin.toString(), newCrossDomainAdmin.publicKey.toString(), "Cross-domain admin should be set");
160
154
  // Verify the SetXDomainAdmin event
161
- let events = await (0, utils_1.readProgramEvents)(provider.connection, program);
155
+ let events = await (0, utils_1.readEventsUntilFound)(provider.connection, tx, [program]);
162
156
  let setXDomainAdminEvents = events.filter((event) => event.name === "setXDomainAdmin");
163
157
  chai_1.assert.equal(setXDomainAdminEvents[0].data.newAdmin.toString(), newCrossDomainAdmin.publicKey.toString(), "SetXDomainAdmin event should indicate the new admin");
164
158
  // Try to set cross-domain admin as non-owner
@@ -142,16 +142,18 @@ describe("svm_spoke.refund_claims", () => {
142
142
  const iVaultBal = (await connection.getTokenAccountBalance(vault)).value.amount;
143
143
  const iRelayerBal = (await connection.getTokenAccountBalance(tokenAccount)).value.amount;
144
144
  // Claim refund for the relayer.
145
- await program.methods.claimRelayerRefundFor(relayer.publicKey).accounts(claimRelayerRefundAccounts).rpc();
145
+ const tx = await program.methods
146
+ .claimRelayerRefundFor(relayer.publicKey)
147
+ .accounts(claimRelayerRefundAccounts)
148
+ .rpc();
146
149
  // The relayer should have received funds from the vault.
147
150
  const fVaultBal = (await connection.getTokenAccountBalance(vault)).value.amount;
148
151
  const fRelayerBal = (await connection.getTokenAccountBalance(tokenAccount)).value.amount;
149
152
  assertSE(BigInt(iVaultBal) - BigInt(fVaultBal), relayerRefund, "Vault balance");
150
153
  assertSE(BigInt(fRelayerBal) - BigInt(iRelayerBal), relayerRefund, "Relayer balance");
151
154
  // Verify the ClaimedRelayerRefund event
152
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for event processing
153
- const events = await (0, utils_1.readProgramEvents)(connection, program);
154
- const event = events.find((event) => event.name === "claimedRelayerRefund").data;
155
+ const events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
156
+ const event = events.find((event) => event.name === "claimedRelayerRefund")?.data;
155
157
  assertSE(event.l2TokenAddress, mint, "l2TokenAddress should match");
156
158
  assertSE(event.claimAmount, relayerRefund, "Relayer refund amount should match");
157
159
  assertSE(event.refundAddress, relayer.publicKey, "Relayer refund address should match");
@@ -300,16 +302,15 @@ describe("svm_spoke.refund_claims", () => {
300
302
  claimRelayerRefundAccounts.tokenAccount = customTokenAccount;
301
303
  claimRelayerRefundAccounts.signer = relayer.publicKey; // Only relayer itself should be able to do this.
302
304
  // Relayer can claim refund to custom token account.
303
- await program.methods.claimRelayerRefund().accounts(claimRelayerRefundAccounts).signers([relayer]).rpc();
305
+ const tx = await program.methods.claimRelayerRefund().accounts(claimRelayerRefundAccounts).signers([relayer]).rpc();
304
306
  // The relayer should have received funds from the vault.
305
307
  const fVaultBal = (await connection.getTokenAccountBalance(vault)).value.amount;
306
308
  const fRelayerBal = (await connection.getTokenAccountBalance(customTokenAccount)).value.amount;
307
309
  assertSE(BigInt(iVaultBal) - BigInt(fVaultBal), relayerRefund, "Vault balance");
308
310
  assertSE(BigInt(fRelayerBal) - BigInt(iRelayerBal), relayerRefund, "Relayer balance");
309
311
  // Verify the ClaimedRelayerRefund event
310
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for event processing
311
- const events = await (0, utils_1.readProgramEvents)(connection, program);
312
- const event = events.find((event) => event.name === "claimedRelayerRefund").data;
312
+ const events = await (0, utils_1.readEventsUntilFound)(connection, tx, [program]);
313
+ const event = events.find((event) => event.name === "claimedRelayerRefund")?.data;
313
314
  assertSE(event.l2TokenAddress, mint, "l2TokenAddress should match");
314
315
  assertSE(event.claimAmount, relayerRefund, "Relayer refund amount should match");
315
316
  assertSE(event.refundAddress, relayer.publicKey, "Relayer refund address should match");
@@ -60,25 +60,29 @@ describe("svm_spoke.routes", () => {
60
60
  });
61
61
  it("Sets, retrieves, and controls access to route enablement", async () => {
62
62
  // Enable the route as owner
63
- await program.methods.setEnableRoute(tokenMint, routeChainId, true).accounts(setEnableRouteAccounts).rpc();
64
- await new Promise((resolve) => setTimeout(resolve, 500));
63
+ const tx = await program.methods
64
+ .setEnableRoute(tokenMint, routeChainId, true)
65
+ .accounts(setEnableRouteAccounts)
66
+ .rpc();
65
67
  // Retrieve and verify the route is enabled
66
68
  let routeAccount = await program.account.route.fetch(routePda);
67
69
  chai_1.assert.isTrue(routeAccount.enabled, "Route should be enabled");
68
70
  // Verify the enabledDepositRoute event
69
- let events = (await (0, utils_1.readProgramEvents)(provider.connection, program)).filter((event) => event.name === "enabledDepositRoute");
71
+ let events = await (0, utils_1.readEventsUntilFound)(provider.connection, tx, [program]);
70
72
  let event = events[0].data;
71
73
  chai_1.assert.strictEqual(event.originToken.toString(), tokenMint.toString(), "originToken event match");
72
74
  chai_1.assert.strictEqual(event.destinationChainId.toString(), routeChainId.toString(), "destinationChainId should match");
73
75
  chai_1.assert.isTrue(event.enabled, "enabledDepositRoute enabled");
74
76
  // Disable the route as owner
75
- await program.methods.setEnableRoute(tokenMint, routeChainId, false).accounts(setEnableRouteAccounts).rpc();
76
- await new Promise((resolve) => setTimeout(resolve, 500));
77
+ const tx2 = await program.methods
78
+ .setEnableRoute(tokenMint, routeChainId, false)
79
+ .accounts(setEnableRouteAccounts)
80
+ .rpc();
77
81
  // Retrieve and verify the route is disabled
78
82
  routeAccount = await program.account.route.fetch(routePda);
79
83
  chai_1.assert.isFalse(routeAccount.enabled, "Route should be disabled");
80
84
  // Verify the enabledDepositRoute event
81
- events = (await (0, utils_1.readProgramEvents)(provider.connection, program)).filter((event) => event.name === "enabledDepositRoute");
85
+ events = await (0, utils_1.readEventsUntilFound)(provider.connection, tx2, [program]);
82
86
  event = events[0].data; // take most recent event, index 0.
83
87
  chai_1.assert.strictEqual(event.originToken.toString(), tokenMint.toString(), "originToken event match");
84
88
  chai_1.assert.strictEqual(event.destinationChainId.toString(), routeChainId.toString(), "destinationChainId should match");
@@ -32,8 +32,8 @@ const MerkleTree_1 = require("@uma/common/dist/MerkleTree");
32
32
  const SvmUtils_1 = require("../../src/SvmUtils");
33
33
  const SvmSpoke_common_1 = require("./SvmSpoke.common");
34
34
  const utils_1 = require("./utils");
35
- const { provider, connection, program, owner, chainId } = SvmSpoke_common_1.common;
36
- const { initializeState, assertSE } = SvmSpoke_common_1.common;
35
+ const { provider, connection, program, owner, chainId, setCurrentTime } = SvmSpoke_common_1.common;
36
+ const { initializeState, assertSE, assert } = SvmSpoke_common_1.common;
37
37
  describe("svm_spoke.slow_fill.across_plus", () => {
38
38
  anchor.setProvider(provider);
39
39
  const payer = anchor.AnchorProvider.env().wallet.payer;
@@ -277,4 +277,61 @@ describe("svm_spoke.slow_fill.across_plus", () => {
277
277
  await fillTokenDistributions(numberOfDistributions, true);
278
278
  });
279
279
  });
280
+ it("Can recover and close fill status PDA from event data", async () => {
281
+ // Construct ix to transfer all tokens from handler to the final recipient.
282
+ const transferIx = (0, spl_token_1.createTransferCheckedInstruction)(handlerATA, mint, finalRecipientATA, handlerSigner, relayData.outputAmount.toNumber(), tokenDecimals);
283
+ const multicallHandlerCoder = new SvmUtils_1.MulticallHandlerCoder([transferIx]);
284
+ const handlerMessage = multicallHandlerCoder.encode();
285
+ const message = new SvmUtils_1.AcrossPlusMessageCoder({
286
+ handler: handlerProgram.programId,
287
+ readOnlyLen: multicallHandlerCoder.readOnlyLen,
288
+ valueAmount: new anchor_1.BN(0),
289
+ accounts: multicallHandlerCoder.compiledMessage.accountKeys,
290
+ handlerMessage,
291
+ });
292
+ const encodedMessage = message.encode();
293
+ // Update relay data with the encoded message.
294
+ const newRelayData = { ...relayData, message: encodedMessage };
295
+ updateRelayData(newRelayData);
296
+ // Prepare request and execute slow fill instructions as we will need to use Address Lookup Table (ALT).
297
+ // Request and execute slow fill.
298
+ const { loadRequestParamsInstructions, requestIx, loadExecuteParamsInstructions, executeIx } = await createSlowFillIx(multicallHandlerCoder, true);
299
+ // Fill using the ALT and submit load params transactions.
300
+ for (let i = 0; i < loadRequestParamsInstructions.length; i += 1) {
301
+ await (0, web3_js_1.sendAndConfirmTransaction)(program.provider.connection, new web3_js_1.Transaction().add(loadRequestParamsInstructions[i]), [relayer]);
302
+ }
303
+ await (0, SvmUtils_1.sendTransactionWithLookupTable)(connection, [requestIx], relayer);
304
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // Make sure request tx gets processed.
305
+ for (let i = 0; i < loadExecuteParamsInstructions.length; i += 1) {
306
+ await (0, web3_js_1.sendAndConfirmTransaction)(program.provider.connection, new web3_js_1.Transaction().add(loadExecuteParamsInstructions[i]), [relayer]);
307
+ }
308
+ const { txSignature } = await (0, SvmUtils_1.sendTransactionWithLookupTable)(connection, [executeIx], relayer);
309
+ await connection.confirmTransaction(txSignature, "confirmed");
310
+ await connection.getTransaction(txSignature, {
311
+ commitment: "confirmed",
312
+ maxSupportedTransactionVersion: 0,
313
+ });
314
+ // We don't close ALT here as that would require ~4 minutes between deactivation and closing, but we demonstrate
315
+ // being able to close the fill status PDA using only event data.
316
+ const events = await (0, SvmUtils_1.readEventsUntilFound)(connection, txSignature, [program]);
317
+ const eventData = events.find((event) => event.name === "filledV3Relay")?.data;
318
+ assert.isNotNull(eventData, "FilledV3Relay event should be emitted");
319
+ // Recover relay hash and derived fill status from event data.
320
+ const relayHashUint8Array = (0, SvmUtils_1.calculateRelayEventHashUint8Array)(eventData, chainId);
321
+ const [fillStatusPDA] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fills"), relayHashUint8Array], program.programId);
322
+ const fillStatusAccount = await program.account.fillStatusAccount.fetch(fillStatusPDA);
323
+ assert.isTrue("filled" in fillStatusAccount.status, "Fill status account should be marked as filled");
324
+ assertSE(fillStatusAccount.relayer, relayer.publicKey, "Relayer should match in the fill status");
325
+ // Set the current time to past the fill deadline
326
+ await setCurrentTime(program, state, relayer, new anchor_1.BN(fillStatusAccount.fillDeadline + 1));
327
+ const closeFillPdaAccounts = {
328
+ signer: relayer.publicKey,
329
+ state,
330
+ fillStatus: fillStatusPDA,
331
+ };
332
+ await program.methods.closeFillPda().accounts(closeFillPdaAccounts).signers([relayer]).rpc();
333
+ // Verify the fill PDA is closed
334
+ const fillStatusAccountAfter = await connection.getAccountInfo(fillStatusPDA);
335
+ assert.isNull(fillStatusAccountAfter, "Fill PDA should be closed after closing");
336
+ });
280
337
  });