Skip to content

Commit

Permalink
Refactor assets slice
Browse files Browse the repository at this point in the history
Refactored assets slice to an object indexed by asset ids. State updates
emitted from the indexing service are diffed and handled faster with this
approach.
  • Loading branch information
hyphenized committed Jul 18, 2023
1 parent d9be0a1 commit faf7412
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 85 deletions.
93 changes: 45 additions & 48 deletions background/redux-slices/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,80 +11,77 @@ import {
} from "../assets"
import { ERC20_INTERFACE } from "../lib/erc20"
import logger from "../lib/logger"
import { EVMNetwork, sameNetwork } from "../networks"
import { EVMNetwork, NetworkBaseAsset, sameNetwork } from "../networks"
import { NormalizedEVMAddress } from "../types"
import { removeAssetReferences, updateAssetReferences } from "./accounts"
import { createBackgroundAsyncThunk } from "./utils"
import { isBaseAssetForNetwork, isSameAsset } from "./utils/asset-utils"
import {
FullAssetID,
getFullAssetID,
isBaseAssetForNetwork,
isBaselineTrustedAsset,
isNetworkBaseAsset,
} from "./utils/asset-utils"
import { getProvider } from "./utils/contract-utils"

export type SingleAssetState = AnyAsset

export type AssetsState = SingleAssetState[]
export type AssetsState = { [assetID: FullAssetID]: SingleAssetState }

export const initialState: AssetsState = []
export const initialState: AssetsState = {}

