Skip to content

Commit 51eea3d

Browse files
authored
pass fillDeadline to deposit. override value for test (#192)
* pass fillDeadline to deposit. override value for test Signed-off-by: Gerhard Steenkamp <[email protected]> * add changeset Signed-off-by: Gerhard Steenkamp <[email protected]> * fix tests for exclusive relayer fills Signed-off-by: Gerhard Steenkamp <[email protected]> * remove log Signed-off-by: Gerhard Steenkamp <[email protected]> * remove getMaxFillDeadline Signed-off-by: Gerhard Steenkamp <[email protected]> --------- Signed-off-by: Gerhard Steenkamp <[email protected]>
1 parent 161ddd7 commit 51eea3d

File tree

7 files changed

+140
-63
lines changed

7 files changed

+140
-63
lines changed

.changeset/itchy-pears-sin.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@across-protocol/app-sdk": minor
3+
---
4+
5+
Pull fillDeadline from suggested-fees response, pass to deposit()

packages/sdk/src/actions/getQuote.ts

+3
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export type Quote = {
7373
recipient: Address;
7474
message: Hex;
7575
quoteTimestamp: number;
76+
fillDeadline: number;
7677
exclusiveRelayer: Address;
7778
exclusivityDeadline: number;
7879
spokePoolAddress: Address;
@@ -221,6 +222,7 @@ export async function getQuote(params: GetQuoteParams): Promise<Quote> {
221222
totalRelayFee,
222223
// misc
223224
estimatedFillTimeSec,
225+
fillDeadline,
224226
} = fees;
225227

226228
return {
@@ -234,6 +236,7 @@ export async function getQuote(params: GetQuoteParams): Promise<Quote> {
234236
exclusivityDeadline,
235237
spokePoolAddress: spokePoolAddress as Address,
236238
destinationSpokePoolAddress: destinationSpokePoolAddress as Address,
239+
fillDeadline,
237240
...route,
238241
},
239242
limits,

packages/sdk/src/actions/simulateDepositTx.ts

+2-35
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ import { spokePoolAbiV3_5 } from "../abis/SpokePool/index.js";
1616
export type SimulateDepositTxParams = {
1717
walletClient: WalletClient;
1818
publicClient: PublicClient;
19-
deposit: Quote["deposit"] & {
20-
fillDeadline?: number;
21-
};
19+
deposit: Quote["deposit"];
2220
integratorId: Hex;
2321
logger?: LoggerT;
2422
};
@@ -37,7 +35,7 @@ export async function simulateDepositTx(params: SimulateDepositTxParams) {
3735
message,
3836
isNative,
3937
spokePoolAddress,
40-
fillDeadline: _fillDeadline,
38+
fillDeadline,
4139
exclusiveRelayer,
4240
exclusivityDeadline,
4341
quoteTimestamp,
@@ -57,37 +55,6 @@ export async function simulateDepositTx(params: SimulateDepositTxParams) {
5755
);
5856
}
5957

60-
// TODO: Add support for `SpokePoolVerifier` contract
61-
62-
let fillDeadline = _fillDeadline;
63-
64-
if (!fillDeadline) {
65-
const [fillDeadlineBufferResult, getCurrentTimeResult] =
66-
await publicClient.multicall({
67-
contracts: [
68-
{
69-
address: spokePoolAddress,
70-
abi: spokePoolAbiV3_5,
71-
functionName: "fillDeadlineBuffer",
72-
},
73-
{
74-
address: spokePoolAddress,
75-
abi: spokePoolAbiV3_5,
76-
functionName: "getCurrentTime",
77-
},
78-
],
79-
});
80-
if (fillDeadlineBufferResult.error || getCurrentTimeResult.error) {
81-
const error =
82-
fillDeadlineBufferResult.error || getCurrentTimeResult.error;
83-
throw new Error(
84-
`Failed to fetch 'fillDeadlineBuffer' or 'getCurrentTime': ${error}`,
85-
);
86-
}
87-
fillDeadline =
88-
Number(getCurrentTimeResult.result) + fillDeadlineBufferResult.result;
89-
}
90-
9158
const useExclusiveRelayer =
9259
exclusiveRelayer !== zeroAddress && exclusivityDeadline > 0;
9360

packages/sdk/test/common/constants.ts

+13-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { getAddress } from "viem";
1+
import { Address, getAddress } from "viem";
2+
import { arbitrum, mainnet } from "viem/chains";
23

34
export const pool = Number(process.env.VITEST_POOL_ID ?? 1);
45

@@ -23,12 +24,15 @@ export const PRIVATE_KEY =
2324
// Named accounts
2425
export const [ALICE, BOB, RELAYER] = ACCOUNTS;
2526

26-
export const USDC_MAINNET = getAddress(
27-
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
28-
);
29-
export const USDC_WHALE = getAddress(
30-
"0x37305B1cD40574E4C5Ce33f8e8306Be057fD7341",
31-
);
27+
export const USDC_ADDRESS: Record<number | string, Address> = {
28+
[mainnet.id]: getAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
29+
[arbitrum.id]: getAddress("0xaf88d065e77c8cc2239327c5edb3a432268e5831"),
30+
};
31+
32+
export const USDC_WHALES: Record<number | string, Address> = {
33+
[mainnet.id]: getAddress("0x37305B1cD40574E4C5Ce33f8e8306Be057fD7341"),
34+
[arbitrum.id]: getAddress("0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7"),
35+
};
3236

3337
function getEnv(key: string): string {
3438
const value = process.env[key];
@@ -52,8 +56,8 @@ export const FORK_URL_SEPOLIA = `https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_
5256
// FORK BLOCK NUMBERS
5357
export const BLOCK_NUMBER_OPTIMISM = BigInt(131833751);
5458
export const BLOCK_NUMBER_BASE = BigInt(26238472);
55-
export const BLOCK_NUMBER_MAINNET = BigInt(21822400);
56-
export const BLOCK_NUMBER_ARBITRUM = BigInt(304904223);
59+
export const BLOCK_NUMBER_MAINNET = BigInt(21915632);
60+
export const BLOCK_NUMBER_ARBITRUM = BigInt(309377890);
5761

5862
export const TENDERLY_KEY = getMaybeEnv("VITE_TENDERLY_KEY");
5963
export const MOCK_API = getMaybeEnv("VITE_MOCK_API") === "true";

packages/sdk/test/common/relayer.ts

+36-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { maxUint256, type Address, type TransactionReceipt } from "viem";
1+
import {
2+
maxUint256,
3+
parseEther,
4+
type Address,
5+
type TransactionReceipt,
6+
} from "viem";
27
import {
38
addressToBytes32,
49
getDepositFromLogs,
@@ -8,7 +13,12 @@ import {
813
} from "../../src/index.js";
914
import type { ChainClient } from "./anvil.js";
1015
import { spokePoolAbiV3_5 } from "../../src/abis/SpokePool/v3_5.js";
11-
import { isAddressDefined, toV3RelayData } from "./utils.js";
16+
import {
17+
fundUsdc,
18+
getUsdcBalance,
19+
isAddressDefined,
20+
toV3RelayData,
21+
} from "./utils.js";
1222

1323
type RelayerParams = {
1424
depositReceipt: TransactionReceipt;
@@ -35,12 +45,6 @@ export async function waitForDepositAndFillV3_5({
3545
spokePoolAddress ??
3646
(await acrossClient.getSpokePoolAddress(destinationPublicClient.chain.id));
3747

38-
if (isAddressDefined(exclusiveRelayer)) {
39-
await chainClient.impersonateAccount({
40-
address: exclusiveRelayer,
41-
});
42-
}
43-
4448
const deposit = getDepositFromLogs({
4549
originChainId: originPublicClient.chain.id,
4650
receipt: depositReceipt,
@@ -54,6 +58,18 @@ export async function waitForDepositAndFillV3_5({
5458
? exclusiveRelayer
5559
: chainClient.account.address;
5660

61+
await chainClient.setBalance({
62+
address: relayerAddress,
63+
value: parseEther("1"),
64+
});
65+
66+
// fund relayer with USDC on destination chain
67+
await fundUsdc(
68+
chainClient,
69+
relayerAddress,
70+
destinationPublicClient?.chain?.id,
71+
);
72+
5773
const v3RelayData = toV3RelayData(deposit);
5874

5975
const args = [
@@ -64,6 +80,18 @@ export async function waitForDepositAndFillV3_5({
6480

6581
console.log("Simulating fill approval...");
6682

83+
await chainClient.impersonateAccount({
84+
address: relayerAddress,
85+
});
86+
87+
const [ethBalance, usdcBalance] = await Promise.all([
88+
destinationPublicClient.getBalance({ address: relayerAddress }),
89+
getUsdcBalance(relayerAddress, destinationPublicClient),
90+
]);
91+
92+
console.log("ethBalance", ethBalance);
93+
console.log("usdcBalance", usdcBalance);
94+
6795
const { request: approveRequest } = await simulateApproveTx({
6896
walletClient: chainClient,
6997
publicClient: destinationPublicClient,

packages/sdk/test/common/utils.ts

+67-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import { Hex, zeroAddress, type Address, type PublicClient } from "viem";
2-
import { USDC_MAINNET, USDC_WHALE } from "./constants.js";
1+
import {
2+
Hex,
3+
parseEther,
4+
zeroAddress,
5+
type Address,
6+
type PublicClient,
7+
} from "viem";
8+
import { USDC_ADDRESS, USDC_WHALES } from "./constants.js";
39
import { type ChainClient } from "./anvil.js";
410
import { addressToBytes32, type Deposit } from "../../src/index.js";
11+
import { spokePoolAbiV3_5 } from "../../src/abis/SpokePool/v3_5.js";
512

613
export function isAddressDefined(address?: Address): address is Address {
714
return address && address !== "0x" && address !== zeroAddress ? true : false;
@@ -44,10 +51,19 @@ export const transferAmount = 1_000_000_000n;
4451

4552
export async function getUsdcBalance(
4653
walletAddress: Address,
47-
publicClient: PublicClient,
54+
publicClient: PublicClient | ChainClient,
4855
) {
56+
const chainId = publicClient.chain?.id;
57+
const tokenAddress = chainId ? USDC_ADDRESS?.[chainId] : undefined;
58+
59+
if (!tokenAddress) {
60+
throw new Error(
61+
`Unable to fetch USDC address on chain: ${publicClient?.chain?.name}`,
62+
);
63+
}
64+
4965
const data = await publicClient.readContract({
50-
address: USDC_MAINNET,
66+
address: tokenAddress,
5167
abi: balanceOfAbi,
5268
functionName: "balanceOf",
5369
args: [walletAddress],
@@ -59,24 +75,49 @@ export async function getUsdcBalance(
5975
export async function fundUsdc(
6076
testClient: ChainClient,
6177
walletAddress: Address,
78+
chainId: number,
6279
amount = transferAmount,
6380
) {
81+
const sender = USDC_WHALES?.[chainId];
82+
const tokenAddress = USDC_ADDRESS?.[chainId];
83+
84+
if (!sender || !tokenAddress) {
85+
throw new Error(`USDC config missing for chainId ${chainId}`);
86+
}
87+
await testClient.setBalance({
88+
address: sender,
89+
value: parseEther("1"),
90+
});
91+
6492
await testClient.impersonateAccount({
65-
address: USDC_WHALE,
93+
address: sender,
6694
});
6795

96+
const amountWithPadding = amount * 2n;
97+
98+
const sponsorBalance = await getUsdcBalance(sender, testClient);
99+
100+
if (sponsorBalance <= amountWithPadding) {
101+
throw new Error(
102+
`Sponsor with address ${testClient.account.address} does not have enough balance. Sponsor balance: ${sponsorBalance}`,
103+
);
104+
}
105+
68106
const { request } = await testClient.simulateContract({
69-
address: USDC_MAINNET,
107+
address: tokenAddress,
70108
abi: transferAbi,
71109
functionName: "transfer",
72-
args: [walletAddress, amount],
73-
account: USDC_WHALE,
110+
args: [walletAddress, amountWithPadding],
111+
account: sender,
74112
});
75113

76114
const hash = await testClient.writeContract(request);
77115
const receipt = await testClient.waitForTransactionReceipt({ hash });
78116

79117
console.log("USDC funded!", receipt);
118+
await testClient.stopImpersonatingAccount({
119+
address: sender,
120+
});
80121

81122
return receipt;
82123
}
@@ -174,3 +215,21 @@ export function checkFields(
174215

175216
return true;
176217
}
218+
219+
export async function getFillDeadline({
220+
publicClient,
221+
spokePoolAddress,
222+
buffer = 3600, // 1 hour
223+
}: {
224+
publicClient: PublicClient;
225+
spokePoolAddress: Address;
226+
buffer?: number;
227+
}): Promise<number> {
228+
const currentTime = await publicClient.readContract({
229+
address: spokePoolAddress,
230+
abi: spokePoolAbiV3_5,
231+
functionName: "getCurrentTime",
232+
});
233+
234+
return Number(currentTime) + buffer;
235+
}

packages/sdk/test/e2e/executeQuote.test.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
BLOCK_NUMBER_ARBITRUM,
2727
BLOCK_NUMBER_MAINNET,
2828
} from "../common/constants.js";
29-
import { fundUsdc } from "../common/utils.js";
29+
import { fundUsdc, getFillDeadline } from "../common/utils.js";
3030
import { waitForDepositAndFillV3_5 } from "../common/relayer.js";
3131

3232
const inputToken = {
@@ -111,15 +111,26 @@ describe("executeQuote", async () => {
111111
}),
112112
]);
113113
// fund test wallet clients with 1000 USDC
114-
await fundUsdc(chainClientMainnet, testWalletMainnet.account.address);
114+
await fundUsdc(
115+
chainClientMainnet,
116+
testWalletMainnet.account.address,
117+
route.originChainId,
118+
);
115119

116120
const latestBlock = await chainClientMainnet.getBlock({
117121
blockTag: "latest",
118122
});
119123

124+
// override the API's fill deadline to compensate for fork
125+
const maxFillDeadline = await getFillDeadline({
126+
publicClient: publicClientArbitrum,
127+
spokePoolAddress: quote.deposit.destinationSpokePoolAddress,
128+
});
129+
120130
// override quote timestamp
121131
const deposit = {
122132
...quote.deposit,
133+
fillDeadline: maxFillDeadline,
123134
quoteTimestamp: Number(latestBlock.timestamp),
124135
};
125136

@@ -163,7 +174,7 @@ describe("executeQuote", async () => {
163174
destinationPublicClient: publicClientArbitrum,
164175
chainClient: chainClientArbitrum,
165176
exclusiveRelayer: deposit.exclusiveRelayer,
166-
});
177+
}).catch((e) => rej(e));
167178
}
168179
}
169180

0 commit comments

Comments
 (0)