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

Token Discovery - Remap redux asset balances #3195

Merged
merged 15 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions background/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AnyAssetAmount } from "./assets"
import { EVMNetwork } from "./networks"
import { AnyAssetAmount, SmartContractFungibleAsset } from "./assets"
import { EVMNetwork, NetworkBaseAsset } from "./networks"
import { HexString } from "./types"

/**
Expand All @@ -15,7 +15,7 @@ export type AccountBalance = {
/**
* The measured balance and the asset in which it's denominated.
*/
assetAmount: AnyAssetAmount
assetAmount: AnyAssetAmount<NetworkBaseAsset | SmartContractFungibleAsset>
/**
* The network on which the account balance was measured.
*/
Expand Down
66 changes: 18 additions & 48 deletions background/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ import {
assetAmountToDesiredDecimals,
convertAssetAmountViaPricePoint,
isSmartContractFungibleAsset,
SmartContractAsset,
SmartContractFungibleAsset,
} from "./assets"
import { FeatureFlags, isEnabled } from "./features"
Expand Down Expand Up @@ -188,7 +187,10 @@ import {
isOneTimeAnalyticsEvent,
OneTimeAnalyticsEvent,
} from "./lib/posthog"
import { isBuiltInNetworkBaseAsset } from "./redux-slices/utils/asset-utils"
import {
isBuiltInNetworkBaseAsset,
isSameAsset,
} from "./redux-slices/utils/asset-utils"
import { getPricePoint, getTokenPrices } from "./lib/prices"
import { DismissableItem } from "./services/preferences"

