From a0fef6c95eca05f1b869dd0a8181894114e3d651 Mon Sep 17 00:00:00 2001 From: Mark Nardi Date: Tue, 5 Mar 2024 15:56:37 +0100 Subject: [PATCH] chore: remove balance changes for evm assets (#2054) * cleanup L2 balance fetches * generate balance changes only if IRC is sent * fix name * cleanup old balance changes * improve erc detection * add prod migration --- .../actions/fetchL2BalanceForAccount.ts | 59 ++++++++----------- .../src/lib/core/layer-2/utils/index.ts | 1 + .../src/lib/core/layer-2/utils/isErcAsset.ts | 7 +++ .../nfts/actions/updateErc721NftOwnership.ts | 3 - .../constants/profile-version.constant.ts | 4 +- .../actions/removeEvmBalanceChanges.ts | 44 ++++++++++++++ .../alpha/alpha-profile-migration-6-to-7.ts | 13 ++++ .../alpha/alpha-profile-migration-map.ts | 2 + .../prod/prod-profile-migration-3-to-4.ts | 13 ++++ .../prod/prod-profile-migration-map.ts | 2 + .../src/lib/core/wallet/actions/index.ts | 1 - .../send/sendAndPersistTransactionFromEvm.ts | 22 ++++--- .../actions/updateL2BalanceWithoutActivity.ts | 14 ----- 13 files changed, 119 insertions(+), 66 deletions(-) create mode 100644 packages/shared/src/lib/core/layer-2/utils/isErcAsset.ts create mode 100644 packages/shared/src/lib/core/profile/migrations/actions/removeEvmBalanceChanges.ts create mode 100644 packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-6-to-7.ts create mode 100644 packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-3-to-4.ts delete mode 100644 packages/shared/src/lib/core/wallet/actions/updateL2BalanceWithoutActivity.ts diff --git a/packages/shared/src/lib/core/layer-2/actions/fetchL2BalanceForAccount.ts b/packages/shared/src/lib/core/layer-2/actions/fetchL2BalanceForAccount.ts index 8efa03a336..d2ae0ded2c 100644 --- a/packages/shared/src/lib/core/layer-2/actions/fetchL2BalanceForAccount.ts +++ b/packages/shared/src/lib/core/layer-2/actions/fetchL2BalanceForAccount.ts @@ -17,13 +17,11 @@ import { import { selectedAccountNfts } from '@core/nfts/stores' import { getActiveProfile } from '@core/profile/stores' import { getOrRequestTokenFromPersistedTokens } from '@core/token/actions' -import { TOKEN_ID_BYTE_LENGTH } from '@core/token/constants' import { Converter } from '@core/utils/convert' import { ISC_MAGIC_CONTRACT_ADDRESS } from '../constants' import { evmAddressToAgentId, getAgentBalanceParameters, getSmartContractHexName } from '../helpers' import { setLayer2AccountBalanceForChain } from '../stores' -import { isTrackedTokenAddress } from '@core/wallet' -import { TokenTrackingStatus } from '@core/token' +import { BASE_TOKEN_ID, TokenTrackingStatus } from '@core/token' import features from '@features/features' import { KeyValue } from '@ui/types' @@ -37,45 +35,37 @@ export function fetchL2BalanceForAccount(account: IAccountState): void { return } - await fetchLayer2Nfts(evmAddress, chain, account) + await fetchL2Irc27Nfts(evmAddress, chain, account) if (features.collectibles.erc721.enabled) { void updateErc721NftsOwnership(account) } - const balances = await getLayer2BalanceForAddress(evmAddress, chain) - if (!balances) { + const l2Balance: { [tokenId: string]: bigint } = {} + + const l2BaseAndIrc30Balances = await getL2NativeTokenBalancesForAddress(evmAddress, chain) + const erc20Balances = await getErc20BalancesForAddress(evmAddress, chain) + if (erc20Balances.length === 0 && l2BaseAndIrc30Balances.length === 0) { return } - const layer2Balance: { [tokenId: string]: bigint } = {} - - for (const { balance, tokenId } of balances) { + for (const { balance, tokenId } of l2BaseAndIrc30Balances) { const adjustedBalance = Number.isNaN(Number(balance)) ? BigInt(0) : balance - const isNativeToken = Converter.hexToBytes(tokenId).length === TOKEN_ID_BYTE_LENGTH - const isErc20TrackedToken = isTrackedTokenAddress(networkId, tokenId) - if (isNativeToken || isErc20TrackedToken) { + if (tokenId !== BASE_TOKEN_ID) { await getOrRequestTokenFromPersistedTokens(tokenId, networkId) + await calculateAndAddPersistedTokenBalanceChange(account, networkId, tokenId, adjustedBalance) } - await calculateAndAddPersistedTokenBalanceChange(account, networkId, tokenId, adjustedBalance) - layer2Balance[tokenId] = adjustedBalance + l2Balance[tokenId] = adjustedBalance } - setLayer2AccountBalanceForChain(index, networkId, layer2Balance) - }) -} -async function getLayer2BalanceForAddress( - evmAddress: string, - chain: IChain -): Promise { - const layer2BaseAndIrc30Balances = await getLayer2NativeTokenBalancesForAddress(evmAddress, chain) - const erc20Balances = await getLayer2Erc20BalancesForAddress(evmAddress, chain) - return [...layer2BaseAndIrc30Balances, ...erc20Balances] + for (const { balance, tokenId } of erc20Balances) { + await getOrRequestTokenFromPersistedTokens(tokenId, networkId) + l2Balance[tokenId] = Number.isNaN(Number(balance)) ? BigInt(0) : balance + } + setLayer2AccountBalanceForChain(index, networkId, l2Balance) + }) } -async function getLayer2NativeTokenBalancesForAddress( - evmAddress: string, - chain: IChain -): Promise { +async function getL2NativeTokenBalancesForAddress(evmAddress: string, chain: IChain): Promise { const accountsCoreContract = getSmartContractHexName('accounts') const getBalanceFunc = getSmartContractHexName('balance') const agentID = evmAddressToAgentId(evmAddress, chain.getConfiguration()) @@ -86,10 +76,11 @@ async function getLayer2NativeTokenBalancesForAddress( .callView(accountsCoreContract, getBalanceFunc, parameters) .call()) as { items: KeyValue[] } - const nativeTokens = nativeTokenResult.items.map((item) => ({ - tokenId: item.key, - balance: Converter.bigIntLikeToBigInt(item.value), - })) + const nativeTokens = + nativeTokenResult.items?.map((item) => ({ + tokenId: item.key, + balance: Converter.bigIntLikeToBigInt(item.value), + })) ?? [] return nativeTokens } catch (e) { @@ -97,7 +88,7 @@ async function getLayer2NativeTokenBalancesForAddress( } } -async function getLayer2Erc20BalancesForAddress(evmAddress: string, chain: IChain): Promise { +async function getErc20BalancesForAddress(evmAddress: string, chain: IChain): Promise { const networkId = chain.getConfiguration().id const trackedTokens = getActiveProfile()?.trackedTokens?.[networkId] ?? {} const erc20TokenBalances = [] @@ -122,7 +113,7 @@ async function getLayer2Erc20BalancesForAddress(evmAddress: string, chain: IChai return erc20TokenBalances } -async function fetchLayer2Nfts(evmAddress: string, chain: IChain, account: IAccountState): Promise { +async function fetchL2Irc27Nfts(evmAddress: string, chain: IChain, account: IAccountState): Promise { const accountsCoreContract = getSmartContractHexName('accounts') const getBalanceFunc = getSmartContractHexName('accountNFTs') const agentID = evmAddressToAgentId(evmAddress, chain.getConfiguration()) diff --git a/packages/shared/src/lib/core/layer-2/utils/index.ts b/packages/shared/src/lib/core/layer-2/utils/index.ts index 90f2b1da1c..e01b6f3029 100644 --- a/packages/shared/src/lib/core/layer-2/utils/index.ts +++ b/packages/shared/src/lib/core/layer-2/utils/index.ts @@ -12,6 +12,7 @@ export * from './getErc721TransferSmartContractData' export * from './getEvmTransactionFromHexString' export * from './getHexEncodedTransaction' export * from './getMethodNameForEvmTransaction' +export * from './isErcAsset' export * from './lookupMethodSignature' export * from './parseLayer2Metadata' export * from './parseLayer2MetadataForTransfer' diff --git a/packages/shared/src/lib/core/layer-2/utils/isErcAsset.ts b/packages/shared/src/lib/core/layer-2/utils/isErcAsset.ts new file mode 100644 index 0000000000..ae02b9d371 --- /dev/null +++ b/packages/shared/src/lib/core/layer-2/utils/isErcAsset.ts @@ -0,0 +1,7 @@ +import { isValidEthereumAddress } from '@core/utils/crypto/utils/isValidEthereumAddress' + +export function isErcAsset(assetId: string): boolean { + const [address] = assetId.split(':') + + return isValidEthereumAddress(address) +} diff --git a/packages/shared/src/lib/core/nfts/actions/updateErc721NftOwnership.ts b/packages/shared/src/lib/core/nfts/actions/updateErc721NftOwnership.ts index 456249f485..cee6632566 100644 --- a/packages/shared/src/lib/core/nfts/actions/updateErc721NftOwnership.ts +++ b/packages/shared/src/lib/core/nfts/actions/updateErc721NftOwnership.ts @@ -5,7 +5,6 @@ import { IErc721Nft } from '../interfaces' import { getAllAccountNfts, persistedNftForActiveProfile, updatePersistedNft } from '../stores' import { getOwnerOfErc721Nft } from '../utils' import { get } from 'svelte/store' -import { calculateAndAddPersistedNftBalanceChange } from '@core/activity' export async function updateErc721NftsOwnership(account: IAccountState): Promise { try { @@ -26,8 +25,6 @@ export async function updateErc721NftsOwnership(account: IAccountState): Promise const l2Address = getAddressFromAccountForNetwork(account, nft.networkId) const isSpendable = updatedOwner === l2Address?.toLowerCase() updateAllAccountNftsForAccount(account.index, { ...nft, isSpendable }) - - calculateAndAddPersistedNftBalanceChange(account, nft.networkId, nft.id, isSpendable) }) await Promise.allSettled(promises) } catch (error) { diff --git a/packages/shared/src/lib/core/profile/constants/profile-version.constant.ts b/packages/shared/src/lib/core/profile/constants/profile-version.constant.ts index 274d9763c2..f1b11dd58c 100644 --- a/packages/shared/src/lib/core/profile/constants/profile-version.constant.ts +++ b/packages/shared/src/lib/core/profile/constants/profile-version.constant.ts @@ -1,7 +1,7 @@ import { AppStage } from '@core/app/enums' export const PROFILE_VERSION: Record = { - [AppStage.ALPHA]: 6, + [AppStage.ALPHA]: 7, [AppStage.BETA]: 0, - [AppStage.PROD]: 3, + [AppStage.PROD]: 4, } diff --git a/packages/shared/src/lib/core/profile/migrations/actions/removeEvmBalanceChanges.ts b/packages/shared/src/lib/core/profile/migrations/actions/removeEvmBalanceChanges.ts new file mode 100644 index 0000000000..03b04dd940 --- /dev/null +++ b/packages/shared/src/lib/core/profile/migrations/actions/removeEvmBalanceChanges.ts @@ -0,0 +1,44 @@ +import { persistedBalanceChanges } from '@core/activity' +import { isErcAsset } from '@core/layer-2' +import { NetworkId } from '@core/network' +import { BASE_TOKEN_ID } from '@core/token' + +export function removeEvmBalanceChanges(profileId: string): void { + persistedBalanceChanges.update((state) => { + const profileBalanceChanges = state[profileId] + if (!profileBalanceChanges) { + return state + } + + for (const accountId of Object.keys(profileBalanceChanges)) { + const accountBalanceChanges = profileBalanceChanges[accountId] + + for (const networkId of Object.keys(accountBalanceChanges)) { + const networkBalanceChanges = accountBalanceChanges[networkId as NetworkId] + if (!networkBalanceChanges) { + continue + } + const tokens = networkBalanceChanges.tokens + const nfts = networkBalanceChanges.nfts + + for (const nftId of Object.keys(nfts ?? {})) { + if (isErcAsset(nftId)) { + delete nfts[nftId] + } + } + + for (const tokenId of Object.keys(tokens ?? {})) { + if (tokenId === BASE_TOKEN_ID || isErcAsset(tokenId)) { + delete tokens[tokenId] + } + } + + accountBalanceChanges[networkId as NetworkId] = networkBalanceChanges + } + profileBalanceChanges[accountId] = accountBalanceChanges + } + + state[profileId] = profileBalanceChanges + return state + }) +} diff --git a/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-6-to-7.ts b/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-6-to-7.ts new file mode 100644 index 0000000000..e61deb14be --- /dev/null +++ b/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-6-to-7.ts @@ -0,0 +1,13 @@ +import { removeEvmBalanceChanges } from '../actions/removeEvmBalanceChanges' + +export function alphaProfileMigration6To7(existingProfile: unknown): Promise { + const profile = existingProfile as { id: string } + + try { + removeEvmBalanceChanges(profile.id) + } catch (error) { + return Promise.reject() + } + + return Promise.resolve() +} diff --git a/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-map.ts b/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-map.ts index 2880c4e60b..79d72737a9 100644 --- a/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-map.ts +++ b/packages/shared/src/lib/core/profile/migrations/alpha/alpha-profile-migration-map.ts @@ -5,6 +5,7 @@ import { alphaProfileMigration2To3 } from './alpha-profile-migration-2-to-3' import { alphaProfileMigration3To4 } from './alpha-profile-migration-3-to-4' import { alphaProfileMigration4To5 } from './alpha-profile-migration-4-to-5' import { alphaProfileMigration5To6 } from './alpha-profile-migration-5-to-6' +import { alphaProfileMigration6To7 } from './alpha-profile-migration-6-to-7' export const ALPHA_PROFILE_MIGRATION_MAP: ProfileMigrationMap = { 0: alphaProfileMigration0To1, @@ -13,4 +14,5 @@ export const ALPHA_PROFILE_MIGRATION_MAP: ProfileMigrationMap = { 3: alphaProfileMigration3To4, 4: alphaProfileMigration4To5, 5: alphaProfileMigration5To6, + 6: alphaProfileMigration6To7, } diff --git a/packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-3-to-4.ts b/packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-3-to-4.ts new file mode 100644 index 0000000000..dcf958824a --- /dev/null +++ b/packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-3-to-4.ts @@ -0,0 +1,13 @@ +import { removeEvmBalanceChanges } from '../actions/removeEvmBalanceChanges' + +export function prodProfileMigration3To4(existingProfile: unknown): Promise { + const profile = existingProfile as { id: string } + + try { + removeEvmBalanceChanges(profile.id) + } catch (error) { + return Promise.reject() + } + + return Promise.resolve() +} diff --git a/packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-map.ts b/packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-map.ts index 87763976b1..bcc42d3641 100644 --- a/packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-map.ts +++ b/packages/shared/src/lib/core/profile/migrations/prod/prod-profile-migration-map.ts @@ -3,9 +3,11 @@ import { ProfileMigrationMap } from '../../types' import { prodProfileMigration0To1 } from './prod-profile-migration-0-to-1' import { prodProfileMigration1To2 } from './prod-profile-migration-1-to-2' import { prodProfileMigration2To3 } from './prod-profile-migration-2-to-3' +import { prodProfileMigration3To4 } from './prod-profile-migration-3-to-4' export const PROD_PROFILE_MIGRATION_MAP: ProfileMigrationMap = { 0: prodProfileMigration0To1, 1: prodProfileMigration1To2, 2: prodProfileMigration2To3, + 3: prodProfileMigration3To4, } diff --git a/packages/shared/src/lib/core/wallet/actions/index.ts b/packages/shared/src/lib/core/wallet/actions/index.ts index b276c4d6ee..02adcbf701 100644 --- a/packages/shared/src/lib/core/wallet/actions/index.ts +++ b/packages/shared/src/lib/core/wallet/actions/index.ts @@ -11,7 +11,6 @@ export * from './mintNft' export * from './mintNftCollection' export * from './rejectActivity' export * from './sendOutput' -export * from './updateL2BalanceWithoutActivity' export * from './signEvmTransaction' export * from './signMessage' diff --git a/packages/shared/src/lib/core/wallet/actions/send/sendAndPersistTransactionFromEvm.ts b/packages/shared/src/lib/core/wallet/actions/send/sendAndPersistTransactionFromEvm.ts index 361465319f..fe39746424 100644 --- a/packages/shared/src/lib/core/wallet/actions/send/sendAndPersistTransactionFromEvm.ts +++ b/packages/shared/src/lib/core/wallet/actions/send/sendAndPersistTransactionFromEvm.ts @@ -4,15 +4,16 @@ import { StardustActivity, StardustActivityType, calculateAndAddPersistedNftBalanceChange, + calculateAndAddPersistedTokenBalanceChange, } from '@core/activity' import { addAccountActivity } from '@core/activity/stores' import { generateActivityFromEvmTransaction } from '@core/activity/utils/evm' -import { EvmTransactionData } from '@core/layer-2' +import { EvmTransactionData, isErcAsset } from '@core/layer-2' import { EvmNetworkId, IChain } from '@core/network' import { LocalEvmTransaction } from '@core/transactions' import { addLocalTransactionToPersistedTransaction } from '@core/transactions/stores' import { sendSignedEvmTransaction } from '@core/wallet/actions/sendSignedEvmTransaction' -import { updateL2BalanceWithoutActivity } from '../updateL2BalanceWithoutActivity' +import { updateLayer2AccountBalanceForTokenOnChain } from '@core/layer-2/stores' export async function sendAndPersistTransactionFromEvm( preparedTransaction: EvmTransactionData, @@ -73,18 +74,15 @@ async function createHiddenBalanceChange(account: IAccountState, activity: Stard const received = activity.direction === ActivityDirection.Incoming const networkId = activity.sourceNetworkId - if (activity.type === StardustActivityType.Nft) { + if (activity.type === StardustActivityType.Nft && !isErcAsset(activity.nftId)) { const owned = received ? true : false calculateAndAddPersistedNftBalanceChange(account, networkId, activity.nftId, owned, true) } - if (activity.tokenTransfer) { - const rawAmount = activity.tokenTransfer.rawAmount - const amount = received ? rawAmount : BigInt(-1) * rawAmount - await updateL2BalanceWithoutActivity(amount, activity.tokenTransfer.tokenId, account, networkId) - } + if (activity.tokenTransfer && !isErcAsset(activity.tokenTransfer.tokenId)) { + const tokenId = activity.tokenTransfer.tokenId + const amount = received ? activity.tokenTransfer.rawAmount : BigInt(-1) * activity.tokenTransfer.rawAmount - const rawBaseTokenAmount = received - ? activity.baseTokenTransfer.rawAmount - : BigInt(-1) * (activity.baseTokenTransfer.rawAmount + BigInt(activity.transactionFee ?? 0)) - await updateL2BalanceWithoutActivity(rawBaseTokenAmount, activity.baseTokenTransfer.tokenId, account, networkId) + const newBalance = updateLayer2AccountBalanceForTokenOnChain(account.index, networkId, tokenId, amount) + await calculateAndAddPersistedTokenBalanceChange(account, networkId, tokenId, newBalance, true) + } } diff --git a/packages/shared/src/lib/core/wallet/actions/updateL2BalanceWithoutActivity.ts b/packages/shared/src/lib/core/wallet/actions/updateL2BalanceWithoutActivity.ts deleted file mode 100644 index f3ed46201e..0000000000 --- a/packages/shared/src/lib/core/wallet/actions/updateL2BalanceWithoutActivity.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IAccountState } from '@core/account/interfaces' -import { calculateAndAddPersistedTokenBalanceChange } from '@core/activity/actions' -import { updateLayer2AccountBalanceForTokenOnChain } from '@core/layer-2/stores' -import { NetworkId } from '@core/network/types' - -export async function updateL2BalanceWithoutActivity( - rawAmount: bigint, - tokenId: string, - account: IAccountState, - networkId: NetworkId -): Promise { - const newBalance = updateLayer2AccountBalanceForTokenOnChain(account.index, networkId, tokenId, rawAmount) - await calculateAndAddPersistedTokenBalanceChange(account, networkId, tokenId, newBalance, true) -}