From 39894ed9b1de0c3c8c8e6b4034768608ab8f043e Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Fri, 6 Sep 2024 12:46:35 +0800 Subject: [PATCH] feat: allow usage of cached `gasUsage` + `SpeedProvider` (#717) --- package.json | 2 +- src/providers/index.ts | 1 + src/providers/speedProvider.ts | 63 +++++++++++++++++++ .../chain-queries/baseQuery.ts | 6 +- src/relayFeeCalculator/relayFeeCalculator.ts | 6 +- src/utils/common.ts | 15 ++--- 6 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 src/providers/speedProvider.ts diff --git a/package.json b/package.json index d7e0ee61..0cb3d782 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@across-protocol/sdk", "author": "UMA Team", - "version": "3.1.31", + "version": "3.1.32", "license": "AGPL-3.0", "homepage": "https://docs.across.to/reference/sdk", "files": [ diff --git a/src/providers/index.ts b/src/providers/index.ts index 5184e853..d067a5da 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,6 +1,7 @@ export * from "./rateLimitedProvider"; export * from "./cachedProvider"; export * from "./retryProvider"; +export * from "./speedProvider"; export * from "./constants"; export * from "./types"; export * from "./utils"; diff --git a/src/providers/speedProvider.ts b/src/providers/speedProvider.ts new file mode 100644 index 00000000..bd2672dd --- /dev/null +++ b/src/providers/speedProvider.ts @@ -0,0 +1,63 @@ +import { ethers } from "ethers"; +import { CachingMechanismInterface } from "../interfaces"; +import { CacheProvider } from "./cachedProvider"; +import { formatProviderError } from "./utils"; +import { PROVIDER_CACHE_TTL } from "./constants"; +import { Logger } from "winston"; + +/** + * RPC provider that sends requests to multiple providers in parallel and returns the fastest response. + */ +export class SpeedProvider extends ethers.providers.StaticJsonRpcProvider { + readonly providers: ethers.providers.StaticJsonRpcProvider[]; + + constructor( + params: ConstructorParameters[], + chainId: number, + readonly maxConcurrencySpeed: number, + readonly maxConcurrencyRateLimit: number, + providerCacheNamespace: string, + pctRpcCallsLogged: number, + redisClient?: CachingMechanismInterface, + standardTtlBlockDistance?: number, + noTtlBlockDistance?: number, + providerCacheTtl = PROVIDER_CACHE_TTL, + logger?: Logger + ) { + // Initialize the super just with the chainId, which stops it from trying to immediately send out a .send before + // this derived class is initialized. + super(undefined, chainId); + this.providers = params.map( + (inputs) => + new CacheProvider( + providerCacheNamespace, + redisClient, + standardTtlBlockDistance, + noTtlBlockDistance, + providerCacheTtl, + maxConcurrencyRateLimit, + pctRpcCallsLogged, + logger, + ...inputs + ) + ); + } + + override async send(method: string, params: Array): Promise { + try { + const providersToUse = this.providers.slice(0, this.maxConcurrencySpeed); + const result = await Promise.any(providersToUse.map((provider) => provider.send(method, params))); + return result; + } catch (error) { + // Only thrown if all providers failed to respond + if (error instanceof AggregateError) { + const errors = error.errors.map((error, index) => { + const provider = this.providers[index]; + return formatProviderError(provider, error.message); + }); + throw new Error("All providers errored:\n" + errors.join("\n")); + } + throw error; + } + } +} diff --git a/src/relayFeeCalculator/chain-queries/baseQuery.ts b/src/relayFeeCalculator/chain-queries/baseQuery.ts index 5d92d71a..21c16cde 100644 --- a/src/relayFeeCalculator/chain-queries/baseQuery.ts +++ b/src/relayFeeCalculator/chain-queries/baseQuery.ts @@ -59,14 +59,14 @@ export class QueryBase implements QueryInterface { * @param deposit V3 deposit instance. * @param relayerAddress Relayer address to simulate with. * @param gasPrice Optional gas price to use for the simulation. - * @param gasLimit Optional gas limit to use for the simulation. + * @param gasUnits Optional gas units to use for the simulation. * @returns The gas estimate for this function call (multiplied with the optional buffer). */ async getGasCosts( deposit: Deposit, relayer = DEFAULT_SIMULATED_RELAYER_ADDRESS, gasPrice = this.fixedGasPrice, - gasLimit?: BigNumberish + gasUnits?: BigNumberish ): Promise { const tx = await populateV3Relay(this.spokePool, deposit, relayer); return estimateTotalGasRequiredByUnsignedTransaction( @@ -75,7 +75,7 @@ export class QueryBase implements QueryInterface { this.provider, this.gasMarkup, gasPrice, - gasLimit + gasUnits ); } diff --git a/src/relayFeeCalculator/relayFeeCalculator.ts b/src/relayFeeCalculator/relayFeeCalculator.ts index beb72791..135b35e8 100644 --- a/src/relayFeeCalculator/relayFeeCalculator.ts +++ b/src/relayFeeCalculator/relayFeeCalculator.ts @@ -342,6 +342,8 @@ export class RelayFeeCalculator { * the relayer. * @param relayerAddress The relayer that will be used for the gas cost simulation * @param _tokenPrice The token price for normalizing fees + * @param gasPrice Optional gas price to use for the simulation + * @param gasUnits Optional gas units to use for the simulation * @returns A resulting `RelayerFeeDetails` object */ async relayerFeeDetails( @@ -351,7 +353,7 @@ export class RelayFeeCalculator { relayerAddress = DEFAULT_SIMULATED_RELAYER_ADDRESS, _tokenPrice?: number, gasPrice?: BigNumberish, - gasLimit?: BigNumberish + gasUnits?: BigNumberish ): Promise { // If the amount to relay is not provided, then we // should use the full deposit amount. @@ -370,7 +372,7 @@ export class RelayFeeCalculator { _tokenPrice, undefined, gasPrice, - gasLimit + gasUnits ); const gasFeeTotal = gasFeePercent.mul(amountToRelay).div(fixedPointAdjustment); const capitalFeePercent = this.capitalFeePercent( diff --git a/src/utils/common.ts b/src/utils/common.ts index 0050719b..9ed49751 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -242,7 +242,7 @@ export type TransactionCostEstimate = { * @param provider A valid ethers provider - will be used to reason the gas price. * @param gasMarkup Markup on the estimated gas cost. For example, 0.2 will increase this resulting value 1.2x. * @param gasPrice A manually provided gas price - if set, this function will not resolve the current gas price. - * @param gasLimit A manually provided gas limit - if set, this function will not estimate the gas limit. + * @param gasUnits A manually provided gas units - if set, this function will not estimate the gas units. * @returns Estimated cost in units of gas and the underlying gas token (gasPrice * estimatedGasUnits). */ export async function estimateTotalGasRequiredByUnsignedTransaction( @@ -251,7 +251,7 @@ export async function estimateTotalGasRequiredByUnsignedTransaction( provider: providers.Provider | L2Provider, gasMarkup: number, gasPrice?: BigNumberish, - gasLimit?: BigNumberish + gasUnits?: BigNumberish ): Promise { assert( gasMarkup > -1 && gasMarkup <= 4, @@ -262,11 +262,12 @@ export async function estimateTotalGasRequiredByUnsignedTransaction( const voidSigner = new VoidSigner(senderAddress, provider); // Estimate the Gas units required to submit this transaction. - let nativeGasCost = await voidSigner.estimateGas({ - ...unsignedTx, - gasPrice, - gasLimit, - }); + let nativeGasCost = gasUnits + ? BigNumber.from(gasUnits) + : await voidSigner.estimateGas({ + ...unsignedTx, + gasPrice, + }); let tokenGasCost: BigNumber; // OP stack is a special case; gas cost is computed by the SDK, without having to query price.