Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

some RPC query optimizations #98

Merged
merged 4 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 5 additions & 90 deletions src/hooks/useAutoSlippageTolerance.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,13 @@
import { MixedRoute, partitionMixedRouteByProtocol, Protocol, Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { Pool } from '@uniswap/v3-sdk'
import { Percent } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import { L2_CHAIN_IDS } from 'constants/chains'
import JSBI from 'jsbi'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react'
import { ClassicTrade } from 'state/routing/types'

import useGasPrice from './useGasPrice'
import useStablecoinPrice, { useStablecoinAmountFromFiatValue, useStablecoinValue } from './useStablecoinPrice'
import { useStablecoinAmountFromFiatValue, useStablecoinValue } from './useStablecoinPrice'

const DEFAULT_AUTO_SLIPPAGE = new Percent(5, 1000) // 0.5%

// Base costs regardless of how many hops in the route
const V3_SWAP_BASE_GAS_ESTIMATE = 100_000
const V2_SWAP_BASE_GAS_ESTIMATE = 135_000

// Extra cost per hop in the route
const V3_SWAP_HOP_GAS_ESTIMATE = 70_000
const V2_SWAP_HOP_GAS_ESTIMATE = 50_000

/**
* Return a guess of the gas cost used in computing slippage tolerance for a given trade
* @param trade the trade for which to _guess_ the amount of gas it would cost to execute
*
* V3 logic is inspired by:
* https://github.com/Uniswap/smart-order-router/blob/main/src/routers/alpha-router/gas-models/v3/v3-heuristic-gas-model.ts
* V2 logic is inspired by:
* https://github.com/Uniswap/smart-order-router/blob/main/src/routers/alpha-router/gas-models/v2/v2-heuristic-gas-model.ts
*/
function guesstimateGas(trade: Trade<Currency, Currency, TradeType> | undefined): number | undefined {
if (trade) {
let gas = 0
for (const { route } of trade.swaps) {
if (route.protocol === Protocol.V2) {
gas += V2_SWAP_BASE_GAS_ESTIMATE + route.pools.length * V2_SWAP_HOP_GAS_ESTIMATE
} else if (route.protocol === Protocol.V3) {
// V3 gas costs scale on initialized ticks being crossed, but we don't have that data here.
// We bake in some tick crossings into the base 100k cost.
gas += V3_SWAP_BASE_GAS_ESTIMATE + route.pools.length * V3_SWAP_HOP_GAS_ESTIMATE
} else if (route.protocol === Protocol.MIXED) {
const sections = partitionMixedRouteByProtocol(route as MixedRoute<Currency, Currency>)
gas += sections.reduce((gas, section) => {
if (section.every((pool) => pool instanceof Pool)) {
return gas + V3_SWAP_BASE_GAS_ESTIMATE + section.length * V3_SWAP_HOP_GAS_ESTIMATE
} else if (section.every((pool) => pool instanceof Pair)) {
return gas + V2_SWAP_BASE_GAS_ESTIMATE + (section.length - 1) * V2_SWAP_HOP_GAS_ESTIMATE
} else {
console.warn('Invalid section')
return gas
}
}, 0)
} else {
// fallback general gas estimation
gas += V3_SWAP_BASE_GAS_ESTIMATE + route.pools.length * V3_SWAP_HOP_GAS_ESTIMATE
}
}
return gas
}
return undefined
}

const MIN_AUTO_SLIPPAGE_TOLERANCE = DEFAULT_AUTO_SLIPPAGE
// assuming normal gas speeds, most swaps complete within 3 blocks and
// there's rarely price movement >5% in that time period
Expand All @@ -77,35 +21,16 @@ export default function useClassicAutoSlippageTolerance(trade?: ClassicTrade): P
const { chainId } = useWeb3React()
const onL2 = chainId && L2_CHAIN_IDS.includes(chainId)
const outputDollarValue = useStablecoinValue(trade?.outputAmount)
const nativeGasPrice = useGasPrice()

const gasEstimate = guesstimateGas(trade)
const gasEstimateUSD = useStablecoinAmountFromFiatValue(trade?.gasUseEstimateUSD) ?? null
const nativeCurrency = useNativeCurrency(chainId)
const nativeCurrencyPrice = useStablecoinPrice((trade && nativeCurrency) ?? undefined)

return useMemo(() => {
if (!trade || onL2) return DEFAULT_AUTO_SLIPPAGE

const nativeGasCost =
nativeGasPrice && typeof gasEstimate === 'number'
? JSBI.multiply(nativeGasPrice, JSBI.BigInt(gasEstimate))
: undefined
const dollarGasCost =
nativeCurrency && nativeGasCost && nativeCurrencyPrice
? nativeCurrencyPrice.quote(CurrencyAmount.fromRawAmount(nativeCurrency, nativeGasCost))
: undefined

// if valid estimate from api and using api trade, use gas estimate from api
// NOTE - dont use gas estimate for L2s yet - need to verify accuracy
// if not, use local heuristic
const dollarCostToUse =
chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) && gasEstimateUSD ? gasEstimateUSD : dollarGasCost

if (outputDollarValue && dollarCostToUse) {
if (outputDollarValue && gasEstimateUSD) {
// optimize for highest possible slippage without getting MEV'd
// so set slippage % such that the difference between expected amount out and minimum amount out < gas fee to sandwich the trade
const fraction = dollarCostToUse.asFraction.divide(outputDollarValue.asFraction)
const fraction = gasEstimateUSD.asFraction.divide(outputDollarValue.asFraction)
const result = new Percent(fraction.numerator, fraction.denominator)
if (result.greaterThan(MAX_AUTO_SLIPPAGE_TOLERANCE)) {
return MAX_AUTO_SLIPPAGE_TOLERANCE
Expand All @@ -119,15 +44,5 @@ export default function useClassicAutoSlippageTolerance(trade?: ClassicTrade): P
}

return DEFAULT_AUTO_SLIPPAGE
}, [
trade,
onL2,
nativeGasPrice,
gasEstimate,
nativeCurrency,
nativeCurrencyPrice,
chainId,
gasEstimateUSD,
outputDollarValue,
])
}, [trade, onL2, gasEstimateUSD, outputDollarValue])
}
14 changes: 2 additions & 12 deletions src/hooks/useCurrentBlockTimestamp.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
import { BigNumber } from '@ethersproject/bignumber'
import { useSingleCallResult } from 'lib/hooks/multicall'
import { useMemo } from 'react'

