Skip to content

Commit

Permalink
perf(multicall): add unit tests and fix a bug (#845)
Browse files Browse the repository at this point in the history
* start with the migrate page

* Add a bunch of tests and bump up the call size

* Show a link to the old portal, disable the WIP page

* Fix lint error
  • Loading branch information
moodysalem authored May 30, 2020
1 parent 320b2e3 commit 83554f4
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 45 deletions.
48 changes: 41 additions & 7 deletions src/data/V1.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ChainId, Pair, Percent, Route, Token, TokenAmount, Trade, TradeType, WETH } from '@uniswap/sdk'
import { ChainId, JSBI, Pair, Percent, Route, Token, TokenAmount, Trade, TradeType, WETH } from '@uniswap/sdk'
import { useMemo } from 'react'
import { useActiveWeb3React } from '../hooks'
import { useAllTokens } from '../hooks/Tokens'
import { useV1FactoryContract } from '../hooks/useContract'
import { NEVER_RELOAD, useSingleCallResult, useSingleContractMultipleData } from '../state/multicall/hooks'
import { useETHBalances, useTokenBalance } from '../state/wallet/hooks'
import { useETHBalances, useTokenBalance, useTokenBalances } from '../state/wallet/hooks'

function useV1PairAddress(tokenAddress?: string): string | undefined {
const contract = useV1FactoryContract()
Expand All @@ -29,17 +30,50 @@ function useMockV1Pair(token?: Token): MockV1Pair | undefined {
: undefined
}

// returns ALL v1 exchange addresses
export function useAllV1ExchangeAddresses(): string[] {
const factory = useV1FactoryContract()
const exchangeCount = useSingleCallResult(factory, 'tokenCount')?.result

const parsedCount = parseInt(exchangeCount?.toString() ?? '0')

const indices = [...Array(parsedCount).keys()].map(ix => [ix])
return (
useSingleContractMultipleData(factory, 'getTokenWithId', indices, NEVER_RELOAD)
?.map(({ result }) => result?.[0])
?.filter(x => x) ?? []
const indices = useMemo(() => [...Array(parsedCount).keys()].map(ix => [ix]), [parsedCount])
const data = useSingleContractMultipleData(factory, 'getTokenWithId', indices, NEVER_RELOAD)

return useMemo(() => data?.map(({ result }) => result?.[0])?.filter(x => x) ?? [], [data])
}

// returns all v1 exchange addresses in the user's token list
export function useAllTokenV1ExchangeAddresses(): string[] {
const allTokens = useAllTokens()
const factory = useV1FactoryContract()
const args = useMemo(() => Object.keys(allTokens).map(tokenAddress => [tokenAddress]), [allTokens])

const data = useSingleContractMultipleData(factory, 'getExchange', args, NEVER_RELOAD)

return useMemo(() => data?.map(({ result }) => result?.[0])?.filter(x => x) ?? [], [data])
}

// returns whether any of the tokens in the user's token list have liquidity on v1
export function useUserProbablyHasV1Liquidity(): boolean | undefined {
const exchangeAddresses = useAllTokenV1ExchangeAddresses()

const { account, chainId } = useActiveWeb3React()

const fakeTokens = useMemo(
() => (chainId ? exchangeAddresses.map(address => new Token(chainId, address, 18, 'UNI-V1')) : []),
[chainId, exchangeAddresses]
)

const balances = useTokenBalances(account ?? undefined, fakeTokens)

return useMemo(
() =>
Object.keys(balances).some(tokenAddress => {
const b = balances[tokenAddress]?.raw
return b && JSBI.greaterThan(b, JSBI.BigInt(0))
}),
[balances]
)
}

Expand Down
40 changes: 40 additions & 0 deletions src/pages/MigrateV1/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { JSBI, Token } from '@uniswap/sdk'
import React, { useMemo } from 'react'
import { RouteComponentProps } from 'react-router'
import { useAllV1ExchangeAddresses } from '../../data/V1'
import { useActiveWeb3React } from '../../hooks'
import { useTokenBalances } from '../../state/wallet/hooks'

const PLACEHOLDER_ACCOUNT = (
<div>
<h1>You must connect a wallet to use this tool.</h1>
</div>
)

/**
* Page component for migrating liquidity from V1
*/
export default function MigrateV1({}: RouteComponentProps) {
const { account, chainId } = useActiveWeb3React()
const v1ExchangeAddresses = useAllV1ExchangeAddresses()

const v1ExchangeTokens: Token[] = useMemo(() => {
return v1ExchangeAddresses.map(exchangeAddress => new Token(chainId, exchangeAddress, 18))
}, [chainId, v1ExchangeAddresses])

const tokenBalances = useTokenBalances(account, v1ExchangeTokens)

const unmigratedExchangeAddresses = useMemo(
() =>
Object.keys(tokenBalances).filter(tokenAddress =>
tokenBalances[tokenAddress] ? JSBI.greaterThan(tokenBalances[tokenAddress]?.raw, JSBI.BigInt(0)) : false
),
[tokenBalances]
)

if (!account) {
return PLACEHOLDER_ACCOUNT
}

return <div>{unmigratedExchangeAddresses?.join('\n')}</div>
}
29 changes: 20 additions & 9 deletions src/pages/Pool/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { RouteComponentProps } from 'react-router-dom'
import Question from '../../components/QuestionHelper'
import SearchModal from '../../components/SearchModal'
import PositionCard from '../../components/PositionCard'
import { useUserProbablyHasV1Liquidity } from '../../data/V1'
import { useTokenBalances } from '../../state/wallet/hooks'
import { Link, TYPE } from '../../theme'
import { Text } from 'rebass'
Expand Down Expand Up @@ -58,6 +59,8 @@ export default function Pool({ history }: RouteComponentProps) {
return <PositionCardWrapper key={i} dummyPair={pair} />
})

const hasV1Liquidity = useUserProbablyHasV1Liquidity()

return (
<AppBody>
<AutoColumn gap="lg" justify="center">
Expand Down Expand Up @@ -92,15 +95,23 @@ export default function Pool({ history }: RouteComponentProps) {
)}
{filteredExchangeList}
<Text textAlign="center" fontSize={14} style={{ padding: '.5rem 0 .5rem 0' }}>
{filteredExchangeList?.length !== 0 ? `Don't see a pool you joined? ` : 'Already joined a pool? '}{' '}
<Link
id="import-pool-link"
onClick={() => {
history.push('/find')
}}
>
Import it.
</Link>
{!hasV1Liquidity ? (
<>
{filteredExchangeList?.length !== 0 ? `Don't see a pool you joined? ` : 'Already joined a pool? '}{' '}
<Link
id="import-pool-link"
onClick={() => {
history.push('/find')
}}
>
Import it.
</Link>
</>
) : (
<Link id="migrate-v1-liquidity-link" href="https://migrate.uniswap.exchange">
Migrate your V1 liquidity.
</Link>
)}
</Text>
</AutoColumn>
<FixedBottom>
Expand Down
83 changes: 83 additions & 0 deletions src/state/multicall/reducer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { addMulticallListeners, removeMulticallListeners } from './actions'
import reducer, { MulticallState } from './reducer'
import { Store, createStore } from '@reduxjs/toolkit'

describe('multicall reducer', () => {
let store: Store<MulticallState>
beforeEach(() => {
store = createStore(reducer)
})

it('has correct initial state', () => {
expect(store.getState().callResults).toEqual({})
expect(store.getState().callListeners).toEqual(undefined)
})

describe('addMulticallListeners', () => {
it('adds listeners', () => {
store.dispatch(
addMulticallListeners({
chainId: 1,
calls: [
{
address: '0x',
callData: '0x'
}
]
})
)
expect(store.getState()).toEqual({
callListeners: {
[1]: {
'0x-0x': {
[1]: 1
}
}
},
callResults: {}
})
})
})

describe('removeMulticallListeners', () => {
it('noop', () => {
store.dispatch(
removeMulticallListeners({
calls: [
{
address: '0x',
callData: '0x'
}
],
chainId: 1
})
)
expect(store.getState()).toEqual({ callResults: {}, callListeners: {} })
})
it('removes listeners', () => {
store.dispatch(
addMulticallListeners({
chainId: 1,
calls: [
{
address: '0x',
callData: '0x'
}
]
})
)
store.dispatch(
removeMulticallListeners({
calls: [
{
address: '0x',
callData: '0x'
}
],
chainId: 1
})
)
expect(store.getState()).toEqual({ callResults: {}, callListeners: { [1]: { '0x-0x': {} } } })
})
})
})
19 changes: 11 additions & 8 deletions src/state/multicall/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
updateMulticallResults
} from './actions'