const assetsSlice = createSlice({
name: "assets",
initialState,
reducers: {
assetsLoaded: (
immerState,
{ payload: newAssets }: { payload: AnyAsset[] }
{
payload: newAssets,
}: { payload: (NetworkBaseAsset | SmartContractFungibleAsset)[] }
) => {
const mappedAssets: { [sym: string]: SingleAssetState[] } = {}
// bin existing known assets
immerState.forEach((asset) => {
if (mappedAssets[asset.symbol] === undefined) {
mappedAssets[asset.symbol] = []
}
// if an asset is already in state, assume unique checks have been done
// no need to check network, contract address, etc
mappedAssets[asset.symbol].push(asset)
})
// merge in new assets
newAssets.forEach((newAsset) => {
if (mappedAssets[newAsset.symbol] === undefined) {
mappedAssets[newAsset.symbol] = [
{
...newAsset,
},
]
} else {
const duplicateIndexes = mappedAssets[newAsset.symbol].reduce<
number[]
>((acc, existingAsset, id) => {
if (isSameAsset(newAsset, existingAsset)) {
acc.push(id)
}
return acc
}, [])
const newAssetId = getFullAssetID(newAsset)

const existing = immerState[newAssetId]
if (existing) {
// Update all properties except metadata for network base assets
if (isNetworkBaseAsset(existing)) {
const { metadata: _, ...rest } = newAsset
Object.assign(existing, rest)
}

// if there aren't duplicates, add the asset
if (duplicateIndexes.length === 0) {
mappedAssets[newAsset.symbol].push({
...newAsset,
})
} else {
// TODO if there are duplicates... when should we replace assets?
duplicateIndexes.forEach((id) => {
// Update only the metadata for the duplicate
mappedAssets[newAsset.symbol][id] = {
...mappedAssets[newAsset.symbol][id],
metadata: newAsset.metadata,
if (isSmartContractFungibleAsset(existing) && newAsset.metadata) {
existing.metadata ??= {}

// Update verified status, token lists or discovery txs for custom assets
if (!isBaselineTrustedAsset(existing)) {
existing.metadata.verified = (<SmartContractFungibleAsset>(
newAsset
))?.metadata?.verified

if (newAsset.metadata?.tokenLists?.length) {
existing.metadata.tokenLists = newAsset.metadata.tokenLists
}

if ("discoveryTxHash" in newAsset.metadata) {
existing.metadata.discoveryTxHash =
newAsset.metadata.discoveryTxHash
}
})
}
}
} else {
immerState[newAssetId] = newAsset
}
})

return Object.values(mappedAssets).flat()
},
removeAsset: (
immerState,
{ payload: removedAsset }: { payload: AnyAsset }
{
payload: removedAsset,
}: { payload: NetworkBaseAsset | SmartContractFungibleAsset }
) => {
return immerState.filter((asset) => !isSameAsset(asset, removedAsset))
delete immerState[getFullAssetID(removedAsset)]
},
},
})
Expand Down
4 changes: 2 additions & 2 deletions background/redux-slices/migrations/to-34.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type OldState = {
}

type NewState = {
assets: unknown[]
assets: Record<string, unknown>
account: {
accountsData: {
evm: {
Expand Down Expand Up @@ -62,6 +62,6 @@ export default (prevState: Record<string, unknown>): NewState => {

return {
...typedPrevState,
assets: [],
assets: {},
}
}
2 changes: 1 addition & 1 deletion background/redux-slices/selectors/0xSwapSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const selectSwapBuyAssets = createSelector(
(state: RootState) => state.assets,
selectCurrentNetwork,
(assets, currentNetwork) => {
return assets.filter((asset): asset is SwappableAsset => {
return Object.values(assets).filter((asset): asset is SwappableAsset => {
// Only list assets for the current network.
const assetIsOnCurrentNetwork =
isBaseAssetForNetwork(asset, currentNetwork) ||
Expand Down
8 changes: 0 additions & 8 deletions background/redux-slices/selectors/uiSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,3 @@ export const selectMainCurrencySign = createSelector(
() => null,
() => hardcodedMainCurrencySign
)

export const selectMainCurrency = createSelector(
(state: RootState) => state.ui,
(state: RootState) => state.assets,
(state: RootState) => selectMainCurrencySymbol(state),
(_, assets, mainCurrencySymbol) =>
assets.find((asset) => asset.symbol === mainCurrencySymbol)
)
6 changes: 3 additions & 3 deletions background/redux-slices/tests/assets.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ const pricesState: PricesState = {
describe("Reducers", () => {
describe("assetsLoaded", () => {
test("updates cached asset metadata", () => {
const state = reducer([], assetsLoaded([asset]))
const state = reducer({}, assetsLoaded([asset]))

expect(state[0].metadata?.verified).not.toBeDefined()
expect(state[getFullAssetID(asset)].metadata?.verified).not.toBeDefined()

const newState = reducer(
state,
assetsLoaded([{ ...asset, metadata: { verified: true } }])
)

expect(newState[0].metadata?.verified).toBeTruthy()
expect(newState[getFullAssetID(asset)].metadata?.verified).toBeTruthy()
})
})
})
Expand Down
2 changes: 1 addition & 1 deletion background/redux-slices/utils/0x-swap-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export async function getAssetPricePoint(
network: EVMNetwork
): Promise<PricePoint | undefined> {
// FIXME: review
const assetPricesNetworks = assets
const assetPricesNetworks = Object.values(assets)
.filter(
(assetItem) =>
"contractAddress" in assetItem &&
Expand Down
13 changes: 7 additions & 6 deletions background/services/indexing/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import logger from "../../lib/logger"
import { HexString } from "../../types"
import { EVMNetwork, sameNetwork } from "../../networks"
import { EVMNetwork, NetworkBaseAsset, sameNetwork } from "../../networks"
import { AccountBalance, AddressOnNetwork } from "../../accounts"
import {
AnyAsset,
AnyAssetMetadata,
FungibleAsset,
isSmartContractFungibleAsset,
Expand Down Expand Up @@ -61,6 +60,8 @@ const FAST_TOKEN_REFRESH_BLOCK_RANGE = 10
// before balance-checking them.
const ACCELERATED_TOKEN_REFRESH_TIMEOUT = 300

type IndexedAsset = SmartContractFungibleAsset | NetworkBaseAsset

interface Events extends ServiceLifecycleEvents {
accountsWithBalances: {
/**
Expand All @@ -75,7 +76,7 @@ interface Events extends ServiceLifecycleEvents {
addressOnNetwork: AddressOnNetwork
}
prices: PricePoint[]
assets: AnyAsset[]
assets: IndexedAsset[]
refreshAsset: SmartContractFungibleAsset
removeAssetData: SmartContractFungibleAsset
}
Expand Down Expand Up @@ -133,7 +134,7 @@ export default class IndexingService extends BaseService<Events> {

private lastPriceAlarmTime = 0

private cachedAssets: Record<EVMNetwork["chainID"], AnyAsset[]> =
private cachedAssets: Record<EVMNetwork["chainID"], IndexedAsset[]> =
Object.fromEntries(
Object.keys(NETWORK_BY_CHAIN_ID).map((network) => [network, []])
)
Expand Down Expand Up @@ -272,7 +273,7 @@ export default class IndexingService extends BaseService<Events> {
* @returns An array of assets, including network base assets, token list
* assets and custom assets.
*/
getCachedAssets(network: EVMNetwork): AnyAsset[] {
getCachedAssets(network: EVMNetwork): IndexedAsset[] {
return this.cachedAssets[network.chainID] ?? []
}

Expand All @@ -288,7 +289,7 @@ export default class IndexingService extends BaseService<Events> {
await this.preferenceService.getTokenListPreferences()
const tokenLists = await this.db.getLatestTokenLists(tokenListPrefs.urls)

this.cachedAssets[network.chainID] = mergeAssets<FungibleAsset>(
this.cachedAssets[network.chainID] = mergeAssets<IndexedAsset>(
[network.baseAsset],
customAssets,
networkAssetsFromLists(network, tokenLists)
Expand Down
35 changes: 19 additions & 16 deletions background/tests/earn/assets.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@ import { ETHEREUM } from "../../constants"
import { PricesState } from "../../redux-slices/prices"
import { getFullAssetID } from "../../redux-slices/utils/asset-utils"

export const assets: SmartContractFungibleAsset[] = [
{
name: "Wrapped Ether",
symbol: "WETH",
decimals: 18,
homeNetwork: ETHEREUM,
contractAddress: "0x0",
},
{
name: "Uniswap",
symbol: "UNI",
decimals: 18,
homeNetwork: ETHEREUM,
contractAddress: "0x0",
},
]
export const assets: Record<string, SmartContractFungibleAsset> =
Object.fromEntries(
[
{
name: "Wrapped Ether",
symbol: "WETH",
decimals: 18,
homeNetwork: ETHEREUM,
contractAddress: "0x0",
},
{
name: "Uniswap",
symbol: "UNI",
decimals: 18,
homeNetwork: ETHEREUM,
contractAddress: "0x0",
},
].map((asset) => [getFullAssetID(asset), asset])
)

export const prices: PricesState = {
[getFullAssetID(assets[0])]: {
Expand Down

0 comments on commit faf7412

Please sign in to comment.