Expand Down Expand Up @@ -1001,62 +1003,30 @@ export default class Main extends BaseService<never> {

const filteredBalancesToDispatch: AccountBalance[] = []

const sortedBalances: AccountBalance[] = []

balances
.filter((balance) => {
const isSmartContract =
"contractAddress" in balance.assetAmount.asset

if (!isSmartContract) {
sortedBalances.push(balance)
}

// Network base assets with smart contract addresses from some networks
// e.g. Optimism, Polygon might have been retrieved through alchemy as
// token balances but they should not be handled here as they would
// not be correctly treated as base assets
return !isBuiltInNetworkBaseAsset(
balance.assetAmount.asset,
balance.network
)
})
.forEach((balance) => {
// TODO support multi-network assets
const balanceHasAnAlreadyTrackedAsset = assetsToTrack.some(
(tracked) => isSameAsset(tracked, balance.assetAmount.asset)
)

if (
isBuiltInNetworkBaseAsset(
balance.assetAmount.asset,
balance.network
)
balance.assetAmount.amount > 0 ||
balanceHasAnAlreadyTrackedAsset
) {
return false
filteredBalancesToDispatch.push(balance)
}

return isSmartContract
})
// Sort verified last to prevent shadowing assets from token lists
// FIXME: Balances should not be indexed by symbol in redux
.sort((balance, otherBalance) => {
const asset = balance.assetAmount.asset as SmartContractAsset
const other = otherBalance.assetAmount.asset as SmartContractAsset

return (
(other.metadata?.tokenLists?.length ?? 0) -
(asset.metadata?.tokenLists?.length ?? 0)
)
})
.forEach((balance) => sortedBalances.unshift(balance))

sortedBalances.forEach((balance) => {
// TODO support multi-network assets
const balanceHasAnAlreadyTrackedAsset = assetsToTrack.some(
(tracked) =>
tracked.symbol === balance.assetAmount.asset.symbol &&
isSmartContractFungibleAsset(balance.assetAmount.asset) &&
normalizeEVMAddress(tracked.contractAddress) ===
normalizeEVMAddress(balance.assetAmount.asset.contractAddress)
)

if (
balance.assetAmount.amount > 0 ||
balanceHasAnAlreadyTrackedAsset
) {
filteredBalancesToDispatch.push(balance)
}
})

this.store.dispatch(
updateAccountBalance({
Expand Down
43 changes: 29 additions & 14 deletions background/redux-slices/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
AssetMainCurrencyAmount,
AssetDecimalAmount,
isBuiltInNetworkBaseAsset,
AssetID,
getAssetID,
isNetworkBaseAsset,
isSameAsset,
isUntrustedAsset,
} from "./utils/asset-utils"
import { DomainName, HexString, URI } from "../types"
import { normalizeEVMAddress, sameEVMAddress } from "../lib/utils"
Expand Down Expand Up @@ -49,7 +53,7 @@ export type AccountData = {
address: HexString
network: Network
balances: {
[assetSymbol: string]: AccountBalance
[assetID: AssetID]: AccountBalance
}
ens: {
name?: DomainName
Expand Down Expand Up @@ -180,13 +184,24 @@ function updateCombinedData(immerState: AccountState) {

immerState.combinedData.assets = Object.values(
combinedAccountBalances.reduce<{
[symbol: string]: AnyAssetAmount
[assetID: string]: AnyAssetAmount
}>((acc, combinedAssetAmount) => {
const assetSymbol = combinedAssetAmount.asset.symbol
const { asset } = combinedAssetAmount
/**
* Asset amounts can be aggregated if the asset is a base network asset
* or comes from a token list, e.g. ETH on Optimism, Mainnet
*/
const canBeAggregated =
isNetworkBaseAsset(asset) || !isUntrustedAsset(asset)

const assetID = canBeAggregated
? asset.symbol
: `${asset.homeNetwork.chainID}/${getAssetID(asset)}`

let { amount } = combinedAssetAmount

if (acc[assetSymbol]?.asset) {
const accAsset = acc[assetSymbol].asset
if (acc[assetID]?.asset) {
const accAsset = acc[assetID].asset
const existingDecimals = isFungibleAsset(accAsset)
? accAsset.decimals
: 0
Expand All @@ -199,13 +214,14 @@ function updateCombinedData(immerState: AccountState) {
}
}

if (acc[assetSymbol]) {
acc[assetSymbol].amount += amount
if (acc[assetID]) {
acc[assetID].amount += amount
} else {
acc[assetSymbol] = {
acc[assetID] = {
...combinedAssetAmount,
}
}

return acc
}, {})
)
Expand Down Expand Up @@ -297,7 +313,7 @@ const accountSlice = createSlice({
network,
assetAmount: { asset },
} = updatedAccountBalance
const { symbol: updatedAssetSymbol } = asset
const assetID = getAssetID(asset)

const normalizedAddress = normalizeEVMAddress(address)
const existingAccountData =
Expand All @@ -311,20 +327,19 @@ const accountSlice = createSlice({
if (existingAccountData !== "loading") {
if (
updatedAccountBalance.assetAmount.amount === 0n &&
existingAccountData.balances[updatedAssetSymbol] === undefined &&
existingAccountData.balances[assetID] === undefined &&
!isBuiltInNetworkBaseAsset(asset, network) // add base asset even if balance is 0
) {
return
}
existingAccountData.balances[updatedAssetSymbol] =
updatedAccountBalance
existingAccountData.balances[assetID] = updatedAccountBalance
} else {
immerState.accountsData.evm[network.chainID][normalizedAddress] = {
// TODO Figure out the best way to handle default name assignment
// TODO across networks.
...newAccountData(address, network, immerState),
balances: {
[updatedAssetSymbol]: updatedAccountBalance,
[assetID]: updatedAccountBalance,
},
}
}
Expand Down Expand Up @@ -433,7 +448,7 @@ const accountSlice = createSlice({
if (account !== "loading") {
Object.values(account.balances).forEach(({ assetAmount }) => {
if (isSameAsset(assetAmount.asset, asset)) {
delete account.balances[assetAmount.asset.symbol]
delete account.balances[getAssetID(assetAmount.asset)]
}
})
}
Expand Down
4 changes: 3 additions & 1 deletion background/redux-slices/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ import to27 from "./to-27"
import to28 from "./to-28"
import to29 from "./to-29"
import to30 from "./to-30"
import to31 from "./to-31"

/**
* The version of persisted Redux state the extension is expecting. Any previous
* state without this version, or with a lower version, ought to be migrated.
*/
export const REDUX_STATE_VERSION = 30
export const REDUX_STATE_VERSION = 31

/**
* Common type for all migration functions.
Expand Down Expand Up @@ -72,6 +73,7 @@ const allMigrations: { [targetVersion: string]: Migration } = {
28: to28,
29: to29,
30: to30,
31: to31,
}

/**
Expand Down
52 changes: 52 additions & 0 deletions background/redux-slices/migrations/to-31.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
type PrevState = {
account: {
accountsData: {
evm: {
[chainID: string]: {
[address: string]: {
balances: {
[symbol: string]: unknown
}
[other: string]: unknown
}
}
}
}
[sliceKey: string]: unknown
}
}

type NewState = {
account: {
accountsData: {
evm: {
[chainID: string]: {
[address: string]: {
balances: {
[assetID: string]: unknown
}
[other: string]: unknown
}
}
}
}
[sliceKey: string]: unknown
}
}

export default (prevState: Record<string, unknown>): NewState => {
const typedPrevState = prevState as PrevState

const {
account: { accountsData },
} = typedPrevState

Object.keys(accountsData.evm).forEach((chainID) =>
Object.keys(accountsData.evm[chainID]).forEach((address) => {
// Clear all accounts cached balances
accountsData.evm[chainID][address].balances = {}
})
)

return { ...typedPrevState }
}
Loading