interface MulticallState {
export interface MulticallState {
callListeners?: {
// on a per-chain basis
[chainId: number]: {
Expand All @@ -23,8 +23,8 @@ interface MulticallState {
callResults: {
[chainId: number]: {
[callKey: string]: {
data: string | null
blockNumber: number
data?: string | null
blockNumber?: number
fetchingBlockNumber?: number
}
}
Expand Down Expand Up @@ -74,10 +74,13 @@ export default createReducer(initialState, builder =>
calls.forEach(call => {
const callKey = toCallKey(call)
const current = state.callResults[chainId][callKey]
if (current && current.blockNumber > fetchingBlockNumber) return
state.callResults[chainId][callKey] = {
...state.callResults[chainId][callKey],
fetchingBlockNumber
if (!current) {
state.callResults[chainId][callKey] = {
fetchingBlockNumber
}
} else {
if (current.fetchingBlockNumber ?? 0 >= fetchingBlockNumber) return
state.callResults[chainId][callKey].fetchingBlockNumber = fetchingBlockNumber
}
})
})
Expand All @@ -94,7 +97,7 @@ export default createReducer(initialState, builder =>
state.callResults[chainId] = state.callResults[chainId] ?? {}
Object.keys(results).forEach(callKey => {
const current = state.callResults[chainId][callKey]
if (current && current.blockNumber > blockNumber) return
if (current?.blockNumber ?? 0 > blockNumber) return
state.callResults[chainId][callKey] = {
data: results[callKey],
blockNumber
Expand Down
Loading

0 comments on commit 83554f4

Please sign in to comment.