Skip to content

Commit

Permalink
perf(ethereum): reduce number of calls by batching all polling node c…
Browse files Browse the repository at this point in the history
…alls (Uniswap#840)

* initial refactoring

* rebase lint error

* start implementing reducer

* multicall reducer

* working multicall!

* clean up performance, re-fix annoying error

* use multicall everywhere

* use multicall for balances

* fix lint warning

* Use checksummed address

* Fix strict warning

* get it to a working state with the more generic form

* convert useETHBalances

* Remove the eth-scan contract completely

* Remove the eth-scan contract completely more

* Default export

* Put the encoding/decoding in the methods that can do it most efficiently

* Avoid duplicate fetches via debounce

* Reduce delay to something less noticeable

* Return null if pair reserves are undefined to indicate it does not exist
  • Loading branch information
moodysalem authored May 29, 2020
1 parent 3c10b57 commit 17b4ff4
Show file tree
Hide file tree
Showing 48 changed files with 881 additions and 735 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ change `REACT_APP_NETWORK_ID` to `"{yourNetworkId}"`, and change `REACT_APP_NETW

Note that the front end only works properly on testnets where both
[Uniswap V2](https://uniswap.org/docs/v2/smart-contracts/factory/) and
[eth-scan](/~https://github.com/MyCryptoHQ/eth-scan) are deployed.
[multicall](/~https://github.com/makerdao/multicall) are deployed.
The frontend will not work on other networks.

## Contributions
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"@ethersproject/units": "^5.0.0-beta.132",
"@ethersproject/wallet": "^5.0.0-beta.141",
"@material-ui/core": "^4.9.5",
"@mycrypto/eth-scan": "^2.1.0",
"@popperjs/core": "^2.4.0",
"@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3",
Expand Down Expand Up @@ -79,7 +78,6 @@
"serve": "^11.3.0",
"start-server-and-test": "^1.11.0",
"styled-components": "^4.2.0",
"swr": "0.1.18",
"typescript": "^3.8.3",
"use-media": "^1.4.0"
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/AccountDetails/Copy.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import styled from 'styled-components'
import { useCopyClipboard } from '../../hooks'
import useCopyClipboard from '../../hooks/useCopyClipboard'

import { Link } from '../../theme'
import { CheckCircle, Copy } from 'react-feather'
Expand Down
3 changes: 2 additions & 1 deletion src/components/AddressInputPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useState, useEffect, useContext } from 'react'
import styled, { ThemeContext } from 'styled-components'
import useDebounce from '../../hooks/useDebounce'

import { isAddress } from '../../utils'
import { useActiveWeb3React, useDebounce } from '../../hooks'
import { useActiveWeb3React } from '../../hooks'
import { Link, TYPE } from '../../theme'
import { AutoColumn } from '../Column'
import { RowBetween } from '../Row'
Expand Down
2 changes: 1 addition & 1 deletion src/components/Menu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React, { useRef, useEffect } from 'react'
import { Info, BookOpen, Code, PieChart, MessageCircle } from 'react-feather'
import styled from 'styled-components'
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
import useToggle from '../../hooks/useToggle'

import { Link } from '../../theme'
import { useToggle } from '../../hooks'

const StyledMenuIcon = styled(MenuIcon)`
path {
Expand Down
9 changes: 6 additions & 3 deletions src/components/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import '@reach/dialog/styles.css'
import { transparentize } from 'polished'
import { useGesture } from 'react-use-gesture'

// errors emitted, fix with /~https://github.com/styled-components/styled-components/pull/3006
const AnimatedDialogOverlay = animated(DialogOverlay)
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ mobile: boolean }>`
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const StyledDialogOverlay = styled(({ mobile, ...rest }) => <AnimatedDialogOverlay {...rest} />)<{ mobile: boolean }>`
&[data-reach-dialog-overlay] {
z-index: 2;
display: flex;
Expand Down Expand Up @@ -41,7 +41,9 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ mobile: boolean }>`

// destructure to not pass custom props to Dialog DOM element
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => <DialogContent {...rest} />)`
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => (
<DialogContent aria-label="content" {...rest} />
))`
&[data-reach-dialog-content] {
margin: 0 0 2rem 0;
border: 1px solid ${({ theme }) => theme.bg1};
Expand Down Expand Up @@ -163,6 +165,7 @@ export default function Modal({
}}
>
<StyledDialogContent
ariaLabel="test"
style={props}
hidden={true}
minHeight={minHeight}
Expand Down
3 changes: 1 addition & 2 deletions src/components/NavigationTabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ import styled from 'styled-components'
import { darken } from 'polished'
import { useTranslation } from 'react-i18next'
import { withRouter, NavLink, Link as HistoryLink, RouteComponentProps } from 'react-router-dom'
import useBodyKeyDown from '../../hooks/useBodyKeyDown'

import { CursorPointer } from '../../theme'
import { ArrowLeft } from 'react-feather'
import { RowBetween } from '../Row'
import QuestionHelper from '../QuestionHelper'

import { useBodyKeyDown } from '../../hooks'

const tabOrder = [
{
path: '/swap',
Expand Down
2 changes: 1 addition & 1 deletion src/components/SearchModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function SearchModal({

const allTokens = useAllTokens()
const allPairs = useAllDummyPairs()
const allTokenBalances = useAllTokenBalancesTreatingWETHasETH()[account] ?? {}
const allTokenBalances = useAllTokenBalancesTreatingWETHasETH() ?? {}
const allPairBalances = useTokenBalances(
account,
allPairs.map(p => p.liquidityToken)
Expand Down
4 changes: 2 additions & 2 deletions src/components/SearchModal/sorting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ function getTokenComparator(
}

export function useTokenComparator(inverted: boolean): (tokenA: Token, tokenB: Token) => number {
const { account, chainId } = useActiveWeb3React()
const { chainId } = useActiveWeb3React()
const weth = WETH[chainId]
const balances = useAllTokenBalancesTreatingWETHasETH()
const comparator = useMemo(() => getTokenComparator(weth, balances[account] ?? {}), [account, balances, weth])
const comparator = useMemo(() => getTokenComparator(weth, balances ?? {}), [balances, weth])
return useMemo(() => {
if (inverted) {
return (tokenA: Token, tokenB: Token) => comparator(tokenA, tokenB) * -1
Expand Down
2 changes: 1 addition & 1 deletion src/components/WalletModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import styled from 'styled-components'
import { isMobile } from 'react-device-detect'
import { UnsupportedChainIdError, useWeb3React } from '@web3-react/core'
import { URI_AVAILABLE } from '@web3-react/walletconnect-connector'
import usePrevious from '../../hooks/usePrevious'
import { useWalletModalOpen, useWalletModalToggle } from '../../state/application/hooks'

import Modal from '../Modal'
import AccountDetails from '../AccountDetails'
import PendingView from './PendingView'
import Option from './Option'
import { SUPPORTED_WALLETS } from '../../constants'
import { usePrevious } from '../../hooks'
import { Link } from '../../theme'
import MetamaskIcon from '../../assets/images/metamask.png'
import { ReactComponent as Close } from '../../assets/images/x.svg'
Expand Down
2 changes: 1 addition & 1 deletion src/components/Web3Status/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
import { useWeb3React, UnsupportedChainIdError } from '@web3-react/core'
import { darken, lighten } from 'polished'
import { Activity } from 'react-feather'
import useENSName from '../../hooks/useENSName'
import { useWalletModalToggle } from '../../state/application/hooks'
import { TransactionDetails } from '../../state/transactions/reducer'

Expand All @@ -19,7 +20,6 @@ import { Spinner } from '../../theme'
import LightCircle from '../../assets/svg/lightcircle.svg'

import { RowBetween } from '../Row'
import { useENSName } from '../../hooks'
import { shortenAddress } from '../../utils'
import { useAllTransactions } from '../../state/transactions/hooks'
import { NetworkContextName } from '../../constants'
Expand Down
6 changes: 6 additions & 0 deletions src/constants/abis/erc20.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Interface } from '@ethersproject/abi'
import ERC20_ABI from './erc20.json'

const ERC20_INTERFACE = new Interface(ERC20_ABI)

export default ERC20_INTERFACE
143 changes: 143 additions & 0 deletions src/constants/multicall/abi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
[
{
"constant": true,
"inputs": [],
"name": "getCurrentBlockTimestamp",
"outputs": [
{
"name": "timestamp",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"components": [
{
"name": "target",
"type": "address"
},
{
"name": "callData",
"type": "bytes"
}
],
"name": "calls",
"type": "tuple[]"
}
],
"name": "aggregate",
"outputs": [
{
"name": "blockNumber",
"type": "uint256"
},
{
"name": "returnData",
"type": "bytes[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getLastBlockHash",
"outputs": [
{
"name": "blockHash",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "addr",
"type": "address"
}
],
"name": "getEthBalance",
"outputs": [
{
"name": "balance",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getCurrentBlockDifficulty",
"outputs": [
{
"name": "difficulty",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getCurrentBlockGasLimit",
"outputs": [
{
"name": "gaslimit",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getCurrentBlockCoinbase",
"outputs": [
{
"name": "coinbase",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "blockNumber",
"type": "uint256"
}
],
"name": "getBlockHash",
"outputs": [
{
"name": "blockHash",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
12 changes: 12 additions & 0 deletions src/constants/multicall/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ChainId } from '@uniswap/sdk'
import MULTICALL_ABI from './abi.json'

const MULTICALL_NETWORKS: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441',
[ChainId.ROPSTEN]: '0x53C43764255c17BD724F74c4eF150724AC50a3ed',
[ChainId.KOVAN]: '0x2cc8688C5f75E365aaEEb4ea8D6a480405A48D2A',
[ChainId.RINKEBY]: '0x42Ad527de7d4e9d9d011aC45B31D8551f8Fe9821',
[ChainId.GÖRLI]: '0x77dCa2C955b15e9dE4dbBCf1246B4B85b651e50e'
}

export { MULTICALL_ABI, MULTICALL_NETWORKS }
29 changes: 10 additions & 19 deletions src/data/Allowances.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
import { Contract } from '@ethersproject/contracts'
import { Token, TokenAmount } from '@uniswap/sdk'
import useSWR from 'swr'
import { useMemo } from 'react'

import { SWRKeys, useKeepSWRDataLiveAsBlocksArrive } from '.'
import { useTokenContract } from '../hooks'
import { useTokenContract } from '../hooks/useContract'
import { useSingleCallResult } from '../state/multicall/hooks'

function getTokenAllowance(contract: Contract, token: Token): (owner: string, spender: string) => Promise<TokenAmount> {
return async (owner: string, spender: string): Promise<TokenAmount> =>
contract
.allowance(owner, spender)
.then((balance: { toString: () => string }) => new TokenAmount(token, balance.toString()))
}

export function useTokenAllowance(token?: Token, owner?: string, spender?: string): TokenAmount {
export function useTokenAllowance(token?: Token, owner?: string, spender?: string): TokenAmount | undefined {
const contract = useTokenContract(token?.address, false)

const shouldFetch = !!contract && typeof owner === 'string' && typeof spender === 'string'
const { data, mutate } = useSWR(
shouldFetch ? [owner, spender, token.address, token.chainId, SWRKeys.Allowances] : null,
getTokenAllowance(contract, token)
)
useKeepSWRDataLiveAsBlocksArrive(mutate)
const inputs = useMemo(() => [owner, spender], [owner, spender])
const allowance = useSingleCallResult(contract, 'allowance', inputs)

return data
return useMemo(() => (token && allowance ? new TokenAmount(token, allowance.toString()) : undefined), [
token,
allowance
])
}
Loading

0 comments on commit 17b4ff4

Please sign in to comment.