From 3c1352ea415230b87687c2eb248c0f344c97b541 Mon Sep 17 00:00:00 2001 From: Theo Date: Tue, 22 Aug 2023 06:39:59 -0700 Subject: [PATCH] Update to stable version 1.0.0: - Change functions naming to more intuitive: 'getGasPrice' returns gas price in hexed number, 'getGasPriceParams' now returns legacy or EIP-1559 gas parameters - The library has become more versatile for working with third-party libraries (without BigNumber from 'ethers' library in return values, only hexed string-numbers) - Simplify functions for refund calculations, no need to provide transaction type, because we need to calculate refund only on user side - Add interface for TokenPriceOracle and change typings --- README.md | 2 +- package.json | 2 +- src/feeOracle.ts | 65 ++++++++++++++++++++--------------------- src/feeOracleV4.ts | 13 +++++---- src/feeOracleV5.ts | 25 +++++++++------- src/index.ts | 3 -- src/tokenPriceOracle.ts | 6 ++-- src/types.ts | 27 +++++++++-------- src/utils.ts | 4 +-- 9 files changed, 76 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 7a5e6c9..3e2808a 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ const incompleteTx: TransactionData = { const feeOracle = new TornadoFeeOracleV5(1, 'https://eth.llamarpc.com'); const transactionType: TxType = 'relayer_withdrawal'; -const gasPrice = await feeOracle.getGasPriceInHex(transactionType); +const gasPrice = await feeOracle.getGasPrice(transactionType); const gasLimit = await feeOracle.getGasLimit(incompleteTx, transactionType); const tx: TransactionData = Object.assign({ gasPrice, gasLimit }, incompleteTx); diff --git a/package.json b/package.json index 29acfe1..fafec3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tornado/tornado-oracles", - "version": "0.3.0", + "version": "1.0.0", "description": "Gas oracle for Tornado-specific transactions", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/src/feeOracle.ts b/src/feeOracle.ts index 190a939..8dde27b 100644 --- a/src/feeOracle.ts +++ b/src/feeOracle.ts @@ -1,7 +1,7 @@ import { GasPriceOracle } from '@tornado/gas-price-oracle'; import { BigNumber, BigNumberish, ethers } from 'ethers'; import { parseUnits } from 'ethers/lib/utils'; -import { TransactionData, TxType, ITornadoFeeOracle, GasPrice, LegacyGasPriceKey } from './types'; +import { TransactionData, TxType, ITornadoFeeOracle, LegacyGasPriceKey, GasPriceParams } from './types'; import { JsonRpcProvider } from '@ethersproject/providers'; import { ChainId, defaultGasPrices } from './config'; import { bump, calculateGasPriceInWei, convertETHToToken, fromGweiToWeiHex, serializeTx } from './utils'; @@ -36,23 +36,23 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { /** * Estimates next block gas for signed, unsigned or incomplete Tornado transaction * @param {TransactionData} tx Transaction data in web3 / ethers format - * @param {TxType} type Tornado transaction type: withdrawal by user, withdrawal by relayer or 'other' + * @param {TxType} txType Tornado transaction type: withdrawal by user, withdrawal by relayer or 'other' * @param {LegacyGasPriceKey} speed Preferred transaction speed, if uses legacy gas (before EIP-1559) * @returns {Promise} Gas value in WEI (hex-format) */ public async getGas( tx?: TransactionData, - type: TxType = 'other', + txType: TxType = 'other', speed: LegacyGasPriceKey = 'fast', - ): Promise { - const gasPrice = await this.getGasPriceInHex(type, speed); + ): Promise { + const gasPrice = await this.getGasPrice(txType, speed); let gas = BigNumber.from(0); gas = gas.add(gasPrice); if (tx) tx = Object.assign(tx, { gasPrice }); - gas = gas.mul(await this.getGasLimit(tx, type)); + gas = gas.mul(await this.getGasLimit(tx, txType)); if (this.chainId === ChainId.OPTIMISM) gas = gas.add(await this.fetchL1OptimismFee(tx)); - return gas; + return gas.toHexString(); } /** @@ -60,18 +60,18 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { * @param {TxType} type Tornado transaction type (to select correct default bump percent) * @param {LegacyGasPriceKey} speed Preferred transaction speed, if uses legacy gas (before EIP-1559) * @param {number} bumpPercent Gas bump percent to prioritize transaction - * @returns {Promise} Estimated gas price info in WEI (hexed) - legacy object with gasPrice property or EIP-1559 object with maxFeePerGas - * and maxPriorityFeePerGas properties + * @returns {Promise} Estimated gas price info in WEI (hexed) - legacy object with gasPrice property or + * EIP-1559 object with maxFeePerGas and maxPriorityFeePerGas properties */ - async getGasPrice( + async getGasPriceParams( type: TxType = 'other', speed: LegacyGasPriceKey = 'fast', bumpPercent: number = 0, - ): Promise { + ): Promise { try { return await this.oracle.getTxGasParams({ legacySpeed: speed, bumpPercent }); } catch (e) { - return { gasPrice: bump(fromGweiToWeiHex(defaultGasPrices[this.chainId][speed]), bumpPercent) }; + return { gasPrice: bump(fromGweiToWeiHex(defaultGasPrices[this.chainId][speed]), bumpPercent).toHexString() }; } } @@ -82,22 +82,22 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { * @param {number} bumpPercent Gas bump percent to prioritize transaction * @returns {Promise} Gas price in WEI (hex string) */ - async getGasPriceInHex( + async getGasPrice( type: TxType = 'other', speed: LegacyGasPriceKey = 'fast', bumpPercent: number = 0, ): Promise { - const gasPrice = await this.getGasPrice(type, speed, bumpPercent); - return calculateGasPriceInWei(gasPrice).toHexString(); + const gasPriceParams = await this.getGasPriceParams(type, speed, bumpPercent); + return calculateGasPriceInWei(gasPriceParams).toHexString(); } /** * Estimates gas limit for transaction (or basic gas limit, if no tx data provided) * @param {TransactionData} tx Transaction data (object in web3 / ethers format) * @param {TxType} type Tornado transaction type: withdrawal by user, withdrawal by relayer, relayer fee check or 'other' - * @returns {Promise} Gas limit + * @returns {Promise} Gas limit */ - abstract getGasLimit(tx?: TransactionData, type?: TxType): Promise; + abstract getGasLimit(tx?: TransactionData, type?: TxType): Promise; /** * If user withdraw non-native tokens on ETH or Goerli, we need to calculate refund value: @@ -106,11 +106,12 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { * * Refund needed that recipient can use tokens after withdrawal (covers gas fee for send/swap) * @param {TransactionData} [tx] Transaction data (object in web3 / ethers format) - * @param {TxType} [type] Tornado transaction type: withdrawal by user, withdrawal by relayer, relayer fee check or 'other' - * @returns {Promise} Refund amount in WEI + * @returns {Promise} Refund amount in WEI (hexed number) */ - async calculateRefundInETH(tx?: TransactionData, type?: TxType): Promise { - return (await this.getGas(tx, type)).mul(2); + async calculateRefundInETH(tx?: TransactionData): Promise { + // In Tornado we need to calculate refund only on user side, relayer get refund value in proof + const gas = await this.getGas(tx, 'user_withdrawal'); + return BigNumber.from(gas).mul(2).toHexString(); } /** @@ -118,17 +119,15 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { * @param {BigNumberish} tokenPriceInEth Token price in WEI in native currency * @param {string | number} tokenDecimals Token (currency) decimals * @param {TransactionData} [tx] Transaction data (object in web3 / ethers format) - * @param {TxType} [type] Tornado transaction type: withdrawal by user, withdrawal by relayer, relayer fee check or 'other' - * @returns {Promise} Refund amount in WEI in selected token + * @returns {Promise} Refund amount in WEI in selected token (hexed number) */ async calculateRefundInToken( tokenPriceInEth: BigNumberish, tokenDecimals: string | number, tx?: TransactionData, - type?: TxType, - ): Promise { - const refundInEth = await this.calculateRefundInETH(tx, type); - return convertETHToToken(refundInEth, tokenDecimals, tokenPriceInEth); + ): Promise { + const refundInEth = await this.calculateRefundInETH(tx); + return convertETHToToken(refundInEth, tokenDecimals, tokenPriceInEth).toHexString(); } /** @@ -143,7 +142,7 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { * @param {number | string } decimals Token (currency) decimals * @param {BigNumberish} [refund=0] Refund in ETH, if withdrawed other tokens on Mainnet (not ETH) * @param {BigNumberish} [tokenPriceInEth] If withdrawing other token on Mainnet or Goerli, need to provide token price in ETH (in WEI) - * @returns {Promise} Fee in WEI + * @returns {Promise} Fee in WEI (hexed string) */ async calculateWithdrawalFeeViaRelayer( type: TxType, @@ -154,8 +153,8 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { decimals: string | number, refund: BigNumberish = 0, tokenPriceInEth?: BigNumberish, - ): Promise { - const gasCosts = await this.getGas(tx, type); + ): Promise { + const gasCosts = BigNumber.from(await this.getGas(tx, type)); const relayerFee = parseUnits(amount.toString(), decimals) .mul(`${relayerFeePercent * 1e10}`) @@ -164,13 +163,13 @@ export abstract class TornadoFeeOracle implements ITornadoFeeOracle { if ((this.chainId === ChainId.MAINNET || this.chainId === ChainId.GOERLI) && currency.toLowerCase() != 'eth') { if (!tokenPriceInEth) { console.error('Token price is required argument, if not native chain token is withdrawed'); - return BigNumber.from(0); + return '0'; } const feeInEth = gasCosts.add(refund); - return convertETHToToken(feeInEth, decimals, tokenPriceInEth).add(relayerFee); + return convertETHToToken(feeInEth, decimals, tokenPriceInEth).add(relayerFee).toHexString(); } - return gasCosts.add(relayerFee); + return gasCosts.add(relayerFee).toHexString(); } } diff --git a/src/feeOracleV4.ts b/src/feeOracleV4.ts index 00ce7e4..d46ab00 100644 --- a/src/feeOracleV4.ts +++ b/src/feeOracleV4.ts @@ -17,17 +17,18 @@ export class TornadoFeeOracleV4 extends TornadoFeeOracle implements ITornadoFeeO super(chainId, rpcUrl, gasPriceOracle); } - async getGasLimit(tx?: TransactionData, type: TxType = 'other', bumpPercent?: number): Promise { - if (type === 'user_withdrawal') return BigNumber.from(defaultWithdrawalGasLimit[this.chainId]); + async getGasLimit(tx?: TransactionData, type: TxType = 'other', bumpPercent?: number): Promise { + if (type === 'user_withdrawal') return BigNumber.from(defaultWithdrawalGasLimit[this.chainId]).toNumber(); // Need to bump relayer gas limit for transaction, because predefined gas limit to small to be 100% sure that transaction will be sent // This leads to fact that relayer often pays extra for gas from his own funds, however, this was designed by previous developers - if (type === 'relayer_withdrawal') return bump(defaultWithdrawalGasLimit[this.chainId], bumpPercent || 25); + if (type === 'relayer_withdrawal') + return bump(defaultWithdrawalGasLimit[this.chainId], bumpPercent || 25).toNumber(); // For compatibility reasons, when wee check user-provided fee for V4 withdrawal transaction, we need dump gas limit // for about 20 percent,so that the transaction will be sent, even if it results in some loss for the relayer - if (type === 'relayer_withdrawal_check_v4') return bump(defaultWithdrawalGasLimit[this.chainId], -25); - if (!tx) return BigNumber.from(21_000); + if (type === 'relayer_withdrawal_check_v4') return bump(defaultWithdrawalGasLimit[this.chainId], -25).toNumber(); + if (!tx) return 21_000; - return this.provider.estimateGas(tx); + return (await this.provider.estimateGas(tx)).toNumber(); } } diff --git a/src/feeOracleV5.ts b/src/feeOracleV5.ts index b1b748a..9b29a72 100644 --- a/src/feeOracleV5.ts +++ b/src/feeOracleV5.ts @@ -1,8 +1,14 @@ -import { ChainId, defaultWithdrawalGasLimit } from './config'; +import { ChainId } from './config'; import { TornadoFeeOracle } from './feeOracle'; -import { ITornadoFeeOracle, GasPrice, TransactionData, TxType, LegacyGasPrices, LegacyGasPriceKey } from './types'; +import { + ITornadoFeeOracle, + TransactionData, + TxType, + LegacyGasPrices, + LegacyGasPriceKey, + GasPriceParams, +} from './types'; import { GasPriceOracle } from '@tornado/gas-price-oracle'; -import { BigNumber } from 'ethers'; import { bump } from './utils'; import { TornadoFeeOracleV4 } from './feeOracleV4'; @@ -25,7 +31,7 @@ export class TornadoFeeOracleV5 extends TornadoFeeOracle implements ITornadoFeeO this.fallbackFeeOracle = new TornadoFeeOracleV4(chainId, rpcUrl, defaultGasPrices); } - async getGasLimit(tx?: TransactionData, type: TxType = 'other', bumpPercent: number = 20): Promise { + async getGasLimit(tx?: TransactionData, type: TxType = 'other', bumpPercent: number = 20): Promise { if (!tx || Object.keys(tx).length === 0) return this.fallbackFeeOracle.getGasLimit(tx, type, bumpPercent); /* Relayer gas limit must be lower so that fluctuations in gas price cannot lead to the fact that @@ -37,18 +43,17 @@ export class TornadoFeeOracleV5 extends TornadoFeeOracle implements ITornadoFeeO try { const fetchedGasLimit = await this.provider.estimateGas(tx); - return bump(fetchedGasLimit, bumpPercent); + return bump(fetchedGasLimit, bumpPercent).toNumber(); } catch (e) { - if (type.endsWith('withdrawal')) return bump(defaultWithdrawalGasLimit[this.chainId], bumpPercent); - return BigNumber.from(21_000); + return this.fallbackFeeOracle.getGasLimit(tx, type, bumpPercent); } } - async getGasPrice( + async getGasPriceParams( type: TxType = 'other', speed: LegacyGasPriceKey = 'fast', bumpPercent?: number, - ): Promise { + ): Promise { // Only if bump percent didn't provided (if user provides 0, no need to recalculate) if (bumpPercent === undefined) { switch (this.chainId) { @@ -65,6 +70,6 @@ export class TornadoFeeOracleV5 extends TornadoFeeOracle implements ITornadoFeeO } } - return super.getGasPrice(type, speed, bumpPercent); + return super.getGasPriceParams(type, speed, bumpPercent); } } diff --git a/src/index.ts b/src/index.ts index 0af80b3..bbf0996 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,3 @@ -import { bump } from './utils'; - export * from './feeOracleV4'; export * from './feeOracleV5'; export * from './tokenPriceOracle'; -export { bump }; diff --git a/src/tokenPriceOracle.ts b/src/tokenPriceOracle.ts index 0906b7f..ec32d1c 100644 --- a/src/tokenPriceOracle.ts +++ b/src/tokenPriceOracle.ts @@ -1,5 +1,5 @@ import { MultiCall } from './contracts/MulticallAbi'; -import { Token, TokenPrices, TokenSymbol } from './types'; +import { ITornadoPriceOracle, Token, TokenPrices, TokenSymbol } from './types'; import { MulticallAbi, OffchainOracleAbi } from './contracts'; import { getMultiCallContract, getOffchainOracleContract } from './contracts/factories'; import { Provider } from '@ethersproject/abstract-provider'; @@ -34,7 +34,7 @@ const defaultTornadoTokenPrices: TokenPrices = { wbtc: '15659889148334216720', }; -export class TokenPriceOracle { +export class TokenPriceOracle implements ITornadoPriceOracle { private oracle: OffchainOracleAbi; private multiCall: MulticallAbi; private provider: Provider; @@ -64,7 +64,7 @@ export class TokenPriceOracle { * @param {Token[]} tokens Tokens array * @returns Valid structure to provide to MultiCall contract */ - prepareCallData(tokens: Token[] = this.tokens): MultiCall.CallStruct[] { + private prepareCallData(tokens: Token[] = this.tokens): MultiCall.CallStruct[] { return tokens.map((token) => ({ to: this.oracle.address, data: this.oracle.interface.encodeFunctionData('getRateToEth', [token.tokenAddress, true]), diff --git a/src/types.ts b/src/types.ts index 86e57d5..1935f02 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,10 +1,9 @@ -import { BigNumber, BigNumberish, BytesLike } from 'ethers'; -import { GasPriceKey } from '@tornado/gas-price-oracle/lib/services'; +import { BigNumberish, BytesLike } from 'ethers'; +import { GasPriceKey, GetTxGasParamsRes } from '@tornado/gas-price-oracle/lib/services'; import { AvailableTokenSymbols } from '@tornado/tornado-config'; export type LegacyGasPriceKey = GasPriceKey; -// Gas Prices in WEI -export type GasPrice = { maxFeePerGas: BigNumberish; maxPriorityFeePerGas: BigNumberish } | { gasPrice: BigNumberish }; +export type GasPriceParams = GetTxGasParamsRes; export type LegacyGasPrices = { [gasPriceType in LegacyGasPriceKey]: number; }; @@ -32,17 +31,16 @@ export interface TransactionData { } export interface ITornadoFeeOracle { - getGas: (tx?: TransactionData, type?: TxType) => Promise; - getGasPrice: (type?: TxType, speed?: LegacyGasPriceKey, bumpPercent?: number) => Promise; - getGasPriceInHex: (type?: TxType, speed?: LegacyGasPriceKey, bumpPercent?: number) => Promise; - getGasLimit: (tx?: TransactionData, type?: TxType, bumpPercent?: number) => Promise; - calculateRefundInETH: (tx?: TransactionData, type?: TxType) => Promise; + getGas: (tx?: TransactionData, type?: TxType) => Promise; + getGasPriceParams: (type?: TxType, speed?: LegacyGasPriceKey, bumpPercent?: number) => Promise; + getGasPrice: (type?: TxType, speed?: LegacyGasPriceKey, bumpPercent?: number) => Promise; + getGasLimit: (tx?: TransactionData, type?: TxType, bumpPercent?: number) => Promise; + calculateRefundInETH: (tx?: TransactionData) => Promise; calculateRefundInToken: ( tokenPriceInEth: BigNumberish, tokenDecimals: string | number, tx?: TransactionData, - type?: TxType, - ) => Promise; + ) => Promise; calculateWithdrawalFeeViaRelayer: ( type: TxType, tx: TransactionData, @@ -52,7 +50,12 @@ export interface ITornadoFeeOracle { decimals: number, refund: BigNumberish, tokenPriceInEth?: BigNumberish, - ) => Promise; + ) => Promise; +} + +export interface ITornadoPriceOracle { + defaultPrices: TokenPrices; + fetchPrices: (tokens?: Token[]) => Promise; } export type WithdrawalData = { diff --git a/src/utils.ts b/src/utils.ts index b7768c7..05b42d9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import { serialize } from '@ethersproject/transactions'; -import { GasPrice, TransactionData } from './types'; +import { GasPriceParams, TransactionData } from './types'; import { BigNumber, BigNumberish } from 'ethers'; import BigNumberFloat from 'bignumber.js'; BigNumberFloat.config({ EXPONENTIAL_AT: 100 }); @@ -13,7 +13,7 @@ export function serializeTx(tx?: TransactionData | string): string { return serialize(tx); } -export function calculateGasPriceInWei(gasPrice: GasPrice): BigNumber { +export function calculateGasPriceInWei(gasPrice: GasPriceParams): BigNumber { // @ts-ignore return BigNumber.from(gasPrice.gasPrice || gasPrice.maxFeePerGas); }