Skip to content

Commit

Permalink
feat(Relayer): Consider all eligible repayment chains if top one is u…
Browse files Browse the repository at this point in the history
…nprofitable (#1426)

* feat(Relayer): Consider all eligible repayment chains if top one is unprofitable

Currently the algorithm is to consider destination chain only if top preferred chain is unprofitable and not equal to destination chain

This PR changes the algo to have `determineRefundChain` to return all eligible repayment chain so that the relayer considers them all before falling back to destination chain, assuming that destination chain wasn't already considered

* lint

* Update Relayer.ts

* Update Relayer.ts

* Re add back tests

* fix tests

* Use net, not gross relayer fee pct in logs

* refactor and run profitablity loop i parallel

* Update Relayer.ts

* Update InventoryClient.ts

* Update InventoryClient.RefundChain.ts

* Update InventoryClient.RefundChain.ts

* Update InventoryClient.RefundChain.ts

* Update InventoryClient.RefundChain.ts

* Update InventoryClient.RefundChain.ts

* Update InventoryClient.RefundChain.ts
  • Loading branch information
nicholaspai authored May 20, 2024
1 parent 2af75df commit 066733b
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 139 deletions.
49 changes: 27 additions & 22 deletions src/clients/InventoryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,26 +383,29 @@ export class InventoryClient {
return isInputTokenUSDC && isOutputTokenBridgedUSDC;
}

// Work out where a relay should be refunded to optimally manage the bots inventory. If the inventory management logic
// not enabled then return funds on the chain the deposit was filled on Else, use the following algorithm for each
// of the origin and destination chain:
// a) Find the chain virtual balance (current balance + pending relays + pending refunds) minus current shortfall.
// b) Find the cumulative virtual balance, including the total refunds on all chains and excluding current shortfall.
// c) Consider the size of a and b post relay (i.e after the relay is paid and all current transfers are settled what
// will the balances be on the target chain and the overall cumulative balance).
// d) Use c to compute what the post relay post current in-flight transactions allocation would be. Compare this
// number to the target threshold and:
// If this number is less than the target for the destination chain + rebalance then select destination chain. We
// slightly prefer destination to origin chain to support relayer capital efficiency.
// Else, if this number is less than the target for the origin chain + rebalance then select origin
// chain.
// Else, take repayment on the Hub chain for ease of transferring out of L1 to any L2.
async determineRefundChainId(deposit: V3Deposit, l1Token?: string): Promise<number> {
/*
* Return all eligible repayment chains for a deposit. If inventory management is enabled, then this function will
* only choose chains where the post-relay balance allocation for a potential repayment chain is under the maximum
* allowed allocation on that chain. Origin, Destination, and HubChains are always evaluated as potential
* repayment chains in addition to "Slow Withdrawal chains" such as Base, Optimism and Arbitrum for which
* taking repayment would reduce HubPool utilization. Post-relay allocation percentages take into
* account pending cross-chain inventory-management transfers, upcoming bundle refunds, token shortfalls
* needed to cover other unfilled deposits in addition to current token balances. Slow withdrawal chains are only
* selected if the SpokePool's running balance for that chain is over the system's desired target.
* @dev The HubChain is always evaluated as a fallback option if the inventory management is enabled and all other
* chains are over-allocated.
* @dev If inventory management is disabled, then destinationChain is used as a default.
* @param deposit Deposit to determine repayment chains for.
* @param l1Token L1Token linked with deposited inputToken and repayement chain refund token.
* @returns list of chain IDs that are possible repayment chains for the deposit, sorted from highest
* to lowest priority.
*/
async determineRefundChainId(deposit: V3Deposit, l1Token?: string): Promise<number[]> {
const { originChainId, destinationChainId, inputToken, outputToken, outputAmount, inputAmount } = deposit;
const hubChainId = this.hubPoolClient.chainId;

if (!this.isInventoryManagementEnabled()) {
return destinationChainId;
return [destinationChainId];
}

// The InventoryClient assumes 1:1 equivalency between input and output tokens. At the moment there is no support
Expand Down Expand Up @@ -469,6 +472,7 @@ export class InventoryClient {
chainsToEvaluate.push(originChainId);
}

const eligibleRefundChains: number[] = [];
// At this point, all chains to evaluate have defined token configs and are sorted in order of
// highest priority to take repayment on, assuming the chain is under-allocated.
for (const _chain of chainsToEvaluate) {
Expand Down Expand Up @@ -535,15 +539,16 @@ export class InventoryClient {
}
);
if (expectedPostRelayAllocation.lte(thresholdPct)) {
return _chain;
eligibleRefundChains.push(_chain);
}
}

// None of the chain allocation percentages are lower than their target so take
// repayment on the hub chain by default. The caller has also set a token config so they are not expecting
// repayments to default to destination chain. If caller wanted repayments to default to destination
// chain, then they should not set a token config.
return hubChainId;
// Always add hubChain as a fallback option if inventory management is enabled. If none of the chainsToEvaluate
// were selected, then this function will return just the hub chain as a fallback option.
if (!eligibleRefundChains.includes(hubChainId)) {
eligibleRefundChains.push(hubChainId);
}
return eligibleRefundChains;
}

/**
Expand Down
25 changes: 16 additions & 9 deletions src/clients/ProfitClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,17 +431,22 @@ export class ProfitClient {
return fillAmount.mul(tokenPriceInUsd).div(bn10.pow(l1TokenInfo.decimals));
}

async getFillProfitability(deposit: V3Deposit, lpFeePct: BigNumber, l1Token: L1Token): Promise<FillProfit> {
async getFillProfitability(
deposit: V3Deposit,
lpFeePct: BigNumber,
l1Token: L1Token,
repaymentChainId: number
): Promise<FillProfit> {
const minRelayerFeePct = this.minRelayerFeePct(l1Token.symbol, deposit.originChainId, deposit.destinationChainId);

const fill = await this.calculateFillProfitability(deposit, lpFeePct, minRelayerFeePct);
if (!fill.profitable || this.debugProfitability) {
const { depositId, originChainId } = deposit;
const { depositId } = deposit;
const profitable = fill.profitable ? "profitable" : "unprofitable";

this.logger.debug({
at: "ProfitClient#getFillProfitability",
message: `${l1Token.symbol} v3 deposit ${depositId} on chain ${originChainId} is ${profitable}`,
message: `${l1Token.symbol} v3 deposit ${depositId} with repayment on ${repaymentChainId} is ${profitable}`,
deposit,
inputTokenPriceUsd: formatEther(fill.inputTokenPriceUsd),
inputTokenAmountUsd: formatEther(fill.inputAmountUsd),
Expand Down Expand Up @@ -470,17 +475,19 @@ export class ProfitClient {
async isFillProfitable(
deposit: V3Deposit,
lpFeePct: BigNumber,
l1Token: L1Token
): Promise<Pick<FillProfit, "profitable" | "nativeGasCost" | "tokenGasCost" | "grossRelayerFeePct">> {
l1Token: L1Token,
repaymentChainId: number
): Promise<Pick<FillProfit, "profitable" | "nativeGasCost" | "tokenGasCost" | "netRelayerFeePct">> {
let profitable = false;
let grossRelayerFeePct = bnZero;
let netRelayerFeePct = bnZero;
let nativeGasCost = uint256Max;
let tokenGasCost = uint256Max;
try {
({ profitable, grossRelayerFeePct, nativeGasCost, tokenGasCost } = await this.getFillProfitability(
({ profitable, netRelayerFeePct, nativeGasCost, tokenGasCost } = await this.getFillProfitability(
deposit,
lpFeePct,
l1Token
l1Token,
repaymentChainId
));
} catch (err) {
this.logger.debug({
Expand All @@ -495,7 +502,7 @@ export class ProfitClient {
profitable: profitable || (this.isTestnet && nativeGasCost.lt(uint256Max)),
nativeGasCost,
tokenGasCost,
grossRelayerFeePct,
netRelayerFeePct,
};
}

Expand Down
Loading

0 comments on commit 066733b

Please sign in to comment.