From 713ea6b2c83f74c8cb8f04e67f3070a0d8b69314 Mon Sep 17 00:00:00 2001 From: James Morris Date: Fri, 5 Aug 2022 15:23:22 -0400 Subject: [PATCH 01/14] feat: add utility function to calculate total gas cost of an unsigned transaction --- src/utils.ts | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 8d7094a55..04f757485 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ -import { BigNumber, ethers } from "ethers"; +import { BigNumber, ethers, PopulatedTransaction, providers, VoidSigner } from "ethers"; import * as uma from "@uma/sdk"; import Decimal from "decimal.js"; +import { isL2Provider, L2Provider } from "@eth-optimism/sdk"; export type BigNumberish = string | number | BigNumber; export type BN = BigNumber; @@ -222,3 +223,33 @@ export async function retry(call: () => Promise, times: number, delayS: nu }); return promiseChain; } + +/** + * Estimates the total gas cost required to submit an unsigned (populated) transaction on-chain + * @param unsignedTx The unsigned transaction that this function will estimate + * @param relayerAddress The address that the transaction will be submitted from () + * @param provider A valid ethers provider - will be used to reason the gas price + * @param gasPrice A manually provided gas price - if set, this function will not resolve the current gas price + * @returns The total gas cost to submit this transaction - i.e. gasPrice * estimatedGasUnits + */ +export async function estimateTotalGasRequiredByUnsignedTransaction( + unsignedTx: PopulatedTransaction, + relayerAddress: string, + provider: providers.Provider | L2Provider, + gasPrice?: BigNumberish +): Promise { + const voidSigner = new VoidSigner(relayerAddress, provider); + // Verify if this provider has been L2Provider wrapped + if (isL2Provider(provider)) { + const populatedTransaction = await voidSigner.populateTransaction(unsignedTx); + return (await provider.estimateTotalGasCost(populatedTransaction)).toString(); + } else { + // Estimate the Gas units required to submit this transaction + const estimatedGasUnits = await voidSigner.estimateGas(unsignedTx); + // Provide a default gas price of the market rate if this condition has not been set + const resolvedGasPrice = gasPrice ?? (await provider.getGasPrice()); + // Find the total gas cost by taking the product of the gas + // price & the estimated number of gas units needed + return BigNumber.from(resolvedGasPrice).mul(estimatedGasUnits).toString(); + } +} From 11c625242d70d26e99461dd74decd02f7b32e7ae Mon Sep 17 00:00:00 2001 From: James Morris Date: Fri, 5 Aug 2022 17:54:48 -0400 Subject: [PATCH 02/14] feat: Add utility function to generate an unsigned fillRelay tx --- src/utils.ts | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 04f757485..9a9af466e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import { BigNumber, ethers, PopulatedTransaction, providers, VoidSigner } from "ethers"; +import { BaseContract, BigNumber, ethers, PopulatedTransaction, providers, VoidSigner } from "ethers"; import * as uma from "@uma/sdk"; import Decimal from "decimal.js"; import { isL2Provider, L2Provider } from "@eth-optimism/sdk"; @@ -253,3 +253,33 @@ export async function estimateTotalGasRequiredByUnsignedTransaction( return BigNumber.from(resolvedGasPrice).mul(estimatedGasUnits).toString(); } } + +/** + * Create an unsigned transaction of a fillRelay contract call + * @param spokePool The specific spokepool that will populate this tx + * @param destinationTokenAddress A valid ERC20 token (system-wide default is UDSC) + * @param simulatedRelayerAddress The relayer address that relays this transaction + * @returns A populated (but unsigned) transaction that can be signed/sent or used for estimating gas costs + */ +export async function createUnsignedFillRelayTransaction( + spokePool: BaseContract, + destinationTokenAddress: string, + simulatedRelayerAddress: string +): Promise { + // Generate a baseline set of function parameters for the fillRelay contract function + const contractFunctionParams = [ + "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", + "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", + destinationTokenAddress, + "10", + "10", + "1", + "1", + "1", + "1", + "1", + { from: simulatedRelayerAddress }, + ]; + // Populate and return an unsigned tx as per the given spoke pool + return await spokePool.populateTransaction.fillRelay(...contractFunctionParams); +} From 16f55d00d5c847baffe67461a78a0cff2bf715ba Mon Sep 17 00:00:00 2001 From: James Morris Date: Sun, 7 Aug 2022 17:46:49 -0400 Subject: [PATCH 03/14] improve: add cohesive call layer to all gas cost estimates --- .../chain-queries/arbitrum.ts | 30 +++++------------- src/relayFeeCalculator/chain-queries/boba.ts | 31 +++++++------------ .../chain-queries/ethereum.ts | 26 +++++++++++----- .../chain-queries/optimism.ts | 27 +++++----------- .../chain-queries/polygon.ts | 28 +++++++++++------ 5 files changed, 65 insertions(+), 77 deletions(-) diff --git a/src/relayFeeCalculator/chain-queries/arbitrum.ts b/src/relayFeeCalculator/chain-queries/arbitrum.ts index 4288239cc..00ea35e8b 100644 --- a/src/relayFeeCalculator/chain-queries/arbitrum.ts +++ b/src/relayFeeCalculator/chain-queries/arbitrum.ts @@ -1,6 +1,10 @@ import { QueryInterface } from "../relayFeeCalculator"; -import { BigNumberish } from "../../utils"; -import { BigNumber, providers } from "ethers"; +import { + BigNumberish, + createUnsignedFillRelayTransaction, + estimateTotalGasRequiredByUnsignedTransaction, +} from "../../utils"; +import { providers } from "ethers"; import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; import { ArbitrumSpokePool__factory, ArbitrumSpokePool } from "@across-protocol/contracts-v2"; @@ -19,9 +23,8 @@ export class ArbitrumQueries implements QueryInterface { } async getGasCosts(_tokenSymbol: string): Promise { - const gasEstimate = await this.estimateGas(); - const gasPrice = BigNumber.from(await this.provider.getGasPrice()); - return gasPrice.mul(gasEstimate).toString(); + const tx = await createUnsignedFillRelayTransaction(this.spokePool, this.usdcAddress, this.simulatedRelayerAddress); + return estimateTotalGasRequiredByUnsignedTransaction(tx, this.simulatedRelayerAddress, this.provider); } async getTokenPrice(tokenSymbol: string): Promise { @@ -34,21 +37,4 @@ export class ArbitrumQueries implements QueryInterface { if (!this.symbolMapping[tokenSymbol]) throw new Error(`${tokenSymbol} does not exist in mapping`); return this.symbolMapping[tokenSymbol].decimals; } - - estimateGas() { - // Create a dummy transaction to estimate. Note: the simulated caller would need to be holding weth and have approved the contract. - return this.spokePool.estimateGas.fillRelay( - "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", - "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", - this.usdcAddress, - "10", - "10", - "1", - "1", - "1", - "1", - "1", - { from: this.simulatedRelayerAddress } - ); - } } diff --git a/src/relayFeeCalculator/chain-queries/boba.ts b/src/relayFeeCalculator/chain-queries/boba.ts index ef42f6884..880e8960d 100644 --- a/src/relayFeeCalculator/chain-queries/boba.ts +++ b/src/relayFeeCalculator/chain-queries/boba.ts @@ -1,5 +1,9 @@ import { QueryInterface } from "../relayFeeCalculator"; -import { BigNumberish } from "../../utils"; +import { + BigNumberish, + createUnsignedFillRelayTransaction, + estimateTotalGasRequiredByUnsignedTransaction, +} from "../../utils"; import { utils, providers } from "ethers"; import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; @@ -11,7 +15,7 @@ export class BobaQueries implements QueryInterface { private spokePool: OptimismSpokePool; constructor( - provider: providers.Provider, + private provider: providers.Provider, readonly symbolMapping = SymbolMapping, spokePoolAddress = "0xBbc6009fEfFc27ce705322832Cb2068F8C1e0A58", private readonly usdcAddress = "0x66a2A913e447d6b4BF33EFbec43aAeF87890FBbc", @@ -22,24 +26,13 @@ export class BobaQueries implements QueryInterface { } async getGasCosts(_tokenSymbol: string): Promise { - // Create a dummy transaction to estimate. Note: the simulated caller would need to be holding weth and have approved the contract. - const gasEstimate = await this.spokePool.estimateGas.fillRelay( - "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", - "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", - this.usdcAddress, - "10", - "10", - "1", - "1", - "1", - "1", - "1", - { from: this.simulatedRelayerAddress } + const tx = await createUnsignedFillRelayTransaction(this.spokePool, this.usdcAddress, this.simulatedRelayerAddress); + return estimateTotalGasRequiredByUnsignedTransaction( + tx, + this.simulatedRelayerAddress, + this.provider, + parseUnits("1", 9) ); - - // Boba's gas price is hardcoded to 1 gwei. - const bobaGasPrice = parseUnits("1", 9); - return gasEstimate.mul(bobaGasPrice).toString(); } async getTokenPrice(tokenSymbol: string): Promise { diff --git a/src/relayFeeCalculator/chain-queries/ethereum.ts b/src/relayFeeCalculator/chain-queries/ethereum.ts index a79573a2d..1300b08ef 100644 --- a/src/relayFeeCalculator/chain-queries/ethereum.ts +++ b/src/relayFeeCalculator/chain-queries/ethereum.ts @@ -1,7 +1,12 @@ import { QueryInterface } from "../relayFeeCalculator"; -import { BigNumberish } from "../../utils"; +import { + BigNumberish, + createUnsignedFillRelayTransaction, + estimateTotalGasRequiredByUnsignedTransaction, +} from "../../utils"; import { Coingecko } from "../../coingecko/Coingecko"; -import { providers, BigNumber } from "ethers"; +import { providers } from "ethers"; +import { EthereumSpokePool, EthereumSpokePool__factory } from "@across-protocol/contracts-v2"; // Note: these are the mainnet addresses for these symbols meant to be used for pricing. export const SymbolMapping: { [symbol: string]: { address: string; decimals: number } } = { @@ -70,15 +75,20 @@ export const SymbolMapping: { [symbol: string]: { address: string; decimals: num export const defaultAverageGas = 116006; export class EthereumQueries implements QueryInterface { + private spokePool: EthereumSpokePool; + constructor( public readonly provider: providers.Provider, - public readonly averageGas = defaultAverageGas, - readonly symbolMapping = SymbolMapping - ) {} + readonly symbolMapping = SymbolMapping, + readonly spokePoolAddress = "0x4D9079Bb4165aeb4084c526a32695dCfd2F77381", + readonly usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + readonly simulatedRelayerAddress = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D" + ) { + this.spokePool = EthereumSpokePool__factory.connect(this.spokePoolAddress, this.provider); + } async getGasCosts(_tokenSymbol: string): Promise { - return BigNumber.from(await this.provider.getGasPrice()) - .mul(this.averageGas) - .toString(); + const tx = await createUnsignedFillRelayTransaction(this.spokePool, this.usdcAddress, this.simulatedRelayerAddress); + return estimateTotalGasRequiredByUnsignedTransaction(tx, this.simulatedRelayerAddress, this.provider); } async getTokenPrice(tokenSymbol: string): Promise { diff --git a/src/relayFeeCalculator/chain-queries/optimism.ts b/src/relayFeeCalculator/chain-queries/optimism.ts index 7549f957e..9d8eeafe6 100644 --- a/src/relayFeeCalculator/chain-queries/optimism.ts +++ b/src/relayFeeCalculator/chain-queries/optimism.ts @@ -1,6 +1,10 @@ import { QueryInterface } from "../relayFeeCalculator"; -import { BigNumberish } from "../../utils"; -import { providers, VoidSigner } from "ethers"; +import { + BigNumberish, + createUnsignedFillRelayTransaction, + estimateTotalGasRequiredByUnsignedTransaction, +} from "../../utils"; +import { providers } from "ethers"; import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; import { OptimismSpokePool__factory, OptimismSpokePool } from "@across-protocol/contracts-v2"; @@ -23,23 +27,8 @@ export class OptimismQueries implements QueryInterface { } async getGasCosts(_tokenSymbol: string): Promise { - // Create a dummy transaction to estimate. Note: the simulated caller would need to be holding weth and have approved the contract. - const tx = await this.spokePool.populateTransaction.fillRelay( - "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", - "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", - this.usdcAddress, - "10", - "10", - "1", - "1", - "1", - "1", - "1" - ); - const populatedTransaction = await new VoidSigner(this.simulatedRelayerAddress, this.provider).populateTransaction( - tx - ); - return (await this.provider.estimateTotalGasCost(populatedTransaction)).toString(); + const tx = await createUnsignedFillRelayTransaction(this.spokePool, this.usdcAddress, this.simulatedRelayerAddress); + return estimateTotalGasRequiredByUnsignedTransaction(tx, this.simulatedRelayerAddress, this.provider); } async getTokenPrice(tokenSymbol: string): Promise { diff --git a/src/relayFeeCalculator/chain-queries/polygon.ts b/src/relayFeeCalculator/chain-queries/polygon.ts index 65ae23382..e5fde2edf 100644 --- a/src/relayFeeCalculator/chain-queries/polygon.ts +++ b/src/relayFeeCalculator/chain-queries/polygon.ts @@ -1,20 +1,30 @@ import { QueryInterface } from "../relayFeeCalculator"; -import { BigNumberish } from "../../utils"; -import { providers, BigNumber } from "ethers"; -import { defaultAverageGas, SymbolMapping } from "./ethereum"; +import { + BigNumberish, + createUnsignedFillRelayTransaction, + estimateTotalGasRequiredByUnsignedTransaction, +} from "../../utils"; +import { providers } from "ethers"; +import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; +import { PolygonSpokePool, PolygonSpokePool__factory } from "@across-protocol/contracts-v2"; export class PolygonQueries implements QueryInterface { + private spokePool: PolygonSpokePool; + constructor( readonly provider: providers.Provider, - public readonly averageGas = defaultAverageGas, - readonly symbolMapping = SymbolMapping - ) {} + readonly symbolMapping = SymbolMapping, + readonly spokePoolAddress = "0x69B5c72837769eF1e7C164Abc6515DcFf217F920", + readonly usdcAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + readonly simulatedRelayerAddress = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D" + ) { + this.spokePool = PolygonSpokePool__factory.connect(spokePoolAddress, provider); + } async getGasCosts(_tokenSymbol: string): Promise { - return BigNumber.from(await this.provider.getGasPrice()) - .mul(this.averageGas) - .toString(); + const tx = await createUnsignedFillRelayTransaction(this.spokePool, this.usdcAddress, this.simulatedRelayerAddress); + return estimateTotalGasRequiredByUnsignedTransaction(tx, this.simulatedRelayerAddress, this.provider); } async getTokenPrice(tokenSymbol: string): Promise { From 00daec1b29f5a7d2511613c9a76017d5b1e0ad75 Mon Sep 17 00:00:00 2001 From: James Morris Date: Sun, 7 Aug 2022 17:47:56 -0400 Subject: [PATCH 04/14] fix: update e2e test to resolve error --- src/relayFeeCalculator/chain-queries/queries.e2e.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/relayFeeCalculator/chain-queries/queries.e2e.ts b/src/relayFeeCalculator/chain-queries/queries.e2e.ts index 41eeadefb..63c697e78 100644 --- a/src/relayFeeCalculator/chain-queries/queries.e2e.ts +++ b/src/relayFeeCalculator/chain-queries/queries.e2e.ts @@ -2,6 +2,8 @@ // NODE_URL_42161 // NODE_URL_288 // NODE_URL_10 +// NODE_URL_1 +// NODE_URL_137 import dotenv from "dotenv"; @@ -35,7 +37,8 @@ describe("Queries", function () { ]); }); test("Ethereum", async function () { - const ethereumQueries = new EthereumQueries(); + const provider = new providers.JsonRpcProvider(process.env.NODE_URL_1); + const ethereumQueries = new EthereumQueries(provider); await Promise.all([ ethereumQueries.getGasCosts("USDC"), ethereumQueries.getTokenDecimals("USDC"), @@ -52,7 +55,8 @@ describe("Queries", function () { ]); }); test("Polygon", async function () { - const polygonQueries = new PolygonQueries(); + const provider = new providers.JsonRpcProvider(process.env.NODE_URL_137); + const polygonQueries = new PolygonQueries(provider); await Promise.all([ polygonQueries.getGasCosts("USDC"), polygonQueries.getTokenDecimals("USDC"), From 1e82c4e296ca903ffcaf57ec7377f76ee2c9aa32 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 8 Aug 2022 10:03:02 -0400 Subject: [PATCH 05/14] fix: make provider access modifiers consistent --- src/relayFeeCalculator/chain-queries/boba.ts | 2 +- src/relayFeeCalculator/chain-queries/ethereum.ts | 2 +- src/relayFeeCalculator/chain-queries/optimism.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/relayFeeCalculator/chain-queries/boba.ts b/src/relayFeeCalculator/chain-queries/boba.ts index 880e8960d..57a7e5a84 100644 --- a/src/relayFeeCalculator/chain-queries/boba.ts +++ b/src/relayFeeCalculator/chain-queries/boba.ts @@ -15,7 +15,7 @@ export class BobaQueries implements QueryInterface { private spokePool: OptimismSpokePool; constructor( - private provider: providers.Provider, + readonly provider: providers.Provider, readonly symbolMapping = SymbolMapping, spokePoolAddress = "0xBbc6009fEfFc27ce705322832Cb2068F8C1e0A58", private readonly usdcAddress = "0x66a2A913e447d6b4BF33EFbec43aAeF87890FBbc", diff --git a/src/relayFeeCalculator/chain-queries/ethereum.ts b/src/relayFeeCalculator/chain-queries/ethereum.ts index 1300b08ef..76df4fb3c 100644 --- a/src/relayFeeCalculator/chain-queries/ethereum.ts +++ b/src/relayFeeCalculator/chain-queries/ethereum.ts @@ -78,7 +78,7 @@ export class EthereumQueries implements QueryInterface { private spokePool: EthereumSpokePool; constructor( - public readonly provider: providers.Provider, + readonly provider: providers.Provider, readonly symbolMapping = SymbolMapping, readonly spokePoolAddress = "0x4D9079Bb4165aeb4084c526a32695dCfd2F77381", readonly usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", diff --git a/src/relayFeeCalculator/chain-queries/optimism.ts b/src/relayFeeCalculator/chain-queries/optimism.ts index 9d8eeafe6..f3a433515 100644 --- a/src/relayFeeCalculator/chain-queries/optimism.ts +++ b/src/relayFeeCalculator/chain-queries/optimism.ts @@ -13,7 +13,7 @@ import { L2Provider, asL2Provider } from "@eth-optimism/sdk"; export class OptimismQueries implements QueryInterface { private spokePool: OptimismSpokePool; - private provider: L2Provider; + readonly provider: L2Provider; constructor( provider: providers.Provider, From cf521fa5ec51d8db9a8c7a36a8029b735201c3a5 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 8 Aug 2022 10:15:16 -0400 Subject: [PATCH 06/14] docs: add comment to highlight that a hardcoded value is a dummy address --- src/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils.ts b/src/utils.ts index 9a9af466e..f4c2bcec4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -267,6 +267,7 @@ export async function createUnsignedFillRelayTransaction( simulatedRelayerAddress: string ): Promise { // Generate a baseline set of function parameters for the fillRelay contract function + // NOTE: 0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B is a dummy address const contractFunctionParams = [ "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", From ffa6704b82773bcbeedf8e5e525d188dd11b2be5 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 8 Aug 2022 10:52:22 -0400 Subject: [PATCH 07/14] improve: remove defaultGas artifact from eth query --- src/relayFeeCalculator/chain-queries/ethereum.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/relayFeeCalculator/chain-queries/ethereum.ts b/src/relayFeeCalculator/chain-queries/ethereum.ts index 76df4fb3c..6b00acabb 100644 --- a/src/relayFeeCalculator/chain-queries/ethereum.ts +++ b/src/relayFeeCalculator/chain-queries/ethereum.ts @@ -72,8 +72,6 @@ export const SymbolMapping: { [symbol: string]: { address: string; decimals: num }, }; -export const defaultAverageGas = 116006; - export class EthereumQueries implements QueryInterface { private spokePool: EthereumSpokePool; From 8be8c9f483d84305a0ba056b1fd4bd574c899742 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 8 Aug 2022 15:59:06 -0400 Subject: [PATCH 08/14] test: add unit test for verifying the gasFeePercent is consistent --- .../relayFeeCalculator.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/relayFeeCalculator/relayFeeCalculator.test.ts b/src/relayFeeCalculator/relayFeeCalculator.test.ts index 365382f78..6c3852bd5 100644 --- a/src/relayFeeCalculator/relayFeeCalculator.test.ts +++ b/src/relayFeeCalculator/relayFeeCalculator.test.ts @@ -39,6 +39,22 @@ describe("RelayFeeCalculator", () => { beforeAll(() => { queries = new ExampleQueries(); }); + it("gasPercentageFee", async () => { + client = new RelayFeeCalculator({ queries }); + // A list of inputs and ground truth [input, ground truth] + const gasFeePercents = [ + [1000, "30557200000000000000000"], + [5000, "6111440000000000000000"], + // A test with a prime number + [104729, "291774007199534035462"], + ]; + for (const [input, truth] of gasFeePercents) { + const result = (await client.gasFeePercent(input, "usdc")).toString(); + expect(result).toEqual(truth); + } + // Test that zero amount fails + await expect(client.gasFeePercent(0, "USDC")).rejects.toThrowError(); + }); it("relayerFeeDetails", async () => { client = new RelayFeeCalculator({ queries }); const result = await client.relayerFeeDetails(100000000, "usdc"); From 451c80e04daffbffb6d2999e873731a4b4c46ba3 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 8 Aug 2022 16:02:02 -0400 Subject: [PATCH 09/14] nit: improve variable name to serve the general case --- src/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index f4c2bcec4..d06e436ec 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -227,18 +227,18 @@ export async function retry(call: () => Promise, times: number, delayS: nu /** * Estimates the total gas cost required to submit an unsigned (populated) transaction on-chain * @param unsignedTx The unsigned transaction that this function will estimate - * @param relayerAddress The address that the transaction will be submitted from () + * @param senderAddress The address that the transaction will be submitted from * @param provider A valid ethers provider - will be used to reason the gas price * @param gasPrice A manually provided gas price - if set, this function will not resolve the current gas price * @returns The total gas cost to submit this transaction - i.e. gasPrice * estimatedGasUnits */ export async function estimateTotalGasRequiredByUnsignedTransaction( unsignedTx: PopulatedTransaction, - relayerAddress: string, + senderAddress: string, provider: providers.Provider | L2Provider, gasPrice?: BigNumberish ): Promise { - const voidSigner = new VoidSigner(relayerAddress, provider); + const voidSigner = new VoidSigner(senderAddress, provider); // Verify if this provider has been L2Provider wrapped if (isL2Provider(provider)) { const populatedTransaction = await voidSigner.populateTransaction(unsignedTx); From ede380a86add898ec16ee34c058fd156eae7a086 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 8 Aug 2022 16:12:50 -0400 Subject: [PATCH 10/14] improve: utilize SpokePool base class instead of BaseContract --- src/relayFeeCalculator/chain-queries/arbitrum.ts | 4 ++-- src/relayFeeCalculator/chain-queries/boba.ts | 4 ++-- src/relayFeeCalculator/chain-queries/ethereum.ts | 4 ++-- src/relayFeeCalculator/chain-queries/optimism.ts | 4 ++-- src/relayFeeCalculator/chain-queries/polygon.ts | 4 ++-- src/utils.ts | 15 +++++++-------- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/relayFeeCalculator/chain-queries/arbitrum.ts b/src/relayFeeCalculator/chain-queries/arbitrum.ts index 00ea35e8b..5df67c3b0 100644 --- a/src/relayFeeCalculator/chain-queries/arbitrum.ts +++ b/src/relayFeeCalculator/chain-queries/arbitrum.ts @@ -7,10 +7,10 @@ import { import { providers } from "ethers"; import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; -import { ArbitrumSpokePool__factory, ArbitrumSpokePool } from "@across-protocol/contracts-v2"; +import { ArbitrumSpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; export class ArbitrumQueries implements QueryInterface { - private spokePool: ArbitrumSpokePool; + private spokePool: SpokePool; constructor( readonly provider: providers.Provider, diff --git a/src/relayFeeCalculator/chain-queries/boba.ts b/src/relayFeeCalculator/chain-queries/boba.ts index 57a7e5a84..4cbb44864 100644 --- a/src/relayFeeCalculator/chain-queries/boba.ts +++ b/src/relayFeeCalculator/chain-queries/boba.ts @@ -7,12 +7,12 @@ import { import { utils, providers } from "ethers"; import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; -import { OptimismSpokePool__factory, OptimismSpokePool } from "@across-protocol/contracts-v2"; +import { OptimismSpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; const { parseUnits } = utils; export class BobaQueries implements QueryInterface { - private spokePool: OptimismSpokePool; + private spokePool: SpokePool; constructor( readonly provider: providers.Provider, diff --git a/src/relayFeeCalculator/chain-queries/ethereum.ts b/src/relayFeeCalculator/chain-queries/ethereum.ts index 6b00acabb..30423dd49 100644 --- a/src/relayFeeCalculator/chain-queries/ethereum.ts +++ b/src/relayFeeCalculator/chain-queries/ethereum.ts @@ -6,7 +6,7 @@ import { } from "../../utils"; import { Coingecko } from "../../coingecko/Coingecko"; import { providers } from "ethers"; -import { EthereumSpokePool, EthereumSpokePool__factory } from "@across-protocol/contracts-v2"; +import { EthereumSpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; // Note: these are the mainnet addresses for these symbols meant to be used for pricing. export const SymbolMapping: { [symbol: string]: { address: string; decimals: number } } = { @@ -73,7 +73,7 @@ export const SymbolMapping: { [symbol: string]: { address: string; decimals: num }; export class EthereumQueries implements QueryInterface { - private spokePool: EthereumSpokePool; + private spokePool: SpokePool; constructor( readonly provider: providers.Provider, diff --git a/src/relayFeeCalculator/chain-queries/optimism.ts b/src/relayFeeCalculator/chain-queries/optimism.ts index f3a433515..1e6da14b3 100644 --- a/src/relayFeeCalculator/chain-queries/optimism.ts +++ b/src/relayFeeCalculator/chain-queries/optimism.ts @@ -7,12 +7,12 @@ import { import { providers } from "ethers"; import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; -import { OptimismSpokePool__factory, OptimismSpokePool } from "@across-protocol/contracts-v2"; +import { OptimismSpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; import { L2Provider, asL2Provider } from "@eth-optimism/sdk"; export class OptimismQueries implements QueryInterface { - private spokePool: OptimismSpokePool; + private spokePool: SpokePool; readonly provider: L2Provider; constructor( diff --git a/src/relayFeeCalculator/chain-queries/polygon.ts b/src/relayFeeCalculator/chain-queries/polygon.ts index e5fde2edf..cfbd0c6b2 100644 --- a/src/relayFeeCalculator/chain-queries/polygon.ts +++ b/src/relayFeeCalculator/chain-queries/polygon.ts @@ -7,10 +7,10 @@ import { import { providers } from "ethers"; import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; -import { PolygonSpokePool, PolygonSpokePool__factory } from "@across-protocol/contracts-v2"; +import { PolygonSpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; export class PolygonQueries implements QueryInterface { - private spokePool: PolygonSpokePool; + private spokePool: SpokePool; constructor( readonly provider: providers.Provider, diff --git a/src/utils.ts b/src/utils.ts index d06e436ec..22ee56975 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,8 @@ -import { BaseContract, BigNumber, ethers, PopulatedTransaction, providers, VoidSigner } from "ethers"; +import { BigNumber, ethers, PopulatedTransaction, providers, VoidSigner } from "ethers"; import * as uma from "@uma/sdk"; import Decimal from "decimal.js"; import { isL2Provider, L2Provider } from "@eth-optimism/sdk"; +import { SpokePool } from "@across-protocol/contracts-v2"; export type BigNumberish = string | number | BigNumber; export type BN = BigNumber; @@ -262,13 +263,13 @@ export async function estimateTotalGasRequiredByUnsignedTransaction( * @returns A populated (but unsigned) transaction that can be signed/sent or used for estimating gas costs */ export async function createUnsignedFillRelayTransaction( - spokePool: BaseContract, + spokePool: SpokePool, destinationTokenAddress: string, simulatedRelayerAddress: string ): Promise { - // Generate a baseline set of function parameters for the fillRelay contract function + // Populate and return an unsigned tx as per the given spoke pool // NOTE: 0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B is a dummy address - const contractFunctionParams = [ + return await spokePool.populateTransaction.fillRelay( "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", "0xBb23Cd0210F878Ea4CcA50e9dC307fb0Ed65Cf6B", destinationTokenAddress, @@ -279,8 +280,6 @@ export async function createUnsignedFillRelayTransaction( "1", "1", "1", - { from: simulatedRelayerAddress }, - ]; - // Populate and return an unsigned tx as per the given spoke pool - return await spokePool.populateTransaction.fillRelay(...contractFunctionParams); + { from: simulatedRelayerAddress } + ); } From d0824419500f03a0dcd73ae6c9f5dd5e96160fcf Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 8 Aug 2022 16:15:32 -0400 Subject: [PATCH 11/14] nit: highlight that the isL2Provider is effectively optimism-specific --- src/utils.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 22ee56975..a6fb21b02 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,7 @@ import { BigNumber, ethers, PopulatedTransaction, providers, VoidSigner } from "ethers"; import * as uma from "@uma/sdk"; import Decimal from "decimal.js"; -import { isL2Provider, L2Provider } from "@eth-optimism/sdk"; +import { isL2Provider as isOptimismL2Provider, L2Provider } from "@eth-optimism/sdk"; import { SpokePool } from "@across-protocol/contracts-v2"; export type BigNumberish = string | number | BigNumber; @@ -241,7 +241,9 @@ export async function estimateTotalGasRequiredByUnsignedTransaction( ): Promise { const voidSigner = new VoidSigner(senderAddress, provider); // Verify if this provider has been L2Provider wrapped - if (isL2Provider(provider)) { + // NOTE: In this case, this will be true if the provider is + // using the Optimism blockchain + if (isOptimismL2Provider(provider)) { const populatedTransaction = await voidSigner.populateTransaction(unsignedTx); return (await provider.estimateTotalGasCost(populatedTransaction)).toString(); } else { From 45df24b38a73b80e4d830afa74ce4572505ea1d5 Mon Sep 17 00:00:00 2001 From: James Morris Date: Mon, 8 Aug 2022 17:17:32 -0400 Subject: [PATCH 12/14] improve: use specified query address --- src/relayFeeCalculator/chain-queries/ethereum.ts | 2 +- src/relayFeeCalculator/chain-queries/polygon.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/relayFeeCalculator/chain-queries/ethereum.ts b/src/relayFeeCalculator/chain-queries/ethereum.ts index 30423dd49..e11a4dfe2 100644 --- a/src/relayFeeCalculator/chain-queries/ethereum.ts +++ b/src/relayFeeCalculator/chain-queries/ethereum.ts @@ -80,7 +80,7 @@ export class EthereumQueries implements QueryInterface { readonly symbolMapping = SymbolMapping, readonly spokePoolAddress = "0x4D9079Bb4165aeb4084c526a32695dCfd2F77381", readonly usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - readonly simulatedRelayerAddress = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D" + readonly simulatedRelayerAddress = "0x893d0D70AD97717052E3AA8903D9615804167759" ) { this.spokePool = EthereumSpokePool__factory.connect(this.spokePoolAddress, this.provider); } diff --git a/src/relayFeeCalculator/chain-queries/polygon.ts b/src/relayFeeCalculator/chain-queries/polygon.ts index cfbd0c6b2..eb858a50e 100644 --- a/src/relayFeeCalculator/chain-queries/polygon.ts +++ b/src/relayFeeCalculator/chain-queries/polygon.ts @@ -17,7 +17,7 @@ export class PolygonQueries implements QueryInterface { readonly symbolMapping = SymbolMapping, readonly spokePoolAddress = "0x69B5c72837769eF1e7C164Abc6515DcFf217F920", readonly usdcAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", - readonly simulatedRelayerAddress = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D" + readonly simulatedRelayerAddress = "0x893d0D70AD97717052E3AA8903D9615804167759" ) { this.spokePool = PolygonSpokePool__factory.connect(spokePoolAddress, provider); } From 0fdc138d6d1a220feaa19cf17fd995308b8fcd6b Mon Sep 17 00:00:00 2001 From: James Morris <96435344+james-a-morris@users.noreply.github.com> Date: Tue, 9 Aug 2022 15:50:17 -0400 Subject: [PATCH 13/14] docs: improve comment to better highlight the meaning of isL2Provider Co-authored-by: Matt Rice --- src/utils.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index a6fb21b02..1926fb401 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -240,9 +240,8 @@ export async function estimateTotalGasRequiredByUnsignedTransaction( gasPrice?: BigNumberish ): Promise { const voidSigner = new VoidSigner(senderAddress, provider); - // Verify if this provider has been L2Provider wrapped - // NOTE: In this case, this will be true if the provider is - // using the Optimism blockchain + // This branches in the Optimism case because they use a special provider, called L2Provider, and special gas logic + // to compute gas costs on Optimism. if (isOptimismL2Provider(provider)) { const populatedTransaction = await voidSigner.populateTransaction(unsignedTx); return (await provider.estimateTotalGasCost(populatedTransaction)).toString(); From ad4fe995c054071da5bd44f1cdeff0bf5b8e8dee Mon Sep 17 00:00:00 2001 From: James Morris Date: Tue, 9 Aug 2022 17:40:34 -0400 Subject: [PATCH 14/14] improve: utilize generic spoke pool factory --- src/relayFeeCalculator/chain-queries/arbitrum.ts | 4 ++-- src/relayFeeCalculator/chain-queries/boba.ts | 4 ++-- src/relayFeeCalculator/chain-queries/ethereum.ts | 4 ++-- src/relayFeeCalculator/chain-queries/optimism.ts | 4 ++-- src/relayFeeCalculator/chain-queries/polygon.ts | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/relayFeeCalculator/chain-queries/arbitrum.ts b/src/relayFeeCalculator/chain-queries/arbitrum.ts index 5df67c3b0..c23cb0c61 100644 --- a/src/relayFeeCalculator/chain-queries/arbitrum.ts +++ b/src/relayFeeCalculator/chain-queries/arbitrum.ts @@ -7,7 +7,7 @@ import { import { providers } from "ethers"; import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; -import { ArbitrumSpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; +import { SpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; export class ArbitrumQueries implements QueryInterface { private spokePool: SpokePool; @@ -19,7 +19,7 @@ export class ArbitrumQueries implements QueryInterface { private readonly usdcAddress = "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", private readonly simulatedRelayerAddress = "0x893d0d70ad97717052e3aa8903d9615804167759" ) { - this.spokePool = ArbitrumSpokePool__factory.connect(spokePoolAddress, provider); + this.spokePool = SpokePool__factory.connect(spokePoolAddress, provider); } async getGasCosts(_tokenSymbol: string): Promise { diff --git a/src/relayFeeCalculator/chain-queries/boba.ts b/src/relayFeeCalculator/chain-queries/boba.ts index 4cbb44864..4f73893d7 100644 --- a/src/relayFeeCalculator/chain-queries/boba.ts +++ b/src/relayFeeCalculator/chain-queries/boba.ts @@ -7,7 +7,7 @@ import { import { utils, providers } from "ethers"; import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; -import { OptimismSpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; +import { SpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; const { parseUnits } = utils; @@ -22,7 +22,7 @@ export class BobaQueries implements QueryInterface { private readonly simulatedRelayerAddress = "0x893d0d70ad97717052e3aa8903d9615804167759" ) { // TODO: replace with address getter. - this.spokePool = OptimismSpokePool__factory.connect(spokePoolAddress, provider); + this.spokePool = SpokePool__factory.connect(spokePoolAddress, provider); } async getGasCosts(_tokenSymbol: string): Promise { diff --git a/src/relayFeeCalculator/chain-queries/ethereum.ts b/src/relayFeeCalculator/chain-queries/ethereum.ts index e11a4dfe2..b8733a2ab 100644 --- a/src/relayFeeCalculator/chain-queries/ethereum.ts +++ b/src/relayFeeCalculator/chain-queries/ethereum.ts @@ -6,7 +6,7 @@ import { } from "../../utils"; import { Coingecko } from "../../coingecko/Coingecko"; import { providers } from "ethers"; -import { EthereumSpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; +import { SpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; // Note: these are the mainnet addresses for these symbols meant to be used for pricing. export const SymbolMapping: { [symbol: string]: { address: string; decimals: number } } = { @@ -82,7 +82,7 @@ export class EthereumQueries implements QueryInterface { readonly usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", readonly simulatedRelayerAddress = "0x893d0D70AD97717052E3AA8903D9615804167759" ) { - this.spokePool = EthereumSpokePool__factory.connect(this.spokePoolAddress, this.provider); + this.spokePool = SpokePool__factory.connect(this.spokePoolAddress, this.provider); } async getGasCosts(_tokenSymbol: string): Promise { const tx = await createUnsignedFillRelayTransaction(this.spokePool, this.usdcAddress, this.simulatedRelayerAddress); diff --git a/src/relayFeeCalculator/chain-queries/optimism.ts b/src/relayFeeCalculator/chain-queries/optimism.ts index 1e6da14b3..7e2314746 100644 --- a/src/relayFeeCalculator/chain-queries/optimism.ts +++ b/src/relayFeeCalculator/chain-queries/optimism.ts @@ -7,7 +7,7 @@ import { import { providers } from "ethers"; import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; -import { OptimismSpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; +import { SpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; import { L2Provider, asL2Provider } from "@eth-optimism/sdk"; @@ -23,7 +23,7 @@ export class OptimismQueries implements QueryInterface { private readonly simulatedRelayerAddress = "0x893d0d70ad97717052e3aa8903d9615804167759" ) { this.provider = asL2Provider(provider); - this.spokePool = OptimismSpokePool__factory.connect(spokePoolAddress, provider); + this.spokePool = SpokePool__factory.connect(spokePoolAddress, provider); } async getGasCosts(_tokenSymbol: string): Promise { diff --git a/src/relayFeeCalculator/chain-queries/polygon.ts b/src/relayFeeCalculator/chain-queries/polygon.ts index eb858a50e..dc19202ec 100644 --- a/src/relayFeeCalculator/chain-queries/polygon.ts +++ b/src/relayFeeCalculator/chain-queries/polygon.ts @@ -7,7 +7,7 @@ import { import { providers } from "ethers"; import { SymbolMapping } from "./ethereum"; import { Coingecko } from "../../coingecko/Coingecko"; -import { PolygonSpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; +import { SpokePool__factory, SpokePool } from "@across-protocol/contracts-v2"; export class PolygonQueries implements QueryInterface { private spokePool: SpokePool; @@ -19,7 +19,7 @@ export class PolygonQueries implements QueryInterface { readonly usdcAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", readonly simulatedRelayerAddress = "0x893d0D70AD97717052E3AA8903D9615804167759" ) { - this.spokePool = PolygonSpokePool__factory.connect(spokePoolAddress, provider); + this.spokePool = SpokePool__factory.connect(spokePoolAddress, provider); } async getGasCosts(_tokenSymbol: string): Promise {