Skip to content

Commit 8f6617e

Browse files
authored
improve(Relayer): Add total gas price to info log when relayer sends fill (#1949)
* improve(Relayer): Add total gas price to `info` log when relayer sends fill This will help debugging and give us broad oversight into how accurate our gas price estimates are. We probably should refactor the overall relay fee calculation flow but this PR uses the data we already have (native and token gas costs) to derive the gas price easily. This PR also adds to the ProfitClient's `updateGasCosts` function a simple change to print out the gas price estimate per chain. A more helpful fix might be to add the `gasPrice` field to the `TransactionCostEstimate` type in the SDK and force functions like `sdk.common.estimateTotalGasRequiredByUnsignedTransaction()` to return the priority fee and base fee broken down so the relayer can log it * Update ProfitClient.ts * revert relayer * Add gasPrice as required elem to profit client * add log by reading from profit client * lint * import from sdk * Update ProfitClient.ConsiderProfitability.ts * Update ProfitClient.ConsiderProfitability.ts * Update ProfitClient.ConsiderProfitability.ts * Update Relayer.ts
1 parent 5554920 commit 8f6617e

File tree

7 files changed

+87
-31
lines changed

7 files changed

+87
-31
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"dependencies": {
1313
"@across-protocol/constants": "^3.1.22",
1414
"@across-protocol/contracts": "^3.0.18",
15-
"@across-protocol/sdk": "^3.3.25",
15+
"@across-protocol/sdk": "^3.3.26",
1616
"@arbitrum/sdk": "^4.0.2",
1717
"@consensys/linea-sdk": "^0.2.1",
1818
"@defi-wonderland/smock": "^2.3.5",

src/clients/ProfitClient.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
TOKEN_SYMBOLS_MAP,
2929
TOKEN_EQUIVALENCE_REMAPPING,
3030
ZERO_ADDRESS,
31+
formatGwei,
3132
} from "../utils";
3233
import { Deposit, DepositWithBlock, L1Token, SpokePoolClientsByChain } from "../interfaces";
3334
import { getAcrossHost } from "./AcrossAPIClient";
@@ -58,6 +59,7 @@ export type FillProfit = {
5859
grossRelayerFeeUsd: BigNumber; // USD value of the relay fee paid by the user.
5960
nativeGasCost: BigNumber; // Cost of completing the fill in the units of gas.
6061
tokenGasCost: BigNumber; // Cost of completing the fill in the relevant gas token.
62+
gasPrice: BigNumber; // Gas price in wei.
6163
gasPadding: BigNumber; // Positive padding applied to nativeGasCost and tokenGasCost before profitability.
6264
gasMultiplier: BigNumber; // Gas multiplier applied to fill cost estimates before profitability.
6365
gasTokenPriceUsd: BigNumber; // Price paid per unit of gas the gas token in USD.
@@ -213,7 +215,7 @@ export class ProfitClient {
213215
deposit,
214216
notificationPath: "across-unprofitable-fills",
215217
});
216-
return { nativeGasCost: uint256Max, tokenGasCost: uint256Max };
218+
return { nativeGasCost: uint256Max, tokenGasCost: uint256Max, gasPrice: uint256Max };
217219
}
218220
}
219221

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

