Skip to content

Commit 7467c40

Browse files
authored
Merge branch 'master' into pxrl/listenerCrash
2 parents 681da40 + 42bd5aa commit 7467c40

File tree

4 files changed

+94
-5
lines changed

4 files changed

+94
-5
lines changed

src/relayer/Relayer.ts

+26-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { utils as sdkUtils } from "@across-protocol/sdk";
33
import { utils as ethersUtils } from "ethers";
44
import { FillStatus, L1Token, V3Deposit, V3DepositWithBlock } from "../interfaces";
55
import {
6+
averageBlockTime,
67
BigNumber,
78
bnZero,
89
bnUint256Max,
@@ -481,15 +482,15 @@ export class Relayer {
481482
sendSlowRelays: boolean
482483
): Promise<void> {
483484
const { depositId, depositor, recipient, destinationChainId, originChainId, inputToken } = deposit;
484-
const { hubPoolClient, profitClient, tokenClient } = this.clients;
485+
const { hubPoolClient, profitClient, spokePoolClients, tokenClient } = this.clients;
485486
const { slowDepositors } = this.config;
486487

487488
// If the deposit does not meet the minimum number of block confirmations, skip it.
489+
const originChain = getNetworkName(originChainId);
488490
if (deposit.blockNumber > maxBlockNumber) {
489-
const chain = getNetworkName(originChainId);
490491
this.logger.debug({
491492
at: "Relayer::evaluateFill",
492-
message: `Skipping ${chain} deposit ${depositId} due to insufficient deposit confirmations.`,
493+
message: `Skipping ${originChain} deposit ${depositId} due to insufficient deposit confirmations.`,
493494
depositId,
494495
blockNumber: deposit.blockNumber,
495496
maxBlockNumber,
@@ -513,6 +514,28 @@ export class Relayer {
513514
return;
514515
}
515516

517+
// If the operator configured a minimum fill time for a destination chain, ensure that the deposit
518+
// is at least that old before filling it. This is mainly useful on chains with long block times,
519+
// where there is a high chance of fill collisions in the first blocks after a deposit is made.
520+
const minFillTime = this.config.minFillTime?.[destinationChainId] ?? 0;
521+
if (minFillTime > 0 && deposit.exclusiveRelayer !== this.relayerAddress) {
522+
const originSpoke = spokePoolClients[originChainId];
523+
const { average: avgBlockTime } = await averageBlockTime(originSpoke.spokePool.provider);
524+
const depositAge = Math.floor(avgBlockTime * (originSpoke.latestBlockSearched - deposit.blockNumber));
525+
526+
if (minFillTime > depositAge) {
527+
const dstChain = getNetworkName(destinationChainId);
528+
this.logger.debug({
529+
at: "Relayer::evaluateFill",
530+
message: `Skipping ${originChain} deposit due to insufficient fill time for ${dstChain}.`,
531+
depositAge,
532+
minFillTime,
533+
transactionHash: deposit.transactionHash,
534+
});
535+
return;
536+
}
537+
}
538+
516539
const l1Token = hubPoolClient.getL1TokenInfoForL2Token(inputToken, originChainId);
517540
const selfRelay = [depositor, recipient].every((address) => address === this.relayerAddress);
518541
if (tokenClient.hasBalanceForFill(deposit) && !selfRelay) {
@@ -531,7 +554,6 @@ export class Relayer {
531554

532555
// Ensure that a limit was identified, and that no upper thresholds would be breached by filling this deposit.
533556
if (this.originChainOvercommitted(originChainId, fillAmountUsd, limitIdx)) {
534-
const originChain = getNetworkName(originChainId);
535557
const limits = this.fillLimits[originChainId].slice(limitIdx);
536558
this.logger.debug({
537559
at: "Relayer::evaluateFill",

src/relayer/RelayerConfig.ts

+5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export class RelayerConfig extends CommonConfig {
4646
readonly relayerGasMultiplier: BigNumber;
4747
readonly relayerMessageGasMultiplier: BigNumber;
4848
readonly minRelayerFeePct: BigNumber;
49+
readonly minFillTime: { [chainId: number]: number } = {};
4950
readonly acceptInvalidFills: boolean;
5051
// List of depositors we only want to send slow fills for.
5152
readonly slowDepositors: string[];
@@ -333,6 +334,10 @@ export class RelayerConfig extends CommonConfig {
333334
});
334335
}
335336

337+
chainIds.forEach(
338+
(chainId) => (this.minFillTime[chainId] = Number(process.env[`RELAYER_MIN_FILL_TIME_${chainId}`] ?? 0))
339+
);
340+
336341
// Only validate config for chains that the relayer cares about.
337342
super.validate(relayerChainIds, logger);
338343
}

src/utils/SDKUtils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export class PriceClient extends sdk.priceClient.PriceClient {}
77
export const { acrossApi, coingecko, defiLlama } = sdk.priceClient.adapters;
88

99
export const {
10+
averageBlockTime,
1011
bnZero,
1112
bnOne,
1213
bnUint32Max,

test/Relayer.BasicFill.ts

+62-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { clients, constants, utils as sdkUtils } from "@across-protocol/sdk";
22
import { AcrossApiClient, ConfigStoreClient, MultiCallerClient, TokenClient } from "../src/clients";
33
import { FillStatus, V3Deposit, V3RelayData } from "../src/interfaces";
44
import { CONFIG_STORE_VERSION } from "../src/common";
5-
import { bnZero, bnOne, bnUint256Max, getNetworkName, getAllUnfilledDeposits } from "../src/utils";
5+
import { averageBlockTime, bnZero, bnOne, bnUint256Max, getNetworkName, getAllUnfilledDeposits } from "../src/utils";
66
import { Relayer } from "../src/relayer/Relayer";
77
import { RelayerConfig } from "../src/relayer/RelayerConfig"; // Tested
88
import {
@@ -499,6 +499,67 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () {
499499
expect(lastSpyLogIncludes(spy, "0 unfilled deposits found.")).to.be.true;
500500
});
501501

502+
it("Correctly defers destination chain fills", async function () {
503+
let { average: avgBlockTime } = await averageBlockTime(spokePool_2.provider);
504+
avgBlockTime = Math.ceil(avgBlockTime);
505+
const minFillTime = 4 * avgBlockTime; // Fill after deposit has aged 4 blocks.
506+
507+
relayerInstance = new Relayer(
508+
relayer.address,
509+
spyLogger,
510+
{
511+
spokePoolClients,
512+
hubPoolClient,
513+
configStoreClient,
514+
tokenClient,
515+
profitClient,
516+
multiCallerClient,
517+
inventoryClient: new MockInventoryClient(null, null, null, null, null, hubPoolClient),
518+
acrossApiClient: new AcrossApiClient(spyLogger, hubPoolClient, chainIds),
519+
},
520+
{
521+
minFillTime: { [destinationChainId]: minFillTime },
522+
relayerTokens: [],
523+
minDepositConfirmations: defaultMinDepositConfirmations,
524+
sendingRelaysEnabled: true,
525+
} as unknown as RelayerConfig
526+
);
527+
528+
const deposit = await depositV3(
529+
spokePool_1,
530+
destinationChainId,
531+
depositor,
532+
inputToken,
533+
inputAmount,
534+
outputToken,
535+
outputAmount
536+
);
537+
await updateAllClients();
538+
let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill();
539+
for (const receipts of Object.values(txnReceipts)) {
540+
expect((await receipts).length).to.equal(0);
541+
}
542+
expect(lastSpyLogIncludes(spy, "due to insufficient fill time for")).to.be.true;
543+
544+
// SpokePool time is overridden and does not increment; it must be cranked manually.
545+
const startTime = Number(await spokePool_2.getCurrentTime());
546+
let nextTime: number;
547+
do {
548+
await fillV3Relay(
549+
spokePool_2,
550+
{ ...deposit, depositId: deposit.depositId + 1, outputAmount: bnZero, recipient: randomAddress() },
551+
relayer
552+
);
553+
nextTime = Number(await spokePool_2.getCurrentTime()) + avgBlockTime;
554+
await spokePool_2.setCurrentTime(nextTime);
555+
} while (startTime + minFillTime > nextTime);
556+
557+
await updateAllClients();
558+
txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill();
559+
const receipts = await txnReceipts[destinationChainId];
560+
expect(receipts.length).to.equal(1);
561+
});
562+
502563
it("Correctly tracks origin chain fill commitments", async function () {
503564
await erc20_2.connect(relayer).approve(spokePool_2.address, MAX_SAFE_ALLOWANCE);
504565

0 commit comments

Comments
 (0)