Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve(Relayer): Add total gas price to info log when relayer sends fill #1949

Merged
merged 16 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"dependencies": {
"@across-protocol/constants": "^3.1.22",
"@across-protocol/contracts": "^3.0.18",
"@across-protocol/sdk": "^3.3.25",
"@across-protocol/sdk": "^3.3.26",
"@arbitrum/sdk": "^4.0.2",
"@consensys/linea-sdk": "^0.2.1",
"@defi-wonderland/smock": "^2.3.5",
Expand Down
29 changes: 22 additions & 7 deletions src/clients/ProfitClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
TOKEN_SYMBOLS_MAP,
TOKEN_EQUIVALENCE_REMAPPING,
ZERO_ADDRESS,
formatGwei,
} from "../utils";
import { Deposit, DepositWithBlock, L1Token, SpokePoolClientsByChain } from "../interfaces";
import { getAcrossHost } from "./AcrossAPIClient";
Expand Down Expand Up @@ -58,6 +59,7 @@ export type FillProfit = {
grossRelayerFeeUsd: BigNumber; // USD value of the relay fee paid by the user.
nativeGasCost: BigNumber; // Cost of completing the fill in the units of gas.
tokenGasCost: BigNumber; // Cost of completing the fill in the relevant gas token.
gasPrice: BigNumber; // Gas price in wei.
gasPadding: BigNumber; // Positive padding applied to nativeGasCost and tokenGasCost before profitability.
gasMultiplier: BigNumber; // Gas multiplier applied to fill cost estimates before profitability.
gasTokenPriceUsd: BigNumber; // Price paid per unit of gas the gas token in USD.
Expand Down Expand Up @@ -213,7 +215,7 @@ export class ProfitClient {
deposit,
notificationPath: "across-unprofitable-fills",
});
return { nativeGasCost: uint256Max, tokenGasCost: uint256Max };
return { nativeGasCost: uint256Max, tokenGasCost: uint256Max, gasPrice: uint256Max };
}
}

Expand All @@ -229,15 +231,21 @@ export class ProfitClient {
return this._getTotalGasCost(deposit, this.relayerAddress);
}

getGasCostsForChain(chainId: number): TransactionCostEstimate {
return this.totalGasCosts[chainId];
}

