fix: remove version check in redux lists update + migrate outdated USDC saved tokens (#7544)

* fix: remove version check lists update

* add migration and test

* pr review
This commit is contained in:
Kristie Huang 2023-11-06 14:20:55 -05:00 committed by GitHub
parent 9eaa22f644
commit 443a00a777
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 131 additions and 96 deletions

@ -12,7 +12,6 @@ import { useAllLists } from 'state/lists/hooks'
import { useFetchListCallback } from '../../hooks/useFetchListCallback' import { useFetchListCallback } from '../../hooks/useFetchListCallback'
import useIsWindowVisible from '../../hooks/useIsWindowVisible' import useIsWindowVisible from '../../hooks/useIsWindowVisible'
import { acceptListUpdate } from './actions' import { acceptListUpdate } from './actions'
import { shouldAcceptVersionUpdate } from './utils'
export default function Updater(): null { export default function Updater(): null {
const { provider } = useWeb3React() const { provider } = useWeb3React()
@ -61,7 +60,7 @@ export default function Updater(): null {
}) })
}, [dispatch, fetchList, lists, rehydrated]) }, [dispatch, fetchList, lists, rehydrated])
// automatically update lists if versions are minor/patch // automatically update lists for every version update
useEffect(() => { useEffect(() => {
Object.keys(lists).forEach((listUrl) => { Object.keys(lists).forEach((listUrl) => {
const list = lists[listUrl] const list = lists[listUrl]
@ -71,13 +70,7 @@ export default function Updater(): null {
case VersionUpgrade.NONE: case VersionUpgrade.NONE:
throw new Error('unexpected no version bump') throw new Error('unexpected no version bump')
case VersionUpgrade.PATCH: case VersionUpgrade.PATCH:
case VersionUpgrade.MINOR: { case VersionUpgrade.MINOR:
if (shouldAcceptVersionUpdate(listUrl, list.current, list.pendingUpdate, bump)) {
dispatch(acceptListUpdate(listUrl))
}
break
}
// update any active or inactive lists
case VersionUpgrade.MAJOR: case VersionUpgrade.MAJOR:
dispatch(acceptListUpdate(listUrl)) dispatch(acceptListUpdate(listUrl))
} }

@ -1,68 +0,0 @@
import { TokenList, VersionUpgrade } from '@uniswap/token-lists'
import { shouldAcceptVersionUpdate } from './utils'
function buildTokenList(count: number): TokenList {
const tokens = []
for (let i = 0; i < count; i++) {
tokens.push({
name: `Token ${i}`,
address: `0x${i.toString().padStart(40, '0')}`,
symbol: `T${i}`,
decimals: 18,
chainId: 1,
logoURI: `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x${i
.toString()
.padStart(40, '0')}/logo.png`,
})
}
return {
name: 'Defi',
logoURI:
'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x514910771AF9Ca656af840dff83E8264EcF986CA/logo.png',
keywords: ['defi', 'uniswap'],
timestamp: '2021-03-12T00:00:00.000Z',
version: {
major: 1,
minor: 0,
patch: 0,
},
tokens,
}
}
describe('shouldAcceptMinorVersionUpdate', () => {
it('returns false for patch when tokens have changed', () => {
jest.spyOn(console, 'debug').mockReturnValue(undefined)
expect(shouldAcceptVersionUpdate('test_list', buildTokenList(1), buildTokenList(2), VersionUpgrade.PATCH)).toEqual(
false
)
expect(console.debug).toHaveBeenCalledWith(expect.stringMatching(/should have been MAJOR/))
})
it('returns true for patch when tokens are the same', () => {
expect(shouldAcceptVersionUpdate('test_list', buildTokenList(1), buildTokenList(1), VersionUpgrade.PATCH)).toEqual(
true
)
})
it('returns true for minor version bump with tokens added', () => {
expect(shouldAcceptVersionUpdate('test_list', buildTokenList(1), buildTokenList(2), VersionUpgrade.MINOR)).toEqual(
true
)
})
it('returns true for no version bump', () => {
expect(shouldAcceptVersionUpdate('test_list', buildTokenList(1), buildTokenList(2), VersionUpgrade.MINOR)).toEqual(
true
)
})
it('returns false for minor version bump with tokens removed', () => {
jest.spyOn(console, 'debug').mockReturnValue(undefined)
expect(shouldAcceptVersionUpdate('test_list', buildTokenList(2), buildTokenList(1), VersionUpgrade.MINOR)).toEqual(
false
)
expect(console.debug).toHaveBeenCalledWith(expect.stringMatching(/should have been MAJOR/))
})
})

@ -1,19 +0,0 @@
import { minVersionBump, TokenList, VersionUpgrade } from '@uniswap/token-lists'
export function shouldAcceptVersionUpdate(
listUrl: string,
current: TokenList,
update: TokenList,
targetBump: VersionUpgrade.PATCH | VersionUpgrade.MINOR
): boolean {
const min = minVersionBump(current.tokens, update.tokens)
// Automatically update minor/patch as long as bump matches the min update.
if (targetBump >= min) {
return true
} else {
console.debug(
`List at url ${listUrl} could not automatically update because the version bump was only PATCH/MINOR while the update had breaking changes and should have been MAJOR`
)
return false
}
}

@ -0,0 +1,74 @@
import { ChainId, Token } from '@uniswap/sdk-core'
import { createMigrate } from 'redux-persist'
import { RouterPreference } from 'state/routing/types'
import { SlippageTolerance } from 'state/user/types'
import { migration1 } from './1'
import { migration2 } from './2'
import { migration3, PersistAppStateV3 } from './3'
const previousState: PersistAppStateV3 = {
user: {
userLocale: null,
userRouterPreference: RouterPreference.API,
userHideClosedPositions: false,
userSlippageTolerance: SlippageTolerance.Auto,
userSlippageToleranceHasBeenMigratedToAuto: true,
userDeadline: 1800,
tokens: {
// wrong tokens
[ChainId.OPTIMISM]: {
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607': new Token(
ChainId.OPTIMISM,
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
6,
'USDC',
'USD Coin'
),
},
[ChainId.BASE]: {
'0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA': new Token(
ChainId.BASE,
'0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA',
6,
'USDC',
'USD Coin'
),
},
},
pairs: {},
timestamp: Date.now(),
hideBaseWalletBanner: false,
},
_persist: {
version: 2,
rehydrated: true,
},
}
describe('migration to v3', () => {
it('should migrate users who currently have outdated USDC.e saved', async () => {
const migrator = createMigrate(
{
1: migration1,
2: migration2,
3: migration3,
},
{ debug: false }
)
const result: any = await migrator(previousState, 3)
expect(Object.keys(result?.user?.tokens).length).toEqual(2)
expect(result?.user?.tokens[ChainId.OPTIMISM]?.['0x7F5c764cBc14f9669B88837ca1490cCa17c31607'].symbol).toEqual(
'USDC.e'
)
expect(result?.user?.tokens[ChainId.OPTIMISM]?.['0x7F5c764cBc14f9669B88837ca1490cCa17c31607'].name).toEqual(
'Bridged USDC'
)
expect(result?.user?.tokens[ChainId.BASE]?.['0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA'].symbol).toEqual('USDbC')
expect(result?.user?.tokens[ChainId.BASE]?.['0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA'].name).toEqual(
'USD Base Coin'
)
expect(result?._persist.version).toEqual(3)
})
})

55
src/state/migrations/3.ts Normal file

@ -0,0 +1,55 @@
import { Token } from '@uniswap/sdk-core'
import { ChainId } from '@uniswap/sdk-core'
import { PersistState } from 'redux-persist'
import { serializeToken } from 'state/user/hooks'
import { UserState } from 'state/user/reducer'
export type PersistAppStateV3 = {
_persist: PersistState
} & { user?: UserState }
/**
* Migration to clear users' imported token lists, after
* breaking changes to token info for multichain native USDC.
*/
export const migration3 = (state: PersistAppStateV3 | undefined) => {
if (state?.user) {
// Update USDC.e tokens to use the the new USDC.e symbol (from USDC)
const USDCe_ADDRESSES: { [key in ChainId]?: string } = {
[ChainId.OPTIMISM]: '0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
[ChainId.OPTIMISM_GOERLI]: '0x7E07E15D2a87A24492740D16f5bdF58c16db0c4E',
[ChainId.ARBITRUM_ONE]: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8',
[ChainId.ARBITRUM_GOERLI]: '0x8FB1E3fC51F3b789dED7557E680551d93Ea9d892',
[ChainId.AVALANCHE]: '0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664',
[ChainId.POLYGON]: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
[ChainId.POLYGON_MUMBAI]: '0xe11a86849d99f524cac3e7a0ec1241828e332c62',
}
for (const [chainId, address] of Object.entries(USDCe_ADDRESSES)) {
const chainIdKey = Number(chainId) as ChainId
if (state.user.tokens[chainIdKey]?.[address]) {
state.user.tokens[chainIdKey][address] = serializeToken(
new Token(chainIdKey, address, 6, 'USDC.e', 'Bridged USDC')
)
}
}
// Update USDbC token to use the new USDbC symbol (from USDC)
const USDbC_BASE = new Token(
ChainId.BASE,
'0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA',
6,
'USDbC',
'USD Base Coin'
)
if (state.user.tokens[ChainId.BASE]?.[USDbC_BASE.address]) {
state.user.tokens[ChainId.BASE][USDbC_BASE.address] = serializeToken(USDbC_BASE)
}
return {
...state,
_persist: {
...state._persist,
version: 3,
},
}
}
return state
}