import { useInterfaceMulticall } from './useContract'

// gets the current timestamp from the blockchain
export default function useCurrentBlockTimestamp(): BigNumber | undefined {
const multicall = useInterfaceMulticall()
const resultStr: string | undefined = useSingleCallResult(
multicall,
'getCurrentBlockTimestamp'
)?.result?.[0]?.toString()
return useMemo(() => (typeof resultStr === 'string' ? BigNumber.from(resultStr) : undefined), [resultStr])
export default function useCurrentBlockTimestamp(): BigNumber {
return BigNumber.from(BigInt(Math.floor(Date.now() / 1000)))
}
27 changes: 0 additions & 27 deletions src/hooks/useGasPrice.ts

This file was deleted.

9 changes: 2 additions & 7 deletions src/hooks/useUSDPrice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,12 @@ import { useMemo } from 'react'

import useStablecoinPrice from './useStablecoinPrice'

export function useUSDPrice(
currencyAmount?: CurrencyAmount<Currency>,
prefetchCurrency?: Currency
): {
export function useUSDPrice(currencyAmount?: CurrencyAmount<Currency>): {
data?: number
isLoading: boolean
} {
const currency = currencyAmount?.currency ?? prefetchCurrency

// Use USDC-based pricing for chains.
const stablecoinPrice = useStablecoinPrice(currency)
const stablecoinPrice = useStablecoinPrice(currencyAmount?.currency)

return useMemo(() => {
if (currencyAmount && stablecoinPrice) {
Expand Down
17 changes: 15 additions & 2 deletions src/lib/hooks/routing/clientSideSmartOrderRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import AppStaticJsonRpcProvider from 'rpc/StaticJsonRpcProvider'
import { GetQuoteArgs, QuoteResult, QuoteState, SwapRouterNativeAssets } from 'state/routing/types'
import { transformSwapRouteToGetQuoteResult } from 'utils/transformSwapRouteToGetQuoteResult'

const tokenValidatorProvider = {
validateTokens: async () => ({ getValidationByToken: () => undefined }),
}

type RouterAndProvider = { router: AlphaRouter; provider: AppStaticJsonRpcProvider | Web3Provider }
let cachedProviderRouter: { chainId: number; routerProvider: RouterAndProvider } | undefined = undefined
const routers = new Map<ChainId, RouterAndProvider>()
Expand Down Expand Up @@ -58,7 +62,13 @@ export function getRouter(chainId: ChainId, web3Provider: Web3Provider | undefin
cachedProviderRouter = {
chainId,
routerProvider: {
router: new AlphaRouter({ chainId, provider: web3Provider, multicall2Provider, onChainQuoteProvider }),
router: new AlphaRouter({
chainId,
provider: web3Provider,
multicall2Provider,
onChainQuoteProvider,
tokenValidatorProvider,
}),
provider: web3Provider,
},
}
Expand All @@ -70,7 +80,10 @@ export function getRouter(chainId: ChainId, web3Provider: Web3Provider | undefin
const supportedChainId = asSupportedChain(chainId)
if (supportedChainId) {
const provider = RPC_PROVIDERS[supportedChainId]
const routerProvider = { router: new AlphaRouter({ chainId, provider }), provider }
const routerProvider = {
router: new AlphaRouter({ chainId, provider, tokenValidatorProvider }),
provider,
}
routers.set(chainId, routerProvider)
return routerProvider
}
Expand Down
10 changes: 2 additions & 8 deletions src/pages/Swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -313,18 +313,12 @@ function Swap({

const showFiatValueInput = Boolean(parsedAmounts[Field.INPUT])
const showFiatValueOutput = Boolean(parsedAmounts[Field.OUTPUT])
const getSingleUnitAmount = (currency?: Currency) => {
if (!currency) return
return CurrencyAmount.fromRawAmount(currency, JSBI.BigInt(10 ** currency.decimals))
}

const fiatValueInput = useUSDPrice(
parsedAmounts[Field.INPUT] ?? getSingleUnitAmount(currencies[Field.INPUT]),
currencies[Field.INPUT]
parsedAmounts[Field.INPUT] && showFiatValueInput ? parsedAmounts[Field.INPUT] : undefined
)
const fiatValueOutput = useUSDPrice(
parsedAmounts[Field.OUTPUT] ?? getSingleUnitAmount(currencies[Field.OUTPUT]),
currencies[Field.OUTPUT]
parsedAmounts[Field.OUTPUT] && showFiatValueOutput ? parsedAmounts[Field.OUTPUT] : undefined
)

const [routeNotFound, routeIsLoading, routeIsSyncing] = useMemo(
Expand Down
Loading