Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: redux migration #6830

Merged
merged 23 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,16 @@ Cypress.Commands.overwrite(
onBeforeLoad(win) {
options?.onBeforeLoad?.(win)

// We want to test from a clean state, so we clear the local storage (which clears redux).
win.localStorage.clear()
// We want to test from a clean state, so we clear the persisted state.
win.indexedDB.deleteDatabase('redux')
just-toby marked this conversation as resolved.
Show resolved Hide resolved

// Set initial user state.
// Since the IndexedDB state is clear, the first migration will run and pick up this legacy state from localStorage.
win.localStorage.setItem(
'redux_localstorage_simple_user', // storage key for the user reducer using 'redux-localstorage-simple'
JSON.stringify({ ...CONNECTED_WALLET_USER_STATE, ...(options?.userState ?? {}) })
'redux_localstorage_simple_user',
JSON.stringify({
...CONNECTED_WALLET_USER_STATE,
...options?.userState,
})
)

// Set feature flags, if configured.
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"terser-webpack-plugin": "^5.3.9",
"ts-jest": "^29.1.1",
"ts-transform-graphql-tag": "^0.2.1",
"tsafe": "^1.6.4",
"typechain": "^5.0.0",
"typescript": "^4.9.4",
"webpack": "^5.88.2",
Expand Down Expand Up @@ -235,6 +236,7 @@
"inter-ui": "^3.13.1",
"jotai": "^1.3.7",
"jsbi": "^3.1.4",
"localforage": "^1.10.0",
"make-plural": "^7.0.0",
"ms": "^2.1.3",
"multicodec": "^3.0.1",
Expand Down Expand Up @@ -265,7 +267,7 @@
"react-window-infinite-loader": "^1.0.8",
"rebass": "^4.0.7",
"redux": "^4.1.2",
"redux-localstorage-simple": "^2.3.1",
"redux-persist": "^6.0.0",
"statsig-react": "^1.22.0",
"styled-components": "^5.3.5",
"tiny-invariant": "^1.2.0",
Expand Down
2 changes: 1 addition & 1 deletion src/state/application/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { sendAnalyticsEvent } from 'analytics'
import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { AppState } from 'state/reducer'

import { AppState } from '../types'
import {
addPopup,
ApplicationModal,
Expand Down
2 changes: 1 addition & 1 deletion src/state/application/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export enum ApplicationModal {
UNISWAP_NFT_AIRDROP_CLAIM,
}

type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>
export type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>

export interface ApplicationState {
readonly chainId: number | null
Expand Down
2 changes: 1 addition & 1 deletion src/state/burn/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import JSBI from 'jsbi'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { ReactNode, useCallback } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { AppState } from 'state/reducer'

import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useV2Pair } from '../../hooks/useV2Pairs'
import { useTokenBalances } from '../connection/hooks'
import { AppState } from '../types'
import { Field, typeInput } from './actions'

export function useBurnState(): AppState['burn'] {
Expand Down
2 changes: 1 addition & 1 deletion src/state/burn/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createReducer } from '@reduxjs/toolkit'

import { Field, typeInput } from './actions'

interface BurnState {
export interface BurnState {
readonly independentField: Field
readonly typedValue: string
}
Expand Down
2 changes: 1 addition & 1 deletion src/state/burn/v3/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
import { PositionDetails } from 'types/position'
import { unwrappedToken } from 'utils/unwrappedToken'

import { AppState } from '../../types'
import { AppState } from '../../reducer'
import { selectPercent } from './actions'

export function useBurnV3State(): AppState['burnV3'] {
Expand Down
2 changes: 1 addition & 1 deletion src/state/burn/v3/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createReducer } from '@reduxjs/toolkit'

import { selectPercent } from './actions'

interface BurnV3State {
export interface BurnV3State {
readonly percent: number
}

Expand Down
45 changes: 25 additions & 20 deletions src/state/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,40 @@
import { configureStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query/react'
import { load, save } from 'redux-localstorage-simple'
import { isTestEnv } from 'utils/env'
import { persistStore } from 'redux-persist'

import { updateVersion } from './global/actions'
import { sentryEnhancer } from './logging'
import reducer from './reducer'
import { routingApi } from './routing/slice'

const PERSISTED_KEYS: string[] = ['user', 'transactions', 'signatures', 'lists']
export function createDefaultStore() {
return configureStore({
reducer,
enhancers: (defaultEnhancers) => defaultEnhancers.concat(sentryEnhancer),
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: true,
serializableCheck: {
// meta.arg and meta.baseQueryMeta are defaults. payload.trade is a nonserializable return value, but that's ok
// because we are not adding it into any persisted store that requires serialization (e.g. localStorage)
ignoredActionPaths: ['meta.arg', 'meta.baseQueryMeta', 'payload.trade'],
ignoredPaths: [routingApi.reducerPath],
ignoredActions: [
// ignore the redux-persist actions
just-toby marked this conversation as resolved.
Show resolved Hide resolved
'persist/PERSIST',
'persist/REHYDRATE',
],
},
}).concat(routingApi.middleware),
})
}

const store = configureStore({
reducer,
enhancers: (defaultEnhancers) => defaultEnhancers.concat(sentryEnhancer),
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: true,
serializableCheck: {
// meta.arg and meta.baseQueryMeta are defaults. payload.trade is a nonserializable return value, but that's ok
// because we are not adding it into any persisted store that requires serialization (e.g. localStorage)
ignoredActionPaths: ['meta.arg', 'meta.baseQueryMeta', 'payload.trade'],
ignoredPaths: [routingApi.reducerPath],
},
})
.concat(routingApi.middleware)
.concat(save({ states: PERSISTED_KEYS, debounce: 1000 })),
preloadedState: load({ states: PERSISTED_KEYS, disableWarnings: isTestEnv() }),
})
const store = createDefaultStore()

store.dispatch(updateVersion())

setupListeners(store.dispatch)

persistStore(store)

export default store
88 changes: 88 additions & 0 deletions src/state/legacyMigrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/misc'
just-toby marked this conversation as resolved.
Show resolved Hide resolved

zzmp marked this conversation as resolved.
Show resolved Hide resolved
import { RouterPreference } from './routing/types'
import { TransactionState } from './transactions/reducer'
import { UserState } from './user/reducer'
import { SlippageTolerance } from './user/types'

const currentTimestamp = () => new Date().getTime()

/**
* These functions handle all migrations that existed before we started tracking version numbers.
*/

export function legacyTransactionMigrations(state: any): TransactionState {
// Make a copy of the object so we can mutate it.
const result = JSON.parse(JSON.stringify(state))
// in case there are any transactions in the store with the old format, remove them
Object.keys(result).forEach((chainId) => {
const chainTransactions = result[chainId as unknown as number]
Object.keys(chainTransactions).forEach((hash) => {
if (!('info' in chainTransactions[hash])) {
// clear old transactions that don't have the right format
delete chainTransactions[hash]
}
})
})
return result
}

export function legacyUserMigrations(state: any): UserState {
// Make a copy of the object so we can mutate it.
const result = JSON.parse(JSON.stringify(state))
// If `selectedWallet` is a WalletConnect v1 wallet, reset to default.
if (state.selectedWallet) {
const selectedWallet = state.selectedWallet as string
if (selectedWallet === 'UNIWALLET' || selectedWallet === 'UNISWAP_WALLET' || selectedWallet === 'WALLET_CONNECT') {
delete state.selectedWallet
}
}

// If `userSlippageTolerance` is not present or its value is invalid, reset to default
if (
typeof result.userSlippageTolerance !== 'number' ||
!Number.isInteger(result.userSlippageTolerance) ||
result.userSlippageTolerance < 0 ||
result.userSlippageTolerance > 5000
) {
result.userSlippageTolerance = SlippageTolerance.Auto
} else {
if (
!result.userSlippageToleranceHasBeenMigratedToAuto &&
[10, 50, 100].indexOf(result.userSlippageTolerance) !== -1
) {
result.userSlippageTolerance = SlippageTolerance.Auto
result.userSlippageToleranceHasBeenMigratedToAuto = true
}
}

// If `userDeadline` is not present or its value is invalid, reset to default
if (
typeof result.userDeadline !== 'number' ||
!Number.isInteger(result.userDeadline) ||
result.userDeadline < 60 ||
result.userDeadline > 180 * 60
) {
result.userDeadline = DEFAULT_DEADLINE_FROM_NOW
}

// If `userRouterPreference` is not present, reset to default
if (typeof result.userRouterPreference !== 'string') {
result.userRouterPreference = RouterPreference.API
}

// If `userRouterPreference` is `AUTO`, migrate to `API`
if ((state.userRouterPreference as string) === 'auto') {
state.userRouterPreference = RouterPreference.API
}

//If `buyFiatFlowCompleted` is present, delete it using filtering
if ('buyFiatFlowCompleted' in result) {
//ignoring due to type errors occuring since we now remove this state
//@ts-ignore
delete result.buyFiatFlowCompleted
}

result.lastUpdateVersionTimestamp = currentTimestamp()
return result
}
2 changes: 1 addition & 1 deletion src/state/lists/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useAppSelector } from 'state/hooks'
import sortByListPriority from 'utils/listSort'

import BROKEN_LIST from '../../constants/tokenLists/broken.tokenlist.json'
import { AppState } from '../types'
import { AppState } from '../reducer'
import { DEFAULT_ACTIVE_LIST_URLS, UNSUPPORTED_LIST_URLS } from './../../constants/lists'
just-toby marked this conversation as resolved.
Show resolved Hide resolved

type Mutable<T> = {
Expand Down
4 changes: 2 additions & 2 deletions src/state/lists/reducer.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import tokenSafetyLookup from 'constants/tokenSafetyLookup'
import { createStore, Store } from 'redux'
import { updateVersion } from 'state/global/actions'

import { DEFAULT_LIST_OF_LISTS } from '../../constants/lists'
import tokenSafetyLookup from '../../constants/tokenSafetyLookup'
import { updateVersion } from '../global/actions'
import { acceptListUpdate, addList, fetchTokenList, removeList } from './actions'
import reducer, { ListsState } from './reducer'

Expand Down
2 changes: 1 addition & 1 deletion src/state/lists/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const NEW_LIST_STATE: ListState = {

type Mutable<T> = { -readonly [P in keyof T]: T[P] extends ReadonlyArray<infer U> ? U[] : T[P] }

const initialState: ListsState = {
export const initialState: ListsState = {
lastInitializedDefaultListOfLists: DEFAULT_LIST_OF_LISTS,
byUrl: {
...DEFAULT_LIST_OF_LISTS.reduce<Mutable<ListsState['byUrl']>>((memo, listUrl) => {
Expand Down
2 changes: 1 addition & 1 deletion src/state/logging.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Sentry from '@sentry/react'
import noop from 'utils/noop'

import { AppState } from './types'
import { AppState } from './reducer'

/* Utility type to mark all properties of a type as optional */
type DeepPartial<T> = T extends object
Expand Down
2 changes: 1 addition & 1 deletion src/state/logs/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'

import { filterToKey, Log } from './utils'

interface LogsState {
export interface LogsState {
[chainId: number]: {
[filterKey: string]: {
listeners: number
Expand Down
Loading
Loading