Skip to content

Commit

Permalink
feat(relayFeeCalculator): Support estimating v3 fills (#568)
Browse files Browse the repository at this point in the history
This is a simple change to permit estimating v3 fills. This is used by
the relayer, but also the FE. It'll be necessary for simulating messages
with fills to recipients who only support handleV3AcrossMessage(), and 
the relayer also needs it prior to v3 going live.
  • Loading branch information
pxrl committed Feb 21, 2024
1 parent 79c0e1a commit 687652e
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 29 deletions.
55 changes: 47 additions & 8 deletions src/relayFeeCalculator/chain-queries/baseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import {
BigNumberish,
createUnsignedFillRelayTransactionFromDeposit,
estimateTotalGasRequiredByUnsignedTransaction,
isV2Deposit,
populateV3Relay,
toBN,
TransactionCostEstimate,
} from "../../utils";
import { Logger, QueryInterface } from "../relayFeeCalculator";
import { Deposit } from "../../interfaces";
import { Deposit, V2Deposit, V3Deposit } from "../../interfaces";

type Provider = providers.Provider;
type OptimismProvider = L2Provider<Provider>;
Expand Down Expand Up @@ -57,24 +59,61 @@ export default abstract class QueryBase implements QueryInterface {
}

/**
* Retrieves the current gas costs of performing a fillRelay contract at the referenced Spoke Pool
* @returns The gas estimate for this function call (multplied with the optional buffer)
* Retrieves the current gas costs of performing a fillRelay contract at the referenced SpokePool.
* @param deposit Deposit instance (v2 or v3).
* @param amountToRelay Amount of the deposit to fill.
* @param relayerAddress Relayer address to simulate with.
* @returns The gas estimate for this function call (multplied with the optional buffer).
*/
async getGasCosts(
getGasCosts(
deposit: Deposit,
fillAmount: BigNumberish,
relayer = DEFAULT_SIMULATED_RELAYER_ADDRESS
): Promise<TransactionCostEstimate> {
relayer ??= this.simulatedRelayerAddress;
return isV2Deposit(deposit)
? this.getV2GasCosts(deposit, fillAmount, relayer)
: this.getV3GasCosts(deposit, relayer);
}

/**
* Retrieves the current gas costs of performing a fillRelay contract at the referenced SpokePool
* @param deposit V2Deposit instance.
* @param amountToRelay Amount of the deposit to fill.
* @param relayer Relayer address to simulate with.
* @returns The gas estimate for this function call (multplied with the optional buffer).
*/
async getV2GasCosts(
deposit: V2Deposit,
amountToRelay: BigNumberish,
relayAddress = DEFAULT_SIMULATED_RELAYER_ADDRESS
relayer: string
): Promise<TransactionCostEstimate> {
const relayerToSimulate = relayAddress ?? this.simulatedRelayerAddress;
const tx = await createUnsignedFillRelayTransactionFromDeposit(
this.spokePool,
deposit,
toBN(amountToRelay),
relayerToSimulate
relayer
);
return estimateTotalGasRequiredByUnsignedTransaction(
tx,
relayerToSimulate,
relayer,
this.provider,
this.gasMarkup,
this.fixedGasPrice
);
}

/**
* Retrieves the current gas costs of performing a fillV3Relay contract at the referenced SpokePool.
* @param deposit V3Deposit instance.
* @param relayer Relayer address to simulate with.
* @returns The gas estimate for this function call (multplied with the optional buffer).
*/
async getV3GasCosts(deposit: V3Deposit, relayer: string): Promise<TransactionCostEstimate> {
const tx = await populateV3Relay(this.spokePool, deposit);
return estimateTotalGasRequiredByUnsignedTransaction(
tx,
relayer,
this.provider,
this.gasMarkup,
this.fixedGasPrice
Expand Down
39 changes: 20 additions & 19 deletions src/relayFeeCalculator/relayFeeCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ export const DEFAULT_LOGGER: Logger = {
error: (...args) => console.error(args),
};

// Small amount to simulate filling with. Should be low enough to guarantee a successful fill.
const safeOutputAmount = toBN(100);

export class RelayFeeCalculator {
private queries: QueryInterface;
private gasDiscountPercent: Required<RelayFeeCalculatorConfig>["gasDiscountPercent"];
Expand Down Expand Up @@ -234,26 +237,24 @@ export class RelayFeeCalculator {
throw new Error(`Could not find token information for ${inputToken}`);
}

const outputAmount = getDepositOutputAmount(deposit);
const getGasCosts = this.queries
.getGasCosts(
{
...deposit,
amount: simulateZeroFill && isV2Deposit(deposit) ? toBN(100) : outputAmount,
},
simulateZeroFill ? toBN(100) : amountToRelay,
relayerAddress
)
.catch((error) => {
this.logger.error({
at: "sdk-v2/gasFeePercent",
message: "Error while fetching gas costs",
error,
simulateZeroFill,
deposit,
});
throw error;
if (simulateZeroFill) {
amountToRelay = safeOutputAmount;
// Reduce the output amount to simulate a full fill with a lower value to estimate
// the fill cost accurately without risking a failure due to insufficient balance.
deposit = isV2Deposit(deposit)
? { ...deposit, amount: amountToRelay }
: { ...deposit, outputAmount: amountToRelay };
}
const getGasCosts = this.queries.getGasCosts(deposit, amountToRelay, relayerAddress).catch((error) => {
this.logger.error({
at: "sdk-v2/gasFeePercent",
message: "Error while fetching gas costs",
error,
simulateZeroFill,
deposit,
});
throw error;
});
const getTokenPrice = this.queries.getTokenPrice(token.symbol).catch((error) => {
this.logger.error({
at: "sdk-v2/gasFeePercent",
Expand Down
32 changes: 30 additions & 2 deletions src/utils/SpokeUtils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,40 @@
import assert from "assert";
import { Contract, utils as ethersUtils } from "ethers";
import { CHAIN_IDs } from "../constants";
import { Contract, PopulatedTransaction, utils as ethersUtils } from "ethers";
import { CHAIN_IDs, ZERO_ADDRESS } from "../constants";
import { FillStatus, RelayData, SlowFillRequest, V2RelayData, V3Deposit, V3Fill, V3RelayData } from "../interfaces";
import { SpokePoolClient } from "../clients";
import { isDefined } from "./TypeGuards";
import { isV2RelayData } from "./V3Utils";
import { getNetworkName } from "./NetworkUtils";

/**
* @param spokePool SpokePool Contract instance.
* @param deposit V3Deopsit instance.
* @param repaymentChainId Optional repaymentChainId (defaults to destinationChainId).
* @returns An Ethers UnsignedTransaction instance.
*/
export function populateV3Relay(
spokePool: Contract,
deposit: V3Deposit,
repaymentChainId = deposit.destinationChainId
): Promise<PopulatedTransaction> {
if (isDefined(deposit.speedUpSignature)) {
assert(isDefined(deposit.updatedRecipient) && deposit.updatedRecipient !== ZERO_ADDRESS);
assert(isDefined(deposit.updatedOutputAmount));
assert(isDefined(deposit.updatedMessage));
return spokePool.populateTransaction.fillV3RelayWithUpdatedDeposit([
deposit,
repaymentChainId,
deposit.updatedOutputAmount,
deposit.updatedRecipient,
deposit.updatedMessage,
deposit.speedUpSignature,
]);
}

return spokePool.populateTransaction.fillV3Relay([deposit, repaymentChainId]);
}

/**
* Find the block range that contains the deposit ID. This is a binary search that searches for the block range
* that contains the deposit ID.
Expand Down

0 comments on commit 687652e

Please sign in to comment.