Commit 7aa0f500 authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

feat: mv block number to atom (#3108)

* feat: mv block number to atom

* fix: add block updater

* fix: fast forward dep
parent 06a8151e
...@@ -7,9 +7,9 @@ import useGasPrice from 'hooks/useGasPrice' ...@@ -7,9 +7,9 @@ import useGasPrice from 'hooks/useGasPrice'
import useMachineTimeMs from 'hooks/useMachineTime' import useMachineTimeMs from 'hooks/useMachineTime'
import useTheme from 'hooks/useTheme' import useTheme from 'hooks/useTheme'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro' import ms from 'ms.macro'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useBlockNumber } from 'state/application/hooks'
import styled, { keyframes } from 'styled-components/macro' import styled, { keyframes } from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme' import { ExternalLink, ThemedText } from 'theme'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink' import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
......
import { skipToken } from '@reduxjs/toolkit/query/react' import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency, Token } from '@uniswap/sdk-core' import { Currency, Token } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk' import { FeeAmount } from '@uniswap/v3-sdk'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro' import ms from 'ms.macro'
import { useMemo } from 'react' import { useMemo } from 'react'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { useBlockNumber } from 'state/application/hooks'
import { useFeeTierDistributionQuery } from 'state/data/enhanced' import { useFeeTierDistributionQuery } from 'state/data/enhanced'
import { FeeTierDistributionQuery } from 'state/data/generated' import { FeeTierDistributionQuery } from 'state/data/generated'
......
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Pool } from '@uniswap/v3-sdk' import { Pool } from '@uniswap/v3-sdk'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useBlockNumber } from 'state/application/hooks'
import { useSingleCallResult } from 'state/multicall/hooks' import { useSingleCallResult } from 'state/multicall/hooks'
import { unwrappedToken } from 'utils/unwrappedToken' import { unwrappedToken } from 'utils/unwrappedToken'
......
...@@ -4,6 +4,7 @@ import 'polyfills' ...@@ -4,6 +4,7 @@ import 'polyfills'
import 'components/analytics' import 'components/analytics'
import { createWeb3ReactRoot, Web3ReactProvider } from '@web3-react/core' import { createWeb3ReactRoot, Web3ReactProvider } from '@web3-react/core'
import { BlockUpdater } from 'lib/hooks/useBlockNumber'
import { StrictMode } from 'react' import { StrictMode } from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
...@@ -39,6 +40,7 @@ function Updaters() { ...@@ -39,6 +40,7 @@ function Updaters() {
<UserUpdater /> <UserUpdater />
<ApplicationUpdater /> <ApplicationUpdater />
<TransactionUpdater /> <TransactionUpdater />
<BlockUpdater />
<MulticallUpdater /> <MulticallUpdater />
<LogsUpdater /> <LogsUpdater />
</> </>
......
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useDebounce from 'hooks/useDebounce'
import useIsWindowVisible from 'hooks/useIsWindowVisible'
import { atom } from 'jotai'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { useCallback, useEffect, useState } from 'react'
function useBlock() {
const { chainId, library } = useActiveWeb3React()
const windowVisible = useIsWindowVisible()
const [state, setState] = useState<{ chainId?: number; block?: number }>({ chainId })
const onBlock = useCallback(
(block: number) => {
setState((state) => {
if (state.chainId === chainId) {
if (typeof state.block !== 'number') return { chainId, block }
return { chainId, block: Math.max(block, state.block) }
}
return state
})
},
[chainId]
)
useEffect(() => {
if (library && chainId && windowVisible) {
setState({ chainId })
library
.getBlockNumber()
.then(onBlock)
.catch((error) => {
console.error(`Failed to get block number for chainId ${chainId}`, error)
})
library.on('block', onBlock)
return () => {
library.removeListener('block', onBlock)
}
}
return undefined
}, [chainId, library, onBlock, windowVisible])
const debouncedBlock = useDebounce(state.block, 100)
return state.block ? debouncedBlock : undefined
}
const blockAtom = atom<number | undefined>(undefined)
export function BlockUpdater() {
const setBlock = useUpdateAtom(blockAtom)
const block = useBlock()
useEffect(() => {
setBlock(block)
}, [block, setBlock])
return null
}
/** Requires that BlockUpdater be installed in the DOM tree. */
export default function useBlockNumber(): number | undefined {
const { chainId } = useActiveWeb3React()
const block = useAtomValue(blockAtom)
return chainId ? block : undefined
}
export function useFastForwardBlockNumber(): (block: number) => void {
return useUpdateAtom(blockAtom)
}
...@@ -6,6 +6,7 @@ import { useActiveLocale } from 'hooks/useActiveLocale' ...@@ -6,6 +6,7 @@ import { useActiveLocale } from 'hooks/useActiveLocale'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { useState } from 'react' import { useState } from 'react'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
...@@ -27,7 +28,7 @@ import { ...@@ -27,7 +28,7 @@ import {
} from '../../constants/governance' } from '../../constants/governance'
import { ZERO_ADDRESS } from '../../constants/misc' import { ZERO_ADDRESS } from '../../constants/misc'
import { UNI } from '../../constants/tokens' import { UNI } from '../../constants/tokens'
import { useBlockNumber, useModalOpen, useToggleDelegateModal, useToggleVoteModal } from '../../state/application/hooks' import { useModalOpen, useToggleDelegateModal, useToggleVoteModal } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer' import { ApplicationModal } from '../../state/application/reducer'
import { import {
ProposalData, ProposalData,
......
import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc' import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks' import { useAppDispatch, useAppSelector } from 'state/hooks'
import { AppState } from '../index' import { AppState } from '../index'
import { addPopup, ApplicationModal, PopupContent, removePopup, setOpenModal } from './reducer' import { addPopup, ApplicationModal, PopupContent, removePopup, setOpenModal } from './reducer'
export function useBlockNumber(): number | undefined {
const { chainId } = useActiveWeb3React()
return useAppSelector((state: AppState) => state.application.blockNumber[chainId ?? -1])
}
export function useModalOpen(modal: ApplicationModal): boolean { export function useModalOpen(modal: ApplicationModal): boolean {
const openModal = useAppSelector((state: AppState) => state.application.openModal) const openModal = useAppSelector((state: AppState) => state.application.openModal)
return openModal === modal return openModal === modal
......
...@@ -6,7 +6,6 @@ import reducer, { ...@@ -6,7 +6,6 @@ import reducer, {
ApplicationState, ApplicationState,
removePopup, removePopup,
setOpenModal, setOpenModal,
updateBlockNumber,
updateChainId, updateChainId,
} from './reducer' } from './reducer'
...@@ -15,9 +14,6 @@ describe('application reducer', () => { ...@@ -15,9 +14,6 @@ describe('application reducer', () => {
beforeEach(() => { beforeEach(() => {
store = createStore(reducer, { store = createStore(reducer, {
blockNumber: {
1: 3,
},
chainId: null, chainId: null,
openModal: null, openModal: null,
popupList: [], popupList: [],
...@@ -70,24 +66,6 @@ describe('application reducer', () => { ...@@ -70,24 +66,6 @@ describe('application reducer', () => {
}) })
}) })
describe('updateBlockNumber', () => {
it('updates block number', () => {
store.dispatch(updateBlockNumber({ chainId: 1, blockNumber: 4 }))
expect(store.getState().blockNumber[1]).toEqual(4)
})
it('no op if late', () => {
store.dispatch(updateBlockNumber({ chainId: 1, blockNumber: 2 }))
expect(store.getState().blockNumber[1]).toEqual(3)
})
it('works with non-set chains', () => {
store.dispatch(updateBlockNumber({ chainId: 3, blockNumber: 2 }))
expect(store.getState().blockNumber).toEqual({
1: 3,
3: 2,
})
})
})
describe('removePopup', () => { describe('removePopup', () => {
beforeEach(() => { beforeEach(() => {
store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'abc' } } })) store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'abc' } } }))
......
...@@ -30,14 +30,12 @@ export enum ApplicationModal { ...@@ -30,14 +30,12 @@ export enum ApplicationModal {
type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }> type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>
export interface ApplicationState { export interface ApplicationState {
readonly blockNumber: { readonly [chainId: number]: number }
readonly chainId: number | null readonly chainId: number | null
readonly openModal: ApplicationModal | null readonly openModal: ApplicationModal | null
readonly popupList: PopupList readonly popupList: PopupList
} }
const initialState: ApplicationState = { const initialState: ApplicationState = {
blockNumber: {},
chainId: null, chainId: null,
openModal: null, openModal: null,
popupList: [], popupList: [],
...@@ -51,14 +49,6 @@ const applicationSlice = createSlice({ ...@@ -51,14 +49,6 @@ const applicationSlice = createSlice({
const { chainId } = action.payload const { chainId } = action.payload
state.chainId = chainId state.chainId = chainId
}, },
updateBlockNumber(state, action) {
const { chainId, blockNumber } = action.payload
if (typeof state.blockNumber[chainId] !== 'number') {
state.blockNumber[chainId] = blockNumber
} else {
state.blockNumber[chainId] = Math.max(blockNumber, state.blockNumber[chainId])
}
},
setOpenModal(state, action) { setOpenModal(state, action) {
state.openModal = action.payload state.openModal = action.payload
}, },
...@@ -82,5 +72,5 @@ const applicationSlice = createSlice({ ...@@ -82,5 +72,5 @@ const applicationSlice = createSlice({
}, },
}) })
export const { updateChainId, updateBlockNumber, setOpenModal, addPopup, removePopup } = applicationSlice.actions export const { updateChainId, setOpenModal, addPopup, removePopup } = applicationSlice.actions
export default applicationSlice.reducer export default applicationSlice.reducer
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import useIsWindowVisible from 'hooks/useIsWindowVisible' import useIsWindowVisible from 'hooks/useIsWindowVisible'
import { useCallback, useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { api, CHAIN_TAG } from 'state/data/enhanced' import { api, CHAIN_TAG } from 'state/data/enhanced'
import { useAppDispatch, useAppSelector } from 'state/hooks' import { useAppDispatch, useAppSelector } from 'state/hooks'
import { supportedChainId } from 'utils/supportedChainId' import { supportedChainId } from 'utils/supportedChainId'
import { updateBlockNumber, updateChainId } from './reducer' import { updateChainId } from './reducer'
function useQueryCacheInvalidator() { function useQueryCacheInvalidator() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
...@@ -26,55 +26,22 @@ export default function Updater(): null { ...@@ -26,55 +26,22 @@ export default function Updater(): null {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const windowVisible = useIsWindowVisible() const windowVisible = useIsWindowVisible()
const [state, setState] = useState<{ chainId: number | undefined; blockNumber: number | null }>({ const [activeChainId, setActiveChainId] = useState(chainId)
chainId,
blockNumber: null,
})
useQueryCacheInvalidator() useQueryCacheInvalidator()
const blockNumberCallback = useCallback(
(blockNumber: number) => {
setState((state) => {
if (chainId === state.chainId) {
if (typeof state.blockNumber !== 'number') return { chainId, blockNumber }
return { chainId, blockNumber: Math.max(blockNumber, state.blockNumber) }
}
return state
})
},
[chainId, setState]
)
// attach/detach listeners
useEffect(() => { useEffect(() => {
if (!library || !chainId || !windowVisible) return undefined if (library && chainId && windowVisible) {
setActiveChainId(chainId)
setState({ chainId, blockNumber: null })
library
.getBlockNumber()
.then(blockNumberCallback)
.catch((error) => console.error(`Failed to get block number for chainId: ${chainId}`, error))
library.on('block', blockNumberCallback)
return () => {
library.removeListener('block', blockNumberCallback)
} }
}, [dispatch, chainId, library, blockNumberCallback, windowVisible]) }, [dispatch, chainId, library, windowVisible])
const debouncedState = useDebounce(state, 100)
useEffect(() => { const debouncedChainId = useDebounce(activeChainId, 100)
if (!debouncedState.chainId || !debouncedState.blockNumber || !windowVisible) return
dispatch(updateBlockNumber({ chainId: debouncedState.chainId, blockNumber: debouncedState.blockNumber }))
}, [windowVisible, dispatch, debouncedState.blockNumber, debouncedState.chainId])
useEffect(() => { useEffect(() => {
dispatch( const chainId = debouncedChainId ? supportedChainId(debouncedChainId) ?? null : null
updateChainId({ chainId: debouncedState.chainId ? supportedChainId(debouncedState.chainId) ?? null : null }) dispatch(updateChainId({ chainId }))
) }, [dispatch, debouncedChainId])
}, [dispatch, debouncedState.chainId])
return null return null
} }
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { useEffect, useMemo } from 'react' import { useEffect, useMemo } from 'react'
import { useBlockNumber } from '../application/hooks'
import { useAppDispatch, useAppSelector } from '../hooks' import { useAppDispatch, useAppSelector } from '../hooks'
import { addListener, removeListener } from './slice' import { addListener, removeListener } from './slice'
import { EventFilter, filterToKey, Log } from './utils' import { EventFilter, filterToKey, Log } from './utils'
......
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { useEffect, useMemo } from 'react' import { useEffect, useMemo } from 'react'
import { useBlockNumber } from '../application/hooks'
import { useAppDispatch, useAppSelector } from '../hooks' import { useAppDispatch, useAppSelector } from '../hooks'
import { fetchedLogs, fetchedLogsError, fetchingLogs } from './slice' import { fetchedLogs, fetchedLogsError, fetchingLogs } from './slice'
import { EventFilter, keyToFilter } from './utils' import { EventFilter, keyToFilter } from './utils'
......
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { SkipFirst } from '../../types/tuple' import { SkipFirst } from '../../types/tuple'
import { useBlockNumber } from '../application/hooks'
import { multicall } from './instance' import { multicall } from './instance'
export type { CallStateResult } from '@uniswap/redux-multicall' // re-export for convenience export type { CallStateResult } from '@uniswap/redux-multicall' // re-export for convenience
export { NEVER_RELOAD } from '@uniswap/redux-multicall' // re-export for convenience export { NEVER_RELOAD } from '@uniswap/redux-multicall' // re-export for convenience
......
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { useInterfaceMulticall } from '../../hooks/useContract' import { useInterfaceMulticall } from '../../hooks/useContract'
import { useBlockNumber } from '../application/hooks'
import { multicall } from './instance' import { multicall } from './instance'
// Create Updater wrappers that pull needed info from store // Create Updater wrappers that pull needed info from store
......
import { skipToken } from '@reduxjs/toolkit/query/react' import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { useStablecoinAmountFromFiatValue } from 'hooks/useUSDCPrice' import { useStablecoinAmountFromFiatValue } from 'hooks/useUSDCPrice'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro' import ms from 'ms.macro'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useBlockNumber } from 'state/application/hooks'
import { useGetQuoteQuery } from 'state/routing/slice' import { useGetQuoteQuery } from 'state/routing/slice'
import { useClientSideRouter } from 'state/user/hooks' import { useClientSideRouter } from 'state/user/hooks'
......
import { DEFAULT_TXN_DISMISS_MS, L2_TXN_DISMISS_MS } from 'constants/misc' import { DEFAULT_TXN_DISMISS_MS, L2_TXN_DISMISS_MS } from 'constants/misc'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useBlockNumber, { useFastForwardBlockNumber } from 'lib/hooks/useBlockNumber'
import { useCallback, useEffect, useMemo } from 'react' import { useCallback, useEffect, useMemo } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks' import { useAppDispatch, useAppSelector } from 'state/hooks'
import { L2_CHAIN_IDS, SupportedChainId } from '../../constants/chains' import { L2_CHAIN_IDS, SupportedChainId } from '../../constants/chains'
import { retry, RetryableError, RetryOptions } from '../../utils/retry' import { retry, RetryableError, RetryOptions } from '../../utils/retry'
import { useAddPopup, useBlockNumber } from '../application/hooks' import { useAddPopup } from '../application/hooks'
import { updateBlockNumber } from '../application/reducer'
import { checkedTransaction, finalizeTransaction } from './actions' import { checkedTransaction, finalizeTransaction } from './actions'
interface TxInterface { interface TxInterface {
...@@ -45,6 +45,7 @@ export default function Updater(): null { ...@@ -45,6 +45,7 @@ export default function Updater(): null {
const { chainId, library } = useActiveWeb3React() const { chainId, library } = useActiveWeb3React()
const lastBlockNumber = useBlockNumber() const lastBlockNumber = useBlockNumber()
const fastForwardBlockNumber = useFastForwardBlockNumber()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const state = useAppSelector((state) => state.transactions) const state = useAppSelector((state) => state.transactions)
...@@ -115,7 +116,7 @@ export default function Updater(): null { ...@@ -115,7 +116,7 @@ export default function Updater(): null {
// the receipt was fetched before the block, fast forward to that block to trigger balance updates // the receipt was fetched before the block, fast forward to that block to trigger balance updates
if (receipt.blockNumber > lastBlockNumber) { if (receipt.blockNumber > lastBlockNumber) {
dispatch(updateBlockNumber({ chainId, blockNumber: receipt.blockNumber })) fastForwardBlockNumber(receipt.blockNumber)
} }
} else { } else {
dispatch(checkedTransaction({ chainId, hash, blockNumber: lastBlockNumber })) dispatch(checkedTransaction({ chainId, hash, blockNumber: lastBlockNumber }))
...@@ -132,7 +133,7 @@ export default function Updater(): null { ...@@ -132,7 +133,7 @@ export default function Updater(): null {
return () => { return () => {
cancels.forEach((cancel) => cancel()) cancels.forEach((cancel) => cancel())
} }
}, [chainId, library, transactions, lastBlockNumber, dispatch, addPopup, getReceipt, isL2]) }, [chainId, library, transactions, lastBlockNumber, dispatch, addPopup, getReceipt, isL2, fastForwardBlockNumber])
return null return null
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment