Skip to content

Commit 475871c

Browse files
authored
Merge pull request #2205 from AmbireTech/hotfix/portfolio-reloading-state
Hotfix/ Portfolio discovery is done before loading is set to true
2 parents 196c914 + ec1506c commit 475871c

2 files changed

Lines changed: 96 additions & 81 deletions

File tree

src/controllers/portfolio/portfolio.test.ts

Lines changed: 66 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -229,23 +229,29 @@ const getMultipleAccountsLearnedAssets = () => {
229229
const tokenHints2 = generateRandomAddresses(10)
230230

231231
const turnHintsToLearnedAssets = (hints: string[]) => {
232-
return hints.reduce((acc, addr) => {
233-
acc[addr] = Date.now()
232+
return hints.reduce(
233+
(acc, addr) => {
234+
acc[addr] = Date.now()
234235

235-
return acc
236-
}, {} as LearnedAssets['erc20s'][string])
236+
return acc
237+
},
238+
{} as LearnedAssets['erc20s'][string]
239+
)
237240
}
238241

239242
const turnCollectionsToLearnedAssetKeys = (
240243
collections: [string, bigint[]][]
241244
): LearnedAssets['erc721s'][string] => {
242-
return collections.reduce((acc, nft) => {
243-
erc721CollectionToLearnedAssetKeys(nft).forEach((key) => {
244-
acc[key] = Date.now()
245-
})
245+
return collections.reduce(
246+
(acc, nft) => {
247+
erc721CollectionToLearnedAssetKeys(nft).forEach((key) => {
248+
acc[key] = Date.now()
249+
})
246250

247-
return acc
248-
}, {} as LearnedAssets['erc721s'][string])
251+
return acc
252+
},
253+
{} as LearnedAssets['erc721s'][string]
254+
)
249255
}
250256

251257
return {
@@ -412,22 +418,22 @@ describe('Portfolio Controller ', () => {
412418
new Promise((resolve) => {
413419
setTimeout(() => {
414420
queueOrder.push('updatePortfolioState - #1 call')
415-
resolve(true)
421+
resolve([true, null])
416422
}, 2000)
417423
})
418424
)
419425
.mockImplementationOnce(
420426
() =>
421427
new Promise((resolve) => {
422428
queueOrder.push('updatePortfolioState - #2 call')
423-
resolve(true)
429+
resolve([true, null])
424430
})
425431
)
426432
.mockImplementationOnce(
427433
() =>
428434
new Promise((resolve) => {
429435
queueOrder.push('updatePortfolioState - #3 call')
430-
resolve(true)
436+
resolve([true, null])
431437
})
432438
)
433439

@@ -741,12 +747,15 @@ describe('Portfolio Controller ', () => {
741747
const firstBatchOf50 = generateRandomAddresses(50)
742748
const startingLearnedAssets: LearnedAssets = {
743749
erc20s: {
744-
[`${1}:${account.addr}`]: firstBatchOf50.reduce((acc, addr, index) => {
745-
// First 20 are still owned, last 30 are no longer owned
746-
acc[addr] = index <= 20 ? Date.now() : Date.now() - 24 * 60 * 60 * 1000
747-
748-
return acc
749-
}, {} as LearnedAssets['erc20s'][string])
750+
[`${1}:${account.addr}`]: firstBatchOf50.reduce(
751+
(acc, addr, index) => {
752+
// First 20 are still owned, last 30 are no longer owned
753+
acc[addr] = index <= 20 ? Date.now() : Date.now() - 24 * 60 * 60 * 1000
754+
755+
return acc
756+
},
757+
{} as LearnedAssets['erc20s'][string]
758+
)
750759
},
751760
erc721s: {}
752761
}
@@ -771,35 +780,44 @@ describe('Portfolio Controller ', () => {
771780
test('To be learned erc721 cleanup mechanism works', async () => {
772781
// A total of 80 collections are added. 30 of them are "no longer owned"
773782
// but only 10 of them should be removed as the threshold of unowned is 20
774-
const firstRandomCollections = generateRandomAddresses(50).reduce((acc, addr, index) => {
775-
acc.push([addr, Math.random() < 0.2 ? [] : [BigInt(index)]] as [string, bigint[]])
783+
const firstRandomCollections = generateRandomAddresses(50).reduce(
784+
(acc, addr, index) => {
785+
acc.push([addr, Math.random() < 0.2 ? [] : [BigInt(index)]] as [string, bigint[]])
776786

777-
return acc
778-
}, [] as [string, bigint[]][])
787+
return acc
788+
},
789+
[] as [string, bigint[]][]
790+
)
779791

780792
const keys = firstRandomCollections.map((c) => erc721CollectionToLearnedAssetKeys(c)).flat()
781793

782794
const startingLearnedAssets: LearnedAssets = {
783795
erc20s: {},
784796
erc721s: {
785-
[`${1}:${account.addr}`]: keys.reduce((acc, key, index) => {
786-
// First 20 are still owned, last 30 are no longer owned
787-
acc[key] = index <= 20 ? Date.now() : Date.now() - 24 * 60 * 60 * 1000
788-
789-
return acc
790-
}, {} as LearnedAssets['erc721s'][string])
797+
[`${1}:${account.addr}`]: keys.reduce(
798+
(acc, key, index) => {
799+
// First 20 are still owned, last 30 are no longer owned
800+
acc[key] = index <= 20 ? Date.now() : Date.now() - 24 * 60 * 60 * 1000
801+
802+
return acc
803+
},
804+
{} as LearnedAssets['erc721s'][string]
805+
)
791806
}
792807
}
793808

794809
const { controller, storageCtrl } = await prepareTest((storageC) =>
795810
storageC.set('learnedAssets', startingLearnedAssets)
796811
)
797812

798-
const nextRandomCollections = generateRandomAddresses(30).reduce((acc, addr, index) => {
799-
acc.push([addr, Math.random() < 0.2 ? [] : [BigInt(index)]] as [string, bigint[]])
813+
const nextRandomCollections = generateRandomAddresses(30).reduce(
814+
(acc, addr, index) => {
815+
acc.push([addr, Math.random() < 0.2 ? [] : [BigInt(index)]] as [string, bigint[]])
800816

801-
return acc
802-
}, [] as [string, bigint[]][])
817+
return acc
818+
},
819+
[] as [string, bigint[]][]
820+
)
803821

804822
const allCurrentlyOwnedCollections = [
805823
...firstRandomCollections.slice(0, 20),
@@ -1043,9 +1061,9 @@ describe('Portfolio Controller ', () => {
10431061

10441062
const toBeLearnedToken = controller
10451063
.getAccountPortfolioState(account.addr)
1046-
['1']?.result?.tokens.find(
1047-
(token) => token.address === '0xA0b73E1Ff0B80914AB6fe0444E65848C4C34450b'
1048-
)
1064+
[
1065+
'1'
1066+
]?.result?.tokens.find((token) => token.address === '0xA0b73E1Ff0B80914AB6fe0444E65848C4C34450b')
10491067

10501068
expect(toBeLearnedToken).toBeTruthy()
10511069

@@ -1090,10 +1108,9 @@ describe('Portfolio Controller ', () => {
10901108

10911109
const toBeLearnedToken = controller
10921110
.getAccountPortfolioState(account2.addr)
1093-
['137']?.result?.tokens.find(
1094-
(token) =>
1095-
token.address === '0xc2132D05D31c914a87C6611C10748AEb04B58e8F' && token.amount > 0n
1096-
)
1111+
[
1112+
'137'
1113+
]?.result?.tokens.find((token) => token.address === '0xc2132D05D31c914a87C6611C10748AEb04B58e8F' && token.amount > 0n)
10971114
expect(toBeLearnedToken).toBeTruthy()
10981115

10991116
const key = `${137}:${account2.addr}`
@@ -1112,9 +1129,9 @@ describe('Portfolio Controller ', () => {
11121129
networksCtrl.networks.forEach((network) => {
11131130
const nativeToken = controller
11141131
.getAccountPortfolioState(account.addr)
1115-
[network.chainId.toString()]?.result?.tokens.find(
1116-
(token) => token.address === ZeroAddress
1117-
)
1132+
[
1133+
network.chainId.toString()
1134+
]?.result?.tokens.find((token) => token.address === ZeroAddress)
11181135

11191136
if (!nativeToken) {
11201137
console.error('Native token not found for network:', network.name)
@@ -1808,9 +1825,9 @@ describe('Portfolio Controller ', () => {
18081825
const getCustomTokenFromPortfolio = () => {
18091826
return controller
18101827
.getAccountPortfolioState(account.addr)
1811-
['1']?.result?.tokens.find(
1812-
(token) => token.address === customToken.address && token.chainId === customToken.chainId
1813-
)
1828+
[
1829+
'1'
1830+
]?.result?.tokens.find((token) => token.address === customToken.address && token.chainId === customToken.chainId)
18141831
}
18151832

18161833
expect(tokenIsSet).toEqual(customToken)
@@ -1870,12 +1887,9 @@ describe('Portfolio Controller ', () => {
18701887

18711888
const hiddenToken = controller
18721889
.getAccountPortfolioState(account.addr)
1873-
['1']?.result?.tokens.find(
1874-
(token) =>
1875-
token.address === preference.address &&
1876-
token.chainId === preference.chainId &&
1877-
token.flags.isHidden
1878-
)
1890+
[
1891+
'1'
1892+
]?.result?.tokens.find((token) => token.address === preference.address && token.chainId === preference.chainId && token.flags.isHidden)
18791893
expect(hiddenToken).toBeTruthy()
18801894
})
18811895
test('Calling toggleHideToken a second time deletes the preference', async () => {

src/controllers/portfolio/portfolio.ts

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -942,16 +942,17 @@ export class PortfolioController extends EventEmitter implements IPortfolioContr
942942
network: Network,
943943
portfolioLib: Portfolio | null,
944944
portfolioProps: Partial<GetOptions> & {
945+
hasKeys: boolean
945946
maxDataAgeMs?: number
947+
defiMaxDataAgeMs?: number
946948
isManualUpdate?: boolean
947-
},
948-
discoveryData: FormattedPortfolioDiscoveryResponse | null
949-
): Promise<boolean> {
949+
}
950+
): Promise<[boolean, FormattedPortfolioDiscoveryResponse | null]> {
950951
const { maxDataAgeMs, isManualUpdate } = portfolioProps
951952
const accountState = this.#state[accountId]
952953

953954
// Can occur if the account is removed while updateSelectedAccount is in progress
954-
if (!accountState) return false
955+
if (!accountState) return [false, null]
955956

956957
if (!accountState[network.chainId.toString()]) {
957958
// isLoading must be false here, otherwise canSkipUpdate will return true
@@ -964,7 +965,7 @@ export class PortfolioController extends EventEmitter implements IPortfolioContr
964965
maxDataAgeMs
965966
)
966967

967-
if (canSkipUpdate) return false
968+
if (canSkipUpdate) return [true, null]
968969

969970
this.#setNetworkLoading(accountId, network.chainId.toString(), true)
970971
const state = accountState[network.chainId.toString()]!
@@ -984,13 +985,32 @@ export class PortfolioController extends EventEmitter implements IPortfolioContr
984985

985986
const networkPriceCache = this.priceCache[network.chainId.toString()] || new Map()
986987

988+
const hintsResponse =
989+
this.#state[accountId]?.[network.chainId.toString()]?.result?.lastExternalApiUpdateData
990+
const discoveryData = await this.getPortfolioFromApiDiscovery({
991+
chainId: network.chainId,
992+
accountAddr: accountId,
993+
baseCurrency: 'usd',
994+
externalApiHintsResponse: hintsResponse || null,
995+
isManualUpdate,
996+
defiMaxDataAgeMs: portfolioProps?.defiMaxDataAgeMs,
997+
hasKeys: portfolioProps.hasKeys
998+
})
999+
const allHints = this.getAllHints(
1000+
accountId,
1001+
network.chainId,
1002+
isManualUpdate,
1003+
discoveryData?.data?.hints
1004+
)
1005+
9871006
// Fetch the portfolio and custom defi positions in parallel
9881007
const [portfolioResult, customPositionsResult] = await Promise.all([
9891008
portfolioLib.get(accountId, {
9901009
priceRecency: 60000 * 5,
9911010
priceCache: networkPriceCache,
9921011
blockTag: 'both',
9931012
fetchPinned: !hasNonZeroTokens,
1013+
...allHints,
9941014
...portfolioProps,
9951015
disableAutoDiscovery: true
9961016
}),
@@ -1074,7 +1094,7 @@ export class PortfolioController extends EventEmitter implements IPortfolioContr
10741094
}
10751095

10761096
this.emitUpdate()
1077-
return true
1097+
return [true, discoveryData]
10781098
} catch (e: any) {
10791099
this.emitError({
10801100
level: 'silent',
@@ -1099,7 +1119,7 @@ export class PortfolioController extends EventEmitter implements IPortfolioContr
10991119
}
11001120
this.emitUpdate()
11011121

1102-
return false
1122+
return [false, null]
11031123
}
11041124
}
11051125

@@ -1350,25 +1370,7 @@ export class PortfolioController extends EventEmitter implements IPortfolioContr
13501370
paramsMaxDataAgeMsUnused
13511371
)
13521372

1353-
const hintsResponse =
1354-
this.#state[accountId]?.[network.chainId.toString()]?.result?.lastExternalApiUpdateData
1355-
const discoveryResponse = await this.getPortfolioFromApiDiscovery({
1356-
chainId: network.chainId,
1357-
accountAddr: accountId,
1358-
baseCurrency: 'usd',
1359-
externalApiHintsResponse: hintsResponse || null,
1360-
isManualUpdate,
1361-
defiMaxDataAgeMs: opts?.defiMaxDataAgeMs,
1362-
hasKeys: this.#keystore.getAccountKeys(selectedAccount).length > 0
1363-
})
1364-
const allHints = this.getAllHints(
1365-
accountId,
1366-
network.chainId,
1367-
isManualUpdate,
1368-
discoveryResponse?.data?.hints
1369-
)
1370-
1371-
const isSuccessful = await this.updatePortfolioState(
1373+
const [isSuccessful, discoveryResponse] = await this.updatePortfolioState(
13721374
accountId,
13731375
network,
13741376
portfolioLib,
@@ -1385,9 +1387,8 @@ export class PortfolioController extends EventEmitter implements IPortfolioContr
13851387
}
13861388
}),
13871389
disableAutoDiscovery: true,
1388-
...allHints
1389-
},
1390-
discoveryResponse
1390+
hasKeys: this.#keystore.getAccountKeys(selectedAccount).length > 0
1391+
}
13911392
)
13921393

13931394
// Learn tokens and nfts from the portfolio lib

0 commit comments

Comments
 (0)