From e045476ce7e2a51794112524a53b3136fd8ddc05 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 24 Aug 2023 11:24:55 -0230 Subject: [PATCH 01/16] Version 10.35.1 --- CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bb54e3bd..df2711ec9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [10.35.1] + ## [10.35.0] ### Added - Add the ability to customize tx nonce on ERC20 approval screens ([#17945](https://github.com/MetaMask/metamask-extension/pull/17945)) @@ -3946,7 +3948,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Uncategorized - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.35.0...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.35.1...HEAD +[10.35.1]: https://github.com/MetaMask/metamask-extension/compare/v10.35.0...v10.35.1 [10.35.0]: https://github.com/MetaMask/metamask-extension/compare/v10.34.5...v10.35.0 [10.34.5]: https://github.com/MetaMask/metamask-extension/compare/v10.34.4...v10.34.5 [10.34.4]: https://github.com/MetaMask/metamask-extension/compare/v10.34.3...v10.34.4 diff --git a/package.json b/package.json index 5c7f6277b..a3170b281 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "10.35.0", + "version": "10.35.1", "private": true, "repository": { "type": "git", From 63dd03e6e7ced61e45f25453a493c6eb2c5e9d72 Mon Sep 17 00:00:00 2001 From: Nicholas Ellul Date: Tue, 18 Jul 2023 16:21:30 -0400 Subject: [PATCH 02/16] Enforce user preferences in incoming transactions controller (#19982) * Enforce user preferences in incoming transactions controller * Combine various conditions to determine tx lookup * Update tests to not use private methods --- .../controllers/incoming-transactions.js | 41 ++++--- .../controllers/incoming-transactions.test.js | 106 +++++++++++++++++- 2 files changed, 132 insertions(+), 15 deletions(-) diff --git a/app/scripts/controllers/incoming-transactions.js b/app/scripts/controllers/incoming-transactions.js index 987f0d87b..9b0b85f33 100644 --- a/app/scripts/controllers/incoming-transactions.js +++ b/app/scripts/controllers/incoming-transactions.js @@ -135,15 +135,12 @@ export default class IncomingTransactionsController { } start() { - const { featureFlags = {} } = this.preferencesController.store.getState(); - const { showIncomingTransactions } = featureFlags; + const chainId = this.getCurrentChainId(); - if (!showIncomingTransactions) { - return; + if (this._allowedToMakeFetchIncomingTx(chainId)) { + this.blockTracker.removeListener('latest', this._onLatestBlock); + this.blockTracker.addListener('latest', this._onLatestBlock); } - - this.blockTracker.removeListener('latest', this._onLatestBlock); - this.blockTracker.addListener('latest', this._onLatestBlock); } stop() { @@ -161,13 +158,9 @@ export default class IncomingTransactionsController { * @param {number} [newBlockNumberDec] - block number to begin fetching from */ async _update(address, newBlockNumberDec) { - const { completedOnboarding } = this.onboardingController.store.getState(); const chainId = this.getCurrentChainId(); - if ( - !Object.hasOwnProperty.call(ETHERSCAN_SUPPORTED_NETWORKS, chainId) || - !address || - !completedOnboarding - ) { + + if (!address || !this._allowedToMakeFetchIncomingTx(chainId)) { return; } try { @@ -302,4 +295,26 @@ export default class IncomingTransactionsController { type: TransactionType.incoming, }; } + + /** + * @param chainId - {string} The chainId of the current network + * @returns {boolean} Whether or not the user has consented to show incoming transactions + */ + _allowedToMakeFetchIncomingTx(chainId) { + const { featureFlags = {} } = this.preferencesController.store.getState(); + const { completedOnboarding } = this.onboardingController.store.getState(); + + const hasIncomingTransactionsFeatureEnabled = Boolean( + featureFlags.showIncomingTransactions, + ); + + const isEtherscanSupportedNetwork = Boolean( + ETHERSCAN_SUPPORTED_NETWORKS[chainId], + ); + return ( + completedOnboarding && + isEtherscanSupportedNetwork && + hasIncomingTransactionsFeatureEnabled + ); + } } diff --git a/app/scripts/controllers/incoming-transactions.test.js b/app/scripts/controllers/incoming-transactions.test.js index bdfac55a7..c46c3190b 100644 --- a/app/scripts/controllers/incoming-transactions.test.js +++ b/app/scripts/controllers/incoming-transactions.test.js @@ -78,11 +78,11 @@ function getMockPreferencesController({ }; } -function getMockOnboardingController() { +function getMockOnboardingController({ completedOnboarding = true } = {}) { return { store: { getState: sinon.stub().returns({ - completedOnboarding: true, + completedOnboarding, }), subscribe: sinon.spy(), }, @@ -98,6 +98,16 @@ function getMockBlockTracker() { }; } +function getDefaultControllerOpts() { + return { + blockTracker: getMockBlockTracker(), + ...getMockNetworkControllerMethods(CHAIN_IDS.GOERLI), + preferencesController: getMockPreferencesController(), + onboardingController: getMockOnboardingController(), + initState: getEmptyInitState(), + }; +} + /** * @typedef {import( * '../../../../app/scripts/controllers/incoming-transactions' @@ -226,6 +236,7 @@ describe('IncomingTransactionsController', function () { preferencesController: getMockPreferencesController(), onboardingController: getMockOnboardingController(), initState: {}, + getCurrentChainId: () => CHAIN_IDS.GOERLI, }, ); @@ -831,6 +842,97 @@ describe('IncomingTransactionsController', function () { }); }); + describe('block explorer lookup', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + function stubFetch() { + return sandbox.stub(window, 'fetch'); + } + + function assertStubNotCalled(stub) { + assert(stub.callCount === 0); + } + + async function triggerUpdate(incomingTransactionsController) { + const subscription = + incomingTransactionsController.preferencesController.store.subscribe.getCall( + 1, + ).args[0]; + + // Sets address causing a call to _update + await subscription({ selectedAddress: MOCK_SELECTED_ADDRESS }); + } + + it('should not happen when incoming transactions feature is disabled', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + ...getDefaultControllerOpts(), + preferencesController: getMockPreferencesController({ + showIncomingTransactions: false, + }), + }, + ); + const fetchStub = stubFetch(); + await triggerUpdate(incomingTransactionsController); + assertStubNotCalled(fetchStub); + }); + + it('should not happen when onboarding is in progress', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + ...getDefaultControllerOpts(), + onboardingController: getMockOnboardingController({ + completedOnboarding: false, + }), + }, + ); + + const fetchStub = stubFetch(); + await triggerUpdate(incomingTransactionsController); + assertStubNotCalled(fetchStub); + }); + + it('should not happen when chain id is not supported', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + ...getDefaultControllerOpts(), + getCurrentChainId: () => FAKE_CHAIN_ID, + }, + ); + + const fetchStub = stubFetch(); + await triggerUpdate(incomingTransactionsController); + assertStubNotCalled(fetchStub); + }); + + it('should make api call when chain id, incoming features, and onboarding status are ok', async function () { + const incomingTransactionsController = new IncomingTransactionsController( + { + ...getDefaultControllerOpts(), + getCurrentChainId: () => CHAIN_IDS.GOERLI, + onboardingController: getMockOnboardingController({ + completedOnboarding: true, + }), + preferencesController: getMockPreferencesController({ + showIncomingTransactions: true, + }), + }, + ); + + const fetchStub = stubFetch(); + await triggerUpdate(incomingTransactionsController); + assert(fetchStub.callCount === 1); + }); + }); + describe('_update', function () { describe('when state is empty (initialized)', function () { it('should use provided block number and update the latest block seen', async function () { From d0e7bfddfc3ff16e9771c2aa0edfa32508e6f763 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Mon, 24 Jul 2023 16:34:51 -0230 Subject: [PATCH 03/16] Ensure chainId comparison in switchEthereumChain handler is case insensitive (#20149) --- .../handlers/switch-ethereum-chain.js | 2 +- .../handlers/switch-ethereum-chain.test.js | 128 ++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js diff --git a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js index 6d71ba602..e2137da87 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js @@ -116,7 +116,7 @@ async function switchEthereumChainHandler( if ( Object.values(BUILT_IN_INFURA_NETWORKS) .map(({ chainId: id }) => id) - .includes(chainId) + .includes(_chainId) ) { await setProviderType(approvedRequestData.type); } else { diff --git a/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js new file mode 100644 index 000000000..410697214 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js @@ -0,0 +1,128 @@ +import { + CHAIN_IDS, + NETWORK_TYPES, +} from '../../../../../shared/constants/network'; +import switchEthereumChain from './switch-ethereum-chain'; + +const NON_INFURA_CHAIN_ID = '0x123456789'; + +const mockRequestUserApproval = ({ requestData }) => { + return Promise.resolve(requestData); +}; + +const MOCK_MAINNET_CONFIGURATION = { + id: 123, + chainId: CHAIN_IDS.MAINNET, + type: NETWORK_TYPES.MAINNET, +}; +const MOCK_LINEA_MAINNET_CONFIGURATION = { + id: 123, + chainId: CHAIN_IDS.LINEA_MAINNET, + type: NETWORK_TYPES.LINEA_MAINNET, +}; + +describe('switchEthereumChainHandler', () => { + it('should call setProviderType when switching to a built in infura network', async () => { + const mockSetProviderType = jest.fn(); + const mockSetActiveNetwork = jest.fn(); + const switchEthereumChainHandler = switchEthereumChain.implementation; + await switchEthereumChainHandler( + { + origin: 'example.com', + params: [{ chainId: CHAIN_IDS.MAINNET }], + }, + {}, + jest.fn(), + jest.fn(), + { + getCurrentChainId: () => NON_INFURA_CHAIN_ID, + findNetworkConfigurationBy: () => MOCK_MAINNET_CONFIGURATION, + setProviderType: mockSetProviderType, + setActiveNetwork: mockSetActiveNetwork, + requestUserApproval: mockRequestUserApproval, + }, + ); + expect(mockSetProviderType).toHaveBeenCalledTimes(1); + expect(mockSetProviderType).toHaveBeenCalledWith( + MOCK_MAINNET_CONFIGURATION.type, + ); + }); + + it('should call setProviderType when switching to a built in infura network, when chainId from request is lower case', async () => { + const mockSetProviderType = jest.fn(); + const mockSetActiveNetwork = jest.fn(); + const switchEthereumChainHandler = switchEthereumChain.implementation; + await switchEthereumChainHandler( + { + origin: 'example.com', + params: [{ chainId: CHAIN_IDS.LINEA_MAINNET.toLowerCase() }], + }, + {}, + jest.fn(), + jest.fn(), + { + getCurrentChainId: () => NON_INFURA_CHAIN_ID, + findNetworkConfigurationBy: () => MOCK_LINEA_MAINNET_CONFIGURATION, + setProviderType: mockSetProviderType, + setActiveNetwork: mockSetActiveNetwork, + requestUserApproval: mockRequestUserApproval, + }, + ); + expect(mockSetProviderType).toHaveBeenCalledTimes(1); + expect(mockSetProviderType).toHaveBeenCalledWith( + MOCK_LINEA_MAINNET_CONFIGURATION.type, + ); + }); + + it('should call setProviderType when switching to a built in infura network, when chainId from request is upper case', async () => { + const mockSetProviderType = jest.fn(); + const mockSetActiveNetwork = jest.fn(); + const switchEthereumChainHandler = switchEthereumChain.implementation; + await switchEthereumChainHandler( + { + origin: 'example.com', + params: [{ chainId: CHAIN_IDS.LINEA_MAINNET.toUpperCase() }], + }, + {}, + jest.fn(), + jest.fn(), + { + getCurrentChainId: () => NON_INFURA_CHAIN_ID, + findNetworkConfigurationBy: () => MOCK_LINEA_MAINNET_CONFIGURATION, + setProviderType: mockSetProviderType, + setActiveNetwork: mockSetActiveNetwork, + requestUserApproval: mockRequestUserApproval, + }, + ); + expect(mockSetProviderType).toHaveBeenCalledTimes(1); + expect(mockSetProviderType).toHaveBeenCalledWith( + MOCK_LINEA_MAINNET_CONFIGURATION.type, + ); + }); + + it('should call setActiveNetwork when switching to a custom network', async () => { + const mockSetProviderType = jest.fn(); + const mockSetActiveNetwork = jest.fn(); + const switchEthereumChainHandler = switchEthereumChain.implementation; + await switchEthereumChainHandler( + { + origin: 'example.com', + params: [{ chainId: NON_INFURA_CHAIN_ID }], + }, + {}, + jest.fn(), + jest.fn(), + { + getCurrentChainId: () => CHAIN_IDS.MAINNET, + findNetworkConfigurationBy: () => MOCK_MAINNET_CONFIGURATION, + setProviderType: mockSetProviderType, + setActiveNetwork: mockSetActiveNetwork, + requestUserApproval: mockRequestUserApproval, + }, + ); + expect(mockSetActiveNetwork).toHaveBeenCalledTimes(1); + expect(mockSetActiveNetwork).toHaveBeenCalledWith( + MOCK_MAINNET_CONFIGURATION.id, + ); + }); +}); From 9323701f9b5110896683685d69bea96a7700684d Mon Sep 17 00:00:00 2001 From: ryanml Date: Tue, 25 Jul 2023 08:44:40 -0700 Subject: [PATCH 04/16] Fixing 'View on Opensea' link for main and testnet NFTs (#19797) --- ui/components/app/nft-details/nft-details.js | 5 +++-- ui/components/app/nft-details/nft-details.test.js | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/components/app/nft-details/nft-details.js b/ui/components/app/nft-details/nft-details.js index 491506339..5a8df995e 100644 --- a/ui/components/app/nft-details/nft-details.js +++ b/ui/components/app/nft-details/nft-details.js @@ -112,12 +112,13 @@ export default function NftDetails({ nft }) { const getOpenSeaLink = () => { switch (currentNetwork) { case CHAIN_IDS.MAINNET: - return `https://opensea.io/assets/${address}/${tokenId}`; + return `https://opensea.io/assets/ethereum/${address}/${tokenId}`; case CHAIN_IDS.POLYGON: return `https://opensea.io/assets/matic/${address}/${tokenId}`; case CHAIN_IDS.GOERLI: + return `https://testnets.opensea.io/assets/goerli/${address}/${tokenId}`; case CHAIN_IDS.SEPOLIA: - return `https://testnets.opensea.io/assets/${address}/${tokenId}`; + return `https://testnets.opensea.io/assets/sepolia/${address}/${tokenId}`; default: return null; } diff --git a/ui/components/app/nft-details/nft-details.test.js b/ui/components/app/nft-details/nft-details.test.js index aab54bd60..7733e75ae 100644 --- a/ui/components/app/nft-details/nft-details.test.js +++ b/ui/components/app/nft-details/nft-details.test.js @@ -165,7 +165,7 @@ describe('NFT Details', () => { await waitFor(() => { expect(global.platform.openTab).toHaveBeenCalledWith({ - url: `https://testnets.opensea.io/assets/${nfts[5].address}/${nfts[5].tokenId}`, + url: `https://testnets.opensea.io/assets/goerli/${nfts[5].address}/${nfts[5].tokenId}`, }); }); }); @@ -200,7 +200,7 @@ describe('NFT Details', () => { await waitFor(() => { expect(global.platform.openTab).toHaveBeenCalledWith({ - url: `https://opensea.io/assets/${nfts[5].address}/${nfts[5].tokenId}`, + url: `https://opensea.io/assets/ethereum/${nfts[5].address}/${nfts[5].tokenId}`, }); }); }); @@ -272,7 +272,7 @@ describe('NFT Details', () => { await waitFor(() => { expect(global.platform.openTab).toHaveBeenCalledWith({ - url: `https://testnets.opensea.io/assets/${nfts[5].address}/${nfts[5].tokenId}`, + url: `https://testnets.opensea.io/assets/sepolia/${nfts[5].address}/${nfts[5].tokenId}`, }); }); }); From d847272f93b0ae0570ccde0780032f010d38bb84 Mon Sep 17 00:00:00 2001 From: ryanml Date: Wed, 26 Jul 2023 01:28:16 -0700 Subject: [PATCH 05/16] Fixing Dapp link on NFT import screen (#19799) --- .../confirm-add-suggested-nft/confirm-add-suggested-nft.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js b/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js index f74dd27e9..d3e4aa6ad 100644 --- a/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js +++ b/ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js @@ -106,9 +106,12 @@ const ConfirmAddSuggestedNFT = () => { }, [history, mostRecentOverviewPage, suggestedNfts]); let origin; + let link; if (suggestedNfts.length) { try { - origin = new URL(suggestedNfts[0].origin)?.host; + const url = new URL(suggestedNfts[0].origin); + origin = url.host; + link = url.href; } catch { origin = 'dapp'; } @@ -138,7 +141,7 @@ const ConfirmAddSuggestedNFT = () => { {origin} From d5de0dd67e2abb1c7e6ba7022411b5301df7a666 Mon Sep 17 00:00:00 2001 From: OGPoyraz Date: Thu, 17 Aug 2023 09:36:30 +0200 Subject: [PATCH 06/16] Fix infinite rerender on network change while active signature request (#20473) --- app/scripts/metamask-controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 43cb9e2bb..7063afea2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1447,6 +1447,7 @@ export default class MetamaskController extends EventEmitter { this.encryptionPublicKeyController.clearUnapproved(); this.decryptMessageController.clearUnapproved(); this.signatureController.clearUnapproved(); + this.approvalController.clear(); }, ); From b684c094cba0591c3b2cd2c648c971c6ba9b8ff5 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Sat, 26 Aug 2023 00:28:24 -0230 Subject: [PATCH 07/16] Fix account selectors when balances are missing (#20385) * Fix account selectors when balances are missing Some of the account selectors we use would return an empty set of accounts if the `AccountTracker` state was missing. This resulted in UI crashes when trying to access the current selected account. The selectors have been updated to use the `identities` as the source- of-truth for the full set of accounts. This ensures that even if the balances are missing, each account will at least be represented by an empty object. * Fix unit test * Fix another unit test * Fix another unit test * Fix another unit test * Fix more unit tests --------- Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com> --- .../edit-gas-fee-popover.test.js | 3 + .../edit-gas-item/edit-gas-item.test.js | 3 + .../edit-gas-tooltip/edit-gas-tooltip.test.js | 3 + .../gas-details-item-title.test.js | 3 + .../custody-confirm-link-modal.js | 4 +- ui/ducks/send/send.test.js | 95 +++++++++++-------- ui/selectors/selectors.js | 64 +++++++++---- ui/store/actions.test.js | 6 ++ 8 files changed, 116 insertions(+), 65 deletions(-) diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js index 3e371a05d..7d9c0adfa 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js @@ -61,6 +61,9 @@ const render = ({ txProps, contextProps } = {}) => { balance: '0x1F4', }, }, + identities: { + '0xAddress': {}, + }, selectedAddress: '0xAddress', featureFlags: { advancedInlineGas: true }, gasFeeEstimates: MOCK_FEE_ESTIMATE, diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js b/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js index 44ac34b87..86f5ba097 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js @@ -67,6 +67,9 @@ const renderComponent = ({ balance: '0x176e5b6f173ebe66', }, }, + identities: { + '0xAddress': {}, + }, selectedAddress: '0xAddress', featureFlags: { advancedInlineGas: true }, gasEstimateType: 'fee-market', diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-tooltip/edit-gas-tooltip.test.js b/ui/components/app/edit-gas-fee-popover/edit-gas-tooltip/edit-gas-tooltip.test.js index dbeda43ac..97797c110 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-tooltip/edit-gas-tooltip.test.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-tooltip/edit-gas-tooltip.test.js @@ -41,6 +41,9 @@ const renderComponent = (componentProps) => { balance: '0x176e5b6f173ebe66', }, }, + identities: { + '0xAddress': {}, + }, selectedAddress: '0xAddress', featureFlags: { advancedInlineGas: true }, }, diff --git a/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.test.js b/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.test.js index 9d8f36c4e..b57c7e32e 100644 --- a/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.test.js +++ b/ui/components/app/gas-details-item/gas-details-item-title/gas-details-item-title.test.js @@ -28,6 +28,9 @@ const render = () => { balance: '0x176e5b6f173ebe66', }, }, + identities: { + '0xAddress': {}, + }, selectedAddress: '0xAddress', }, }); diff --git a/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.js b/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.js index e7d0c4d5b..55747a204 100644 --- a/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.js +++ b/ui/components/institutional/custody-confirm-link-modal/custody-confirm-link-modal.js @@ -11,7 +11,7 @@ import withModalProps from '../../../helpers/higher-order-components/with-modal- import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; import { mmiActionsFactory } from '../../../store/institutional/institution-background'; import { setSelectedAddress } from '../../../store/actions'; -import { getMetaMaskAccountsRaw } from '../../../selectors'; +import { getMetaMaskIdentities } from '../../../selectors'; import { getMMIAddressFromModalOrAddress, getCustodyAccountDetails, @@ -42,7 +42,7 @@ const CustodyConfirmLink = ({ hideModal }) => { const dispatch = useDispatch(); const mmiActions = mmiActionsFactory(); const trackEvent = useContext(MetaMetricsContext); - const mmiAccounts = useSelector(getMetaMaskAccountsRaw); + const mmiAccounts = useSelector(getMetaMaskIdentities); const address = useSelector(getMMIAddressFromModalOrAddress); const custodyAccountDetails = useSelector(getCustodyAccountDetails); const { custodians } = useSelector(getMMIConfiguration); diff --git a/ui/ducks/send/send.test.js b/ui/ducks/send/send.test.js index cb10c7a87..e946edefb 100644 --- a/ui/ducks/send/send.test.js +++ b/ui/ducks/send/send.test.js @@ -77,6 +77,8 @@ import { draftTransactionInitialState, editExistingTransaction } from '.'; const mockStore = createMockStore([thunk]); +const mockAddress1 = '0xdafea492d9c6733ae3d56b7ed1adb60692c98123'; + jest.mock('./send', () => { const actual = jest.requireActual('./send'); return { @@ -1063,7 +1065,7 @@ describe('Send Slice', () => { describe('QR Code Detected', () => { const qrCodestate = getInitialSendStateWithExistingTxState({ recipient: { - address: '0xAddress', + address: mockAddress1, }, }); @@ -1102,7 +1104,7 @@ describe('Send Slice', () => { const draftTransaction = getTestUUIDTx(result); - expect(draftTransaction.recipient.address).toStrictEqual('0xAddress'); + expect(draftTransaction.recipient.address).toStrictEqual(mockAddress1); expect(draftTransaction.recipient.error).toStrictEqual( INVALID_RECIPIENT_ADDRESS_ERROR, ); @@ -1115,7 +1117,7 @@ describe('Send Slice', () => { ...INITIAL_SEND_STATE_FOR_EXISTING_DRAFT, selectedAccount: { balance: '0x0', - address: '0xAddress', + address: mockAddress1, }, }; @@ -1144,7 +1146,7 @@ describe('Send Slice', () => { ...INITIAL_SEND_STATE_FOR_EXISTING_DRAFT, selectedAccount: { balance: '0x0', - address: '0xAddress', + address: mockAddress1, }, }; @@ -1158,7 +1160,7 @@ describe('Send Slice', () => { const result = sendReducer(olderState, action); expect(result.selectedAccount.balance).toStrictEqual('0x0'); - expect(result.selectedAccount.address).toStrictEqual('0xAddress'); + expect(result.selectedAccount.address).toStrictEqual(mockAddress1); }); }); @@ -1167,13 +1169,13 @@ describe('Send Slice', () => { const accountsChangedState = { ...getInitialSendStateWithExistingTxState({ fromAccount: { - address: '0xAddress', + address: mockAddress1, balance: '0x0', }, }), stage: SEND_STAGES.EDIT, selectedAccount: { - address: '0xAddress', + address: mockAddress1, balance: '0x0', }, }; @@ -1182,7 +1184,7 @@ describe('Send Slice', () => { type: 'ACCOUNT_CHANGED', payload: { account: { - address: '0xAddress', + address: mockAddress1, balance: '0x1', }, }, @@ -1201,13 +1203,13 @@ describe('Send Slice', () => { const accountsChangedState = { ...getInitialSendStateWithExistingTxState({ fromAccount: { - address: '0xAddress', + address: mockAddress1, balance: '0x0', }, }), stage: SEND_STAGES.EDIT, selectedAccount: { - address: '0xAddress', + address: mockAddress1, balance: '0x0', }, }; @@ -1231,7 +1233,7 @@ describe('Send Slice', () => { ...INITIAL_SEND_STATE_FOR_EXISTING_DRAFT, stage: SEND_STAGES.EDIT, selectedAccount: { - address: '0xAddress', + address: mockAddress1, balance: '0x0', }, }; @@ -1274,23 +1276,23 @@ describe('Send Slice', () => { 1559: true, }, }, - selectedAddress: '0xAddress', - identities: { '0xAddress': { address: '0xAddress' } }, + selectedAddress: mockAddress1, + identities: { [mockAddress1]: { address: mockAddress1 } }, keyrings: [ { type: KeyringType.hdKeyTree, - accounts: ['0xAddress'], + accounts: [mockAddress1], }, ], accounts: { - '0xAddress': { - address: '0xAddress', + [mockAddress1]: { + address: mockAddress1, balance: '0x0', }, }, cachedBalances: { 0x5: { - '0xAddress': '0x0', + [mockAddress1]: '0x0', }, }, providerConfig: { @@ -1580,14 +1582,17 @@ describe('Send Slice', () => { }, cachedBalances: { [CHAIN_IDS.GOERLI]: { - '0xAddress': '0x0', + [mockAddress1]: '0x0', }, }, accounts: { - '0xAddress': { - address: '0xAddress', + [mockAddress1]: { + address: mockAddress1, }, }, + identities: { + [mockAddress1]: {}, + }, }, send: { ...getInitialSendStateWithExistingTxState({ @@ -1607,7 +1612,7 @@ describe('Send Slice', () => { userInputHexData: '', }), selectedAccount: { - address: '0xAddress', + address: mockAddress1, }, }, }; @@ -2379,16 +2384,18 @@ describe('Send Slice', () => { addressBook: { [CHAIN_IDS.GOERLI]: {}, }, - identities: {}, + identities: { + [mockAddress1]: {}, + }, accounts: { - '0xAddress': { - address: '0xAddress', + [mockAddress1]: { + address: mockAddress1, balance: '0x0', }, }, cachedBalances: { [CHAIN_IDS.GOERLI]: { - '0xAddress': '0x0', + [mockAddress1]: '0x0', }, }, tokenList: {}, @@ -2397,7 +2404,7 @@ describe('Send Slice', () => { id: 1, txParams: { data: '', - from: '0xAddress', + from: mockAddress1, to: '0xRecipientAddress', gas: GAS_LIMITS.SIMPLE, gasPrice: '0x3b9aca00', // 1000000000 @@ -2414,7 +2421,7 @@ describe('Send Slice', () => { ...getInitialSendStateWithExistingTxState({ id: 1, fromAccount: { - address: '0xAddress', + address: mockAddress1, }, }), }, @@ -2443,7 +2450,7 @@ describe('Send Slice', () => { type: AssetType.native, }, fromAccount: { - address: '0xAddress', + address: mockAddress1, balance: '0x0', }, gas: { @@ -2514,16 +2521,18 @@ describe('Send Slice', () => { addressBook: { [CHAIN_IDS.GOERLI]: {}, }, - identities: {}, + identities: { + [mockAddress1]: {}, + }, accounts: { - '0xAddress': { - address: '0xAddress', + [mockAddress1]: { + address: mockAddress1, balance: '0x0', }, }, cachedBalances: { [CHAIN_IDS.GOERLI]: { - '0xAddress': '0x0', + [mockAddress1]: '0x0', }, }, tokenList: {}, @@ -2533,10 +2542,10 @@ describe('Send Slice', () => { txParams: { data: generateERC721TransferData({ toAddress: BURN_ADDRESS, - fromAddress: '0xAddress', + fromAddress: mockAddress1, tokenId: BigNumber.from(15000).toString(), }), - from: '0xAddress', + from: mockAddress1, to: '0xNftAddress', gas: GAS_LIMITS.BASE_TOKEN_ESTIMATE, gasPrice: '0x3b9aca00', // 1000000000 @@ -2586,7 +2595,7 @@ describe('Send Slice', () => { type: AssetType.native, }, fromAccount: { - address: '0xAddress', + address: mockAddress1, balance: '0x0', }, gas: { @@ -2697,16 +2706,18 @@ describe('Send Slice', () => { addressBook: { [CHAIN_IDS.GOERLI]: {}, }, - identities: {}, + identities: { + [mockAddress1]: {}, + }, accounts: { - '0xAddress': { - address: '0xAddress', + [mockAddress1]: { + address: mockAddress1, balance: '0x0', }, }, cachedBalances: { [CHAIN_IDS.GOERLI]: { - '0xAddress': '0x0', + [mockAddress1]: '0x0', }, }, unapprovedTxs: { @@ -2722,7 +2733,7 @@ describe('Send Slice', () => { decimals: 18, }, }), - from: '0xAddress', + from: mockAddress1, to: '0xTokenAddress', gas: GAS_LIMITS.BASE_TOKEN_ESTIMATE, gasPrice: '0x3b9aca00', // 1000000000 @@ -2740,7 +2751,7 @@ describe('Send Slice', () => { }, }), selectedAccount: { - address: '0xAddress', + address: mockAddress1, balance: '0x0', }, stage: SEND_STAGES.EDIT, @@ -2777,7 +2788,7 @@ describe('Send Slice', () => { type: AssetType.native, }, fromAccount: { - address: '0xAddress', + address: mockAddress1, balance: '0x0', }, gas: { diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 9cc5b74f3..4129cdcd2 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -281,28 +281,38 @@ export function deprecatedGetCurrentNetworkId(state) { return state.metamask.networkId ?? 'loading'; } +/** + * Get MetaMask accounts, including account name and balance. + */ export const getMetaMaskAccounts = createSelector( - getMetaMaskAccountsRaw, + getMetaMaskIdentities, + getMetaMaskAccountBalances, getMetaMaskCachedBalances, - (currentAccounts, cachedBalances) => - Object.entries(currentAccounts).reduce( - (selectedAccounts, [accountID, account]) => { - if (account.balance === null || account.balance === undefined) { - return { - ...selectedAccounts, - [accountID]: { - ...account, - balance: cachedBalances && cachedBalances[accountID], - }, - }; - } - return { - ...selectedAccounts, - [accountID]: account, + (identities, balances, cachedBalances) => + Object.keys(identities).reduce((accounts, address) => { + // TODO: mix in the identity state here as well, consolidating this + // selector with `accountsWithSendEtherInfoSelector` + let account = {}; + + if (balances[address]) { + account = { + ...account, + ...balances[address], }; - }, - {}, - ), + } + + if (account.balance === null || account.balance === undefined) { + account = { + ...account, + balance: cachedBalances && cachedBalances[address], + }; + } + + return { + ...accounts, + [address]: account, + }; + }, {}), ); export function getSelectedAddress(state) { @@ -325,11 +335,23 @@ export function getMetaMaskKeyrings(state) { return state.metamask.keyrings; } +/** + * Get identity state. + * + * @param {object} state - Redux state + * @returns {object} A map of account addresses to identities (which includes the account name) + */ export function getMetaMaskIdentities(state) { return state.metamask.identities; } -export function getMetaMaskAccountsRaw(state) { +/** + * Get account balances state. + * + * @param {object} state - Redux state + * @returns {object} A map of account addresses to account objects (which includes the account balance) + */ +export function getMetaMaskAccountBalances(state) { return state.metamask.accounts; } @@ -368,7 +390,7 @@ export const getMetaMaskAccountsConnected = createSelector( export function isBalanceCached(state) { const selectedAccountBalance = - state.metamask.accounts[getSelectedAddress(state)].balance; + getMetaMaskAccountBalances(state)[getSelectedAddress(state)]?.balance; const cachedBalance = getSelectedAccountCachedBalance(state); return Boolean(!selectedAccountBalance && cachedBalance); diff --git a/ui/store/actions.test.js b/ui/store/actions.test.js index ebf77ff77..21157703f 100644 --- a/ui/store/actions.test.js +++ b/ui/store/actions.test.js @@ -244,6 +244,9 @@ describe('Actions', () => { '0xAnotherAddress': '0x0', }, }, + identities: { + '0xAnotherAddress': {}, + }, }), ); @@ -1876,6 +1879,9 @@ describe('Actions', () => { balance: '0x0', }, }, + identities: { + '0xFirstAddress': {}, + }, cachedBalances: { '0x1': { '0xFirstAddress': '0x0', From e2c481639492d46b2712b1201916013fdb4a0650 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Sat, 26 Aug 2023 19:58:26 -0500 Subject: [PATCH 08/16] split advancedGasFee by network and erase previous options (#20576) * Split out advanced gas fees by network and delete old values * use arrow functions in preferences test * changes * added back priorityFeeProperCase to en messages * update types * remove case change --- app/_locales/de/messages.json | 9 - app/_locales/el/messages.json | 9 - app/_locales/en/messages.json | 9 +- app/_locales/es/messages.json | 9 - app/_locales/es_419/messages.json | 9 - app/_locales/fr/messages.json | 9 - app/_locales/hi/messages.json | 9 - app/_locales/id/messages.json | 9 - app/_locales/it/messages.json | 6 - app/_locales/ja/messages.json | 9 - app/_locales/ko/messages.json | 9 - app/_locales/pt/messages.json | 9 - app/_locales/pt_BR/messages.json | 9 - app/_locales/ru/messages.json | 9 - app/_locales/tl/messages.json | 9 - app/_locales/tr/messages.json | 9 - app/_locales/vi/messages.json | 9 - app/_locales/zh_CN/messages.json | 9 - app/scripts/controllers/app-state.js | 4 + app/scripts/controllers/preferences.js | 16 +- app/scripts/controllers/preferences.test.js | 24 ++- app/scripts/controllers/transactions/index.js | 8 +- .../controllers/transactions/index.test.js | 3 + app/scripts/migrations/092.3.test.ts | 184 ++++++++++++++++++ app/scripts/migrations/092.3.ts | 98 ++++++++++ app/scripts/migrations/index.js | 2 + test/data/mock-state.json | 7 +- ...rs-after-init-opt-in-background-state.json | 1 + .../errors-after-init-opt-in-ui-state.json | 1 + .../advanced-gas-fee-defaults.js | 25 ++- .../advanced-gas-fee-defaults.test.js | 57 +++--- .../priority-fee-input.test.js | 3 +- .../edit-gas-fee-popover.test.js | 1 + .../edit-gas-item/edit-gas-item.test.js | 11 +- .../signature-request-header.test.js.snap | 4 +- .../signature-request-original.test.js.snap | 4 +- .../signature-request-siwe.test.js.snap | 4 +- ...ture-request-header.component.test.js.snap | 4 +- ui/selectors/selectors.js | 33 ++-- ui/selectors/selectors.test.js | 5 - ui/store/actions.ts | 2 +- 41 files changed, 413 insertions(+), 247 deletions(-) create mode 100644 app/scripts/migrations/092.3.test.ts create mode 100644 app/scripts/migrations/092.3.ts diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index d7a90dc46..3876d50be 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "Erweiterte Einstellungen" }, - "advancedGasFeeDefaultOptIn": { - "message": "Speichern Sie diese $1 als Standard für \"Erweitert\"" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Immer diese Werte und erweiterte Einstellung als Standard verwenden." - }, "advancedGasFeeModalTitle": { "message": "Erweiterte Gasgebühr" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "Token importiert" }, - "newValues": { - "message": "neue Werte" - }, "next": { "message": "Weiter" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index af006198f..bcff6bc6f 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "Προηγμένη ρύθμιση παραμέτρων" }, - "advancedGasFeeDefaultOptIn": { - "message": "Αποθηκεύστε αυτά τα $1 ως προεπιλογή μου για το \"Προηγμένο\"" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Να χρησιμοποιούνται πάντα αυτές τις τιμές και η ρύθμιση για προχωρημένους." - }, "advancedGasFeeModalTitle": { "message": "Προηγμένη χρέωση τελών συναλλαγής" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "Τα token εισήχθησαν" }, - "newValues": { - "message": "νέες τιμές" - }, "next": { "message": "Επόμενο" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index c267d5feb..3fbc3e374 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -306,10 +306,8 @@ "message": "Advanced configuration" }, "advancedGasFeeDefaultOptIn": { - "message": "Save these $1 as my default for \"Advanced\"" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Always use these values and advanced setting as default." + "message": "Save these values as my default for the $1 network.", + "description": "$1 is the current network name." }, "advancedGasFeeModalTitle": { "message": "Advanced gas fee" @@ -2483,9 +2481,6 @@ "newTokensImportedTitle": { "message": "Token imported" }, - "newValues": { - "message": "new values" - }, "next": { "message": "Next" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 22f8a2071..58fae7bac 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "Configuración avanzada" }, - "advancedGasFeeDefaultOptIn": { - "message": "Guarda estos 1$ como mi valor predeterminado para \"Avanzado\"" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Usar siempre estos valores y la configuración avanzada como valores predeterminados." - }, "advancedGasFeeModalTitle": { "message": "Tarifa de gas avanzada" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "Token importado" }, - "newValues": { - "message": "nuevos valores" - }, "next": { "message": "Siguiente" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 74d738b6e..260bd4501 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -148,12 +148,6 @@ "advancedBaseGasFeeToolTip": { "message": "Cuando su transacción se incluya en el bloque, se reembolsará cualquier diferencia entre su tarifa base máxima y la tarifa base real. El importe total se calcula como tarifa base máxima (en GWEI) * límite de gas." }, - "advancedGasFeeDefaultOptIn": { - "message": "Guarda estos 1$ como mi valor predeterminado para \"Avanzado\"" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Usar siempre estos valores y la configuración avanzada como valores predeterminados." - }, "advancedGasFeeModalTitle": { "message": "Tarifa de gas avanzada" }, @@ -1431,9 +1425,6 @@ "newPassword": { "message": "Contraseña nueva (mín. de 8 caracteres)" }, - "newValues": { - "message": "nuevos valores" - }, "next": { "message": "Siguiente" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index a8974ea1d..3a5e98789 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "Configuration avancée" }, - "advancedGasFeeDefaultOptIn": { - "message": "Enregistrer ces $1 comme valeur par défaut pour « Avancé »" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Toujours utiliser par défaut ces valeurs et les paramètres avancés." - }, "advancedGasFeeModalTitle": { "message": "Frais de carburant avancés" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "Jeton importé" }, - "newValues": { - "message": "nouvelles valeurs" - }, "next": { "message": "Suivant" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index b0ffcb805..55e2cd873 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "उन्नत कंफिगुरेशन" }, - "advancedGasFeeDefaultOptIn": { - "message": "इन $1 को \"एडवांस\" के लिए मेरे डिफॉल्ट के रूप में सहेजें" - }, - "advancedGasFeeDefaultOptOut": { - "message": "हमेशा इन मूल्यों और एडवांस सेटिंग को डिफॉल्ट के रूप में उपयोग करें।" - }, "advancedGasFeeModalTitle": { "message": "एडवांस गैस शुल्क" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "टोकन इम्पोर्ट हो गया" }, - "newValues": { - "message": "नए मान" - }, "next": { "message": "अगला" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index cf38c0ad8..479345522 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "Konfigurasi lanjutan" }, - "advancedGasFeeDefaultOptIn": { - "message": "Simpan $1 ini sebagai default saya untuk \"Lanjutan\"" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Selalu gunakan nilai ini dan pengaturan lanjutan sebagai default." - }, "advancedGasFeeModalTitle": { "message": "Biaya gas lanjutan" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "Token diimpor" }, - "newValues": { - "message": "nilai baru" - }, "next": { "message": "Berikutnya" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index 05b14eaaf..d6000ff93 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -213,12 +213,6 @@ "advancedBaseGasFeeToolTip": { "message": "Quando la tua transazione viene inclusa nel blocco, ogni differenza tra la tua offerta massima di gas e il gas effettivamente utilizzato viene restituita a te. Il totale viene calcolato come offerta massima di gas (in GEWI) * limite di gas." }, - "advancedGasFeeDefaultOptIn": { - "message": "Salva queste $1 come mie preferite per \"Avanzate\"" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Utilizzare sempre questi valori e l'impostazione avanzata come predefiniti." - }, "advancedGasFeeModalTitle": { "message": "Tariffa gas avanzata" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index f9738e5a1..bd1137b30 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "詳細設定" }, - "advancedGasFeeDefaultOptIn": { - "message": "これらの$1を「高度な設定」のデフォルトとして保存" - }, - "advancedGasFeeDefaultOptOut": { - "message": "常にこれらの値と高度な設定をデフォルトとして使用します。" - }, "advancedGasFeeModalTitle": { "message": "高度なガス代" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "トークンがインポートされました" }, - "newValues": { - "message": "新しい値" - }, "next": { "message": "次へ" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index f45b9eca4..5735d71c8 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "고급 옵션" }, - "advancedGasFeeDefaultOptIn": { - "message": "이 $1 옵션을 \"고급\"의 기본값으로 저장합니다" - }, - "advancedGasFeeDefaultOptOut": { - "message": "항상 이 값과 고급 설정을 기본값으로 사용합니다." - }, "advancedGasFeeModalTitle": { "message": "고급 가스 요금" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "불러온 토큰" }, - "newValues": { - "message": "새로운 가치" - }, "next": { "message": "다음" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 85e6744f6..82bec5580 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "Configurações avançadas" }, - "advancedGasFeeDefaultOptIn": { - "message": "Salvar estes $1 como meu padrão para \"Avançado\"" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Sempre utilizar esses valores e a configuração avançada por padrão." - }, "advancedGasFeeModalTitle": { "message": "Taxa de gás avançada" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "Token importado" }, - "newValues": { - "message": "novos valores" - }, "next": { "message": "Próximo" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index 3ddc1aa19..1f049e28b 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -148,12 +148,6 @@ "advancedBaseGasFeeToolTip": { "message": "Quando a sua transação for incluída no bloco, qualquer diferença entre a sua taxa de base máxima e a taxa de base real será reembolsada. O cálculo do valor total é feito da seguinte forma: taxa de base máxima (em GWEI) * limite de gás." }, - "advancedGasFeeDefaultOptIn": { - "message": "Salvar estes $1 como meu padrão para \"Avançado\"" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Sempre utilizar esses valores e a configuração avançada por padrão." - }, "advancedGasFeeModalTitle": { "message": "Taxa de gás avançada" }, @@ -1431,9 +1425,6 @@ "newPassword": { "message": "Nova senha (no mínimo 8 caracteres)" }, - "newValues": { - "message": "novos valores" - }, "next": { "message": "Seguinte" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index a72f54a58..45e602cfd 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "Расширенная конфигурация" }, - "advancedGasFeeDefaultOptIn": { - "message": "Сохранить этот $1 в качестве моего значения по умолчанию для «Дополнительной» настройки" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Всегда использовать эти значения и дополнительную настройку по умолчанию." - }, "advancedGasFeeModalTitle": { "message": "Дополнительная плата за газ" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "Токен импортирован" }, - "newValues": { - "message": "новые значения" - }, "next": { "message": "Далее" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 9203fa22b..cb0a2f75a 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "Advanced na pagsasaayos" }, - "advancedGasFeeDefaultOptIn": { - "message": "I-save itong mga $1bilang aking default para sa \"Advanced\"" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Laging gamitin ang mga value na ito at advanced setting bilang default." - }, "advancedGasFeeModalTitle": { "message": "Advanced na gas fee" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "Na-import ang token" }, - "newValues": { - "message": "bagong value" - }, "next": { "message": "Susunod" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 9a6f53aad..cd04f8aff 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "Gelişmiş yapılandırma" }, - "advancedGasFeeDefaultOptIn": { - "message": "\"Gelişmiş\" için şunları varsayılanım olarak kaydet: $1" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Varsayılan olarak her zaman bu değerleri ve gelişmiş ayarı kullan." - }, "advancedGasFeeModalTitle": { "message": "Gelişmiş gaz ücreti" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "Token içe aktarıldı" }, - "newValues": { - "message": "yeni değerler" - }, "next": { "message": "Sonraki" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 4cee139f5..2acb4f625 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "Cấu hình nâng cao" }, - "advancedGasFeeDefaultOptIn": { - "message": "Lưu $1 này làm mặc định của tôi cho \"Nâng cao\"" - }, - "advancedGasFeeDefaultOptOut": { - "message": "Luôn sử dụng các giá trị và thiết lập nâng cao này làm mặc định." - }, "advancedGasFeeModalTitle": { "message": "Phí gas nâng cao" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "Đã nhập token" }, - "newValues": { - "message": "giá trị mới" - }, "next": { "message": "Tiếp theo" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index c36fcce77..9107f71a2 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -275,12 +275,6 @@ "advancedConfiguration": { "message": "高级配置" }, - "advancedGasFeeDefaultOptIn": { - "message": "将这些 $1 保存为“高级”默认值" - }, - "advancedGasFeeDefaultOptOut": { - "message": "始终使用这些值和高级设置作为默认值。" - }, "advancedGasFeeModalTitle": { "message": "高级燃料费" }, @@ -2101,9 +2095,6 @@ "newTokensImportedTitle": { "message": "已导入代币" }, - "newValues": { - "message": "新的值" - }, "next": { "message": "下一步" }, diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 1bbf4a4f8..3eec99a87 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -52,6 +52,10 @@ export default class AppStateController extends EventEmitter { ...initState, qrHardware: {}, nftsDropdownState: {}, + // This key is only used for checking if the user had set advancedGasFee + // prior to Migration 92.3 where we split out the setting to support + // multiple networks. + hadAdvancedGasFeesSetPriorToMigration92_3: false, usedNetworks: { '0x1': true, '0x5': true, diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 9be792c49..a30d83d76 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -41,7 +41,7 @@ export default class PreferencesController { useNftDetection: false, useCurrencyRateCheck: true, openSeaEnabled: false, - advancedGasFee: null, + advancedGasFee: {}, // WARNING: Do not use feature flags for security-sensitive things. // Feature flag toggling is available in the global namespace @@ -188,10 +188,18 @@ export default class PreferencesController { /** * Setter for the `advancedGasFee` property * - * @param {object} val - holds the maxBaseFee and PriorityFee that the user set as default advanced settings. + * @param {object} options + * @param {string} options.chainId - The chainId the advancedGasFees should be set on + * @param {object} options.gasFeePreferences - The advancedGasFee options to set */ - setAdvancedGasFee(val) { - this.store.updateState({ advancedGasFee: val }); + setAdvancedGasFee({ chainId, gasFeePreferences }) { + const { advancedGasFee } = this.store.getState(); + this.store.updateState({ + advancedGasFee: { + ...advancedGasFee, + [chainId]: gasFeePreferences, + }, + }); } /** diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index af740eddc..3a1e39aac 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -2,6 +2,7 @@ import { strict as assert } from 'assert'; import sinon from 'sinon'; import { ControllerMessenger } from '@metamask/base-controller'; import { TokenListController } from '@metamask/assets-controllers'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import PreferencesController from './preferences'; describe('preferences controller', function () { @@ -250,24 +251,31 @@ describe('preferences controller', function () { }); describe('setAdvancedGasFee', function () { - it('should default to null', function () { - const state = preferencesController.store.getState(); - assert.equal(state.advancedGasFee, null); + it('should default to an empty object', function () { + assert.deepEqual( + preferencesController.store.getState().advancedGasFee, + {}, + ); }); it('should set the setAdvancedGasFee property in state', function () { const state = preferencesController.store.getState(); - assert.equal(state.advancedGasFee, null); + assert.deepEqual(state.advancedGasFee, {}); preferencesController.setAdvancedGasFee({ - maxBaseFee: '1.5', - priorityFee: '2', + chainId: CHAIN_IDS.GOERLI, + gasFeePreferences: { + maxBaseFee: '1.5', + priorityFee: '2', + }, }); assert.equal( - preferencesController.store.getState().advancedGasFee.maxBaseFee, + preferencesController.store.getState().advancedGasFee[CHAIN_IDS.GOERLI] + .maxBaseFee, '1.5', ); assert.equal( - preferencesController.store.getState().advancedGasFee.priorityFee, + preferencesController.store.getState().advancedGasFee[CHAIN_IDS.GOERLI] + .priorityFee, '2', ); }); diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 37de2830e..3388828b6 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -1962,9 +1962,13 @@ export default class TransactionController extends EventEmitter { */ this.getTransactions = (opts) => this.txStateManager.getTransactions(opts); - /** @returns {object} the saved default values for advancedGasFee */ + /** + * @returns {object} the saved default values for advancedGasFee + */ this.getAdvancedGasFee = () => - this.preferencesStore.getState().advancedGasFee; + this.preferencesStore.getState().advancedGasFee[ + this._getCurrentChainId() + ]; } // called once on startup diff --git a/app/scripts/controllers/transactions/index.test.js b/app/scripts/controllers/transactions/index.test.js index 8600f08dc..04a170e4e 100644 --- a/app/scripts/controllers/transactions/index.test.js +++ b/app/scripts/controllers/transactions/index.test.js @@ -59,6 +59,7 @@ describe('Transaction Controller', function () { fromAccount, fragmentExists, networkStatusStore, + preferencesStore, getCurrentChainId, messengerMock, resultCallbacksMock, @@ -81,6 +82,7 @@ describe('Transaction Controller', function () { }).provider; networkStatusStore = new ObservableStore(currentNetworkStatus); + preferencesStore = new ObservableStore({ advancedGasFee: {} }); fromAccount = getTestAccounts()[0]; const blockTrackerStub = new EventEmitter(); @@ -129,6 +131,7 @@ describe('Transaction Controller', function () { getAccountType: () => 'MetaMask', getDeviceModel: () => 'N/A', securityProviderRequest: () => undefined, + preferencesStore, messenger: messengerMock, }); diff --git a/app/scripts/migrations/092.3.test.ts b/app/scripts/migrations/092.3.test.ts new file mode 100644 index 000000000..f55ea30c6 --- /dev/null +++ b/app/scripts/migrations/092.3.test.ts @@ -0,0 +1,184 @@ +import { migrate } from './092.3'; + +const PREFERENCES_CONTROLLER_MOCK = { + useBlockie: false, + useNonceField: false, + usePhishDetect: true, + dismissSeedBackUpReminder: false, + disabledRpcMethodPreferences: { + eth_sign: false, + }, + useMultiAccountBalanceChecker: true, + useTokenDetection: false, + useNftDetection: false, + use4ByteResolution: true, + useCurrencyRateCheck: true, + openSeaEnabled: false, + advancedGasFee: null, + featureFlags: { + showIncomingTransactions: true, + }, + knownMethodData: {}, + currentLocale: 'EN', + identities: {}, + lostIdentities: {}, + forgottenPassword: false, + preferences: { + autoLockTimeLimit: undefined, + showFiatInTestnets: false, + showTestNetworks: false, + useNativeCurrencyAsPrimaryCurrency: true, + hideZeroBalanceTokens: false, + }, + // ENS decentralized website resolution + ipfsGateway: '', + useAddressBarEnsResolution: true, + infuraBlocked: null, + ledgerTransportType: 'U2F', + snapRegistryList: {}, + transactionSecurityCheckEnabled: false, + theme: 'OS', + isLineaMainnetReleased: false, +}; + +describe('migration #92.3', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: 92.2 }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version: 92.3 }); + }); + + it('does nothing if no PreferencesController state', async () => { + const oldData = { + some: 'data', + }; + + const oldStorage = { + meta: { version: 92.2 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('does nothing if no AppStateController state', async () => { + const oldData = { + some: 'data', + }; + + const oldStorage = { + meta: { version: 92.2 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('changes advancedGasFee from null to an empty object, and sets hadAdvancedGasFeesSetPriorToMigration92_3 to false', async () => { + const oldData = { + some: 'data', + PreferencesController: { + ...PREFERENCES_CONTROLLER_MOCK, + }, + AppStateController: {}, + }; + + const oldStorage = { + meta: { version: 92.2 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + PreferencesController: { + ...PREFERENCES_CONTROLLER_MOCK, + advancedGasFee: {}, + }, + AppStateController: { + hadAdvancedGasFeesSetPriorToMigration92_3: false, + }, + }); + }); + + it('changes advancedGasFee from an object of values to an empty object and sets hadAdvancedGasFeesSetPriorToMigration92_3 to true', async () => { + const oldData = { + some: 'data', + PreferencesController: { + ...PREFERENCES_CONTROLLER_MOCK, + advancedGasFee: { + priorityFee: '0x1', + maxBaseFee: '0x1', + }, + }, + AppStateController: {}, + }; + + const oldStorage = { + meta: { version: 92.2 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + PreferencesController: { + ...PREFERENCES_CONTROLLER_MOCK, + advancedGasFee: {}, + }, + AppStateController: { + hadAdvancedGasFeesSetPriorToMigration92_3: true, + }, + }); + }); + + it('does not erase advancedGasFee if it does not contain the expected data prior to this migration', async () => { + const oldData = { + some: 'data', + PreferencesController: { + ...PREFERENCES_CONTROLLER_MOCK, + advancedGasFee: { + '0x5': { + priorityFee: '0x1', + maxBaseFee: '0x1', + }, + }, + }, + AppStateController: {}, + }; + + const oldStorage = { + meta: { version: 92.2 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + some: oldData.some, + PreferencesController: { + ...PREFERENCES_CONTROLLER_MOCK, + advancedGasFee: { + '0x5': { + priorityFee: '0x1', + maxBaseFee: '0x1', + }, + }, + }, + AppStateController: { + hadAdvancedGasFeesSetPriorToMigration92_3: false, + }, + }); + }); +}); diff --git a/app/scripts/migrations/092.3.ts b/app/scripts/migrations/092.3.ts new file mode 100644 index 000000000..a2659068f --- /dev/null +++ b/app/scripts/migrations/092.3.ts @@ -0,0 +1,98 @@ +import { hasProperty, isNullOrUndefined, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; +import log from 'loglevel'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 92.3; + +/** + * This migration does the following: + * + * - Deletes currently stored advancedGasFee in preferences controller, + * replacing the default with an empty object + * - Sets hadAdvancedGasFeesSetPriorToMigration92_3 flag on AppStateController + * to indicate if the user had previously had advancedGasFee set in their + * preferences. This will be used to display a whats new entry to inform users + * that we wiped these settings and made them apply per network. + * + * @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; + migrateData(versionedData.data); + return versionedData; +} + +function migrateData(state: Record): void { + changeShapeAndRemoveOldAdvancedGasFeePreference(state); +} + +function changeShapeAndRemoveOldAdvancedGasFeePreference( + state: Record, +) { + if (isNullOrUndefined(state.PreferencesController)) { + log.warn( + `Migration #${version}: preferences controller null or undefined, skipping migration`, + ); + return; + } + + if ( + hasProperty(state, 'AppStateController') && + isObject(state.AppStateController) && + hasProperty(state, 'PreferencesController') && + isObject(state.PreferencesController) + ) { + const possibleOriginalValue = state.PreferencesController?.advancedGasFee; + + // Will be false if the keys set on the object are anything other than the + // maxBaseFee or priorityFee. Essentially if the object is already keyed + // by chainId it won't show as hadFeesSet. + const hadFeesSet = + isObject(possibleOriginalValue) && + hasFeePreferenceKeys(possibleOriginalValue); + + state.AppStateController.hadAdvancedGasFeesSetPriorToMigration92_3 = + hadFeesSet; + + if ( + state.PreferencesController.advancedGasFee === null || + (isObject(state.PreferencesController.advancedGasFee) && + hasFeePreferenceKeys(state.PreferencesController.advancedGasFee)) + ) { + state.PreferencesController.advancedGasFee = {}; + } + } else if (isObject(state.AppStateController) === false) { + global.sentry?.captureException?.( + new Error( + `typeof state.AppStateController is ${typeof state.AppStateController}`, + ), + ); + } else if (isObject(state.PreferencesController) === false) { + global.sentry?.captureException?.( + new Error( + `typeof state.PreferencesController is ${typeof state.PreferencesController}`, + ), + ); + } +} + +function hasFeePreferenceKeys(objectToCheck: Record): boolean { + const keys = Object.keys(objectToCheck); + + if (keys.includes('maxBaseFee') || keys.includes('priorityFee')) { + return true; + } + return false; +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index f5c169a1f..3dcbf1b26 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -97,6 +97,7 @@ import * as m090 from './090'; import * as m091 from './091'; import * as m092 from './092'; import * as m092point1 from './092.1'; +import * as m092point3 from './092.3'; const migrations = [ m002, @@ -191,6 +192,7 @@ const migrations = [ m091, m092, m092point1, + m092point3, ]; export default migrations; diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 77d17841f..6cfd84ea0 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -113,6 +113,7 @@ "networkStatus": "available", "providerConfig": { "type": "rpc", + "nickname": "goerli", "chainId": "0x5", "ticker": "ETH", "id": "chain5" @@ -339,8 +340,10 @@ "useTokenDetection": true, "useCurrencyRateCheck": true, "advancedGasFee": { - "maxBaseFee": "75", - "priorityFee": "2" + "0x5": { + "maxBaseFee": "75", + "priorityFee": "2" + } }, "nftsDropdownState": { "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index f8045ea4a..a01f4d664 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -33,6 +33,7 @@ "qrHardware": "object", "usedNetworks": "object", "snapsInstallPrivacyWarningShown": "boolean", + "hadAdvancedGasFeesSetPriorToMigration92_3": "boolean", "serviceWorkerLastActiveTime": "number" }, "ApprovalController": "object", diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 74cbdb1fc..ff188c72f 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -55,6 +55,7 @@ "qrHardware": "object", "usedNetworks": "object", "snapsInstallPrivacyWarningShown": "boolean", + "hadAdvancedGasFeesSetPriorToMigration92_3": "boolean", "serviceWorkerLastActiveTime": "number", "currentAppVersion": "10.35.0", "previousAppVersion": "", diff --git a/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-defaults/advanced-gas-fee-defaults.js b/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-defaults/advanced-gas-fee-defaults.js index b764b2351..c4762f614 100644 --- a/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-defaults/advanced-gas-fee-defaults.js +++ b/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-defaults/advanced-gas-fee-defaults.js @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; +import { capitalize } from 'lodash'; import { useTransactionEventFragment } from '../../../../hooks/useTransactionEventFragment'; import { EditGasModes } from '../../../../../shared/constants/gas'; import Box from '../../../ui/box'; @@ -11,7 +12,11 @@ import { TextColor, TextVariant, } from '../../../../helpers/constants/design-system'; -import { getAdvancedGasFeeValues } from '../../../../selectors'; +import { + getAdvancedGasFeeValues, + getCurrentChainId, + getNetworkIdentifier, +} from '../../../../selectors'; import { setAdvancedGasFee } from '../../../../store/actions'; import { useGasFeeContext } from '../../../../contexts/gasFee'; import { useAdvancedGasFeePopoverContext } from '../context'; @@ -24,6 +29,9 @@ const AdvancedGasFeeDefaults = () => { const { gasErrors, maxBaseFee, maxPriorityFeePerGas } = useAdvancedGasFeePopoverContext(); const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues); + // This will need to use a different chainId in multinetwork + const chainId = useSelector(getCurrentChainId); + const networkIdentifier = useSelector(getNetworkIdentifier); const { updateTransactionEventFragment } = useTransactionEventFragment(); const { editGasMode } = useGasFeeContext(); const [isDefaultSettingsSelected, setDefaultSettingsSelected] = useState( @@ -42,7 +50,7 @@ const AdvancedGasFeeDefaults = () => { const handleUpdateDefaultSettings = () => { if (isDefaultSettingsSelected) { - dispatch(setAdvancedGasFee(null)); + dispatch(setAdvancedGasFee({ chainId, gasFeePreferences: undefined })); setDefaultSettingsSelected(false); updateTransactionEventFragment({ properties: { @@ -53,8 +61,11 @@ const AdvancedGasFeeDefaults = () => { } else { dispatch( setAdvancedGasFee({ - maxBaseFee, - priorityFee: maxPriorityFeePerGas, + chainId, + gasFeePreferences: { + maxBaseFee, + priorityFee: maxPriorityFeePerGas, + }, }), ); updateTransactionEventFragment({ @@ -91,11 +102,7 @@ const AdvancedGasFeeDefaults = () => { as="h6" color={TextColor.textAlternative} > - {isDefaultSettingsSelected - ? t('advancedGasFeeDefaultOptOut') - : t('advancedGasFeeDefaultOptIn', [ - {t('newValues')}, - ])} + {t('advancedGasFeeDefaultOptIn', [capitalize(networkIdentifier)])} diff --git a/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-defaults/advanced-gas-fee-defaults.test.js b/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-defaults/advanced-gas-fee-defaults.test.js index ca6253774..e93c1c44a 100644 --- a/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-defaults/advanced-gas-fee-defaults.test.js +++ b/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-defaults/advanced-gas-fee-defaults.test.js @@ -15,8 +15,11 @@ import { GasFeeContextProvider } from '../../../../contexts/gasFee'; import configureStore from '../../../../store/store'; import AdvancedGasFeeInputs from '../advanced-gas-fee-inputs'; +import { CHAIN_IDS } from '../../../../../shared/constants/network'; import AdvancedGasFeeDefaults from './advanced-gas-fee-defaults'; +const TEXT_SELECTOR = 'Save these values as my default for the Goerli network.'; + jest.mock('../../../../store/actions', () => ({ disconnectGasFeeEstimatePoller: jest.fn(), getGasFeeEstimatesAndStartPolling: jest @@ -62,68 +65,58 @@ const render = (defaultGasParams, contextParams) => { }; describe('AdvancedGasFeeDefaults', () => { it('should renders correct message when the default is not set', () => { - render({ advancedGasFee: null }); - expect(screen.queryByText('new values')).toBeInTheDocument(); + render({ advancedGasFee: {} }); + expect(screen.queryByText(TEXT_SELECTOR)).toBeInTheDocument(); }); it('should renders correct message when the default values are set', () => { render({ - advancedGasFee: { maxBaseFee: 50, priorityFee: 2 }, + advancedGasFee: { + [CHAIN_IDS.GOERLI]: { maxBaseFee: 50, priorityFee: 2 }, + }, }); - expect( - screen.queryByText( - 'Always use these values and advanced setting as default.', - ), - ).toBeInTheDocument(); + expect(screen.queryByText(TEXT_SELECTOR)).toBeInTheDocument(); }); it('should renders correct message when the default values are set and the maxBaseFee values are updated', () => { render({ - advancedGasFee: { maxBaseFee: 50, priorityFee: 2 }, + advancedGasFee: { + [CHAIN_IDS.GOERLI]: { maxBaseFee: 50, priorityFee: 2 }, + }, }); expect(document.getElementsByTagName('input')[2]).toBeChecked(); - expect( - screen.queryByText( - 'Always use these values and advanced setting as default.', - ), - ).toBeInTheDocument(); + expect(screen.queryByText(TEXT_SELECTOR)).toBeInTheDocument(); fireEvent.change(document.getElementsByTagName('input')[0], { target: { value: 75 }, }); expect(document.getElementsByTagName('input')[0]).toHaveValue(75); - expect(screen.queryByText('new values')).toBeInTheDocument(); - expect( - screen.queryByText('Save these as my default for "Advanced"'), - ).toBeInTheDocument(); + expect(screen.queryByText(TEXT_SELECTOR)).toBeInTheDocument(); + expect(screen.queryByText(TEXT_SELECTOR)).toBeInTheDocument(); }); it('should renders correct message when the default values are set and the priorityFee values are updated', () => { render({ - advancedGasFee: { maxBaseFee: 50, priorityFee: 2 }, + advancedGasFee: { + [CHAIN_IDS.GOERLI]: { maxBaseFee: 50, priorityFee: 2 }, + }, }); expect(document.getElementsByTagName('input')[2]).toBeChecked(); - expect( - screen.queryByText( - 'Always use these values and advanced setting as default.', - ), - ).toBeInTheDocument(); + expect(screen.queryByText(TEXT_SELECTOR)).toBeInTheDocument(); fireEvent.change(document.getElementsByTagName('input')[1], { target: { value: 5 }, }); expect(document.getElementsByTagName('input')[1]).toHaveValue(5); - expect(screen.queryByText('new values')).toBeInTheDocument(); - expect( - screen.queryByText('Save these as my default for "Advanced"'), - ).toBeInTheDocument(); + expect(screen.queryByText(TEXT_SELECTOR)).toBeInTheDocument(); + expect(screen.queryByText(TEXT_SELECTOR)).toBeInTheDocument(); }); it('should call action setAdvancedGasFee when checkbox or label text is clicked', () => { render({ - advancedGasFee: { maxBaseFee: 50, priorityFee: 2 }, + advancedGasFee: { + [CHAIN_IDS.GOERLI]: { maxBaseFee: 50, priorityFee: 2 }, + }, }); const mock = jest .spyOn(Actions, 'setAdvancedGasFee') .mockReturnValue({ type: 'test' }); - const checkboxLabel = screen.queryByText( - 'Always use these values and advanced setting as default.', - ); + const checkboxLabel = screen.queryByText(TEXT_SELECTOR); fireEvent.click(checkboxLabel); expect(mock).toHaveBeenCalledTimes(1); const checkbox = document.querySelector('input[type=checkbox]'); diff --git a/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-inputs/priority-fee-input/priority-fee-input.test.js b/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-inputs/priority-fee-input/priority-fee-input.test.js index 83540a3cd..eab4d43ac 100644 --- a/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-inputs/priority-fee-input/priority-fee-input.test.js +++ b/ui/components/app/advanced-gas-fee-popover/advanced-gas-fee-inputs/priority-fee-input/priority-fee-input.test.js @@ -13,6 +13,7 @@ import configureStore from '../../../../../store/store'; import { AdvancedGasFeePopoverContextProvider } from '../../context'; import AdvancedGasFeeGasLimit from '../../advanced-gas-fee-gas-limit'; +import { CHAIN_IDS } from '../../../../../../shared/constants/network'; import PriorityfeeInput from './priority-fee-input'; jest.mock('../../../../../store/actions', () => ({ @@ -34,7 +35,7 @@ const render = (txProps, contextProps) => { balance: '0x1F4', }, }, - advancedGasFee: { priorityFee: 100 }, + advancedGasFee: { [CHAIN_IDS.GOERLI]: { priorityFee: 100 } }, featureFlags: { advancedInlineGas: true }, gasFeeEstimates: mockEstimates[GasEstimateTypes.feeMarket].gasFeeEstimates, diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js index 7d9c0adfa..b7382bc5d 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-fee-popover.test.js @@ -67,6 +67,7 @@ const render = ({ txProps, contextProps } = {}) => { selectedAddress: '0xAddress', featureFlags: { advancedInlineGas: true }, gasFeeEstimates: MOCK_FEE_ESTIMATE, + advancedGasFee: {}, }, }); diff --git a/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js b/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js index 86f5ba097..ea3191e2d 100644 --- a/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js +++ b/ui/components/app/edit-gas-fee-popover/edit-gas-item/edit-gas-item.test.js @@ -10,6 +10,7 @@ import { ETH } from '../../../../helpers/constants/common'; import configureStore from '../../../../store/store'; import { GasFeeContextProvider } from '../../../../contexts/gasFee'; +import { CHAIN_IDS } from '../../../../../shared/constants/network'; import EditGasItem from './edit-gas-item'; jest.mock('../../../../store/actions', () => ({ @@ -59,7 +60,9 @@ const renderComponent = ({ const store = configureStore({ metamask: { nativeCurrency: ETH, - providerConfig: {}, + providerConfig: { + chainId: CHAIN_IDS.GOERLI, + }, cachedBalances: {}, accounts: { '0xAddress': { @@ -75,8 +78,10 @@ const renderComponent = ({ gasEstimateType: 'fee-market', gasFeeEstimates: MOCK_FEE_ESTIMATE, advancedGasFee: { - maxBaseFee: '100', - priorityFee: '2', + [CHAIN_IDS.GOERLI]: { + maxBaseFee: '100', + priorityFee: '2', + }, }, }, }); diff --git a/ui/components/app/signature-request-header/__snapshots__/signature-request-header.test.js.snap b/ui/components/app/signature-request-header/__snapshots__/signature-request-header.test.js.snap index 58642cc3e..1f9dedfed 100644 --- a/ui/components/app/signature-request-header/__snapshots__/signature-request-header.test.js.snap +++ b/ui/components/app/signature-request-header/__snapshots__/signature-request-header.test.js.snap @@ -61,7 +61,7 @@ exports[`SignatureRequestHeader should match snapshot 1`] = ` - U + G @@ -71,7 +71,7 @@ exports[`SignatureRequestHeader should match snapshot 1`] = `
- Unknown private network + goerli
- U + G @@ -147,7 +147,7 @@ exports[`SignatureRequestOriginal should match snapshot 1`] = `
- Unknown private network + goerli
- U + G @@ -144,7 +144,7 @@ exports[`SignatureRequestSIWE (Sign in with Ethereum) should match snapshot 1`]
- Unknown private network + goerli
- Private network + goerli @@ -127,7 +127,7 @@ exports[`SignatureRequestHeader renders correctly without fromAccount 1`] = ` - Private network + goerli diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 4129cdcd2..3c7006a86 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -1331,23 +1331,26 @@ export function getIsMultiLayerFeeNetwork(state) { * To retrieve the maxBaseFee and priorityFee the user has set as default * * @param {*} state - * @returns Boolean + * @returns {{maxBaseFee: string, priorityFee: string} | undefined} */ export function getAdvancedGasFeeValues(state) { - return state.metamask.advancedGasFee; -} - -/** - * To check if the user has set advanced gas fee settings as default with a non empty maxBaseFee and priotityFee. - * - * @param {*} state - * @returns Boolean - */ -export function getIsAdvancedGasFeeDefault(state) { - const { advancedGasFee } = state.metamask; - return ( - Boolean(advancedGasFee?.maxBaseFee) && Boolean(advancedGasFee?.priorityFee) - ); + // This will not work when we switch to supporting multi-chain. + // There are four non-test files that use this selector. + // advanced-gas-fee-defaults + // base-fee-input + // priority-fee-input + // useGasItemFeeDetails + // The first three are part of the AdvancedGasFeePopover + // The latter is used by the EditGasPopover + // Both of those are used in Confirmations as well as transaction-list-item + // All of the call sites have access to the GasFeeContext, which has a + // transaction object set on it, but there are currently no guarantees that + // the transaction has a chainId associated with it. To have this method + // support multichain we'll need a reliable way for the chainId of the + // transaction being modified to be available to all callsites and either + // pass it in to the selector as a second parameter, or access it at the + // callsite. + return state.metamask.advancedGasFee[getCurrentChainId(state)]; } /** diff --git a/ui/selectors/selectors.test.js b/ui/selectors/selectors.test.js index e63252ee7..4c7f44df9 100644 --- a/ui/selectors/selectors.test.js +++ b/ui/selectors/selectors.test.js @@ -600,11 +600,6 @@ describe('Selectors', () => { priorityFee: '2', }); }); - it('#getIsAdvancedGasFeeDefault', () => { - const isAdvancedGasFeeDefault = - selectors.getIsAdvancedGasFeeDefault(mockState); - expect(isAdvancedGasFeeDefault).toStrictEqual(true); - }); it('#getAppIsLoading', () => { const appIsLoading = selectors.getAppIsLoading(mockState); expect(appIsLoading).toStrictEqual(false); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 8040b01b8..a07eed427 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -3035,7 +3035,7 @@ export function detectNfts(): ThunkAction< } export function setAdvancedGasFee( - val: { maxBaseFee?: Hex; priorityFee?: Hex } | null, + val: { chainId: Hex; maxBaseFee?: Hex; priorityFee?: Hex } | null, ): ThunkAction { return (dispatch: MetaMaskReduxDispatch) => { dispatch(showLoadingIndication()); From 19b6baefb4508e70da3209354c85940936ed8071 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Mon, 28 Aug 2023 17:55:56 +0100 Subject: [PATCH 09/16] fix: Remove obsolete network controller state properties (#20586) * fix: remove obsolete network controller state properties * address comments * address comments * address comments * Add additional precautions for data mutation in migration tests --------- Co-authored-by: Mark Stacey --- app/scripts/background.js | 2 +- app/scripts/migrations/092.2.test.ts | 99 ++++++++++++++++++++++++++++ app/scripts/migrations/092.2.ts | 75 +++++++++++++++++++++ app/scripts/migrations/index.js | 2 + 4 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 app/scripts/migrations/092.2.test.ts create mode 100644 app/scripts/migrations/092.2.ts diff --git a/app/scripts/background.js b/app/scripts/background.js index 44f9d177d..dd6848f55 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -360,7 +360,7 @@ async function loadPhishingWarningPage() { } catch (error) { if (error instanceof PhishingWarningPageTimeoutError) { console.warn( - 'Phishing warning page timeout; page not guaraneteed to work offline.', + 'Phishing warning page timeout; page not guaranteed to work offline.', ); } else { console.error('Failed to initialize phishing warning page', error); diff --git a/app/scripts/migrations/092.2.test.ts b/app/scripts/migrations/092.2.test.ts new file mode 100644 index 000000000..513ac4f0f --- /dev/null +++ b/app/scripts/migrations/092.2.test.ts @@ -0,0 +1,99 @@ +import { NetworkType, toHex } from '@metamask/controller-utils'; +import { NetworkStatus } from '@metamask/network-controller'; +import { cloneDeep } from 'lodash'; +import { version as currentStateVersion, migrate } from './092.2'; + +const TEST_NETWORK_CONTROLLER_STATE = { + networkId: 'network-id', + networkStatus: NetworkStatus.Available, + providerConfig: { + type: NetworkType.rpc, + chainId: toHex(42), + nickname: 'Funky Town Chain', + ticker: 'ETH', + id: 'test-network-client-id', + }, + networkDetails: { EIPS: {} }, + networkConfigurations: { + 'network-configuration-id-1': { + chainId: toHex(42), + nickname: 'Localhost 8545', + rpcPrefs: {}, + rpcUrl: 'http://localhost:8545', + ticker: 'ETH', + }, + }, +}; + +const anyPreviousStateVersion = 91; + +describe('migration #96', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should update the state version number in the appropriate metadata field', async () => { + const originalVersionedState = { + meta: { version: anyPreviousStateVersion }, + data: {}, + }; + + const newStorage = await migrate(cloneDeep(originalVersionedState)); + + expect(newStorage.meta).toStrictEqual({ version: currentStateVersion }); + }); + + it('should return state unaltered if there is no network controller state', async () => { + const originalMetaMaskState = { + anotherController: 'another-controller-state', + }; + const originalVersionedState = { + meta: { version: anyPreviousStateVersion }, + data: originalMetaMaskState, + }; + + const updatedVersionedState = await migrate( + cloneDeep(originalVersionedState), + ); + expect(updatedVersionedState.data).toStrictEqual(originalMetaMaskState); + }); + + it('should return unaltered state if there are no obsolete network controller state properties', async () => { + const originalMetaMaskState = { + anotherController: 'another-controller-state', + NetworkController: TEST_NETWORK_CONTROLLER_STATE, + }; + const originalVersionedState = { + meta: { version: anyPreviousStateVersion }, + data: originalMetaMaskState, + }; + + const updatedVersionedState = await migrate( + cloneDeep(originalVersionedState), + ); + expect(updatedVersionedState.data).toStrictEqual(originalMetaMaskState); + }); + + it('should return updated state without obsolete network controller state properties', async () => { + const originalMetaMaskState = { + anotherController: 'another-controller-state', + NetworkController: { + ...TEST_NETWORK_CONTROLLER_STATE, + someSortOfRogueObsoleteStateProperty: 'exists', + }, + }; + const originalVersionedState = { + meta: { version: anyPreviousStateVersion }, + data: originalMetaMaskState, + }; + + const updatedVersionedState = await migrate( + cloneDeep(originalVersionedState), + ); + expect(updatedVersionedState.data).not.toStrictEqual(originalMetaMaskState); + expect(updatedVersionedState.data).toStrictEqual({ + anotherController: 'another-controller-state', + NetworkController: TEST_NETWORK_CONTROLLER_STATE, + }); + }); +}); diff --git a/app/scripts/migrations/092.2.ts b/app/scripts/migrations/092.2.ts new file mode 100644 index 000000000..ef36a9fa9 --- /dev/null +++ b/app/scripts/migrations/092.2.ts @@ -0,0 +1,75 @@ +import { hasProperty } from '@metamask/utils'; +import { captureException } from '@sentry/browser'; +import { cloneDeep, isObject, pick } from 'lodash'; + +type MetaMaskState = Record; +type VersionedState = { + meta: { version: number }; + data: MetaMaskState; +}; + +export const version = 92.2; + +/** + * This migration removes obsolete NetworkController state properties. + * + * @param originalVersionedState - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedState.meta - State metadata. + * @param originalVersionedState.meta.version - The current state version. + * @param originalVersionedState.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned of MetaMask extension state. + */ +export async function migrate( + originalVersionedState: VersionedState, +): Promise { + const updatedVersionedState = cloneDeep(originalVersionedState); + + updatedVersionedState.meta.version = version; + updatedVersionedState.data = transformState(updatedVersionedState.data); + + return updatedVersionedState; +} + +function transformState(originalState: MetaMaskState): MetaMaskState { + const updatedState = + filterOutObsoleteNetworkControllerStateProperties(originalState); + + return updatedState; +} + +function filterOutObsoleteNetworkControllerStateProperties( + state: MetaMaskState, +): MetaMaskState { + // https://github.com/MetaMask/core/blob/%40metamask/network-controller%4010.3.1/packages/network-controller/src/NetworkController.ts#L336-L342 + const CURRENT_NETWORK_CONTROLLER_STATE_PROPS = [ + 'networkId', + 'networkStatus', + 'providerConfig', + 'networkDetails', + 'networkConfigurations', + ]; + + if ( + !hasProperty(state, 'NetworkController') || + !isObject(state.NetworkController) + ) { + captureException( + `Migration ${version}: Invalid NetworkController state: ${typeof state.NetworkController}`, + ); + + return state; + } + + const networkControllerState = state.NetworkController; + + // delete network state properties that are not currently in use + const updatedNetworkController = pick( + networkControllerState, + CURRENT_NETWORK_CONTROLLER_STATE_PROPS, + ); + + return { + ...state, + NetworkController: updatedNetworkController, + }; +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 3dcbf1b26..a07a71fa0 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -97,6 +97,7 @@ import * as m090 from './090'; import * as m091 from './091'; import * as m092 from './092'; import * as m092point1 from './092.1'; +import * as m092point2 from './092.2'; import * as m092point3 from './092.3'; const migrations = [ @@ -192,6 +193,7 @@ const migrations = [ m091, m092, m092point1, + m092point2, m092point3, ]; From 180ac559b860be8c7333fefc320d17cbe8337990 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 21 Aug 2023 12:53:04 -0230 Subject: [PATCH 10/16] Remove snapshot update from release process (#20546) The Sentry e2e state snapshots now mask the application version and migration version, ensuring that the snapshots don't need a extra update in each release candidate branch and post-release sync branch. The values are masked rather than removed so that the test still shows they are present in error reports, where they can be quite useful for diagnostic purposes. --- test/e2e/tests/errors.spec.js | 8 ++++++++ .../errors-after-init-opt-in-background-state.json | 4 ++-- .../errors-after-init-opt-in-ui-state.json | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 5c557b256..7d7e12424 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -9,9 +9,17 @@ const FixtureBuilder = require('../fixture-builder'); const maskedBackgroundFields = [ 'CurrencyController.conversionDate', // This is a timestamp that changes each run + // App metadata is masked so that we don't have to update the snapshot as + // part of the release process + 'AppMetadataController.currentAppVersion', + 'AppMetadataController.currentMigrationVersion', ]; const maskedUiFields = [ 'metamask.conversionDate', // This is a timestamp that changes each run + // App metadata is masked so that we don't have to update the snapshot as + // part of the release process + 'metamask.currentAppVersion', + 'metamask.currentMigrationVersion', ]; const removedBackgroundFields = [ diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index a01f4d664..e8191f915 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -8,10 +8,10 @@ }, "AnnouncementController": "object", "AppMetadataController": { - "currentAppVersion": "10.35.0", + "currentAppVersion": "string", "previousAppVersion": "", "previousMigrationVersion": 0, - "currentMigrationVersion": 92.1 + "currentMigrationVersion": "number" }, "AppStateController": { "connectedStatusPopoverHasBeenShown": true, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index ff188c72f..64a08f7b4 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -57,10 +57,10 @@ "snapsInstallPrivacyWarningShown": "boolean", "hadAdvancedGasFeesSetPriorToMigration92_3": "boolean", "serviceWorkerLastActiveTime": "number", - "currentAppVersion": "10.35.0", + "currentAppVersion": "string", "previousAppVersion": "", "previousMigrationVersion": 0, - "currentMigrationVersion": 92.1, + "currentMigrationVersion": "number", "networkId": "1337", "networkStatus": "available", "providerConfig": { From e80171499237a25460529a3872981386bc44b41f Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Tue, 1 Aug 2023 16:10:52 -0500 Subject: [PATCH 11/16] fix weird lockfile irregularities (#20333) Co-authored-by: Mark Stacey --- lavamoat/browserify/desktop/policy.json | 4 +- lavamoat/browserify/flask/policy.json | 4 +- lavamoat/build-system/policy.json | 38 +++------------ yarn.lock | 62 +++++++------------------ 4 files changed, 29 insertions(+), 79 deletions(-) diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index 799ae791f..907a67b9b 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -2205,7 +2205,9 @@ "packages": { "@metamask/snaps-controllers-flask>concat-stream>readable-stream": true, "browserify>buffer": true, - "pumpify>inherits": true + "browserify>concat-stream>typedarray": true, + "pumpify>inherits": true, + "terser>source-map-support>buffer-from": true } }, "@metamask/snaps-controllers-flask>concat-stream>readable-stream": { diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 799ae791f..907a67b9b 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -2205,7 +2205,9 @@ "packages": { "@metamask/snaps-controllers-flask>concat-stream>readable-stream": true, "browserify>buffer": true, - "pumpify>inherits": true + "browserify>concat-stream>typedarray": true, + "pumpify>inherits": true, + "terser>source-map-support>buffer-from": true } }, "@metamask/snaps-controllers-flask>concat-stream>readable-stream": { diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index fab32164f..2b4fa98c9 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -1752,20 +1752,7 @@ "process.platform": true }, "packages": { - "browserify>browser-resolve>resolve": true - } - }, - "browserify>browser-resolve>resolve": { - "builtin": { - "fs.readFile": true, - "fs.readFileSync": true, - "fs.stat": true, - "fs.statSync": true, - "path": true - }, - "globals": { - "process.nextTick": true, - "process.platform": true + "brfs>resolve": true } }, "browserify>cached-path-relative": { @@ -1874,6 +1861,7 @@ }, "packages": { "brfs>resolve": true, + "browserify>browser-resolve": true, "browserify>cached-path-relative": true, "browserify>concat-stream": true, "browserify>duplexer2": true, @@ -1881,7 +1869,6 @@ "browserify>module-deps>stream-combiner2": true, "browserify>module-deps>through2": true, "browserify>parents": true, - "lavamoat-browserify>browser-resolve": true, "loose-envify": true, "pumpify>inherits": true, "readable-stream": true, @@ -6149,8 +6136,8 @@ }, "packages": { "@lavamoat/lavapack": true, + "browserify>browser-resolve": true, "duplexify": true, - "lavamoat-browserify>browser-resolve": true, "lavamoat-browserify>concat-stream": true, "lavamoat-browserify>readable-stream": true, "lavamoat-browserify>through2": true, @@ -6159,29 +6146,16 @@ "lavamoat>lavamoat-core": true } }, - "lavamoat-browserify>browser-resolve": { - "builtin": { - "fs.readFile": true, - "fs.readFileSync": true, - "path": true - }, - "globals": { - "__dirname": true, - "process.platform": true - }, - "packages": { - "brfs>resolve": true - } - }, "lavamoat-browserify>concat-stream": { "globals": { "Buffer.concat": true, - "Buffer.from": true, "Buffer.isBuffer": true }, "packages": { + "browserify>concat-stream>typedarray": true, "lavamoat-browserify>readable-stream": true, - "pumpify>inherits": true + "pumpify>inherits": true, + "terser>source-map-support>buffer-from": true } }, "lavamoat-browserify>readable-stream": { diff --git a/yarn.lock b/yarn.lock index faa9438a0..47e99f71f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11249,15 +11249,6 @@ __metadata: languageName: node linkType: hard -"browser-resolve@npm:^1.11.0": - version: 1.11.3 - resolution: "browser-resolve@npm:1.11.3" - dependencies: - resolve: 1.1.7 - checksum: 431bfc1a17406362a3010a2c35503eb7d1253dbcb8081c1ce236ddb0b954a33d52dcaf0b07f64c0f20394d6eeec1be4f6551da3734ce9ed5dcc38e876c96d5d5 - languageName: node - linkType: hard - "browser-resolve@npm:^2.0.0": version: 2.0.0 resolution: "browser-resolve@npm:2.0.0" @@ -11361,13 +11352,13 @@ __metadata: linkType: hard "browserify@npm:^16.5.1": - version: 16.5.1 - resolution: "browserify@npm:16.5.1" + version: 16.5.2 + resolution: "browserify@npm:16.5.2" dependencies: JSONStream: ^1.0.3 assert: ^1.4.0 browser-pack: ^6.0.1 - browser-resolve: ^1.11.0 + browser-resolve: ^2.0.0 browserify-zlib: ~0.2.0 buffer: ~5.2.1 cached-path-relative: ^1.0.0 @@ -11388,7 +11379,7 @@ __metadata: insert-module-globals: ^7.0.0 labeled-stream-splicer: ^2.0.0 mkdirp-classic: ^0.5.2 - module-deps: ^6.0.0 + module-deps: ^6.2.3 os-browserify: ~0.3.0 parents: ^1.0.1 path-browserify: ~0.0.0 @@ -11414,7 +11405,7 @@ __metadata: xtend: ^4.0.0 bin: browserify: bin/cmd.js - checksum: 71c02959ffbcedc6364fb77db75dbf9ac7d945747414774287202327b7be9ae390257acfbf788455a90a088daa18c5134ec7b46315b747ff51ccddb73ad2e3ea + checksum: 75dacf5c82355146b49a2febb3bf9f7898893931973cf901849791827e44782afcb562be7bc3a893d9022ae528fd6fccdf24fc8812cb5aa1b081bb7ce34c46b5 languageName: node linkType: hard @@ -12907,11 +12898,13 @@ __metadata: "concat-stream@npm:^2.0.0": version: 2.0.0 - resolution: "concat-stream@https://github.com/hugomrdias/concat-stream.git#commit=057bc7b5d6d8df26c8cf00a3f151b6721a0a8034" + resolution: "concat-stream@npm:2.0.0" dependencies: + buffer-from: ^1.0.0 inherits: ^2.0.3 readable-stream: ^3.0.2 - checksum: 1cef636e7061f310088706b34fe774e3960dff60a5039158b5e5c84795f6dd8a3411659324280405b8c5f1d7e8e3d4f68fa48e55963ed14953a44fef66423329 + typedarray: ^0.0.6 + checksum: d7f75d48f0ecd356c1545d87e22f57b488172811b1181d96021c7c4b14ab8855f5313280263dca44bb06e5222f274d047da3e290a38841ef87b59719bde967c7 languageName: node linkType: hard @@ -14828,11 +14821,11 @@ __metadata: linkType: hard "dot-prop@npm:^4.1.0": - version: 5.2.0 - resolution: "dot-prop@npm:5.2.0" + version: 4.2.1 + resolution: "dot-prop@npm:4.2.1" dependencies: - is-obj: ^2.0.0 - checksum: 709a8208bff4fc4d5a11e357957a9e59ed625d7db909d14ea1e0dbeb30d26c25325a6e64ea27ed96fb17978cc13c7e38cf30bac17bb81eb9b5a740a4fe909a87 + is-obj: ^1.0.0 + checksum: 5f4f19aa440bc548670d87f2adcbd105fa6842cd1fba3165a8a2b1380568ae82862acf8ebafcc6093fa062505d7d08d7155c7ba9a88da212f7348e95ef2bdce6 languageName: node linkType: hard @@ -16331,11 +16324,11 @@ __metadata: "ethereumjs-abi@git+https://github.com/ethereumjs/ethereumjs-abi.git, ethereumjs-abi@npm:0.6.8, ethereumjs-abi@npm:^0.6.4, ethereumjs-abi@npm:^0.6.8": version: 0.6.8 - resolution: "ethereumjs-abi@https://github.com/ethereumjs/ethereumjs-abi.git#commit=ee3994657fa7a427238e6ba92a84d0b529bbcde0" + resolution: "ethereumjs-abi@npm:0.6.8" dependencies: bn.js: ^4.11.8 ethereumjs-util: ^6.0.0 - checksum: ae074be0bb012857ab5d3ae644d1163b908a48dd724b7d2567cfde309dc72222d460438f2411936a70dc949dc604ce1ef7118f7273bd525815579143c907e336 + checksum: cede2a8ae7c7e04eeaec079c2f925601a25b2ef75cf9230e7c5da63b4ea27883b35447365a47e35c1e831af520973a2252af89022c292c18a09a4607821a366b languageName: node linkType: hard @@ -20779,20 +20772,13 @@ __metadata: languageName: node linkType: hard -"is-obj@npm:^1.0.1": +"is-obj@npm:^1.0.0, is-obj@npm:^1.0.1": version: 1.0.1 resolution: "is-obj@npm:1.0.1" checksum: 3ccf0efdea12951e0b9c784e2b00e77e87b2f8bd30b42a498548a8afcc11b3287342a2030c308e473e93a7a19c9ea7854c99a8832a476591c727df2a9c79796c languageName: node linkType: hard -"is-obj@npm:^2.0.0": - version: 2.0.0 - resolution: "is-obj@npm:2.0.0" - checksum: c9916ac8f4621962a42f5e80e7ffdb1d79a3fab7456ceaeea394cd9e0858d04f985a9ace45be44433bf605673c8be8810540fe4cc7f4266fc7526ced95af5a08 - languageName: node - linkType: hard - "is-object@npm:^1.0.1, is-object@npm:~1.0.1": version: 1.0.1 resolution: "is-object@npm:1.0.1" @@ -25831,7 +25817,7 @@ __metadata: languageName: node linkType: hard -"module-deps@npm:^6.0.0, module-deps@npm:^6.2.3": +"module-deps@npm:^6.2.3": version: 6.2.3 resolution: "module-deps@npm:6.2.3" dependencies: @@ -30618,13 +30604,6 @@ __metadata: languageName: node linkType: hard -"resolve@npm:1.1.7": - version: 1.1.7 - resolution: "resolve@npm:1.1.7" - checksum: afd20873fbde7641c9125efe3f940c2a99f6b1f90f1b7b743e744bdaac1cb105b2e4e0317bcc052ed7e31d57afa86b394a4dc9a1b33a297977be134fdf0250ab - languageName: node - linkType: hard - "resolve@npm:^1.1.4, resolve@npm:^1.1.5, resolve@npm:^1.1.6, resolve@npm:^1.1.7, resolve@npm:^1.10.0, resolve@npm:^1.10.1, resolve@npm:^1.11.1, resolve@npm:^1.14.2, resolve@npm:^1.15.1, resolve@npm:^1.17.0, resolve@npm:^1.18.1, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.21.0, resolve@npm:^1.22.0, resolve@npm:^1.22.1, resolve@npm:^1.22.3, resolve@npm:^1.4.0": version: 1.22.3 resolution: "resolve@npm:1.22.3" @@ -30651,13 +30630,6 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@1.1.7#~builtin": - version: 1.1.7 - resolution: "resolve@patch:resolve@npm%3A1.1.7#~builtin::version=1.1.7&hash=07638b" - checksum: e9dbca78600ae56835c43a09f1276876c883e4b4bbd43e2683fa140671519d2bdebeb1c1576ca87c8c508ae2987b3ec481645ac5d3054b0f23254cfc1ce49942 - languageName: node - linkType: hard - "resolve@patch:resolve@^1.1.4#~builtin, resolve@patch:resolve@^1.1.5#~builtin, resolve@patch:resolve@^1.1.6#~builtin, resolve@patch:resolve@^1.1.7#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.10.1#~builtin, resolve@patch:resolve@^1.11.1#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.15.1#~builtin, resolve@patch:resolve@^1.17.0#~builtin, resolve@patch:resolve@^1.18.1#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.21.0#~builtin, resolve@patch:resolve@^1.22.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin, resolve@patch:resolve@^1.22.3#~builtin, resolve@patch:resolve@^1.4.0#~builtin": version: 1.22.3 resolution: "resolve@patch:resolve@npm%3A1.22.3#~builtin::version=1.22.3&hash=07638b" From ec23b00fc6ce0ac25e4c37407bd2b44a46e5dc7b Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 29 Aug 2023 11:55:25 -0230 Subject: [PATCH 12/16] Update protobufjs checksum --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 47e99f71f..eb1a68cf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28709,7 +28709,7 @@ __metadata: bin: pbjs: bin/pbjs pbts: bin/pbts - checksum: 6b7fd7540d74350d65c38f69f398c9995ae019da070e79d9cd464a458c6d19b40b07c9a026be4e10704c824a344b603307745863310c50026ebd661ce4da0663 + checksum: b2fc6a01897b016c2a7e43a854ab4a3c57080f61be41e552235436e7a730711b8e89e47cb4ae52f0f065b5ab5d5989fc932f390337ce3a8ccf07203415700850 languageName: node linkType: hard From 6d2cc98b81d2ada5e283aee1cf1c9bf84e8fbfcc Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Tue, 29 Aug 2023 16:15:35 -0500 Subject: [PATCH 13/16] Add whats new popup for changes to advanced gas fee (#20632) * Add whats new popup for changes to advanced gas fee * Update shared/notifications/index.js Co-authored-by: Mark Stacey * updated text * verified working, changed order of init * use first renderer for formatting issue --------- Co-authored-by: Mark Stacey Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com> --- app/_locales/en/messages.json | 9 +++++++++ app/scripts/controllers/app-state.js | 6 +++--- shared/notifications/index.js | 15 +++++++++++++++ .../app/whats-new-popup/whats-new-popup.js | 4 ++++ ui/selectors/selectors.js | 1 + 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 3fbc3e374..0d9c8dbe2 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2747,6 +2747,15 @@ "notifications22Title": { "message": "Looking for your account details or the block explorer URL?" }, + "notifications24ActionText": { + "message": "Got it" + }, + "notifications24Description": { + "message": "Advanced gas fee settings are now remembered based on the network you're using. This means you can set specific advanced gas fees for each network and avoid overpaying for gas or stuck transactions." + }, + "notifications24Title": { + "message": "Advanced gas fees by network" + }, "notifications3ActionText": { "message": "Read more", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 3eec99a87..28e6d964b 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -49,13 +49,13 @@ export default class AppStateController extends EventEmitter { showProductTour: true, trezorModel: null, currentPopupId: undefined, - ...initState, - qrHardware: {}, - nftsDropdownState: {}, // This key is only used for checking if the user had set advancedGasFee // prior to Migration 92.3 where we split out the setting to support // multiple networks. hadAdvancedGasFeesSetPriorToMigration92_3: false, + ...initState, + qrHardware: {}, + nftsDropdownState: {}, usedNetworks: { '0x1': true, '0x5': true, diff --git a/shared/notifications/index.js b/shared/notifications/index.js index f5d496b8b..f7f38ceec 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -121,6 +121,10 @@ export const UI_NOTIFICATIONS = { src: 'images/global-menu-block-explorer.svg', }, }, + 24: { + id: 24, + date: null, + }, }; export const getTranslatedUINotifications = (t, locale) => { @@ -331,5 +335,16 @@ export const getTranslatedUINotifications = (t, locale) => { ) : '', }, + 24: { + ...UI_NOTIFICATIONS[24], + title: t('notifications24Title'), + description: t('notifications24Description'), + actionText: t('notifications24ActionText'), + date: UI_NOTIFICATIONS[24].date + ? new Intl.DateTimeFormat(formattedLocale).format( + new Date(UI_NOTIFICATIONS[24].date), + ) + : '', + }, }; }; diff --git a/ui/components/app/whats-new-popup/whats-new-popup.js b/ui/components/app/whats-new-popup/whats-new-popup.js index 94631434e..15ec13270 100644 --- a/ui/components/app/whats-new-popup/whats-new-popup.js +++ b/ui/components/app/whats-new-popup/whats-new-popup.js @@ -103,6 +103,9 @@ function getActionFunctionById(id, history) { 22: () => { updateViewedNotifications({ 22: true }); }, + 24: () => { + updateViewedNotifications({ 24: true }); + }, }; return actionFunctions[id]; @@ -364,6 +367,7 @@ export default function WhatsNewPopup({ 19: renderFirstNotification, 21: renderFirstNotification, 22: renderFirstNotification, + 24: renderFirstNotification, }; return ( diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 3c7006a86..e6ae9e425 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -1031,6 +1031,7 @@ function getAllowedAnnouncementIds(state) { 20: currentKeyringIsLedger && isFirefox, 21: isSwapsChain, 22: true, + 24: state.metamask.hadAdvancedGasFeesSetPriorToMigration92_3 === true, }; } From 052e90ec7c4e5c4c700410ed24ba13ffb73faedc Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 29 Aug 2023 18:49:44 -0230 Subject: [PATCH 14/16] Update changelog for v10.35.1 (#20650) --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index df2711ec9..7b769c6f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [10.35.1] +### Changed +- Allow quick Add Account name based on default name ([#20168](https://github.com/MetaMask/metamask-extension/pull/20168)) +- Store default gas settings by network ([#20576](https://github.com/MetaMask/metamask-extension/pull/20576), [#20632](https://github.com/MetaMask/metamask-extension/pull/20632)) +- Add more diagnostic information upon failure ([#20595](https://github.com/MetaMask/metamask-extension/pull/20595)) + +### Fixed +- Fix bug resulting in custom network configuration being lost upon restart ([#20586](https://github.com/MetaMask/metamask-extension/pull/20586)) +- Fix UI crash when balances are missing ([#20385](https://github.com/MetaMask/metamask-extension/pull/20385)) +- Fix infinite rerender on network change while signature request is pending ([#20473](https://github.com/MetaMask/metamask-extension/pull/20473)) +- Fix Dapp link on NFT import screen ([#19799](https://github.com/MetaMask/metamask-extension/pull/19799)) +- Fix 'View on Opensea' link for main and testnet NFTs ([#19797](https://github.com/MetaMask/metamask-extension/pull/19797)) +- Ensure chainId comparison in switchEthereumChain handler is case insensitive ([#20149](https://github.com/MetaMask/metamask-extension/pull/20149)) +- Enforce user preferences in incoming transactions controller ([#19982](https://github.com/MetaMask/metamask-extension/pull/19982)) ## [10.35.0] ### Added From e5ad6ef2b737b83b7489d243b5423df0420a3c63 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Wed, 30 Aug 2023 00:17:58 +0100 Subject: [PATCH 15/16] feat: Add more state props from controllers to Sentry mask (#20595) * feat: update sentry mask adding controller props to improve error monitoring * fix:remove changes in chrome-driver dependency * Remove properties from mask * Add more values to mask * Sort the mask alphabetically * Add termsOfUseLastAgreed to mask * Fix test imports * Update policy gap test to compare UI mask * Reorganize tests under one describe block * Update snapshots * Mask another timestamp in state snapshots * Mask browser environment properties * Add missing UI field mask, and refactor field masking/removal * Eliminate remaining policy gaps * Simplify ganache options * Eliminate extra mask properties * Update mask to capture dynamic keys The mask now supports dynamic keys. This lets set more fine-grained rules for which data to include within dynamic data structures. The mask has been updated to include just top-level keys for various token-related data collections in state. This lets us see the chain IDs that users have tokens on. This will be useful in debugging Sentry reports of invalid keys in these data structures. * Add additional 'expected missing state' entries * Remove unnecessary properties from state snapshot * Add providerConfig.chainId to state snapshot * Update error state snapshots --------- Co-authored-by: Danica Shen Co-authored-by: Mark Stacey --- app/scripts/lib/setupSentry.js | 194 +++++++++++++++++- shared/modules/object.utils.js | 40 +++- test/e2e/tests/errors.spec.js | 157 ++++++++++++-- ...rs-after-init-opt-in-background-state.json | 164 ++++++++++----- .../errors-after-init-opt-in-ui-state.json | 121 ++++++----- ...s-before-init-opt-in-background-state.json | 90 +++++--- .../errors-before-init-opt-in-ui-state.json | 90 +++++--- 7 files changed, 657 insertions(+), 199 deletions(-) diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index c6d29995e..befecf21c 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -1,6 +1,7 @@ import * as Sentry from '@sentry/browser'; import { Dedupe, ExtraErrorData } from '@sentry/integrations'; +import { AllProperties } from '../../../shared/modules/object.utils'; import { FilterEvents } from './sentry-filter-events'; import extractEthjsErrorMessage from './extractEthjsErrorMessage'; @@ -28,75 +29,254 @@ export const ERROR_URL_ALLOWLIST = { // debugging, and they do not contain any identifiable information. export const SENTRY_BACKGROUND_STATE = { AccountTracker: { + accounts: false, currentBlockGasLimit: true, }, + AddressBookController: { + addressBook: false, + }, AlertController: { alertEnabledness: true, + unconnectedAccountAlertShownOrigins: false, + web3ShimUsageOrigins: false, + }, + AnnouncementController: { + announcements: false, }, AppMetadataController: { currentAppVersion: true, + currentMigrationVersion: true, previousAppVersion: true, previousMigrationVersion: true, - currentMigrationVersion: true, + }, + ApprovalController: { + approvalFlows: false, + pendingApprovals: false, + pendingApprovalCount: false, }, AppStateController: { + browserEnvironment: true, connectedStatusPopoverHasBeenShown: true, + currentPopupId: false, defaultHomeActiveTabName: true, + fullScreenGasPollTokens: true, + hadAdvancedGasFeesSetPriorToMigration92_3: true, + nftsDetectionNoticeDismissed: true, + nftsDropdownState: true, + notificationGasPollTokens: true, + outdatedBrowserWarningLastShown: true, + popupGasPollTokens: true, + qrHardware: true, + recoveryPhraseReminderHasBeenShown: true, + recoveryPhraseReminderLastShown: true, + serviceWorkerLastActiveTime: true, + showBetaHeader: true, + showProductTour: true, + showTestnetMessageInDropdown: true, + snapsInstallPrivacyWarningShown: true, + termsOfUseLastAgreed: true, + timeoutMinutes: true, + trezorModel: true, + usedNetworks: true, + }, + CachedBalancesController: { + cachedBalances: false, }, CurrencyController: { conversionDate: true, conversionRate: true, currentCurrency: true, nativeCurrency: true, + pendingCurrentCurrency: true, + pendingNativeCurrency: true, + usdConversionRate: true, }, DecryptMessageController: { + unapprovedDecryptMsgs: false, unapprovedDecryptMsgCount: true, }, - DesktopController: { - desktopEnabled: true, - }, EncryptionPublicKeyController: { + unapprovedEncryptionPublicKeyMsgs: false, unapprovedEncryptionPublicKeyMsgCount: true, }, + EnsController: { + ensResolutionsByAddress: false, + }, + GasFeeController: { + estimatedGasFeeTimeBounds: true, + gasEstimateType: true, + gasFeeEstimates: true, + }, IncomingTransactionsController: { + incomingTransactions: false, incomingTxLastFetchedBlockByChainId: true, }, KeyringController: { + encryptionKey: false, isUnlocked: true, + keyrings: false, + keyringTypes: false, }, MetaMetricsController: { + eventsBeforeMetricsOptIn: false, + fragments: false, metaMetricsId: true, participateInMetaMetrics: true, + previousUserTraits: false, + segmentApiCalls: false, + traits: false, }, NetworkController: { + networkConfigurations: false, + networkDetails: false, networkId: true, networkStatus: true, providerConfig: { + chainId: true, + id: true, nickname: true, + rpcPrefs: false, + rpcUrl: false, ticker: true, type: true, }, }, + NftController: { + allNftContracts: false, + allNfts: false, + ignoredNfts: false, + }, OnboardingController: { completedOnboarding: true, firstTimeFlowType: true, + onboardingTabs: false, seedPhraseBackedUp: true, }, + PermissionController: { + subjects: false, + }, + PermissionLogController: { + permissionActivityLog: false, + permissionHistory: false, + }, + PhishingController: {}, PreferencesController: { + advancedGasFee: true, currentLocale: true, + disabledRpcMethodPreferences: true, + dismissSeedBackUpReminder: true, featureFlags: true, forgottenPassword: true, - ipfsGateway: true, - preferences: true, + identities: false, + infuraBlocked: true, + ipfsGateway: false, + isLineaMainnetReleased: true, + knownMethodData: false, + ledgerTransportType: true, + lostIdentities: false, + openSeaEnabled: true, + preferences: { + autoLockTimeLimit: true, + hideZeroBalanceTokens: true, + showFiatInTestnets: true, + showTestNetworks: true, + useNativeCurrencyAsPrimaryCurrency: true, + }, + selectedAddress: false, + snapRegistryList: false, + theme: true, + transactionSecurityCheckEnabled: true, useBlockie: true, + useCurrencyRateCheck: true, + useMultiAccountBalanceChecker: true, + useNftDetection: true, useNonceField: true, usePhishDetect: true, + useTokenDetection: true, }, SignatureController: { unapprovedMsgCount: true, + unapprovedMsgs: false, unapprovedPersonalMsgCount: true, + unapprovedPersonalMsgs: false, + unapprovedTypedMessages: false, unapprovedTypedMessagesCount: true, }, + SmartTransactionsController: { + smartTransactionsState: { + fees: { + approvalTxFees: true, + tradeTxFees: true, + }, + liveness: true, + smartTransactions: false, + userOptIn: true, + }, + }, + SubjectMetadataController: { + subjectMetadata: false, + }, + SwapsController: { + swapsState: { + approveTxId: false, + customApproveTxData: false, + customGasPrice: true, + customMaxFeePerGas: true, + customMaxGas: true, + customMaxPriorityFeePerGas: true, + errorKey: true, + fetchParams: true, + quotes: false, + quotesLastFetched: true, + quotesPollingLimitEnabled: true, + routeState: true, + saveFetchedQuotes: true, + selectedAggId: true, + swapsFeatureFlags: true, + swapsFeatureIsLive: true, + swapsQuotePrefetchingRefreshTime: true, + swapsQuoteRefreshTime: true, + swapsStxBatchStatusRefreshTime: true, + swapsStxGetTransactionsRefreshTime: true, + swapsStxMaxFeeMultiplier: true, + swapsUserFeeLevel: true, + tokens: false, + topAggId: false, + tradeTxId: false, + }, + }, + TokenListController: { + preventPollingOnNetworkRestart: true, + tokenList: false, + tokensChainsCache: { + [AllProperties]: false, + }, + }, + TokenRatesController: { + contractExchangeRates: false, + }, + TokensController: { + allDetectedTokens: { + [AllProperties]: false, + }, + allIgnoredTokens: { + [AllProperties]: false, + }, + allTokens: { + [AllProperties]: false, + }, + detectedTokens: false, + ignoredTokens: false, + tokens: false, + }, + TransactionController: { + currentNetworkTxList: false, + lastFetchedBlockNumbers: false, + }, + TxController: { + currentNetworkTxList: false, + unapprovedTxs: false, + }, }; const flattenedBackgroundStateMask = Object.values( @@ -121,7 +301,9 @@ export const SENTRY_UI_STATE = { // These properties are in the `metamask` slice but not in the background state customNonceValue: true, isAccountMenuOpen: true, + isNetworkMenuOpen: true, nextNonce: true, + pendingTokens: false, welcomeScreenSeen: true, }, unconnectedAccount: true, diff --git a/shared/modules/object.utils.js b/shared/modules/object.utils.js index ce7eb1da1..218b5389f 100644 --- a/shared/modules/object.utils.js +++ b/shared/modules/object.utils.js @@ -1,25 +1,43 @@ /** - * Return a "masked" copy of the given object. + * This symbol matches all object properties when used in a mask + */ +export const AllProperties = Symbol('*'); + +/** + * Return a "masked" copy of the given object. The returned object includes + * only the properties present in the mask. * - * The returned object includes only the properties present in the mask. The - * mask is an object that mirrors the structure of the given object, except - * the only values are `true` or a sub-mask. `true` implies the property - * should be included, and a sub-mask implies the property should be further - * masked according to that sub-mask. + * The mask is an object that mirrors the structure of the given object, except + * the only values are `true`, `false, a sub-mask, or the 'AllProperties" + * symbol. `true` implies the property should be included, and `false` will + * exclude it. A sub-mask implies the property should be further masked + * according to that sub-mask. The "AllProperties" symbol is used for objects + * with dynamic keys, and applies a rule (either `true`, `false`, or a + * sub-mask`) to every property in that object. * - * If a property is not found in the last, its type is included instead. + * If a property is excluded, its type is included instead. * * @param {object} object - The object to mask * @param {Object} mask - The mask to apply to the object */ export function maskObject(object, mask) { + let maskAllProperties = false; + if (Object.keys(mask).includes(AllProperties)) { + if (Object.keys(mask).length > 1) { + throw new Error('AllProperties mask key does not support sibling keys'); + } + maskAllProperties = true; + } return Object.keys(object).reduce((state, key) => { - if (mask[key] === true) { + const maskKey = maskAllProperties ? mask[AllProperties] : mask[key]; + if (maskKey === true) { state[key] = object[key]; - } else if (mask[key]) { - state[key] = maskObject(object[key], mask[key]); - } else { + } else if (maskKey && typeof maskKey === 'object') { + state[key] = maskObject(object[key], maskKey); + } else if (maskKey === undefined || maskKey === false) { state[key] = typeof object[key]; + } else { + throw new Error(`Unsupported mask entry: ${maskKey}`); } return state; }, {}); diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 7d7e12424..9c4927a78 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -4,8 +4,24 @@ const { strict: assert } = require('assert'); const { get, has, set, unset } = require('lodash'); const { Browser } = require('selenium-webdriver'); const { format } = require('prettier'); -const { convertToHexValue, withFixtures } = require('../helpers'); +const { isObject } = require('@metamask/utils'); +const { SENTRY_UI_STATE } = require('../../../app/scripts/lib/setupSentry'); const FixtureBuilder = require('../fixture-builder'); +const { convertToHexValue, withFixtures } = require('../helpers'); + +/** + * Derive a UI state field from a background state field. + * + * @param {string} backgroundField - The path of a background field. + * @returns {string} The path for the corresponding UI field. + */ +function backgroundToUiField(backgroundField) { + // The controller name is lost in the UI due to state flattening + const [, ...rest] = backgroundField.split('.'); + const flattenedBackgroundField = rest.join('.'); + // Controller state is under the 'metamask' slice in the UI + return `metamask.${flattenedBackgroundField}`; +} const maskedBackgroundFields = [ 'CurrencyController.conversionDate', // This is a timestamp that changes each run @@ -13,14 +29,13 @@ const maskedBackgroundFields = [ // part of the release process 'AppMetadataController.currentAppVersion', 'AppMetadataController.currentMigrationVersion', + 'AppStateController.browserEnvironment.browser', + 'AppStateController.browserEnvironment.os', + 'AppStateController.outdatedBrowserWarningLastShown', + 'AppStateController.recoveryPhraseReminderLastShown', + 'AppStateController.termsOfUseLastAgreed', ]; -const maskedUiFields = [ - 'metamask.conversionDate', // This is a timestamp that changes each run - // App metadata is masked so that we don't have to update the snapshot as - // part of the release process - 'metamask.currentAppVersion', - 'metamask.currentMigrationVersion', -]; +const maskedUiFields = maskedBackgroundFields.map(backgroundToUiField); const removedBackgroundFields = [ // This property is timing-dependent @@ -30,13 +45,7 @@ const removedBackgroundFields = [ 'AppStateController.timeoutMinutes', ]; -const removedUiFields = [ - // This property is timing-dependent - 'metamask.currentBlockGasLimit', - // These properties are set to undefined, causing inconsistencies between Chrome and Firefox - 'metamask.currentPopupId', - 'metamask.timeoutMinutes', -]; +const removedUiFields = removedBackgroundFields.map(backgroundToUiField); /** * Transform background state to make it consistent between test runs. @@ -115,6 +124,38 @@ async function matchesSnapshot({ } } +/** + * Get an object consisting of all properties in the complete + * object that are missing from the given object. + * + * @param {object} complete - The complete object to compare to. + * @param {object} object - The object to test for missing properties. + */ +function getMissingProperties(complete, object) { + const missing = {}; + for (const [key, value] of Object.entries(complete)) { + if (key in object) { + if (isObject(value) && isObject(object[key])) { + const missingNestedProperties = getMissingProperties( + value, + object[key], + ); + if (Object.keys(missingNestedProperties).length > 0) { + missing[key] = missingNestedProperties; + } else { + // no missing nested properties + } + } else { + // Skip non-object values, they are considered as present + // even if they represent masked data structures + } + } else { + missing[key] = value; + } + } + return missing; +} + describe('Sentry errors', function () { const migrationError = process.env.SELENIUM_BROWSER === Browser.CHROME @@ -320,7 +361,10 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformBackgroundState(appState.persistedState), + data: { + ...appState.persistedState, + data: transformBackgroundState(appState.persistedState.data), + }, snapshot: 'errors-before-init-opt-in-background-state', }); }, @@ -468,7 +512,10 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformBackgroundState(appState.persistedState), + data: { + ...appState.persistedState, + data: transformBackgroundState(appState.persistedState.data), + }, snapshot: 'errors-before-init-opt-in-ui-state', }); }, @@ -736,4 +783,80 @@ describe('Sentry errors', function () { ); }); }); + + it('should have no policy gaps for UI controller state', async function () { + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.findElement('#password'); + + const fullUiState = await driver.executeScript(() => + window.stateHooks?.getCleanAppState?.(), + ); + + const missingState = getMissingProperties( + fullUiState.metamask, + SENTRY_UI_STATE.metamask, + ); + assert.deepEqual(missingState, {}); + }, + ); + }); + + it('should not have extra properties in UI state mask', async function () { + const expectedMissingState = { + currentPopupId: false, // Initialized as undefined + // Part of transaction controller store, but missing from the initial + // state + lastFetchedBlockNumbers: false, + preferences: { + autoLockTimeLimit: true, // Initialized as undefined + }, + smartTransactionsState: { + fees: { + approvalTxFees: true, // Initialized as undefined + tradeTxFees: true, // Initialized as undefined + }, + userOptIn: true, // Initialized as undefined + }, + swapsState: { + // This can get wiped out during initialization due to a bug in + // the "resetState" method + swapsFeatureFlags: true, + }, + // This can get erased due to a bug in the app state controller's + // preferences state change handler + timeoutMinutes: true, + }; + await withFixtures( + { + fixtures: new FixtureBuilder().build(), + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.findElement('#password'); + + const fullUiState = await driver.executeScript(() => + window.stateHooks?.getCleanAppState?.(), + ); + + const extraMaskProperties = getMissingProperties( + SENTRY_UI_STATE.metamask, + fullUiState.metamask, + ); + const unexpectedExtraMaskProperties = getMissingProperties( + extraMaskProperties, + expectedMissingState, + ); + assert.deepEqual(unexpectedExtraMaskProperties, {}); + }, + ); + }); }); diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index e8191f915..8edcbc646 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -1,12 +1,12 @@ { "AccountTracker": { "accounts": "object" }, - "AddressBookController": "object", + "AddressBookController": { "addressBook": "object" }, "AlertController": { "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, "unconnectedAccountAlertShownOrigins": "object", "web3ShimUsageOrigins": "object" }, - "AnnouncementController": "object", + "AnnouncementController": { "announcements": "object" }, "AppMetadataController": { "currentAppVersion": "string", "previousAppVersion": "", @@ -16,37 +16,41 @@ "AppStateController": { "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, - "browserEnvironment": "object", - "popupGasPollTokens": "object", - "notificationGasPollTokens": "object", - "fullScreenGasPollTokens": "object", - "recoveryPhraseReminderHasBeenShown": "boolean", + "browserEnvironment": { "os": "string", "browser": "string" }, + "popupGasPollTokens": [], + "notificationGasPollTokens": [], + "fullScreenGasPollTokens": [], + "recoveryPhraseReminderHasBeenShown": true, "recoveryPhraseReminderLastShown": "number", "outdatedBrowserWarningLastShown": "number", - "nftsDetectionNoticeDismissed": "boolean", - "showTestnetMessageInDropdown": "boolean", - "showBetaHeader": "boolean", - "showProductTour": "boolean", - "trezorModel": "object", - "nftsDropdownState": "object", + "nftsDetectionNoticeDismissed": false, + "showTestnetMessageInDropdown": true, + "showBetaHeader": false, + "showProductTour": true, + "trezorModel": null, + "hadAdvancedGasFeesSetPriorToMigration92_3": false, + "nftsDropdownState": {}, "termsOfUseLastAgreed": "number", - "qrHardware": "object", - "usedNetworks": "object", - "snapsInstallPrivacyWarningShown": "boolean", - "hadAdvancedGasFeesSetPriorToMigration92_3": "boolean", - "serviceWorkerLastActiveTime": "number" + "qrHardware": {}, + "usedNetworks": { "0x1": true, "0x5": true, "0x539": true }, + "snapsInstallPrivacyWarningShown": true, + "serviceWorkerLastActiveTime": 0 + }, + "ApprovalController": { + "pendingApprovals": "object", + "pendingApprovalCount": "number", + "approvalFlows": "object" }, - "ApprovalController": "object", "BackupController": "undefined", - "CachedBalancesController": "object", + "CachedBalancesController": { "cachedBalances": "object" }, "CurrencyController": { "conversionDate": "number", "conversionRate": 1700, "nativeCurrency": "ETH", "currentCurrency": "usd", - "pendingCurrentCurrency": "object", - "pendingNativeCurrency": "object", - "usdConversionRate": "number" + "pendingCurrentCurrency": null, + "pendingNativeCurrency": null, + "usdConversionRate": 1700 }, "DecryptMessageController": { "unapprovedDecryptMsgs": "object", @@ -56,8 +60,12 @@ "unapprovedEncryptionPublicKeyMsgs": "object", "unapprovedEncryptionPublicKeyMsgCount": 0 }, - "EnsController": "object", - "GasFeeController": "object", + "EnsController": { "ensResolutionsByAddress": "object" }, + "GasFeeController": { + "gasFeeEstimates": {}, + "estimatedGasFeeTimeBounds": {}, + "gasEstimateType": "none" + }, "IncomingTransactionsController": { "incomingTransactions": "object", "incomingTxLastFetchedBlockByChainId": { @@ -87,38 +95,45 @@ "networkId": "1337", "networkStatus": "available", "providerConfig": { - "chainId": "string", + "chainId": "0x539", "nickname": "Localhost 8545", "rpcPrefs": "object", "rpcUrl": "string", "ticker": "ETH", "type": "rpc", - "id": "string" + "id": "networkConfigurationId" }, "networkDetails": "object", "networkConfigurations": "object" }, - "NftController": "object", + "NftController": { + "allNftContracts": "object", + "allNfts": "object", + "ignoredNfts": "object" + }, "OnboardingController": { "seedPhraseBackedUp": true, "firstTimeFlowType": "import", "completedOnboarding": true, "onboardingTabs": "object" }, - "PermissionController": "object", - "PermissionLogController": "object", + "PermissionController": { "subjects": "object" }, + "PermissionLogController": { + "permissionHistory": "object", + "permissionActivityLog": "object" + }, "PreferencesController": { "useBlockie": false, "useNonceField": false, "usePhishDetect": true, - "dismissSeedBackUpReminder": "boolean", - "disabledRpcMethodPreferences": "object", - "useMultiAccountBalanceChecker": "boolean", - "useTokenDetection": "boolean", - "useNftDetection": "boolean", - "useCurrencyRateCheck": "boolean", - "openSeaEnabled": "boolean", - "advancedGasFee": "object", + "dismissSeedBackUpReminder": true, + "disabledRpcMethodPreferences": { "eth_sign": false }, + "useMultiAccountBalanceChecker": true, + "useTokenDetection": false, + "useNftDetection": false, + "useCurrencyRateCheck": true, + "openSeaEnabled": false, + "advancedGasFee": {}, "featureFlags": { "showIncomingTransactions": true }, "knownMethodData": "object", "currentLocale": "en", @@ -131,13 +146,13 @@ "showTestNetworks": false, "useNativeCurrencyAsPrimaryCurrency": true }, - "ipfsGateway": "dweb.link", - "infuraBlocked": "boolean", - "ledgerTransportType": "string", + "ipfsGateway": "string", + "infuraBlocked": false, + "ledgerTransportType": "webhid", "snapRegistryList": "object", - "transactionSecurityCheckEnabled": "boolean", - "theme": "string", - "isLineaMainnetReleased": "boolean", + "transactionSecurityCheckEnabled": false, + "theme": "light", + "isLineaMainnetReleased": true, "selectedAddress": "string" }, "SignatureController": { @@ -148,11 +163,58 @@ "unapprovedPersonalMsgCount": 0, "unapprovedTypedMessagesCount": 0 }, - "SmartTransactionsController": "object", - "SubjectMetadataController": "object", - "SwapsController": "object", - "TokenListController": "object", - "TokenRatesController": "object", - "TokensController": "object", - "TxController": "object" + "SmartTransactionsController": { + "smartTransactionsState": { + "fees": {}, + "liveness": true, + "smartTransactions": "object" + } + }, + "SubjectMetadataController": { "subjectMetadata": "object" }, + "SwapsController": { + "swapsState": { + "quotes": "object", + "quotesPollingLimitEnabled": false, + "fetchParams": null, + "tokens": "object", + "tradeTxId": "object", + "approveTxId": "object", + "quotesLastFetched": null, + "customMaxGas": "", + "customGasPrice": null, + "customMaxFeePerGas": null, + "customMaxPriorityFeePerGas": null, + "swapsUserFeeLevel": "", + "selectedAggId": null, + "customApproveTxData": "string", + "errorKey": "", + "topAggId": "object", + "routeState": "", + "swapsFeatureIsLive": true, + "saveFetchedQuotes": false, + "swapsQuoteRefreshTime": 60000, + "swapsQuotePrefetchingRefreshTime": 60000, + "swapsStxBatchStatusRefreshTime": 10000, + "swapsStxGetTransactionsRefreshTime": 10000, + "swapsStxMaxFeeMultiplier": 2 + } + }, + "TokenListController": { + "tokenList": "object", + "tokensChainsCache": {}, + "preventPollingOnNetworkRestart": true + }, + "TokenRatesController": { "contractExchangeRates": "object" }, + "TokensController": { + "tokens": "object", + "ignoredTokens": "object", + "detectedTokens": "object", + "allTokens": {}, + "allIgnoredTokens": {}, + "allDetectedTokens": {} + }, + "TxController": { + "unapprovedTxs": "object", + "currentNetworkTxList": "object" + } } diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 64a08f7b4..546004f9f 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -11,7 +11,7 @@ "isInitialized": true, "isUnlocked": false, "isAccountMenuOpen": false, - "isNetworkMenuOpen": "boolean", + "isNetworkMenuOpen": false, "identities": "object", "unapprovedTxs": "object", "networkConfigurations": "object", @@ -38,25 +38,25 @@ "nativeCurrency": "ETH", "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, - "browserEnvironment": "object", - "popupGasPollTokens": "object", - "notificationGasPollTokens": "object", - "fullScreenGasPollTokens": "object", - "recoveryPhraseReminderHasBeenShown": "boolean", + "browserEnvironment": { "os": "string", "browser": "string" }, + "popupGasPollTokens": [], + "notificationGasPollTokens": [], + "fullScreenGasPollTokens": [], + "recoveryPhraseReminderHasBeenShown": true, "recoveryPhraseReminderLastShown": "number", "outdatedBrowserWarningLastShown": "number", - "nftsDetectionNoticeDismissed": "boolean", - "showTestnetMessageInDropdown": "boolean", - "showBetaHeader": "boolean", - "showProductTour": "boolean", - "trezorModel": "object", - "nftsDropdownState": "object", + "nftsDetectionNoticeDismissed": false, + "showTestnetMessageInDropdown": true, + "showBetaHeader": false, + "showProductTour": true, + "trezorModel": null, + "hadAdvancedGasFeesSetPriorToMigration92_3": false, + "nftsDropdownState": {}, "termsOfUseLastAgreed": "number", - "qrHardware": "object", - "usedNetworks": "object", - "snapsInstallPrivacyWarningShown": "boolean", - "hadAdvancedGasFeesSetPriorToMigration92_3": "boolean", - "serviceWorkerLastActiveTime": "number", + "qrHardware": {}, + "usedNetworks": { "0x1": true, "0x5": true, "0x539": true }, + "snapsInstallPrivacyWarningShown": true, + "serviceWorkerLastActiveTime": 0, "currentAppVersion": "string", "previousAppVersion": "", "previousMigrationVersion": 0, @@ -64,13 +64,13 @@ "networkId": "1337", "networkStatus": "available", "providerConfig": { - "chainId": "string", + "chainId": "0x539", "nickname": "Localhost 8545", "rpcPrefs": "object", "rpcUrl": "string", "ticker": "ETH", "type": "rpc", - "id": "string" + "id": "networkConfigurationId" }, "networkDetails": "object", "cachedBalances": "object", @@ -79,23 +79,23 @@ "encryptionKey": "object", "useNonceField": false, "usePhishDetect": true, - "dismissSeedBackUpReminder": "boolean", - "disabledRpcMethodPreferences": "object", - "useMultiAccountBalanceChecker": "boolean", - "useTokenDetection": "boolean", - "useNftDetection": "boolean", - "useCurrencyRateCheck": "boolean", - "openSeaEnabled": "boolean", - "advancedGasFee": "object", + "dismissSeedBackUpReminder": true, + "disabledRpcMethodPreferences": { "eth_sign": false }, + "useMultiAccountBalanceChecker": true, + "useTokenDetection": false, + "useNftDetection": false, + "useCurrencyRateCheck": true, + "openSeaEnabled": false, + "advancedGasFee": {}, "lostIdentities": "object", "forgottenPassword": false, - "ipfsGateway": "dweb.link", - "infuraBlocked": "boolean", - "ledgerTransportType": "string", + "ipfsGateway": "string", + "infuraBlocked": false, + "ledgerTransportType": "webhid", "snapRegistryList": "object", - "transactionSecurityCheckEnabled": "boolean", - "theme": "string", - "isLineaMainnetReleased": "boolean", + "transactionSecurityCheckEnabled": false, + "theme": "light", + "isLineaMainnetReleased": true, "selectedAddress": "string", "metaMetricsId": "fake-metrics-id", "eventsBeforeMetricsOptIn": "object", @@ -105,9 +105,9 @@ "previousUserTraits": "object", "conversionDate": "number", "currentCurrency": "usd", - "pendingCurrentCurrency": "object", - "pendingNativeCurrency": "object", - "usdConversionRate": "number", + "pendingCurrentCurrency": null, + "pendingNativeCurrency": null, + "usdConversionRate": 1300, "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, "unconnectedAccountAlertShownOrigins": "object", "web3ShimUsageOrigins": "object", @@ -126,19 +126,23 @@ "permissionActivityLog": "object", "subjectMetadata": "object", "announcements": "object", - "gasFeeEstimates": "object", - "estimatedGasFeeTimeBounds": "object", - "gasEstimateType": "string", + "gasFeeEstimates": {}, + "estimatedGasFeeTimeBounds": {}, + "gasEstimateType": "none", "tokenList": "object", - "tokensChainsCache": "object", - "preventPollingOnNetworkRestart": "boolean", + "tokensChainsCache": {}, + "preventPollingOnNetworkRestart": true, "tokens": "object", "ignoredTokens": "object", "detectedTokens": "object", - "allTokens": "object", - "allIgnoredTokens": "object", - "allDetectedTokens": "object", - "smartTransactionsState": "object", + "allTokens": {}, + "allIgnoredTokens": {}, + "allDetectedTokens": {}, + "smartTransactionsState": { + "fees": {}, + "liveness": true, + "smartTransactions": "object" + }, "allNftContracts": "object", "allNfts": "object", "ignoredNfts": "object", @@ -154,7 +158,32 @@ "unapprovedMsgCount": 0, "unapprovedPersonalMsgCount": 0, "unapprovedTypedMessagesCount": 0, - "swapsState": "object", + "swapsState": { + "quotes": "object", + "quotesPollingLimitEnabled": false, + "fetchParams": null, + "tokens": "object", + "tradeTxId": "object", + "approveTxId": "object", + "quotesLastFetched": null, + "customMaxGas": "", + "customGasPrice": null, + "customMaxFeePerGas": null, + "customMaxPriorityFeePerGas": null, + "swapsUserFeeLevel": "", + "selectedAggId": null, + "customApproveTxData": "string", + "errorKey": "", + "topAggId": "object", + "routeState": "", + "swapsFeatureIsLive": true, + "saveFetchedQuotes": false, + "swapsQuoteRefreshTime": 60000, + "swapsQuotePrefetchingRefreshTime": 60000, + "swapsStxBatchStatusRefreshTime": 10000, + "swapsStxGetTransactionsRefreshTime": 10000, + "swapsStxMaxFeeMultiplier": 2 + }, "ensResolutionsByAddress": "object", "pendingApprovals": "object", "pendingApprovalCount": "number", diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json index f3adc0cd9..394e93c79 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-background-state.json @@ -5,33 +5,42 @@ "unconnectedAccountAlertShownOrigins": "object", "web3ShimUsageOrigins": "object" }, - "AnnouncementController": "object", + "AnnouncementController": { "announcements": "object" }, "AppStateController": { - "browserEnvironment": "object", - "nftsDropdownState": "object", + "browserEnvironment": {}, + "nftsDropdownState": {}, "connectedStatusPopoverHasBeenShown": true, "termsOfUseLastAgreed": "number", "defaultHomeActiveTabName": null, - "fullScreenGasPollTokens": "object", - "notificationGasPollTokens": "object", - "popupGasPollTokens": "object", - "qrHardware": "object", - "recoveryPhraseReminderHasBeenShown": "boolean", + "fullScreenGasPollTokens": [], + "notificationGasPollTokens": [], + "popupGasPollTokens": [], + "qrHardware": {}, + "recoveryPhraseReminderHasBeenShown": true, "recoveryPhraseReminderLastShown": "number", - "showTestnetMessageInDropdown": "boolean", - "trezorModel": "object", - "usedNetworks": "object", - "snapsInstallPrivacyWarningShown": "boolean" + "showTestnetMessageInDropdown": true, + "trezorModel": null, + "usedNetworks": { + "0x1": true, + "0xe708": true, + "0x5": true, + "0x539": true + }, + "snapsInstallPrivacyWarningShown": true }, - "CachedBalancesController": "object", + "CachedBalancesController": { "cachedBalances": "object" }, "CurrencyController": { - "conversionDate": 1665507600, + "conversionDate": "number", "conversionRate": 1300, "currentCurrency": "usd", "nativeCurrency": "ETH", - "usdConversionRate": "number" + "usdConversionRate": 1300 + }, + "GasFeeController": { + "estimatedGasFeeTimeBounds": {}, + "gasEstimateType": "none", + "gasFeeEstimates": {} }, - "GasFeeController": "object", "IncomingTransactionsController": { "incomingTransactions": "object", "incomingTxLastFetchedBlockByChainId": { @@ -54,13 +63,13 @@ "networkId": "1337", "networkStatus": "available", "providerConfig": { - "chainId": "string", + "chainId": "0x539", "nickname": "Localhost 8545", "rpcPrefs": "object", "rpcUrl": "string", "ticker": "ETH", "type": "rpc", - "id": "string" + "id": "networkConfigurationId" }, "networkConfigurations": "object" }, @@ -70,20 +79,20 @@ "onboardingTabs": "object", "seedPhraseBackedUp": true }, - "PermissionController": "object", + "PermissionController": { "subjects": "object" }, "PreferencesController": { - "advancedGasFee": "object", + "advancedGasFee": null, "currentLocale": "en", - "dismissSeedBackUpReminder": "boolean", + "dismissSeedBackUpReminder": true, "featureFlags": { "showIncomingTransactions": true }, "forgottenPassword": false, "identities": "object", - "infuraBlocked": "boolean", - "ipfsGateway": "dweb.link", + "infuraBlocked": false, + "ipfsGateway": "string", "knownMethodData": "object", - "ledgerTransportType": "string", + "ledgerTransportType": "webhid", "lostIdentities": "object", - "openSeaEnabled": "boolean", + "openSeaEnabled": false, "preferences": { "hideZeroBalanceTokens": false, "showFiatInTestnets": false, @@ -91,19 +100,32 @@ "useNativeCurrencyAsPrimaryCurrency": true }, "selectedAddress": "string", - "theme": "string", + "theme": "light", "useBlockie": false, - "useNftDetection": "boolean", + "useNftDetection": false, "useNonceField": false, "usePhishDetect": true, - "useTokenDetection": "boolean", - "useCurrencyRateCheck": "boolean", - "useMultiAccountBalanceChecker": "boolean" + "useTokenDetection": false, + "useCurrencyRateCheck": true, + "useMultiAccountBalanceChecker": true }, - "SmartTransactionsController": "object", - "SubjectMetadataController": "object", - "TokensController": "object", - "TransactionController": "object", + "SmartTransactionsController": { + "smartTransactionsState": { + "fees": {}, + "liveness": true, + "smartTransactions": "object" + } + }, + "SubjectMetadataController": { "subjectMetadata": "object" }, + "TokensController": { + "allDetectedTokens": {}, + "allIgnoredTokens": {}, + "allTokens": {}, + "detectedTokens": "object", + "ignoredTokens": "object", + "tokens": "object" + }, + "TransactionController": { "transactions": "object" }, "config": "object", "firstTimeInfo": "object" } diff --git a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json index 704026b0b..474b0205b 100644 --- a/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -5,33 +5,42 @@ "unconnectedAccountAlertShownOrigins": "object", "web3ShimUsageOrigins": "object" }, - "AnnouncementController": "object", + "AnnouncementController": { "announcements": "object" }, "AppStateController": { - "browserEnvironment": "object", - "nftsDropdownState": "object", + "browserEnvironment": {}, + "nftsDropdownState": {}, "connectedStatusPopoverHasBeenShown": true, "termsOfUseLastAgreed": "number", "defaultHomeActiveTabName": null, - "fullScreenGasPollTokens": "object", - "notificationGasPollTokens": "object", - "popupGasPollTokens": "object", - "qrHardware": "object", - "recoveryPhraseReminderHasBeenShown": "boolean", + "fullScreenGasPollTokens": [], + "notificationGasPollTokens": [], + "popupGasPollTokens": [], + "qrHardware": {}, + "recoveryPhraseReminderHasBeenShown": true, "recoveryPhraseReminderLastShown": "number", - "showTestnetMessageInDropdown": "boolean", - "trezorModel": "object", - "usedNetworks": "object", - "snapsInstallPrivacyWarningShown": "boolean" + "showTestnetMessageInDropdown": true, + "trezorModel": null, + "usedNetworks": { + "0x1": true, + "0xe708": true, + "0x5": true, + "0x539": true + }, + "snapsInstallPrivacyWarningShown": true }, - "CachedBalancesController": "object", + "CachedBalancesController": { "cachedBalances": "object" }, "CurrencyController": { - "conversionDate": 1665507600, + "conversionDate": "number", "conversionRate": 1300, "currentCurrency": "usd", "nativeCurrency": "ETH", - "usdConversionRate": "number" + "usdConversionRate": 1300 + }, + "GasFeeController": { + "estimatedGasFeeTimeBounds": {}, + "gasEstimateType": "none", + "gasFeeEstimates": {} }, - "GasFeeController": "object", "IncomingTransactionsController": { "incomingTransactions": "object", "incomingTxLastFetchedBlockByChainId": { @@ -54,13 +63,13 @@ "networkId": "1337", "networkStatus": "available", "providerConfig": { - "chainId": "string", + "chainId": "0x539", "nickname": "Localhost 8545", "rpcPrefs": "object", "rpcUrl": "string", "ticker": "ETH", "type": "rpc", - "id": "string" + "id": "networkConfigurationId" }, "networkConfigurations": "object" }, @@ -70,20 +79,20 @@ "onboardingTabs": "object", "seedPhraseBackedUp": true }, - "PermissionController": "object", + "PermissionController": { "subjects": "object" }, "PreferencesController": { - "advancedGasFee": "object", + "advancedGasFee": null, "currentLocale": "en", - "dismissSeedBackUpReminder": "boolean", + "dismissSeedBackUpReminder": true, "featureFlags": { "showIncomingTransactions": true }, "forgottenPassword": false, "identities": "object", - "infuraBlocked": "boolean", - "ipfsGateway": "dweb.link", + "infuraBlocked": false, + "ipfsGateway": "string", "knownMethodData": "object", - "ledgerTransportType": "string", + "ledgerTransportType": "webhid", "lostIdentities": "object", - "openSeaEnabled": "boolean", + "openSeaEnabled": false, "preferences": { "hideZeroBalanceTokens": false, "showFiatInTestnets": false, @@ -91,19 +100,32 @@ "useNativeCurrencyAsPrimaryCurrency": true }, "selectedAddress": "string", - "theme": "string", + "theme": "light", "useBlockie": false, - "useNftDetection": "boolean", + "useNftDetection": false, "useNonceField": false, "usePhishDetect": true, - "useTokenDetection": "boolean", - "useCurrencyRateCheck": "boolean", - "useMultiAccountBalanceChecker": "boolean" + "useTokenDetection": false, + "useCurrencyRateCheck": true, + "useMultiAccountBalanceChecker": true }, - "SmartTransactionsController": "object", - "SubjectMetadataController": "object", - "TokensController": "object", - "TransactionController": "object", + "SmartTransactionsController": { + "smartTransactionsState": { + "fees": {}, + "liveness": true, + "smartTransactions": "object" + } + }, + "SubjectMetadataController": { "subjectMetadata": "object" }, + "TokensController": { + "allDetectedTokens": {}, + "allIgnoredTokens": {}, + "allTokens": {}, + "detectedTokens": "object", + "ignoredTokens": "object", + "tokens": "object" + }, + "TransactionController": { "transactions": "object" }, "config": "object", "firstTimeInfo": "object" }, From 6c644c5793e72011525904ceeffe51959f8d1c45 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 30 Aug 2023 16:20:55 -0230 Subject: [PATCH 16/16] Update changelog to remove #20168 --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b769c6f7..3e2752c27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [10.35.1] ### Changed -- Allow quick Add Account name based on default name ([#20168](https://github.com/MetaMask/metamask-extension/pull/20168)) - Store default gas settings by network ([#20576](https://github.com/MetaMask/metamask-extension/pull/20576), [#20632](https://github.com/MetaMask/metamask-extension/pull/20632)) - Add more diagnostic information upon failure ([#20595](https://github.com/MetaMask/metamask-extension/pull/20595))