Skip to content
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
1 change: 1 addition & 0 deletions packages/assets-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Fetch balances when switching account groups, enabling RPC-only networks, or after a new account is added to the account tree ([#9388](https://github.com/MetaMask/core/pull/9388))
- Add temporary `tempMigrateAssetsInfoMetadataAssets3346` constructor option that heals `assetsInfo` metadata (and custom-asset tracking) wiped by a prior defect for tokens on niche EVM chains, using legacy `TokensController` state provided by the host ([#9393](https://github.com/MetaMask/core/pull/9393))

## [10.0.1]

Expand Down
120 changes: 120 additions & 0 deletions packages/assets-controller/src/AssetsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { PriceDataSourceConfig } from './data-sources/PriceDataSource';
import { PriceDataSource } from './data-sources/PriceDataSource';
import { TokenDataSource } from './data-sources/TokenDataSource';
import { buildDefaultAssetsInfo } from './defaults';
import type { Assets3346MigrationState } from './migrations/healAssetsInfoMetadata';
import type {
Caip19AssetId,
AccountId,
Expand Down Expand Up @@ -125,6 +126,8 @@ type WithControllerOptions = {
trace: TraceCallback;
priceDataSourceConfig: PriceDataSourceConfig;
isEnabled: () => boolean;
captureException: (error: Error) => void;
tempMigrateAssetsInfoMetadataAssets3346: () => Assets3346MigrationState;
}>;
};

Expand Down Expand Up @@ -320,6 +323,123 @@ describe('AssetsController', () => {
});
});

describe('temporary assetsInfo metadata healing (tempMigrateAssetsInfoMetadataAssets3346)', () => {
const HEALED_ASSET_ID =
'eip155:14/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' as Caip19AssetId;
const LEGACY_ACCOUNT_ADDRESS =
'0x1234567890123456789012345678901234567890';
const legacyState: Assets3346MigrationState = {
TokensController: {
allTokens: {
// Flare (0xe / 14) is not covered by the Accounts API.
'0xe': {
[LEGACY_ACCOUNT_ADDRESS]: [
{
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
symbol: 'TST',
name: 'Test Token',
decimals: 18,
},
],
},
},
},
AccountsController: {
internalAccounts: {
accounts: {
[MOCK_ACCOUNT_ID]: { address: LEGACY_ACCOUNT_ADDRESS },
},
},
},
};

it('heals wiped niche-chain token metadata from legacy state on construction', async () => {
await withController(
{
controllerOptions: {
tempMigrateAssetsInfoMetadataAssets3346: () => legacyState,
},
},
({ controller }) => {
expect(controller.state.assetsInfo[HEALED_ASSET_ID]).toStrictEqual({
type: 'erc20',
symbol: 'TST',
name: 'Test Token',
decimals: 18,
});
expect(
controller.state.customAssets[MOCK_ACCOUNT_ID],
).toStrictEqual([HEALED_ASSET_ID]);
},
);
});

it('does not overwrite existing assetsInfo metadata', async () => {
const existingMetadata: FungibleAssetMetadata = {
type: 'erc20',
symbol: 'EXISTING',
name: 'Existing Token',
decimals: 6,
};

await withController(
{
state: {
assetsInfo: { [HEALED_ASSET_ID]: existingMetadata },
},
controllerOptions: {
tempMigrateAssetsInfoMetadataAssets3346: () => legacyState,
},
},
({ controller }) => {
expect(controller.state.assetsInfo[HEALED_ASSET_ID]).toStrictEqual(
existingMetadata,
);
},
);
});

it('leaves state untouched when the legacy state has nothing restorable', async () => {
await withController(
{
controllerOptions: {
tempMigrateAssetsInfoMetadataAssets3346: () => ({}),
},
},
({ controller }) => {
expect(controller.state).toStrictEqual(
getDefaultAssetsControllerState(),
);
},
);
});

it('reports getter errors via captureException without breaking construction', async () => {
const captureException = jest.fn();

await withController(
{
controllerOptions: {
captureException,
tempMigrateAssetsInfoMetadataAssets3346: () => {
throw new Error('legacy state unavailable');
},
},
},
({ controller }) => {
expect(controller.state).toStrictEqual(
getDefaultAssetsControllerState(),
);
expect(captureException).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('legacy state unavailable'),
}),
);
},
);
});
});

it('initializes normally when isEnabled returns true', async () => {
await withController(({ controller, messenger }) => {
// Controller should have default state
Expand Down
21 changes: 21 additions & 0 deletions packages/assets-controller/src/AssetsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ import {
createParallelMiddleware,
} from './middlewares/ParallelMiddleware';
import { RpcFallbackMiddleware } from './middlewares/RpcFallbackMiddleware';
import type { Assets3346MigrationState } from './migrations/healAssetsInfoMetadata';
import { tempHealAssetsInfoMetadata } from './migrations/healAssetsInfoMetadata';
import type {
AccountId,
AssetPreferences,
Expand Down Expand Up @@ -435,6 +437,12 @@ export type AssetsControllerOptions = {
* Defaults to () => true.
*/
isOnboarded?: () => boolean;

/**
* TEMPORARY — will be removed in a future release.
* Issue: https://consensyssoftware.atlassian.net/browse/ASSETS-3346
*/
tempMigrateAssetsInfoMetadataAssets3346?: () => Assets3346MigrationState;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can have this patten for future migration , agreed on keep it temp for now but we can consider later

};

// ============================================================================
Expand Down Expand Up @@ -849,6 +857,7 @@ export class AssetsController extends BaseController<
priceDataSourceConfig,
stakedBalanceDataSourceConfig,
isOnboarded,
tempMigrateAssetsInfoMetadataAssets3346,
}: AssetsControllerOptions) {
super({
name: CONTROLLER_NAME,
Expand All @@ -868,6 +877,18 @@ export class AssetsController extends BaseController<
this.#queryApiClient = queryApiClient;
const rpcConfig = rpcDataSourceConfig ?? {};

// TEMPORARY: heal assetsInfo metadata wiped by a prior defect
// (see extension migration #215 / ASSETS-3346). Remove in a future release.
if (tempMigrateAssetsInfoMetadataAssets3346) {
this.update(() =>
tempHealAssetsInfoMetadata({
state: this.state,
getMigrationState: tempMigrateAssetsInfoMetadataAssets3346,
captureException,
}),
);
}

this.#initializeNativeAssetsMap(queryApiClient);

this.#onActiveChainsUpdated = (
Expand Down
Loading