use multicall2

This commit is contained in:
Moody Salem 2021-04-23 18:26:24 -05:00
parent 60d1f8743f
commit bff3811faf
No known key found for this signature in database
GPG Key ID: 8CB5CD10385138DB
7 changed files with 52 additions and 40 deletions

@ -10,7 +10,7 @@ import COINBASE_ICON_URL from '../assets/images/coinbaseWalletIcon.svg'
import FORTMATIC_ICON_URL from '../assets/images/fortmaticIcon.png' import FORTMATIC_ICON_URL from '../assets/images/fortmaticIcon.png'
import PORTIS_ICON_URL from '../assets/images/portisIcon.png' import PORTIS_ICON_URL from '../assets/images/portisIcon.png'
export const MULTICALL_ADDRESSES: { [chainId in ChainId]: string } = { export const MULTICALL2_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '', [ChainId.MAINNET]: '',
[ChainId.ROPSTEN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696', [ChainId.ROPSTEN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',
[ChainId.KOVAN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696', [ChainId.KOVAN]: '0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696',

@ -26,7 +26,7 @@ import {
MERKLE_DISTRIBUTOR_ADDRESS, MERKLE_DISTRIBUTOR_ADDRESS,
V1_MIGRATOR_ADDRESS, V1_MIGRATOR_ADDRESS,
UNI, UNI,
MULTICALL_ADDRESSES, MULTICALL2_ADDRESSES,
} from 'constants/index' } from 'constants/index'
import { abi as NFTPositionManagerABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json' import { abi as NFTPositionManagerABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { import {
@ -40,6 +40,7 @@ import { TickLens, UniswapV3Factory, UniswapV3Pool } from 'types/v3'
import { NonfungiblePositionManager } from 'types/v3/NonfungiblePositionManager' import { NonfungiblePositionManager } from 'types/v3/NonfungiblePositionManager'
import { V3Migrator } from 'types/v3/V3Migrator' import { V3Migrator } from 'types/v3/V3Migrator'
import { getContract } from 'utils' import { getContract } from 'utils'
import { Multicall2 } from '../abis/types'
import { useActiveWeb3React } from './index' import { useActiveWeb3React } from './index'
// returns null on errors // returns null on errors
@ -113,9 +114,9 @@ export function usePairContract(pairAddress?: string, withSignerIfPossible?: boo
return useContract(pairAddress, IUniswapV2PairABI, withSignerIfPossible) return useContract(pairAddress, IUniswapV2PairABI, withSignerIfPossible)
} }
export function useMulticallContract(): Contract | null { export function useMulticall2Contract(): Multicall2 | null {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
return useContract(chainId && MULTICALL_ADDRESSES[chainId], MULTICALL_ABI, false) return useContract(chainId && MULTICALL2_ADDRESSES[chainId], MULTICALL_ABI, false) as Multicall2
} }
export function useMerkleDistributorContract(): Contract | null { export function useMerkleDistributorContract(): Contract | null {

@ -1,9 +1,9 @@
import { BigNumber } from 'ethers' import { BigNumber } from 'ethers'
import { useSingleCallResult } from '../state/multicall/hooks' import { useSingleCallResult } from '../state/multicall/hooks'
import { useMulticallContract } from './useContract' import { useMulticall2Contract } from './useContract'
// gets the current timestamp from the blockchain // gets the current timestamp from the blockchain
export default function useCurrentBlockTimestamp(): BigNumber | undefined { export default function useCurrentBlockTimestamp(): BigNumber | undefined {
const multicall = useMulticallContract() const multicall = useMulticall2Contract()
return useSingleCallResult(multicall, 'getCurrentBlockTimestamp')?.result?.[0] return useSingleCallResult(multicall, 'getCurrentBlockTimestamp')?.result?.[0]
} }

@ -69,13 +69,13 @@ export function useV3Positions(account: string | null | undefined): UseV3Positio
const positionManager = useV3NFTPositionManagerContract() const positionManager = useV3NFTPositionManagerContract()
const { loading: balanceLoading, error: balanceError, result: balanceResult } = useSingleCallResult( const { loading: balanceLoading, error: balanceError, result: balanceResult } = useSingleCallResult(
positionManager ?? undefined, positionManager,
'balanceOf', 'balanceOf',
[account ?? undefined] [account ?? undefined]
) )
// we don't expect any account balance to ever exceed the bounds of max safe int // we don't expect any account balance to ever exceed the bounds of max safe int
const accountBalance: number | undefined = balanceResult?.[0] ? Number.parseInt(balanceResult[0]) : undefined const accountBalance: number | undefined = balanceResult?.[0]?.toNumber()
const tokenIdsArgs = useMemo(() => { const tokenIdsArgs = useMemo(() => {
if (accountBalance && account) { if (accountBalance && account) {
@ -88,11 +88,7 @@ export function useV3Positions(account: string | null | undefined): UseV3Positio
return [] return []
}, [account, accountBalance]) }, [account, accountBalance])
const tokenIdResults = useSingleContractMultipleData( const tokenIdResults = useSingleContractMultipleData(positionManager, 'tokenOfOwnerByIndex', tokenIdsArgs)
positionManager ?? undefined,
'tokenOfOwnerByIndex',
tokenIdsArgs
)
const tokenIds = useMemo(() => { const tokenIds = useMemo(() => {
if (account) { if (account) {

@ -73,6 +73,7 @@ const ResponsiveButtonPrimary = styled(ButtonPrimary)`
width: 49%; width: 49%;
`}; `};
` `
const MainContentWrapper = styled.main` const MainContentWrapper = styled.main`
background-color: ${({ theme }) => theme.bg0}; background-color: ${({ theme }) => theme.bg0};
padding: 16px; padding: 16px;
@ -80,17 +81,14 @@ const MainContentWrapper = styled.main`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
` `
export default function Pool() { export default function Pool() {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const toggleWalletModal = useWalletModalToggle() const toggleWalletModal = useWalletModalToggle()
const { t } = useTranslation() const { t } = useTranslation()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const { error, positions } = useV3Positions(account) const { positions } = useV3Positions(account)
if (error) {
console.error('error fetching v3 positions', error)
}
const hasPositions = useMemo(() => Boolean(positions && positions.length > 0), [positions]) const hasPositions = useMemo(() => Boolean(positions && positions.length > 0), [positions])
@ -131,6 +129,7 @@ export default function Pool() {
external: false, external: false,
}) })
} }
return ( return (
<> <>
<PageWrapper> <PageWrapper>

@ -1,8 +1,9 @@
import { Contract } from '@ethersproject/contracts' import { BigNumber } from 'ethers'
import { useEffect, useMemo, useRef } from 'react' import { useEffect, useMemo, useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { Multicall2 } from '../../abis/types'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useMulticallContract } from '../../hooks/useContract' import { useMulticall2Contract } from '../../hooks/useContract'
import useDebounce from '../../hooks/useDebounce' import useDebounce from '../../hooks/useDebounce'
import chunkArray from '../../utils/chunkArray' import chunkArray from '../../utils/chunkArray'
import { CancelledError, retry, RetryableError } from '../../utils/retry' import { CancelledError, retry, RetryableError } from '../../utils/retry'
@ -18,20 +19,24 @@ import {
/** /**
* Fetches a chunk of calls, enforcing a minimum block number constraint * Fetches a chunk of calls, enforcing a minimum block number constraint
* @param multicallContract multicall contract to fetch against * @param multicall2Contract multicall contract to fetch against
* @param chunk chunk of calls to make * @param chunk chunk of calls to make
* @param minBlockNumber minimum block number of the result set * @param minBlockNumber minimum block number of the result set
*/ */
async function fetchChunk( async function fetchChunk(
multicallContract: Contract, multicall2Contract: Multicall2,
chunk: Call[], chunk: Call[],
minBlockNumber: number minBlockNumber: number
): Promise<{ results: string[]; blockNumber: number }> { ): Promise<{
console.debug('Fetching chunk', multicallContract, chunk, minBlockNumber) results: { success: boolean; returnData: string }[]
let resultsBlockNumber, returnData blockNumber: number
}> {
console.debug('Fetching chunk', multicall2Contract, chunk, minBlockNumber)
let resultsBlockNumber: BigNumber
let results: { success: boolean; returnData: string }[]
try { try {
;[resultsBlockNumber, returnData] = await multicallContract.callStatic.aggregate( ;[resultsBlockNumber, , results] = await multicall2Contract.callStatic.blockAndAggregate(
chunk.map((obj) => [obj.address, obj.callData]) chunk.map((obj) => ({ target: obj.address, callData: obj.callData }))
) )
} catch (error) { } catch (error) {
console.debug('Failed to fetch chunk inside retry', error) console.debug('Failed to fetch chunk inside retry', error)
@ -41,7 +46,7 @@ async function fetchChunk(
console.debug(`Fetched results for old block number: ${resultsBlockNumber.toString()} vs. ${minBlockNumber}`) console.debug(`Fetched results for old block number: ${resultsBlockNumber.toString()} vs. ${minBlockNumber}`)
throw new RetryableError('Fetched for old block number') throw new RetryableError('Fetched for old block number')
} }
return { results: returnData, blockNumber: resultsBlockNumber.toNumber() } return { results, blockNumber: resultsBlockNumber.toNumber() }
} }
/** /**
@ -116,7 +121,7 @@ export default function Updater(): null {
const debouncedListeners = useDebounce(state.callListeners, 100) const debouncedListeners = useDebounce(state.callListeners, 100)
const latestBlockNumber = useBlockNumber() const latestBlockNumber = useBlockNumber()
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const multicallContract = useMulticallContract() const multicall2Contract = useMulticall2Contract()
const cancellations = useRef<{ blockNumber: number; cancellations: (() => void)[] }>() const cancellations = useRef<{ blockNumber: number; cancellations: (() => void)[] }>()
const listeningKeys: { [callKey: string]: number } = useMemo(() => { const listeningKeys: { [callKey: string]: number } = useMemo(() => {
@ -132,7 +137,7 @@ export default function Updater(): null {
]) ])
useEffect(() => { useEffect(() => {
if (!latestBlockNumber || !chainId || !multicallContract) return if (!latestBlockNumber || !chainId || !multicall2Contract) return
const outdatedCallKeys: string[] = JSON.parse(serializedOutdatedCallKeys) const outdatedCallKeys: string[] = JSON.parse(serializedOutdatedCallKeys)
if (outdatedCallKeys.length === 0) return if (outdatedCallKeys.length === 0) return
@ -155,7 +160,7 @@ export default function Updater(): null {
cancellations.current = { cancellations.current = {
blockNumber: latestBlockNumber, blockNumber: latestBlockNumber,
cancellations: chunkedCalls.map((chunk, index) => { cancellations: chunkedCalls.map((chunk, index) => {
const { cancel, promise } = retry(() => fetchChunk(multicallContract, chunk, latestBlockNumber), { const { cancel, promise } = retry(() => fetchChunk(multicall2Contract, chunk, latestBlockNumber), {
n: Infinity, n: Infinity,
minWait: 2500, minWait: 2500,
maxWait: 3500, maxWait: 3500,
@ -168,18 +173,29 @@ export default function Updater(): null {
const firstCallKeyIndex = chunkedCalls.slice(0, index).reduce<number>((memo, curr) => memo + curr.length, 0) const firstCallKeyIndex = chunkedCalls.slice(0, index).reduce<number>((memo, curr) => memo + curr.length, 0)
const lastCallKeyIndex = firstCallKeyIndex + returnData.length const lastCallKeyIndex = firstCallKeyIndex + returnData.length
const slice = outdatedCallKeys.slice(firstCallKeyIndex, lastCallKeyIndex)
dispatch( dispatch(
updateMulticallResults({ updateMulticallResults({
chainId, chainId,
results: outdatedCallKeys results: slice.reduce<{ [callKey: string]: string | null }>((memo, callKey, i) => {
.slice(firstCallKeyIndex, lastCallKeyIndex) if (returnData[i].success) {
.reduce<{ [callKey: string]: string | null }>((memo, callKey, i) => { memo[callKey] = returnData[i].returnData ?? null
memo[callKey] = returnData[i] ?? null }
return memo return memo
}, {}), }, {}),
blockNumber: fetchBlockNumber, blockNumber: fetchBlockNumber,
}) })
) )
// todo: dispatch an error for each call that failed, i.e. returnData[i].success === false
// dispatch(
// errorFetchingMulticallResults({
// calls: // todo: compute this,
// chainId,
// fetchingBlockNumber: latestBlockNumber,
// })
// )
}) })
.catch((error: any) => { .catch((error: any) => {
if (error instanceof CancelledError) { if (error instanceof CancelledError) {
@ -198,7 +214,7 @@ export default function Updater(): null {
return cancel return cancel
}), }),
} }
}, [chainId, multicallContract, dispatch, serializedOutdatedCallKeys, latestBlockNumber]) }, [chainId, multicall2Contract, dispatch, serializedOutdatedCallKeys, latestBlockNumber])
return null return null
} }

@ -3,7 +3,7 @@ import { JSBI } from '@uniswap/v2-sdk'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useAllTokens } from '../../hooks/Tokens' import { useAllTokens } from '../../hooks/Tokens'
import { useMulticallContract } from '../../hooks/useContract' import { useMulticall2Contract } from '../../hooks/useContract'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import { useUserUnclaimedAmount } from '../claim/hooks' import { useUserUnclaimedAmount } from '../claim/hooks'
import { useMultipleContractSingleData, useSingleContractMultipleData } from '../multicall/hooks' import { useMultipleContractSingleData, useSingleContractMultipleData } from '../multicall/hooks'
@ -18,7 +18,7 @@ import { Erc20Interface } from 'abis/types/Erc20'
export function useETHBalances( export function useETHBalances(
uncheckedAddresses?: (string | undefined)[] uncheckedAddresses?: (string | undefined)[]
): { [address: string]: CurrencyAmount | undefined } { ): { [address: string]: CurrencyAmount | undefined } {
const multicallContract = useMulticallContract() const multicallContract = useMulticall2Contract()
const addresses: string[] = useMemo( const addresses: string[] = useMemo(
() => () =>