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'
import useMachineTimeMs from 'hooks/useMachineTime'
import useTheme from 'hooks/useTheme'
import JSBI from 'jsbi'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro'
import { useEffect, useState } from 'react'
import { useBlockNumber } from 'state/application/hooks'
import styled, { keyframes } from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
......
import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency, Token } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro'
import { useMemo } from 'react'
import ReactGA from 'react-ga'
import { useBlockNumber } from 'state/application/hooks'
import { useFeeTierDistributionQuery } from 'state/data/enhanced'
import { FeeTierDistributionQuery } from 'state/data/generated'
......
import { BigNumber } from '@ethersproject/bignumber'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Pool } from '@uniswap/v3-sdk'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { useEffect, useState } from 'react'
import { useBlockNumber } from 'state/application/hooks'
import { useSingleCallResult } from 'state/multicall/hooks'
import { unwrappedToken } from 'utils/unwrappedToken'
......
......@@ -4,6 +4,7 @@ import 'polyfills'
import 'components/analytics'
import { createWeb3ReactRoot, Web3ReactProvider } from '@web3-react/core'
import { BlockUpdater } from 'lib/hooks/useBlockNumber'
import { StrictMode } from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
......@@ -39,6 +40,7 @@ function Updaters() {
<UserUpdater />
<ApplicationUpdater />
<TransactionUpdater />
<BlockUpdater />
<MulticallUpdater />
<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'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import JSBI from 'jsbi'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { useState } from 'react'
import { ArrowLeft } from 'react-feather'
import ReactMarkdown from 'react-markdown'
......@@ -27,7 +28,7 @@ import {
} from '../../constants/governance'
import { ZERO_ADDRESS } from '../../constants/misc'
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 {
ProposalData,
......
import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCallback, useMemo } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { AppState } from '../index'
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 {
const openModal = useAppSelector((state: AppState) => state.application.openModal)
return openModal === modal
......
......@@ -6,7 +6,6 @@ import reducer, {
ApplicationState,
removePopup,
setOpenModal,
updateBlockNumber,
updateChainId,
} from './reducer'
......@@ -15,9 +14,6 @@ describe('application reducer', () => {
beforeEach(() => {
store = createStore(reducer, {
blockNumber: {
1: 3,
},
chainId: null,
openModal: null,
popupList: [],
......@@ -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', () => {
beforeEach(() => {
store.dispatch(addPopup({ key: 'abc', content: { txn: { hash: 'abc' } } }))
......
......@@ -30,14 +30,12 @@ export enum ApplicationModal {
type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>
export interface ApplicationState {
readonly blockNumber: { readonly [chainId: number]: number }
readonly chainId: number | null
readonly openModal: ApplicationModal | null
readonly popupList: PopupList
}
const initialState: ApplicationState = {
blockNumber: {},
chainId: null,
openModal: null,
popupList: [],
......@@ -51,14 +49,6 @@ const applicationSlice = createSlice({
const { chainId } = action.payload
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) {
state.openModal = action.payload
},
......@@ -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
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useDebounce from 'hooks/useDebounce'
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 { useAppDispatch, useAppSelector } from 'state/hooks'
import { supportedChainId } from 'utils/supportedChainId'
import { updateBlockNumber, updateChainId } from './reducer'
import { updateChainId } from './reducer'
function useQueryCacheInvalidator() {
const dispatch = useAppDispatch()
......@@ -26,55 +26,22 @@ export default function Updater(): null {
const dispatch = useAppDispatch()
const windowVisible = useIsWindowVisible()
const [state, setState] = useState<{ chainId: number | undefined; blockNumber: number | null }>({
chainId,
blockNumber: null,
})
const [activeChainId, setActiveChainId] = useState(chainId)
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(() => {
if (!library || !chainId || !windowVisible) return undefined
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)
if (library && chainId && windowVisible) {
setActiveChainId(chainId)
}
}, [dispatch, chainId, library, blockNumberCallback, windowVisible])
const debouncedState = useDebounce(state, 100)
}, [dispatch, chainId, library, windowVisible])
useEffect(() => {
if (!debouncedState.chainId || !debouncedState.blockNumber || !windowVisible) return
dispatch(updateBlockNumber({ chainId: debouncedState.chainId, blockNumber: debouncedState.blockNumber }))
}, [windowVisible, dispatch, debouncedState.blockNumber, debouncedState.chainId])
const debouncedChainId = useDebounce(activeChainId, 100)
useEffect(() => {
dispatch(
updateChainId({ chainId: debouncedState.chainId ? supportedChainId(debouncedState.chainId) ?? null : null })
)
}, [dispatch, debouncedState.chainId])
const chainId = debouncedChainId ? supportedChainId(debouncedChainId) ?? null : null
dispatch(updateChainId({ chainId }))
}, [dispatch, debouncedChainId])
return null
}
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { useEffect, useMemo } from 'react'
import { useBlockNumber } from '../application/hooks'
import { useAppDispatch, useAppSelector } from '../hooks'
import { addListener, removeListener } from './slice'
import { EventFilter, filterToKey, Log } from './utils'
......
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { useEffect, useMemo } from 'react'
import { useBlockNumber } from '../application/hooks'
import { useAppDispatch, useAppSelector } from '../hooks'
import { fetchedLogs, fetchedLogsError, fetchingLogs } from './slice'
import { EventFilter, keyToFilter } from './utils'
......
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { SkipFirst } from '../../types/tuple'
import { useBlockNumber } from '../application/hooks'
import { multicall } from './instance'
export type { CallStateResult } 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 useBlockNumber from 'lib/hooks/useBlockNumber'
import { useInterfaceMulticall } from '../../hooks/useContract'
import { useBlockNumber } from '../application/hooks'
import { multicall } from './instance'
// Create Updater wrappers that pull needed info from store
......
import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { useStablecoinAmountFromFiatValue } from 'hooks/useUSDCPrice'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro'
import { useMemo } from 'react'
import { useBlockNumber } from 'state/application/hooks'
import { useGetQuoteQuery } from 'state/routing/slice'
import { useClientSideRouter } from 'state/user/hooks'
......
import { DEFAULT_TXN_DISMISS_MS, L2_TXN_DISMISS_MS } from 'constants/misc'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useBlockNumber, { useFastForwardBlockNumber } from 'lib/hooks/useBlockNumber'
import { useCallback, useEffect, useMemo } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { L2_CHAIN_IDS, SupportedChainId } from '../../constants/chains'
import { retry, RetryableError, RetryOptions } from '../../utils/retry'
import { useAddPopup, useBlockNumber } from '../application/hooks'
import { updateBlockNumber } from '../application/reducer'
import { useAddPopup } from '../application/hooks'
import { checkedTransaction, finalizeTransaction } from './actions'
interface TxInterface {
......@@ -45,6 +45,7 @@ export default function Updater(): null {
const { chainId, library } = useActiveWeb3React()
const lastBlockNumber = useBlockNumber()
const fastForwardBlockNumber = useFastForwardBlockNumber()
const dispatch = useAppDispatch()
const state = useAppSelector((state) => state.transactions)
......@@ -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
if (receipt.blockNumber > lastBlockNumber) {
dispatch(updateBlockNumber({ chainId, blockNumber: receipt.blockNumber }))
fastForwardBlockNumber(receipt.blockNumber)
}
} else {
dispatch(checkedTransaction({ chainId, hash, blockNumber: lastBlockNumber }))
......@@ -132,7 +133,7 @@ export default function Updater(): null {
return () => {
cancels.forEach((cancel) => cancel())
}
}, [chainId, library, transactions, lastBlockNumber, dispatch, addPopup, getReceipt, isL2])
}, [chainId, library, transactions, lastBlockNumber, dispatch, addPopup, getReceipt, isL2, fastForwardBlockNumber])
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