234+
getGasCostsForChain(chainId: number): TransactionCostEstimate {
235+
return this.totalGasCosts[chainId];
236+
}
237+
232238
// Estimate the gas cost of filling this relay.
233239
async estimateFillCost(
234240
deposit: Deposit
235-
): Promise<Pick<FillProfit, "nativeGasCost" | "tokenGasCost" | "gasTokenPriceUsd" | "gasCostUsd">> {
241+
): Promise<Pick<FillProfit, "nativeGasCost" | "tokenGasCost" | "gasTokenPriceUsd" | "gasCostUsd" | "gasPrice">> {
236242
const { destinationChainId: chainId } = deposit;
237243

238244
const gasToken = this.resolveGasToken(chainId);
239245
const gasTokenPriceUsd = this.getPriceOfToken(gasToken.symbol);
240-
let { nativeGasCost, tokenGasCost } = await this.getTotalGasCost(deposit);
246+
const totalGasCost = await this.getTotalGasCost(deposit);
247+
let { nativeGasCost, tokenGasCost } = totalGasCost;
248+
const gasPrice = totalGasCost.gasPrice;
241249

242250
Object.entries({
243251
"gas consumption": nativeGasCost, // raw gas units
@@ -265,6 +273,7 @@ export class ProfitClient {
265273
return {
266274
nativeGasCost,
267275
tokenGasCost,
276+
gasPrice,
268277
gasTokenPriceUsd,
269278
gasCostUsd,
270279
};
@@ -363,7 +372,9 @@ export class ProfitClient {
363372
: bnZero;
364373

365374
// Estimate the gas cost of filling this relay.
366-
const { nativeGasCost, tokenGasCost, gasTokenPriceUsd, gasCostUsd } = await this.estimateFillCost(deposit);
375+
const { nativeGasCost, tokenGasCost, gasTokenPriceUsd, gasCostUsd, gasPrice } = await this.estimateFillCost(
376+
deposit
377+
);
367378

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

435447
this.logger.debug({
436448
at: "ProfitClient#getFillProfitability",
437-
message: `${l1Token.symbol} v3 deposit ${depositId} with repayment on ${repaymentChainId} is ${profitable}`,
449+
message: `${l1Token.symbol} deposit ${depositId} with repayment on ${repaymentChainId} is ${profitable}`,
438450
deposit,
439451
inputTokenPriceUsd: formatEther(fill.inputTokenPriceUsd),
440452
inputTokenAmountUsd: formatEther(fill.inputAmountUsd),
@@ -445,6 +457,7 @@ export class ProfitClient {
445457
grossRelayerFeePct: `${formatFeePct(fill.grossRelayerFeePct)}%`,
446458
nativeGasCost: fill.nativeGasCost,
447459
tokenGasCost: formatEther(fill.tokenGasCost),
460+
gasPrice: formatGwei(fill.gasPrice.toString()),
448461
gasPadding: this.gasPadding,
449462
gasMultiplier: formatEther(this.resolveGasMultiplier(deposit)),
450463
gasTokenPriceUsd: formatEther(fill.gasTokenPriceUsd),
@@ -465,13 +478,14 @@ export class ProfitClient {
465478
lpFeePct: BigNumber,
466479
l1Token: L1Token,
467480
repaymentChainId: number
468-
): Promise<Pick<FillProfit, "profitable" | "nativeGasCost" | "tokenGasCost" | "netRelayerFeePct">> {
481+
): Promise<Pick<FillProfit, "profitable" | "nativeGasCost" | "gasPrice" | "tokenGasCost" | "netRelayerFeePct">> {
469482
let profitable = false;
470483
let netRelayerFeePct = bnZero;
471484
let nativeGasCost = uint256Max;
472485
let tokenGasCost = uint256Max;
486+
let gasPrice = uint256Max;
473487
try {
474-
({ profitable, netRelayerFeePct, nativeGasCost, tokenGasCost } = await this.getFillProfitability(
488+
({ profitable, netRelayerFeePct, nativeGasCost, tokenGasCost, gasPrice } = await this.getFillProfitability(
475489
deposit,
476490
lpFeePct,
477491
l1Token,
@@ -490,6 +504,7 @@ export class ProfitClient {
490504
profitable: profitable || (this.isTestnet && nativeGasCost.lt(uint256Max)),
491505
nativeGasCost,
492506
tokenGasCost,
507+
gasPrice,
493508
netRelayerFeePct,
494509
};
495510
}

src/relayer/Relayer.ts

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
TransactionResponse,
2222
ZERO_ADDRESS,
2323
Profiler,
24+
formatGwei,
2425
} from "../utils";
2526
import { RelayerClients } from "./RelayerClientHelper";
2627
import { RelayerConfig } from "./RelayerConfig";
@@ -36,6 +37,7 @@ type BatchLPFees = { [depositKey: string]: RepaymentFee[] };
3637
type RepaymentChainProfitability = {
3738
gasLimit: BigNumber;
3839
gasCost: BigNumber;
40+
gasPrice: BigNumber;
3941
relayerFeePct: BigNumber;
4042
lpFeePct: BigNumber;
4143
};
@@ -673,7 +675,13 @@ export class Relayer {
673675
l1Token,
674676
lpFees
675677
);
676-
const { relayerFeePct, gasCost, gasLimit: _gasLimit, lpFeePct: realizedLpFeePct } = repaymentChainProfitability;
678+
const {
679+
relayerFeePct,
680+
gasCost,
681+
gasLimit: _gasLimit,
682+
lpFeePct: realizedLpFeePct,
683+
gasPrice,
684+
} = repaymentChainProfitability;
677685
if (!isDefined(repaymentChainId)) {
678686
profitClient.captureUnprofitableFill(deposit, realizedLpFeePct, relayerFeePct, gasCost);
679687
} else {
@@ -702,7 +710,7 @@ export class Relayer {
702710
tokenClient.decrementLocalBalance(destinationChainId, outputToken, outputAmount);
703711

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

976-
fillRelay(deposit: Deposit, repaymentChainId: number, realizedLpFeePct: BigNumber, gasLimit?: BigNumber): void {
991+
fillRelay(
992+
deposit: Deposit,
993+
repaymentChainId: number,
994+
realizedLpFeePct: BigNumber,
995+
gasPrice: BigNumber,
996+
gasLimit?: BigNumber
997+
): void {
977998
const { spokePoolClients } = this.clients;
978999
this.logger.debug({
9791000
at: "Relayer::fillRelay",
@@ -1005,7 +1026,7 @@ export class Relayer {
10051026
];
10061027

10071028
const message = `Filled v3 deposit ${messageModifier}🚀`;
1008-
const mrkdwn = this.constructRelayFilledMrkdwn(deposit, repaymentChainId, realizedLpFeePct);
1029+
const mrkdwn = this.constructRelayFilledMrkdwn(deposit, repaymentChainId, realizedLpFeePct, gasPrice);
10091030
const contract = spokePoolClients[deposit.destinationChainId].spokePool;
10101031
const chainId = deposit.destinationChainId;
10111032
const multiCallerClient = this.getMulticaller(chainId);
@@ -1056,6 +1077,7 @@ export class Relayer {
10561077
repaymentChainProfitability: {
10571078
gasLimit: bnZero,
10581079
gasCost: bnUint256Max,
1080+
gasPrice: bnUint256Max,
10591081
relayerFeePct: bnZero,
10601082
lpFeePct: bnUint256Max,
10611083
},
@@ -1086,17 +1108,25 @@ export class Relayer {
10861108
const getRepaymentChainProfitability = async (
10871109
preferredChainId: number,
10881110
lpFeePct: BigNumber
1089-
): Promise<{ profitable: boolean; gasLimit: BigNumber; gasCost: BigNumber; relayerFeePct: BigNumber }> => {
1111+
): Promise<{
1112+
profitable: boolean;
1113+
gasLimit: BigNumber;
1114+
gasCost: BigNumber;
1115+
gasPrice: BigNumber;
1116+
relayerFeePct: BigNumber;
1117+
}> => {
10901118
const {
10911119
profitable,
10921120
nativeGasCost: gasLimit,
10931121
tokenGasCost: gasCost,
1122+
gasPrice,
10941123
netRelayerFeePct: relayerFeePct, // net relayer fee is equal to total fee minus the lp fee.
10951124
} = await profitClient.isFillProfitable(deposit, lpFeePct, hubPoolToken, preferredChainId);
10961125
return {
10971126
profitable,
10981127
gasLimit,
10991128
gasCost,
1129+
gasPrice,
11001130
relayerFeePct,
11011131
};
11021132
};
@@ -1116,10 +1146,11 @@ export class Relayer {
11161146
// @dev The following internal function should be the only one used to set `preferredChain` above.
11171147
const getProfitabilityDataForPreferredChainIndex = (preferredChainIndex: number): RepaymentChainProfitability => {
11181148
const lpFeePct = lpFeePcts[preferredChainIndex];
1119-
const { gasLimit, gasCost, relayerFeePct } = repaymentChainProfitabilities[preferredChainIndex];
1149+
const { gasLimit, gasCost, relayerFeePct, gasPrice } = repaymentChainProfitabilities[preferredChainIndex];
11201150
return {
11211151
gasLimit,
11221152
gasCost,
1153+
gasPrice,
11231154
relayerFeePct,
11241155
lpFeePct,
11251156
};
@@ -1344,9 +1375,14 @@ export class Relayer {
13441375
}
13451376
}
13461377

1347-
private constructRelayFilledMrkdwn(deposit: Deposit, repaymentChainId: number, realizedLpFeePct: BigNumber): string {
1378+
private constructRelayFilledMrkdwn(
1379+
deposit: Deposit,
1380+
repaymentChainId: number,
1381+
realizedLpFeePct: BigNumber,
1382+
gasPrice: BigNumber
1383+
): string {
13481384
let mrkdwn =
1349-
this.constructBaseFillMarkdown(deposit, realizedLpFeePct) +
1385+
this.constructBaseFillMarkdown(deposit, realizedLpFeePct, gasPrice) +
13501386
` Relayer repayment: ${getNetworkName(repaymentChainId)}.`;
13511387

13521388
if (isDepositSpedUp(deposit)) {
@@ -1361,7 +1397,7 @@ export class Relayer {
13611397
return mrkdwn;
13621398
}
13631399

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

13851423
return msg;
13861424
}

src/utils/SDKUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const {
4040
formatFeePct,
4141
shortenHexStrings,
4242
convertFromWei,
43+
formatGwei,
4344
max,
4445
min,
4546
utf8ToHex,

test/ProfitClient.ConsiderProfitability.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,13 @@ describe("ProfitClient: Consider relay profit", () => {
7878

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

8384
profitClient.setTokenPrice(gasToken.address, gasTokenPriceUsd);
84-
profitClient.setGasCost(chainId, { nativeGasCost, tokenGasCost });
85+
profitClient.setGasCost(chainId, { nativeGasCost, tokenGasCost, gasPrice });
8586

86-
return { nativeGasCost, tokenGasCost, gasTokenPriceUsd };
87+
return { nativeGasCost, tokenGasCost, gasPrice, gasTokenPriceUsd };
8788
};
8889

8990
const tokens = Object.fromEntries(
@@ -106,8 +107,9 @@ describe("ProfitClient: Consider relay profit", () => {
106107
chainIds.map((chainId) => {
107108
const nativeGasCost = toBN(100_000); // Assume 100k gas for a single fill
108109
const gasTokenPrice = toBN(chainId);
109-
const tokenGasCost = nativeGasCost.mul(gasTokenPrice);
110-
return [chainId, { nativeGasCost, tokenGasCost }];
110+
const gasPrice = gasTokenPrice;
111+
const tokenGasCost = nativeGasCost.mul(gasPrice);
112+
return [chainId, { nativeGasCost, tokenGasCost, gasPrice }];
111113
})
112114
);
113115

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

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

302303
for (const originChainId of chainIds) {
303304
for (const destinationChainId of chainIds.filter((chainId) => chainId !== originChainId)) {
304-
const { nativeGasCost: baseNativeGasCost } = gasCost[destinationChainId];
305+
const { nativeGasCost: baseNativeGasCost, gasPrice } = gasCost[destinationChainId];
305306

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

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

test/mocks/MockProfitClient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export class MockProfitClient extends ProfitClient {
5757
const defaultGasCost = {
5858
nativeGasCost: defaultFillCost,
5959
tokenGasCost: defaultGasPrice.mul(defaultFillCost),
60+
gasPrice: defaultGasPrice,
6061
};
6162
Object.values(spokePoolClients).map(({ chainId }) => {
6263
this.setGasCost(chainId, defaultGasCost); // gas/fill

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@
5353
yargs "^17.7.2"
5454
zksync-web3 "^0.14.3"
5555

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

0 commit comments

Comments
 (0)