From 615cc1584e92b42f32bf00caaf7455ad15970561 Mon Sep 17 00:00:00 2001 From: Brian Bergeron Date: Thu, 12 Sep 2024 08:20:11 -0700 Subject: [PATCH] feat(migration): enable token auto-detection when basic functionality is on (#26406) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a migration that enables token auto-detection if the basic functionality toggle is on. It also removes an unused property `showTokenAutodetectModalOnUpgrade` from the app metadata controller. It is versioned 125.1 because it's planned to be cherry picked to release 12.3, and that's the subsequent migration version in that branch. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/26406?quickstart=1) - Install an older build like tag `v12.1.0` - In settings, disable token auto detection but keep basic functionality on - Upgrade to this build, reload extension - should see `Running migration 125.1...` in console - Verify token autodetection is enabled in settings - Download state from advanced settings - Verify state does not contain `showTokenAutodetectModalOnUpgrade` - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: sahar-fehri --- app/scripts/migrations/125.1.test.ts | 107 ++++++++++++++++++ app/scripts/migrations/125.1.ts | 50 ++++++++ app/scripts/migrations/index.js | 1 + ...rs-after-init-opt-in-background-state.json | 2 +- .../errors-after-init-opt-in-ui-state.json | 2 +- .../multichain/asset-picker-send.spec.ts | 13 ++- 6 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 app/scripts/migrations/125.1.test.ts create mode 100644 app/scripts/migrations/125.1.ts diff --git a/app/scripts/migrations/125.1.test.ts b/app/scripts/migrations/125.1.test.ts new file mode 100644 index 000000000000..eb00db9d1e07 --- /dev/null +++ b/app/scripts/migrations/125.1.test.ts @@ -0,0 +1,107 @@ +import { migrate, version } from './125.1'; + +const oldVersion = 125; + +describe(`migration #${version}`, () => { + afterEach(() => jest.resetAllMocks()); + + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('Gracefully handles empty/undefined PreferencesController', async () => { + for (const PreferencesController of [{}, undefined, null, 1, '', []]) { + const oldStorage = { + meta: { version: oldVersion }, + data: { PreferencesController }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data.TxController).toStrictEqual(undefined); + } + }); + + it('Enables token autodetection when basic functionality is on', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + useExternalServices: true, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toEqual({ + PreferencesController: { + useExternalServices: true, + useTokenDetection: true, + }, + }); + }); + + it('Does not enable token autodetection when basic functionality is off', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + PreferencesController: { + useExternalServices: false, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toEqual({ + PreferencesController: { + useExternalServices: false, + }, + }); + }); + + it('Removes showTokenAutodetectModalOnUpgrade from the app metadata controller', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + AppMetadataController: { + previousMigrationVersion: oldVersion, + currentMigrationVersion: version, + showTokenAutodetectModalOnUpgrade: null, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toEqual({ + AppMetadataController: { + previousMigrationVersion: oldVersion, + currentMigrationVersion: version, + }, + }); + }); + + it('Does nothing if showTokenAutodetectModalOnUpgrade is not in the app metadata controller', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + AppMetadataController: { + previousMigrationVersion: oldVersion, + currentMigrationVersion: version, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toEqual({ + AppMetadataController: { + previousMigrationVersion: oldVersion, + currentMigrationVersion: version, + }, + }); + }); +}); diff --git a/app/scripts/migrations/125.1.ts b/app/scripts/migrations/125.1.ts new file mode 100644 index 000000000000..d3c975a78a11 --- /dev/null +++ b/app/scripts/migrations/125.1.ts @@ -0,0 +1,50 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 125.1; + +/** + * This migration enables token auto-detection if the basic functionality toggle is on. + * + * It also removes an unused property `showTokenAutodetectModalOnUpgrade` from the app metadata controller. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by + * controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + if ( + hasProperty(state, 'PreferencesController') && + isObject(state.PreferencesController) && + state.PreferencesController.useExternalServices === true + ) { + state.PreferencesController.useTokenDetection = true; + } + + if ( + hasProperty(state, 'AppMetadataController') && + isObject(state.AppMetadataController) + ) { + delete state.AppMetadataController.showTokenAutodetectModalOnUpgrade; + } + + return state; +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 0146779d408f..bb64ec957f75 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -144,6 +144,7 @@ const migrations = [ require('./123'), require('./124'), require('./125'), + require('./125.1'), ]; export default migrations; diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index 7a4d6335dab5..2d2c362b30f8 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -175,7 +175,7 @@ "dismissSeedBackUpReminder": true, "useMultiAccountBalanceChecker": true, "useSafeChainsListValidation": "boolean", - "useTokenDetection": false, + "useTokenDetection": true, "useNftDetection": false, "use4ByteResolution": true, "useCurrencyRateCheck": true, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index c74b7f35c85d..d5ff2eecc5eb 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -109,7 +109,7 @@ "dismissSeedBackUpReminder": true, "useMultiAccountBalanceChecker": true, "useSafeChainsListValidation": true, - "useTokenDetection": false, + "useTokenDetection": true, "useNftDetection": false, "useCurrencyRateCheck": true, "useRequestQueue": true, diff --git a/test/e2e/tests/multichain/asset-picker-send.spec.ts b/test/e2e/tests/multichain/asset-picker-send.spec.ts index 8358e8242f69..5accb14c6074 100644 --- a/test/e2e/tests/multichain/asset-picker-send.spec.ts +++ b/test/e2e/tests/multichain/asset-picker-send.spec.ts @@ -34,6 +34,15 @@ describe('AssetPickerSendFlow @no-mmi', function () { async ({ driver }: { driver: Driver }) => { await unlockWallet(driver); + // Disable token auto detection + await driver.openNewURL( + `${driver.extensionUrl}/home.html#settings/security`, + ); + await driver.clickElement( + '[data-testid="autoDetectTokens"] .toggle-button', + ); + await driver.navigate(); + // Open the send flow openActionMenuAndStartSendFlow(driver); @@ -72,13 +81,13 @@ describe('AssetPickerSendFlow @no-mmi', function () { assert.equal(tokenListSecondaryValue, '$250,000.00'); - // Search for BNB + // Search for CHZ const searchInputField = await driver.waitForSelector( '[data-testid="asset-picker-modal-search-input"]', ); await searchInputField.sendKeys('CHZ'); - // check that BNB is disabled + // check that CHZ is disabled const [, tkn] = await driver.findElements( '[data-testid="multichain-token-list-button"]', );