// Estimate the gas cost of filling this relay.
async estimateFillCost(
deposit: Deposit
): Promise<Pick<FillProfit, "nativeGasCost" | "tokenGasCost" | "gasTokenPriceUsd" | "gasCostUsd">> {
): Promise<Pick<FillProfit, "nativeGasCost" | "tokenGasCost" | "gasTokenPriceUsd" | "gasCostUsd" | "gasPrice">> {
const { destinationChainId: chainId } = deposit;

const gasToken = this.resolveGasToken(chainId);
const gasTokenPriceUsd = this.getPriceOfToken(gasToken.symbol);
let { nativeGasCost, tokenGasCost } = await this.getTotalGasCost(deposit);
const totalGasCost = await this.getTotalGasCost(deposit);
let { nativeGasCost, tokenGasCost } = totalGasCost;
const gasPrice = totalGasCost.gasPrice;

Object.entries({
"gas consumption": nativeGasCost, // raw gas units
Expand Down Expand Up @@ -265,6 +273,7 @@ export class ProfitClient {
return {
nativeGasCost,
tokenGasCost,
gasPrice,
gasTokenPriceUsd,
gasCostUsd,
};
Expand Down Expand Up @@ -363,7 +372,9 @@ export class ProfitClient {
: bnZero;

// Estimate the gas cost of filling this relay.
const { nativeGasCost, tokenGasCost, gasTokenPriceUsd, gasCostUsd } = await this.estimateFillCost(deposit);
const { nativeGasCost, tokenGasCost, gasTokenPriceUsd, gasCostUsd, gasPrice } = await this.estimateFillCost(
deposit
);

// Determine profitability. netRelayerFeePct effectively represents the capital cost to the relayer;
// i.e. how much it pays out to the recipient vs. the net fee that it receives for doing so.
Expand All @@ -386,6 +397,7 @@ export class ProfitClient {
grossRelayerFeeUsd,
nativeGasCost,
tokenGasCost,
gasPrice,
gasPadding: this.gasPadding,
gasMultiplier: this.resolveGasMultiplier(deposit),
gasTokenPriceUsd,
Expand Down Expand Up @@ -434,7 +446,7 @@ export class ProfitClient {

this.logger.debug({
at: "ProfitClient#getFillProfitability",
message: `${l1Token.symbol} v3 deposit ${depositId} with repayment on ${repaymentChainId} is ${profitable}`,
message: `${l1Token.symbol} deposit ${depositId} with repayment on ${repaymentChainId} is ${profitable}`,
deposit,
inputTokenPriceUsd: formatEther(fill.inputTokenPriceUsd),
inputTokenAmountUsd: formatEther(fill.inputAmountUsd),
Expand All @@ -445,6 +457,7 @@ export class ProfitClient {
grossRelayerFeePct: `${formatFeePct(fill.grossRelayerFeePct)}%`,
nativeGasCost: fill.nativeGasCost,
tokenGasCost: formatEther(fill.tokenGasCost),
gasPrice: formatGwei(fill.gasPrice.toString()),
gasPadding: this.gasPadding,
gasMultiplier: formatEther(this.resolveGasMultiplier(deposit)),
gasTokenPriceUsd: formatEther(fill.gasTokenPriceUsd),
Expand All @@ -465,13 +478,14 @@ export class ProfitClient {
lpFeePct: BigNumber,
l1Token: L1Token,
repaymentChainId: number
): Promise<Pick<FillProfit, "profitable" | "nativeGasCost" | "tokenGasCost" | "netRelayerFeePct">> {
): Promise<Pick<FillProfit, "profitable" | "nativeGasCost" | "gasPrice" | "tokenGasCost" | "netRelayerFeePct">> {
let profitable = false;
let netRelayerFeePct = bnZero;
let nativeGasCost = uint256Max;
let tokenGasCost = uint256Max;
let gasPrice = uint256Max;
try {
({ profitable, netRelayerFeePct, nativeGasCost, tokenGasCost } = await this.getFillProfitability(
({ profitable, netRelayerFeePct, nativeGasCost, tokenGasCost, gasPrice } = await this.getFillProfitability(
deposit,
lpFeePct,
l1Token,
Expand All @@ -490,6 +504,7 @@ export class ProfitClient {
profitable: profitable || (this.isTestnet && nativeGasCost.lt(uint256Max)),
nativeGasCost,
tokenGasCost,
gasPrice,
netRelayerFeePct,
};
}
Expand Down
60 changes: 49 additions & 11 deletions src/relayer/Relayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
TransactionResponse,
ZERO_ADDRESS,
Profiler,
formatGwei,
} from "../utils";
import { RelayerClients } from "./RelayerClientHelper";
import { RelayerConfig } from "./RelayerConfig";
Expand All @@ -36,6 +37,7 @@ type BatchLPFees = { [depositKey: string]: RepaymentFee[] };
type RepaymentChainProfitability = {
gasLimit: BigNumber;
gasCost: BigNumber;
gasPrice: BigNumber;
relayerFeePct: BigNumber;
lpFeePct: BigNumber;
};
Expand Down Expand Up @@ -673,7 +675,13 @@ export class Relayer {
l1Token,
lpFees
);
const { relayerFeePct, gasCost, gasLimit: _gasLimit, lpFeePct: realizedLpFeePct } = repaymentChainProfitability;
const {
relayerFeePct,
gasCost,
gasLimit: _gasLimit,
lpFeePct: realizedLpFeePct,
gasPrice,
} = repaymentChainProfitability;
if (!isDefined(repaymentChainId)) {
profitClient.captureUnprofitableFill(deposit, realizedLpFeePct, relayerFeePct, gasCost);
} else {
Expand Down Expand Up @@ -702,7 +710,7 @@ export class Relayer {
tokenClient.decrementLocalBalance(destinationChainId, outputToken, outputAmount);

const gasLimit = isMessageEmpty(resolveDepositMessage(deposit)) ? undefined : _gasLimit;
this.fillRelay(deposit, repaymentChainId, realizedLpFeePct, gasLimit);
this.fillRelay(deposit, repaymentChainId, realizedLpFeePct, gasPrice, gasLimit);
}
} else if (selfRelay) {
// Prefer exiting early here to avoid fast filling any deposits we send. This approach assumes that we always
Expand All @@ -720,7 +728,14 @@ export class Relayer {
// relayer is both the depositor and the recipient, because a deposit on a cheap SpokePool chain could cause
// expensive fills on (for example) mainnet.
const { lpFeePct } = lpFees.find((lpFee) => lpFee.paymentChainId === destinationChainId);
this.fillRelay(deposit, destinationChainId, lpFeePct);
// For self-relays, gas price is not a concern because we are bypassing profitability requirements so
// use profit client's gasprice.
this.fillRelay(
deposit,
destinationChainId,
lpFeePct,
this.clients.profitClient.getGasCostsForChain(destinationChainId).gasPrice
);
} else {
// TokenClient.getBalance returns that we don't have enough balance to submit the fast fill.
// At this point, capture the shortfall so that the inventory manager can rebalance the token inventory.
Expand Down Expand Up @@ -973,7 +988,13 @@ export class Relayer {
this.setFillStatus(deposit, FillStatus.RequestedSlowFill);
}

fillRelay(deposit: Deposit, repaymentChainId: number, realizedLpFeePct: BigNumber, gasLimit?: BigNumber): void {
fillRelay(
deposit: Deposit,
repaymentChainId: number,
realizedLpFeePct: BigNumber,
gasPrice: BigNumber,
gasLimit?: BigNumber
): void {
const { spokePoolClients } = this.clients;
this.logger.debug({
at: "Relayer::fillRelay",
Expand Down Expand Up @@ -1005,7 +1026,7 @@ export class Relayer {
];

const message = `Filled v3 deposit ${messageModifier}🚀`;
const mrkdwn = this.constructRelayFilledMrkdwn(deposit, repaymentChainId, realizedLpFeePct);
const mrkdwn = this.constructRelayFilledMrkdwn(deposit, repaymentChainId, realizedLpFeePct, gasPrice);
const contract = spokePoolClients[deposit.destinationChainId].spokePool;
const chainId = deposit.destinationChainId;
const multiCallerClient = this.getMulticaller(chainId);
Expand Down Expand Up @@ -1056,6 +1077,7 @@ export class Relayer {
repaymentChainProfitability: {
gasLimit: bnZero,
gasCost: bnUint256Max,
gasPrice: bnUint256Max,
relayerFeePct: bnZero,
lpFeePct: bnUint256Max,
},
Expand Down Expand Up @@ -1086,17 +1108,25 @@ export class Relayer {
const getRepaymentChainProfitability = async (
preferredChainId: number,
lpFeePct: BigNumber
): Promise<{ profitable: boolean; gasLimit: BigNumber; gasCost: BigNumber; relayerFeePct: BigNumber }> => {
): Promise<{
profitable: boolean;
gasLimit: BigNumber;
gasCost: BigNumber;
gasPrice: BigNumber;
relayerFeePct: BigNumber;
}> => {
const {
profitable,
nativeGasCost: gasLimit,
tokenGasCost: gasCost,
gasPrice,
netRelayerFeePct: relayerFeePct, // net relayer fee is equal to total fee minus the lp fee.
} = await profitClient.isFillProfitable(deposit, lpFeePct, hubPoolToken, preferredChainId);
return {
profitable,
gasLimit,
gasCost,
gasPrice,
relayerFeePct,
};
};
Expand All @@ -1116,10 +1146,11 @@ export class Relayer {
// @dev The following internal function should be the only one used to set `preferredChain` above.
const getProfitabilityDataForPreferredChainIndex = (preferredChainIndex: number): RepaymentChainProfitability => {
const lpFeePct = lpFeePcts[preferredChainIndex];
const { gasLimit, gasCost, relayerFeePct } = repaymentChainProfitabilities[preferredChainIndex];
const { gasLimit, gasCost, relayerFeePct, gasPrice } = repaymentChainProfitabilities[preferredChainIndex];
return {
gasLimit,
gasCost,
gasPrice,
relayerFeePct,
lpFeePct,
};
Expand Down Expand Up @@ -1344,9 +1375,14 @@ export class Relayer {
}
}

private constructRelayFilledMrkdwn(deposit: Deposit, repaymentChainId: number, realizedLpFeePct: BigNumber): string {
private constructRelayFilledMrkdwn(
deposit: Deposit,
repaymentChainId: number,
realizedLpFeePct: BigNumber,
gasPrice: BigNumber
): string {
let mrkdwn =
this.constructBaseFillMarkdown(deposit, realizedLpFeePct) +
this.constructBaseFillMarkdown(deposit, realizedLpFeePct, gasPrice) +
` Relayer repayment: ${getNetworkName(repaymentChainId)}.`;

if (isDepositSpedUp(deposit)) {
Expand All @@ -1361,7 +1397,7 @@ export class Relayer {
return mrkdwn;
}

private constructBaseFillMarkdown(deposit: Deposit, _realizedLpFeePct: BigNumber): string {
private constructBaseFillMarkdown(deposit: Deposit, _realizedLpFeePct: BigNumber, _gasPriceGwei: BigNumber): string {
const { symbol, decimals } = this.clients.hubPoolClient.getTokenInfoForDeposit(deposit);
const srcChain = getNetworkName(deposit.originChainId);
const dstChain = getNetworkName(deposit.destinationChainId);
Expand All @@ -1380,7 +1416,9 @@ export class Relayer {
const _outputAmount = createFormatFunction(2, 4, false, outputTokenDecimals)(deposit.outputAmount.toString());
msg +=
` and output ${_outputAmount} ${outputTokenSymbol}, with depositor ${depositor}.` +
` Realized LP fee: ${realizedLpFeePct}%, total fee: ${totalFeePct}%.`;
` Realized LP fee: ${realizedLpFeePct}%, total fee: ${totalFeePct}%. Gas price used in profit calc: ${formatGwei(
_gasPriceGwei.toString()
)} Gwei.`;

return msg;
}
Expand Down
1 change: 1 addition & 0 deletions src/utils/SDKUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const {
formatFeePct,
shortenHexStrings,
convertFromWei,
formatGwei,
max,
min,
utf8ToHex,
Expand Down
17 changes: 9 additions & 8 deletions test/ProfitClient.ConsiderProfitability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,13 @@ describe("ProfitClient: Consider relay profit", () => {

// Randomise the fillRelay cost in units of gas.
const nativeGasCost = toBN(random(80_000, 100_000));
const tokenGasCost = nativeGasCost.mul(toGWei(random(1, 100))).div(toBN(10).pow(9));
const gasPrice = toGWei(random(1, 100));
const tokenGasCost = nativeGasCost.mul(gasPrice).div(toBN(10).pow(9));

profitClient.setTokenPrice(gasToken.address, gasTokenPriceUsd);
profitClient.setGasCost(chainId, { nativeGasCost, tokenGasCost });
profitClient.setGasCost(chainId, { nativeGasCost, tokenGasCost, gasPrice });

return { nativeGasCost, tokenGasCost, gasTokenPriceUsd };
return { nativeGasCost, tokenGasCost, gasPrice, gasTokenPriceUsd };
};

const tokens = Object.fromEntries(
Expand All @@ -106,8 +107,9 @@ describe("ProfitClient: Consider relay profit", () => {
chainIds.map((chainId) => {
const nativeGasCost = toBN(100_000); // Assume 100k gas for a single fill
const gasTokenPrice = toBN(chainId);
const tokenGasCost = nativeGasCost.mul(gasTokenPrice);
return [chainId, { nativeGasCost, tokenGasCost }];
const gasPrice = gasTokenPrice;
const tokenGasCost = nativeGasCost.mul(gasPrice);
return [chainId, { nativeGasCost, tokenGasCost, gasPrice }];
})
);

Expand Down Expand Up @@ -296,12 +298,11 @@ describe("ProfitClient: Consider relay profit", () => {
});

it("Considers gas cost when computing profitability", async () => {
const gasPrice = toGWei(10);
const gasCostMultipliers = ["0.1", "0.5", "1", "2", "5", "10"].map((n) => toBNWei(n));

for (const originChainId of chainIds) {
for (const destinationChainId of chainIds.filter((chainId) => chainId !== originChainId)) {
const { nativeGasCost: baseNativeGasCost } = gasCost[destinationChainId];
const { nativeGasCost: baseNativeGasCost, gasPrice } = gasCost[destinationChainId];

for (const token of Object.values(tokens)) {
const inputToken = randomAddress();
Expand Down Expand Up @@ -339,7 +340,7 @@ describe("ProfitClient: Consider relay profit", () => {
const nativeGasCost = baseNativeGasCost.mul(gasCostMultiplier).div(fixedPoint);
const tokenGasCost = nativeGasCost.mul(gasPrice);
const gasCostUsd = tokenGasCost.mul(gasTokenPriceUsd).div(fixedPoint);
profitClient.setGasCost(destinationChainId, { nativeGasCost, tokenGasCost });
profitClient.setGasCost(destinationChainId, { nativeGasCost, tokenGasCost, gasPrice });

const gasCostPct = gasCostUsd.mul(fixedPoint).div(outputAmountUsd);

Expand Down
1 change: 1 addition & 0 deletions test/mocks/MockProfitClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class MockProfitClient extends ProfitClient {
const defaultGasCost = {
nativeGasCost: defaultFillCost,
tokenGasCost: defaultGasPrice.mul(defaultFillCost),
gasPrice: defaultGasPrice,
};
Object.values(spokePoolClients).map(({ chainId }) => {
this.setGasCost(chainId, defaultGasCost); // gas/fill
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@
yargs "^17.7.2"
zksync-web3 "^0.14.3"

"@across-protocol/sdk@^3.3.25":
version "3.3.25"
resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.3.25.tgz#6eec255fb7a1025050e0415b56f1bf8681936b1e"
integrity sha512-nBBrXY/kslvfsYnVd6kTNOuDSomlfRTw6v4uI40au/rEzPQ6G8X5d/F+DGN3iPfi3ltHY5BEiqE+E6s7AxHA8A==
"@across-protocol/sdk@^3.3.26":
version "3.3.26"
resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.3.26.tgz#b33aaf1545af9ff2969dd99db5ac39f3d785f689"
integrity sha512-RaEkwtme9k24NAQDKWTFaywrhQd7RqxRRitRwSXoiGm6Aw4iOXHoh/CjT3Z1wjcCBxHVeRozYUXESQlJZ+dOTw==
dependencies:
"@across-protocol/across-token" "^1.0.0"
"@across-protocol/constants" "^3.1.22"
Expand Down
Loading