Skip to content

Commit

Permalink
Merge branch 'main' into 231128-upgrade-ComposableController-to-BaseC…
Browse files Browse the repository at this point in the history
…ontrollerV2
  • Loading branch information
MajorLift committed Dec 5, 2023
2 parents c957946 + ae1c9eb commit 0a3d642
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 248 deletions.
2 changes: 2 additions & 0 deletions packages/assets-controllers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Changed
- Update TokenListController to fetch prefiltered set of tokens from the API, reducing response data and removing the need for filtering logic ([#2054](https://github.com/MetaMask/core/pull/2054))

## [20.0.0]
### Added
Expand Down
2 changes: 1 addition & 1 deletion packages/assets-controllers/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = merge(baseConfig, {
coverageThreshold: {
global: {
branches: 88.2,
functions: 96,
functions: 95.95,
lines: 96.25,
statements: 96.5,
},
Expand Down
17 changes: 15 additions & 2 deletions packages/assets-controllers/src/TokenDetectionController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
ProviderConfig,
} from '@metamask/network-controller';
import { PreferencesController } from '@metamask/preferences-controller';
import type { Hex } from '@metamask/utils';
import { BN } from 'ethereumjs-util';
import nock from 'nock';
import * as sinon from 'sinon';
Expand Down Expand Up @@ -149,7 +150,7 @@ describe('TokenDetectionController', () => {

beforeEach(async () => {
nock(TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.mainnet)}`)
.get(getTokensPath(ChainId.mainnet))
.reply(200, sampleTokenList)
.get(
`/token/${convertHexToDecimal(ChainId.mainnet)}?address=${
Expand Down Expand Up @@ -437,7 +438,7 @@ describe('TokenDetectionController', () => {
it('should not call getBalancesInSingleCall after stopping polling, and then switching between networks that support token detection', async () => {
const polygonDecimalChainId = '137';
nock(TOKEN_END_POINT_API)
.get(`/tokens/${polygonDecimalChainId}`)
.get(getTokensPath(toHex(polygonDecimalChainId)))
.reply(200, sampleTokenList);

const stub = sinon.stub();
Expand Down Expand Up @@ -657,3 +658,15 @@ describe('TokenDetectionController', () => {
});
});
});

/**
* Construct the path used to fetch tokens that we can pass to `nock`.
*
* @param chainId - The chain ID.
* @returns The constructed path.
*/
function getTokensPath(chainId: Hex) {
return `/tokens/${convertHexToDecimal(
chainId,
)}?occurrenceFloor=3&includeNativeAssets=false&includeDuplicateSymbolAssets=false&includeTokenFees=false&includeAssetType=false`;
}
191 changes: 26 additions & 165 deletions packages/assets-controllers/src/TokenListController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
ProviderConfig,
} from '@metamask/network-controller';
import { NetworkStatus } from '@metamask/network-controller';
import type { Hex } from '@metamask/utils';
import nock from 'nock';
import * as sinon from 'sinon';

Expand Down Expand Up @@ -105,91 +106,6 @@ const sampleMainnetTokensChainsCache = sampleMainnetTokenList.reduce(
{} as TokenListMap,
);

const sampleWithDuplicateSymbols = [
{
address: '0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c',
symbol: 'BNT',
decimals: 18,
occurrences: 11,
name: 'Bancor',
iconUrl:
'https://static.metafi.codefi.network/api/v1/tokenIcons/1/0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c.png',
aggregators: [
'Bancor',
'CMC',
'CoinGecko',
'1inch',
'Paraswap',
'PMM',
'Zapper',
'Zerion',
'0x',
],
},
];

const sampleWithDuplicateSymbolsTokensChainsCache =
sampleWithDuplicateSymbols.reduce((output, current) => {
output[current.address] = current;
return output;
}, {} as TokenListMap);

const sampleWithLessThan3OccurencesResponse = [
{
address: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f',
symbol: 'SNX',
decimals: 18,
occurrences: 2,
name: 'Synthetix',
iconUrl:
'https://static.metafi.codefi.network/api/v1/tokenIcons/1/0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f.png',
aggregators: [
'Aave',
'Bancor',
'CMC',
'Crypto.com',
'CoinGecko',
'1inch',
'Paraswap',
'PMM',
'Synthetix',
'Zapper',
'Zerion',
'0x',
],
},
{
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
occurrences: 11,
name: 'Chainlink',
iconUrl:
'https://static.metafi.codefi.network/api/v1/tokenIcons/1/0x514910771af9ca656af840dff83e8264ecf986ca.png',
aggregators: [
'Aave',
'Bancor',
'CMC',
'Crypto.com',
'CoinGecko',
'1inch',
'Paraswap',
'PMM',
'Zapper',
'Zerion',
'0x',
],
},
];

const sampleWith3OrMoreOccurrences =
sampleWithLessThan3OccurencesResponse.reduce((output, token) => {
if (token.occurrences >= 3) {
output[token.address] = token;
}
return output;
}, {} as TokenListMap);

const sampleBinanceTokenList = [
{
address: '0x7083609fce4d1d8dc0c979aab8c869ea2c873402',
Expand Down Expand Up @@ -734,7 +650,7 @@ describe('TokenListController', () => {

it('should update tokenList state when network updates are passed via onNetworkStateChange callback', async () => {
nock(tokenService.TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.mainnet)}`)
.get(getTokensPath(ChainId.mainnet))
.reply(200, sampleMainnetTokenList)
.persist();

Expand Down Expand Up @@ -897,7 +813,7 @@ describe('TokenListController', () => {

it('should update token list from api', async () => {
nock(tokenService.TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.mainnet)}`)
.get(getTokensPath(ChainId.mainnet))
.reply(200, sampleMainnetTokenList)
.persist();

Expand Down Expand Up @@ -935,12 +851,12 @@ describe('TokenListController', () => {

it('should update the cache before threshold time if the current data is undefined', async () => {
nock(tokenService.TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.mainnet)}`)
.get(getTokensPath(ChainId.mainnet))
.once()
.reply(200, undefined);

nock(tokenService.TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.mainnet)}`)
.get(getTokensPath(ChainId.mainnet))
.reply(200, sampleMainnetTokenList)
.persist();

Expand Down Expand Up @@ -988,76 +904,9 @@ describe('TokenListController', () => {
controller.destroy();
});

it('should update token list after removing data with duplicate symbols', async () => {
nock(tokenService.TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.mainnet)}`)
.reply(200, sampleWithDuplicateSymbols)
.persist();

const controllerMessenger = getControllerMessenger();
const messenger = getRestrictedMessenger(controllerMessenger);
const controller = new TokenListController({
chainId: ChainId.mainnet,
preventPollingOnNetworkRestart: false,
messenger,
});
await controller.start();
expect(controller.state.tokenList).toStrictEqual({
'0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c': {
address: '0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c',
symbol: 'BNT',
decimals: 18,
occurrences: 11,
name: 'Bancor',
iconUrl:
'https://static.metafi.codefi.network/api/v1/tokenIcons/1/0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c.png',
aggregators: [
'Bancor',
'CMC',
'CoinGecko',
'1inch',
'Paraswap',
'PMM',
'Zapper',
'Zerion',
'0x',
],
},
});

expect(
controller.state.tokensChainsCache[ChainId.mainnet].data,
).toStrictEqual(sampleWithDuplicateSymbolsTokensChainsCache);
controller.destroy();
});

it('should update token list after removing data less than 3 occurrences', async () => {
nock(tokenService.TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.mainnet)}`)
.reply(200, sampleWithLessThan3OccurencesResponse)
.persist();

const controllerMessenger = getControllerMessenger();
const messenger = getRestrictedMessenger(controllerMessenger);
const controller = new TokenListController({
chainId: ChainId.mainnet,
preventPollingOnNetworkRestart: false,
messenger,
});
await controller.start();
expect(controller.state.tokenList).toStrictEqual(
sampleWith3OrMoreOccurrences,
);

expect(
controller.state.tokensChainsCache[ChainId.mainnet].data,
).toStrictEqual(sampleWith3OrMoreOccurrences);
controller.destroy();
});

it('should update token list when the token property changes', async () => {
nock(tokenService.TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.mainnet)}`)
.get(getTokensPath(ChainId.mainnet))
.reply(200, sampleMainnetTokenList)
.persist();

Expand Down Expand Up @@ -1085,7 +934,7 @@ describe('TokenListController', () => {

it('should update the cache when the timestamp expires', async () => {
nock(tokenService.TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.mainnet)}`)
.get(getTokensPath(ChainId.mainnet))
.reply(200, sampleMainnetTokenList)
.persist();

Expand Down Expand Up @@ -1115,11 +964,11 @@ describe('TokenListController', () => {

it('should update token list when the chainId change', async () => {
nock(tokenService.TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.mainnet)}`)
.get(getTokensPath(ChainId.mainnet))
.reply(200, sampleMainnetTokenList)
.get(`/tokens/${convertHexToDecimal(ChainId.goerli)}`)
.get(getTokensPath(ChainId.goerli))
.reply(200, { error: 'ChainId 5 is not supported' })
.get(`/tokens/56`)
.get(getTokensPath(toHex(56)))
.reply(200, sampleBinanceTokenList)
.persist();

Expand Down Expand Up @@ -1212,11 +1061,11 @@ describe('TokenListController', () => {

it('should update preventPollingOnNetworkRestart and restart the polling on network restart', async () => {
nock(tokenService.TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.mainnet)}`)
.get(getTokensPath(ChainId.mainnet))
.reply(200, sampleMainnetTokenList)
.get(`/tokens/${convertHexToDecimal(ChainId.goerli)}`)
.get(getTokensPath(ChainId.goerli))
.reply(200, { error: 'ChainId 5 is not supported' })
.get(`/tokens/56`)
.get(getTokensPath(toHex(56)))
.reply(200, sampleBinanceTokenList)
.persist();

Expand Down Expand Up @@ -1301,7 +1150,7 @@ describe('TokenListController', () => {

it('should call fetchTokenListByChainId with the correct chainId', async () => {
nock(tokenService.TOKEN_END_POINT_API)
.get(`/tokens/${convertHexToDecimal(ChainId.sepolia)}`)
.get(getTokensPath(ChainId.sepolia))
.reply(200, sampleSepoliaTokenList)
.persist();

Expand Down Expand Up @@ -1482,3 +1331,15 @@ describe('TokenListController', () => {
});
});
});

/**
* Construct the path used to fetch tokens that we can pass to `nock`.
*
* @param chainId - The chain ID.
* @returns The constructed path.
*/
function getTokensPath(chainId: Hex) {
return `/tokens/${convertHexToDecimal(
chainId,
)}?occurrenceFloor=3&includeNativeAssets=false&includeDuplicateSymbolAssets=false&includeTokenFees=false&includeAssetType=false`;
}
21 changes: 1 addition & 20 deletions packages/assets-controllers/src/TokenListController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,26 +293,7 @@ export class TokenListController extends PollingController<
});
return;
}
// Filtering out tokens with less than 3 occurrences and native tokens
const filteredTokenList = tokensFromAPI.filter(
(token) =>
token.occurrences &&
token.occurrences >= 3 &&
token.address !== '0x0000000000000000000000000000000000000000',
);
// Removing the tokens with symbol conflicts
const symbolsList = filteredTokenList.map((token) => token.symbol);
const duplicateSymbols = [
...new Set(
symbolsList.filter(
(symbol, index) => symbolsList.indexOf(symbol) !== index,
),
),
];
const uniqueTokenList = filteredTokenList.filter(
(token) => !duplicateSymbols.includes(token.symbol),
);
for (const token of uniqueTokenList) {
for (const token of tokensFromAPI) {
const formattedToken: TokenListToken = {
...token,
aggregators: formatAggregatorNames(token.aggregators),
Expand Down
Loading

0 comments on commit 0a3d642

Please sign in to comment.