diff --git a/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch b/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch new file mode 100644 index 000000000000..02e6d3f694e5 --- /dev/null +++ b/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch @@ -0,0 +1,61 @@ +diff --git a/dist/multicall.cjs b/dist/multicall.cjs +index bf9aa5e86573fc1651f421cc0b64f5af121c3ab2..43a0531ed86cd3ee1774dcda3f990dd40f7f52de 100644 +--- a/dist/multicall.cjs ++++ b/dist/multicall.cjs +@@ -342,9 +342,22 @@ const multicallOrFallback = async (calls, chainId, provider, maxCallsPerMultical + return []; + } + const multicallAddress = MULTICALL_CONTRACT_BY_CHAINID[chainId]; +- return await (multicallAddress +- ? multicall(calls, multicallAddress, provider, maxCallsPerMulticall) +- : fallback(calls, maxCallsParallel)); ++ if (multicallAddress) { ++ try { ++ return await multicall(calls, multicallAddress, provider, maxCallsPerMulticall); ++ } ++ catch (error) { ++ // Fallback only on revert ++ // https://docs.ethers.org/v5/troubleshooting/errors/#help-CALL_EXCEPTION ++ if (!error || ++ typeof error !== 'object' || ++ !('code' in error) || ++ error.code !== 'CALL_EXCEPTION') { ++ throw error; ++ } ++ } ++ } ++ return await fallback(calls, maxCallsParallel); + }; + exports.multicallOrFallback = multicallOrFallback; + //# sourceMappingURL=multicall.cjs.map +\ No newline at end of file +diff --git a/dist/multicall.mjs b/dist/multicall.mjs +index 8fbe0112303d5df1d868e0357a9d31e43a3b6cf9..860dfdbddd813659cb2be5f7faed5d4016db5966 100644 +--- a/dist/multicall.mjs ++++ b/dist/multicall.mjs +@@ -339,8 +339,21 @@ export const multicallOrFallback = async (calls, chainId, provider, maxCallsPerM + return []; + } + const multicallAddress = MULTICALL_CONTRACT_BY_CHAINID[chainId]; +- return await (multicallAddress +- ? multicall(calls, multicallAddress, provider, maxCallsPerMulticall) +- : fallback(calls, maxCallsParallel)); ++ if (multicallAddress) { ++ try { ++ return await multicall(calls, multicallAddress, provider, maxCallsPerMulticall); ++ } ++ catch (error) { ++ // Fallback only on revert ++ // https://docs.ethers.org/v5/troubleshooting/errors/#help-CALL_EXCEPTION ++ if (!error || ++ typeof error !== 'object' || ++ !('code' in error) || ++ error.code !== 'CALL_EXCEPTION') { ++ throw error; ++ } ++ } ++ } ++ return await fallback(calls, maxCallsParallel); + }; + //# sourceMappingURL=multicall.mjs.map +\ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index eda15db00496..869e3a70a93a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [12.9.3] +### Fixed +- Fix some cases where users were incorrectly seeing 0 token balances ([#29361](https://github.com/MetaMask/metamask-extension/pull/29361)) +- Ensure users that opt out of smart transaction decoding don't send network requests to related APIs ([#29341](https://github.com/ +MetaMask/metamask-extension/pull/29341)) + ## [12.9.2] ### Changed - Display the "Amount" row within the advanced view of contract interaction confirmations, and whenever the amount being sent differs from the "You Send" row of the transaction simulation information by more than 5% ([#29131](https://github.com/MetaMask/metamask-extension/pull/29131)) @@ -5487,7 +5493,8 @@ Update styles and spacing on the critical error page ([#20350](https://github.c - Added the ability to restore accounts from seed words. -[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v12.9.2...HEAD +[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v12.9.3...HEAD +[12.9.3]: https://github.com/MetaMask/metamask-extension/compare/v12.9.2...v12.9.3 [12.9.2]: https://github.com/MetaMask/metamask-extension/compare/v12.9.1...v12.9.2 [12.9.1]: https://github.com/MetaMask/metamask-extension/compare/v12.9.0...v12.9.1 [12.9.0]: https://github.com/MetaMask/metamask-extension/compare/v12.8.1...v12.9.0 diff --git a/app/scripts/controllers/permissions/background-api.js b/app/scripts/controllers/permissions/background-api.js index b778ff42385d..8a0942667f17 100644 --- a/app/scripts/controllers/permissions/background-api.js +++ b/app/scripts/controllers/permissions/background-api.js @@ -1,4 +1,4 @@ -import nanoid from 'nanoid'; +import { nanoid } from 'nanoid'; import { CaveatTypes, RestrictedMethods, diff --git a/app/scripts/lib/createMainFrameOriginMiddleware.ts b/app/scripts/lib/createMainFrameOriginMiddleware.ts new file mode 100644 index 000000000000..bcbc2cb7d6fd --- /dev/null +++ b/app/scripts/lib/createMainFrameOriginMiddleware.ts @@ -0,0 +1,24 @@ +// Request and responses are currently untyped. +/* eslint-disable @typescript-eslint/no-explicit-any */ + +/** + * Returns a middleware that appends the mainFrameOrigin to request + * + * @param {{ mainFrameOrigin: string }} opts - The middleware options + * @returns {Function} + */ + +export default function createMainFrameOriginMiddleware({ + mainFrameOrigin, +}: { + mainFrameOrigin: string; +}) { + return function mainFrameOriginMiddleware( + req: any, + _res: any, + next: () => void, + ) { + req.mainFrameOrigin = mainFrameOrigin; + next(); + }; +} diff --git a/app/scripts/lib/ppom/ppom-util.test.ts b/app/scripts/lib/ppom/ppom-util.test.ts index 8acb6dd9788c..d3ab62b09d88 100644 --- a/app/scripts/lib/ppom/ppom-util.test.ts +++ b/app/scripts/lib/ppom/ppom-util.test.ts @@ -10,7 +10,7 @@ import { SignatureController, SignatureRequest, } from '@metamask/signature-controller'; -import { Hex } from '@metamask/utils'; +import { Hex, JsonRpcRequest } from '@metamask/utils'; import { BlockaidReason, BlockaidResultType, @@ -22,6 +22,8 @@ import { AppStateController } from '../../controllers/app-state-controller'; import { generateSecurityAlertId, isChainSupported, + METHOD_SIGN_TYPED_DATA_V3, + METHOD_SIGN_TYPED_DATA_V4, updateSecurityAlertResponse, validateRequestWithPPOM, } from './ppom-util'; @@ -57,6 +59,10 @@ const TRANSACTION_PARAMS_MOCK_1: TransactionParams = { value: '0x123', }; +const SIGN_TYPED_DATA_PARAMS_MOCK_1 = '0x123'; +const SIGN_TYPED_DATA_PARAMS_MOCK_2 = + '{"primaryType":"Permit","domain":{},"types":{}}'; + const TRANSACTION_PARAMS_MOCK_2: TransactionParams = { ...TRANSACTION_PARAMS_MOCK_1, to: '0x456', @@ -259,6 +265,48 @@ describe('PPOM Utils', () => { ); }); + // @ts-expect-error This is missing from the Mocha type definitions + it.each([METHOD_SIGN_TYPED_DATA_V3, METHOD_SIGN_TYPED_DATA_V4])( + 'sanitizes request params if method is %s', + async (method: string) => { + const ppom = createPPOMMock(); + const ppomController = createPPOMControllerMock(); + + ppomController.usePPOM.mockImplementation( + (callback) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any + callback(ppom as any) as any, + ); + + const firstTwoParams = [ + SIGN_TYPED_DATA_PARAMS_MOCK_1, + SIGN_TYPED_DATA_PARAMS_MOCK_2, + ]; + + const unwantedParams = [{}, undefined, 1, null]; + + const params = [...firstTwoParams, ...unwantedParams]; + + const request = { + ...REQUEST_MOCK, + method, + params, + } as unknown as JsonRpcRequest; + + await validateRequestWithPPOM({ + ...validateRequestWithPPOMOptionsBase, + ppomController, + request, + }); + + expect(ppom.validateJsonRpc).toHaveBeenCalledTimes(1); + expect(ppom.validateJsonRpc).toHaveBeenCalledWith({ + ...request, + params: firstTwoParams, + }); + }, + ); + it('updates response indicating chain is not supported', async () => { const ppomController = {} as PPOMController; const CHAIN_ID_UNSUPPORTED_MOCK = '0x2'; diff --git a/app/scripts/lib/ppom/ppom-util.ts b/app/scripts/lib/ppom/ppom-util.ts index d27ec6c8e505..6572aa3c3839 100644 --- a/app/scripts/lib/ppom/ppom-util.ts +++ b/app/scripts/lib/ppom/ppom-util.ts @@ -29,6 +29,8 @@ import { const { sentry } = global; const METHOD_SEND_TRANSACTION = 'eth_sendTransaction'; +export const METHOD_SIGN_TYPED_DATA_V3 = 'eth_signTypedData_v3'; +export const METHOD_SIGN_TYPED_DATA_V4 = 'eth_signTypedData_v4'; const SECURITY_ALERT_RESPONSE_ERROR = { result_type: BlockaidResultType.Errored, @@ -169,7 +171,7 @@ function normalizePPOMRequest( request, ) ) { - return request; + return sanitizeRequest(request); } const transactionParams = request.params[0]; @@ -181,6 +183,22 @@ function normalizePPOMRequest( }; } +function sanitizeRequest(request: JsonRpcRequest): JsonRpcRequest { + // This is a temporary fix to prevent a PPOM bypass + if ( + request.method === METHOD_SIGN_TYPED_DATA_V4 || + request.method === METHOD_SIGN_TYPED_DATA_V3 + ) { + if (Array.isArray(request.params)) { + return { + ...request, + params: request.params.slice(0, 2), + }; + } + } + return request; +} + function getErrorMessage(error: unknown) { if (error instanceof Error) { return `${error.name}: ${error.message}`; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index d60d937e1c3c..3d73379dfc93 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -47,7 +47,7 @@ import { rawChainData } from 'eth-chainlist'; import { MetaMaskKeyring as QRHardwareKeyring } from '@keystonehq/metamask-airgapped-keyring'; import EthQuery from '@metamask/eth-query'; import EthJSQuery from '@metamask/ethjs-query'; -import nanoid from 'nanoid'; +import { nanoid } from 'nanoid'; import { captureException } from '@sentry/browser'; import { AddressBookController } from '@metamask/address-book-controller'; import { @@ -296,6 +296,7 @@ import { createUnsupportedMethodMiddleware, } from './lib/rpc-method-middleware'; import createOriginMiddleware from './lib/createOriginMiddleware'; +import createMainFrameOriginMiddleware from './lib/createMainFrameOriginMiddleware'; import createTabIdMiddleware from './lib/createTabIdMiddleware'; import { NetworkOrderController } from './controllers/network-order'; import { AccountOrderController } from './controllers/account-order'; @@ -5669,11 +5670,18 @@ export default class MetamaskController extends EventEmitter { tabId = sender.tab.id; } + let mainFrameOrigin = origin; + if (sender.tab && sender.tab.url) { + // If sender origin is an iframe, then get the top-level frame's origin + mainFrameOrigin = new URL(sender.tab.url).origin; + } + const engine = this.setupProviderEngineEip1193({ origin, sender, subjectType, tabId, + mainFrameOrigin, }); const dupeReqFilterStream = createDupeReqFilterStream(); @@ -5794,13 +5802,25 @@ export default class MetamaskController extends EventEmitter { * @param {MessageSender | SnapSender} options.sender - The sender object. * @param {string} options.subjectType - The type of the sender subject. * @param {tabId} [options.tabId] - The tab ID of the sender - if the sender is within a tab + * @param {mainFrameOrigin} [options.mainFrameOrigin] - The origin of the main frame if the sender is an iframe */ - setupProviderEngineEip1193({ origin, subjectType, sender, tabId }) { + setupProviderEngineEip1193({ + origin, + subjectType, + sender, + tabId, + mainFrameOrigin, + }) { const engine = new JsonRpcEngine(); // Append origin to each request engine.push(createOriginMiddleware({ origin })); + // Append mainFrameOrigin to each request if present + if (mainFrameOrigin) { + engine.push(createMainFrameOriginMiddleware({ mainFrameOrigin })); + } + // Append selectedNetworkClientId to each request engine.push(createSelectedNetworkMiddleware(this.controllerMessenger)); diff --git a/app/scripts/migrations/133.2.test.ts b/app/scripts/migrations/133.2.test.ts new file mode 100644 index 000000000000..18251d8f4b2b --- /dev/null +++ b/app/scripts/migrations/133.2.test.ts @@ -0,0 +1,185 @@ +import { migrate, version } from './133.2'; + +const oldVersion = 133.1; + +describe(`migration #${version}`, () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ version }); + }); + + it('does nothing if theres no tokens controller state defined', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: {}, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if theres empty tokens controller state', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: {}, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if theres empty tokens controller state for allTokens', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: {}, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('does nothing if theres empty tokens controller state for mainnet', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x1': {}, + }, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('Does nothing if theres no tokens with empty address', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x1': { + '0x123': [ + { address: '0x1', symbol: 'TOKEN1', decimals: 18 }, + { address: '0x2', symbol: 'TOKEN2', decimals: 18 }, + ], + '0x123456': [ + { address: '0x3', symbol: 'TOKEN3', decimals: 18 }, + { address: '0x4', symbol: 'TOKEN4', decimals: 18 }, + ], + }, + }, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); + + it('Removes tokens with empty address', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x1': { + '0x123': [ + { + address: '0x0000000000000000000000000000000000000000', + symbol: 'eth', + decimals: 18, + }, + { address: '0x2', symbol: 'TOKEN2', decimals: 18 }, + ], + }, + }, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + TokensController: { + allTokens: { + '0x1': { + '0x123': [{ address: '0x2', symbol: 'TOKEN2', decimals: 18 }], + }, + }, + }, + }); + }); + + it('Removes tokens with empty address across multiple accounts', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x1': { + '0x123': [ + { + address: '0x0000000000000000000000000000000000000000', + symbol: 'eth', + decimals: 18, + }, + { address: '0x2', symbol: 'TOKEN2', decimals: 18 }, + ], + '0x456': [ + { + address: '0x0000000000000000000000000000000000000000', + symbol: 'eth', + decimals: 18, + }, + { address: '0x3', symbol: 'TOKEN3', decimals: 18 }, + ], + '0x789': [{ address: '0x4', symbol: 'TOKEN4', decimals: 18 }], + }, + }, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + TokensController: { + allTokens: { + '0x1': { + '0x123': [{ address: '0x2', symbol: 'TOKEN2', decimals: 18 }], + '0x456': [{ address: '0x3', symbol: 'TOKEN3', decimals: 18 }], + '0x789': [{ address: '0x4', symbol: 'TOKEN4', decimals: 18 }], + }, + }, + }, + }); + }); + + it('Does not change state on chains other than mainnet', async () => { + const oldStorage = { + meta: { version: oldVersion }, + data: { + TokensController: { + allTokens: { + '0x999': { + '0x123': [ + { + address: '0x0000000000000000000000000000000000000000', + symbol: 'eth', + decimals: 18, + }, + { address: '0x2', symbol: 'TOKEN2', decimals: 18 }, + ], + }, + }, + }, + }, + }; + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldStorage.data); + }); +}); diff --git a/app/scripts/migrations/133.2.ts b/app/scripts/migrations/133.2.ts new file mode 100644 index 000000000000..6ad8ff888cfd --- /dev/null +++ b/app/scripts/migrations/133.2.ts @@ -0,0 +1,53 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 133.2; + +/** + * This migration removes tokens on mainnet with the + * zero address, since this is not a valid erc20 token. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly + * what we persist to disk. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record): void { + if ( + !hasProperty(state, 'TokensController') || + !isObject(state.TokensController) || + !isObject(state.TokensController.allTokens) + ) { + return; + } + + const chainIds = ['0x1']; + + for (const chainId of chainIds) { + const allTokensOnChain = state.TokensController.allTokens[chainId]; + + if (isObject(allTokensOnChain)) { + for (const [account, tokens] of Object.entries(allTokensOnChain)) { + if (Array.isArray(tokens)) { + allTokensOnChain[account] = tokens.filter( + (token) => + token?.address !== '0x0000000000000000000000000000000000000000', + ); + } + } + } + } +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index fbee63b7f7f2..3783cfa0b8bd 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -156,6 +156,7 @@ const migrations = [ require('./132'), require('./133'), require('./133.1'), + require('./133.2'), ]; export default migrations; diff --git a/attribution.txt b/attribution.txt index aaeef31a2fbe..30695b63c042 100644 --- a/attribution.txt +++ b/attribution.txt @@ -21249,33 +21249,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ****************************** nanoid -2.1.11 -The MIT License (MIT) - -Copyright 2017 Andrey Sitnik - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -****************************** - -nanoid -3.3.7 +3.3.8 The MIT License (MIT) Copyright 2017 Andrey Sitnik diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index b3b118eb6bb8..ddd7640564ab 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -629,9 +629,9 @@ "console.info": true }, "packages": { - "@metamask/approval-controller>nanoid": true, "@metamask/base-controller": true, - "@metamask/rpc-errors": true + "@metamask/rpc-errors": true, + "nanoid": true } }, "@metamask/approval-controller>nanoid": { @@ -2044,11 +2044,11 @@ "@metamask/base-controller": true, "@metamask/controller-utils": true, "@metamask/json-rpc-engine": true, - "@metamask/permission-controller>nanoid": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "deep-freeze-strict": true, - "immer": true + "immer": true, + "nanoid": true } }, "@metamask/permission-controller>nanoid": { @@ -2506,7 +2506,6 @@ "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>get-npm-tarball-url": true, - "@metamask/snaps-controllers>nanoid": true, "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, "@metamask/snaps-rpc-methods": true, @@ -2517,6 +2516,7 @@ "browserify>browserify-zlib": true, "eslint>fast-deep-equal": true, "immer": true, + "nanoid": true, "readable-stream": true, "semver": true } @@ -4601,9 +4601,7 @@ }, "nanoid": { "globals": { - "crypto": true, - "msCrypto": true, - "navigator": true + "crypto.getRandomValues": true } }, "nock>debug": { diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index b3b118eb6bb8..ddd7640564ab 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -629,9 +629,9 @@ "console.info": true }, "packages": { - "@metamask/approval-controller>nanoid": true, "@metamask/base-controller": true, - "@metamask/rpc-errors": true + "@metamask/rpc-errors": true, + "nanoid": true } }, "@metamask/approval-controller>nanoid": { @@ -2044,11 +2044,11 @@ "@metamask/base-controller": true, "@metamask/controller-utils": true, "@metamask/json-rpc-engine": true, - "@metamask/permission-controller>nanoid": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "deep-freeze-strict": true, - "immer": true + "immer": true, + "nanoid": true } }, "@metamask/permission-controller>nanoid": { @@ -2506,7 +2506,6 @@ "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>get-npm-tarball-url": true, - "@metamask/snaps-controllers>nanoid": true, "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, "@metamask/snaps-rpc-methods": true, @@ -2517,6 +2516,7 @@ "browserify>browserify-zlib": true, "eslint>fast-deep-equal": true, "immer": true, + "nanoid": true, "readable-stream": true, "semver": true } @@ -4601,9 +4601,7 @@ }, "nanoid": { "globals": { - "crypto": true, - "msCrypto": true, - "navigator": true + "crypto.getRandomValues": true } }, "nock>debug": { diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index b3b118eb6bb8..ddd7640564ab 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -629,9 +629,9 @@ "console.info": true }, "packages": { - "@metamask/approval-controller>nanoid": true, "@metamask/base-controller": true, - "@metamask/rpc-errors": true + "@metamask/rpc-errors": true, + "nanoid": true } }, "@metamask/approval-controller>nanoid": { @@ -2044,11 +2044,11 @@ "@metamask/base-controller": true, "@metamask/controller-utils": true, "@metamask/json-rpc-engine": true, - "@metamask/permission-controller>nanoid": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "deep-freeze-strict": true, - "immer": true + "immer": true, + "nanoid": true } }, "@metamask/permission-controller>nanoid": { @@ -2506,7 +2506,6 @@ "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>get-npm-tarball-url": true, - "@metamask/snaps-controllers>nanoid": true, "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, "@metamask/snaps-rpc-methods": true, @@ -2517,6 +2516,7 @@ "browserify>browserify-zlib": true, "eslint>fast-deep-equal": true, "immer": true, + "nanoid": true, "readable-stream": true, "semver": true } @@ -4601,9 +4601,7 @@ }, "nanoid": { "globals": { - "crypto": true, - "msCrypto": true, - "navigator": true + "crypto.getRandomValues": true } }, "nock>debug": { diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index b4f5e137de6b..e9a50dd8057d 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -721,9 +721,9 @@ "console.info": true }, "packages": { - "@metamask/approval-controller>nanoid": true, "@metamask/base-controller": true, - "@metamask/rpc-errors": true + "@metamask/rpc-errors": true, + "nanoid": true } }, "@metamask/approval-controller>nanoid": { @@ -2136,11 +2136,11 @@ "@metamask/base-controller": true, "@metamask/controller-utils": true, "@metamask/json-rpc-engine": true, - "@metamask/permission-controller>nanoid": true, "@metamask/rpc-errors": true, "@metamask/utils": true, "deep-freeze-strict": true, - "immer": true + "immer": true, + "nanoid": true } }, "@metamask/permission-controller>nanoid": { @@ -2598,7 +2598,6 @@ "@metamask/snaps-controllers>@xstate/fsm": true, "@metamask/snaps-controllers>concat-stream": true, "@metamask/snaps-controllers>get-npm-tarball-url": true, - "@metamask/snaps-controllers>nanoid": true, "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, "@metamask/snaps-rpc-methods": true, @@ -2609,6 +2608,7 @@ "browserify>browserify-zlib": true, "eslint>fast-deep-equal": true, "immer": true, + "nanoid": true, "readable-stream": true, "semver": true } @@ -4693,9 +4693,7 @@ }, "nanoid": { "globals": { - "crypto": true, - "msCrypto": true, - "navigator": true + "crypto.getRandomValues": true } }, "nock>debug": { diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index 5338922720ef..32f1b088ad71 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -6560,7 +6560,7 @@ "process.env.NODE_ENV": true }, "packages": { - "postcss>nanoid": true, + "nanoid": true, "postcss>picocolors": true, "postcss>source-map-js": true } diff --git a/package.json b/package.json index 446133b81a48..4532335b3aa1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-crx", - "version": "12.9.2", + "version": "12.9.3", "private": true, "repository": { "type": "git", @@ -286,7 +286,7 @@ "@metamask/address-book-controller": "^6.0.0", "@metamask/announcement-controller": "^7.0.0", "@metamask/approval-controller": "^7.0.0", - "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch", + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A45.1.0%23~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch%3A%3Aversion=45.1.0&hash=cfcadc#~/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch", "@metamask/base-controller": "^7.0.0", "@metamask/bitcoin-wallet-snap": "^0.8.2", "@metamask/browser-passworder": "^4.3.0", @@ -397,7 +397,7 @@ "loglevel": "^1.8.1", "lottie-web": "^5.12.2", "luxon": "^3.2.1", - "nanoid": "^2.1.6", + "nanoid": "^3.3.8", "pify": "^5.0.0", "promise-to-callback": "^1.0.0", "prop-types": "^15.6.1", diff --git a/shared/lib/confirmation.utils.test.ts b/shared/lib/confirmation.utils.test.ts index 552d78827a2e..add03a95813d 100644 --- a/shared/lib/confirmation.utils.test.ts +++ b/shared/lib/confirmation.utils.test.ts @@ -43,6 +43,17 @@ describe('confirmation.utils', () => { }), ).toBe(false); }); + + it('should return false if decoding is disabled', () => { + expect( + shouldUseRedesignForTransactions({ + transactionMetadataType: unsupportedTransactionType, + isRedesignedTransactionsUserSettingEnabled: true, // user setting enabled + isRedesignedConfirmationsDeveloperEnabled: false, // developer mode disabled + isDecodingEnabled: false, + }), + ).toBe(false); + }); }); describe('when developer mode is enabled', () => { @@ -93,6 +104,17 @@ describe('confirmation.utils', () => { }), ).toBe(false); }); + + it('should return false if decoding is disabled', () => { + expect( + shouldUseRedesignForTransactions({ + transactionMetadataType: unsupportedTransactionType, + isRedesignedTransactionsUserSettingEnabled: false, // user setting disabled + isRedesignedConfirmationsDeveloperEnabled: true, // developer mode enabled + isDecodingEnabled: false, + }), + ).toBe(false); + }); }); describe('when both user setting and developer mode are disabled', () => { diff --git a/shared/lib/confirmation.utils.ts b/shared/lib/confirmation.utils.ts index 24c5f258a5d0..1813211582be 100644 --- a/shared/lib/confirmation.utils.ts +++ b/shared/lib/confirmation.utils.ts @@ -37,11 +37,17 @@ export function shouldUseRedesignForTransactions({ transactionMetadataType, isRedesignedTransactionsUserSettingEnabled, isRedesignedConfirmationsDeveloperEnabled, + isDecodingEnabled, }: { transactionMetadataType?: TransactionType; isRedesignedTransactionsUserSettingEnabled: boolean; isRedesignedConfirmationsDeveloperEnabled: boolean; + isDecodingEnabled?: boolean; }): boolean { + if (isDecodingEnabled === false) { + return false; + } + return ( shouldUseRedesignForTransactionsUserMode( isRedesignedTransactionsUserSettingEnabled, diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.test.ts b/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.test.ts index 32a711abf754..c12ce7025cb5 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.test.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.test.ts @@ -76,6 +76,32 @@ describe('useDecodedTransactionData', () => { expect(result).toStrictEqual({ pending: false, value: undefined }); }); + it('returns undefined if decode disabled', async () => { + decodeTransactionDataMock.mockResolvedValue(TRANSACTION_DECODE_SOURCIFY); + + const result = await runHook( + getMockConfirmStateForTransaction( + { + id: '123', + chainId: CHAIN_ID_MOCK, + type: TransactionType.contractInteraction, + status: TransactionStatus.unapproved, + txParams: { + data: TRANSACTION_DATA_UNISWAP, + to: CONTRACT_ADDRESS_MOCK, + } as TransactionParams, + }, + { + metamask: { + use4ByteResolution: false, + }, + }, + ), + ); + + expect(result).toStrictEqual({ pending: false, value: undefined }); + }); + it('returns the decoded data', async () => { decodeTransactionDataMock.mockResolvedValue(TRANSACTION_DECODE_SOURCIFY); diff --git a/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.ts b/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.ts index 5276e02eaad1..3486f16ed864 100644 --- a/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.ts +++ b/ui/pages/confirmations/components/confirm/info/hooks/useDecodedTransactionData.ts @@ -1,6 +1,7 @@ import { Hex } from '@metamask/utils'; import { TransactionMeta } from '@metamask/transaction-controller'; +import { useSelector } from 'react-redux'; import { AsyncResult, useAsyncResult, @@ -9,11 +10,13 @@ import { decodeTransactionData } from '../../../../../../store/actions'; import { DecodedTransactionDataResponse } from '../../../../../../../shared/types/transaction-decode'; import { useConfirmContext } from '../../../../context/confirm'; import { hasTransactionData } from '../../../../../../../shared/modules/transaction.utils'; +import { use4ByteResolutionSelector } from '../../../../../../selectors'; export function useDecodedTransactionData( transactionTypeFilter?: string, ): AsyncResult { const { currentConfirmation } = useConfirmContext(); + const isDecodeEnabled = useSelector(use4ByteResolutionSelector); const currentTransactionType = currentConfirmation?.type; const chainId = currentConfirmation?.chainId as Hex; @@ -23,6 +26,7 @@ export function useDecodedTransactionData( return useAsyncResult(async () => { if ( + !isDecodeEnabled || !hasTransactionData(transactionData) || !transactionTo || (transactionTypeFilter && @@ -36,5 +40,11 @@ export function useDecodedTransactionData( chainId, contractAddress, }); - }, [transactionData, transactionTo, chainId, contractAddress]); + }, [ + isDecodeEnabled, + transactionData, + transactionTo, + chainId, + contractAddress, + ]); } diff --git a/ui/pages/confirmations/hooks/useCurrentConfirmation.test.ts b/ui/pages/confirmations/hooks/useCurrentConfirmation.test.ts index 8c66873d448c..efd26d14da39 100644 --- a/ui/pages/confirmations/hooks/useCurrentConfirmation.test.ts +++ b/ui/pages/confirmations/hooks/useCurrentConfirmation.test.ts @@ -59,6 +59,7 @@ function buildState({ redesignedTransactionsEnabled, transaction, isRedesignedConfirmationsDeveloperEnabled, + isDecodingEnabled, }: { // eslint-disable-next-line @typescript-eslint/no-explicit-any message?: Partial; @@ -67,6 +68,7 @@ function buildState({ redesignedTransactionsEnabled?: boolean; transaction?: Partial; isRedesignedConfirmationsDeveloperEnabled?: boolean; + isDecodingEnabled?: boolean; }) { return { ...mockState, @@ -83,6 +85,7 @@ function buildState({ unapprovedPersonalMsgs: message ? { [message.id as string]: message } : {}, + use4ByteResolution: isDecodingEnabled ?? true, }, }; } @@ -290,6 +293,19 @@ describe('useCurrentConfirmation', () => { expect(currentConfirmation).toStrictEqual(TRANSACTION_MOCK); }); + it('returns undefined if transaction type correct and redesign enabled but decoding disabled', () => { + const currentConfirmation = runHook({ + pendingApprovals: [{ ...APPROVAL_MOCK, type: ApprovalType.Transaction }], + redesignedConfirmationsEnabled: true, + transaction: TRANSACTION_MOCK, + redesignedTransactionsEnabled: true, + isRedesignedConfirmationsDeveloperEnabled: false, + isDecodingEnabled: false, + }); + + expect(currentConfirmation).toBeUndefined(); + }); + describe('useCurrentConfirmation with env var', () => { beforeAll(() => { jest.resetModules(); diff --git a/ui/pages/confirmations/hooks/useCurrentConfirmation.ts b/ui/pages/confirmations/hooks/useCurrentConfirmation.ts index 1771f807de25..b2ccc5fd4594 100644 --- a/ui/pages/confirmations/hooks/useCurrentConfirmation.ts +++ b/ui/pages/confirmations/hooks/useCurrentConfirmation.ts @@ -11,6 +11,7 @@ import { getUnapprovedTransaction, oldestPendingConfirmationSelector, selectPendingApproval, + use4ByteResolutionSelector, } from '../../../selectors'; import { selectUnapprovedMessage } from '../../../selectors/signatures'; import { @@ -30,6 +31,7 @@ const useCurrentConfirmation = () => { const { id: paramsConfirmationId } = useParams<{ id: string }>(); const oldestPendingApproval = useSelector(oldestPendingConfirmationSelector); const confirmationId = paramsConfirmationId ?? oldestPendingApproval?.id; + const isDecodingEnabled = Boolean(useSelector(use4ByteResolutionSelector)); const isRedesignedSignaturesUserSettingEnabled = useSelector( getRedesignedConfirmationsEnabled, @@ -66,6 +68,7 @@ const useCurrentConfirmation = () => { transactionMetadataType: transactionMetadata?.type, isRedesignedTransactionsUserSettingEnabled, isRedesignedConfirmationsDeveloperEnabled, + isDecodingEnabled, }); const shouldUseRedesign = diff --git a/yarn.lock b/yarn.lock index 70482a79a761..5ee17e2614d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4985,7 +4985,7 @@ __metadata: languageName: node linkType: hard -"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch": +"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch::version=45.1.0&hash=cfcadc": version: 45.1.0 resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch::version=45.1.0&hash=cfcadc" dependencies: @@ -5024,6 +5024,45 @@ __metadata: languageName: node linkType: hard +"@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A45.1.0%23~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch%3A%3Aversion=45.1.0&hash=cfcadc#~/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch": + version: 45.1.0 + resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A45.1.0%23~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch%3A%3Aversion=45.1.0&hash=cfcadc#~/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch::version=45.1.0&hash=4e79dd" + dependencies: + "@ethereumjs/util": "npm:^8.1.0" + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/address": "npm:^5.7.0" + "@ethersproject/bignumber": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/providers": "npm:^5.7.0" + "@metamask/abi-utils": "npm:^2.0.3" + "@metamask/base-controller": "npm:^7.0.2" + "@metamask/contract-metadata": "npm:^2.4.0" + "@metamask/controller-utils": "npm:^11.4.3" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/metamask-eth-abis": "npm:^3.1.1" + "@metamask/polling-controller": "npm:^12.0.1" + "@metamask/rpc-errors": "npm:^7.0.1" + "@metamask/utils": "npm:^10.0.0" + "@types/bn.js": "npm:^5.1.5" + "@types/uuid": "npm:^8.3.0" + async-mutex: "npm:^0.5.0" + bn.js: "npm:^5.2.1" + cockatiel: "npm:^3.1.2" + immer: "npm:^9.0.6" + lodash: "npm:^4.17.21" + multiformats: "npm:^13.1.0" + single-call-balance-checker-abi: "npm:^1.0.0" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/accounts-controller": ^20.0.0 + "@metamask/approval-controller": ^7.0.0 + "@metamask/keyring-controller": ^19.0.0 + "@metamask/network-controller": ^22.0.0 + "@metamask/preferences-controller": ^15.0.0 + checksum: 10/26260472e67d0995b3730870fed99ba081c421ea64e8ca70f02ca8184fb9350fd2c607b75f45507743ba73d7336e831cc55a7aaf9a32f569a58eb7abb9275451 + languageName: node + linkType: hard + "@metamask/auto-changelog@npm:^2.1.0": version: 2.6.1 resolution: "@metamask/auto-changelog@npm:2.6.1" @@ -26596,7 +26635,7 @@ __metadata: "@metamask/announcement-controller": "npm:^7.0.0" "@metamask/api-specs": "npm:^0.9.3" "@metamask/approval-controller": "npm:^7.0.0" - "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A45.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch" + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A45.1.0%23~/.yarn/patches/@metamask-assets-controllers-npm-45.1.0-d914c453f0.patch%3A%3Aversion=45.1.0&hash=cfcadc#~/.yarn/patches/@metamask-assets-controllers-patch-d6ed5f8213.patch" "@metamask/auto-changelog": "npm:^2.1.0" "@metamask/base-controller": "npm:^7.0.0" "@metamask/bitcoin-wallet-snap": "npm:^0.8.2" @@ -26871,7 +26910,7 @@ __metadata: mocha: "npm:^10.2.0" mocha-junit-reporter: "npm:^2.2.1" mockttp: "npm:^3.10.1" - nanoid: "npm:^2.1.6" + nanoid: "npm:^3.3.8" nock: "patch:nock@npm%3A13.5.4#~/.yarn/patches/nock-npm-13.5.4-2c4f77b249.patch" node-fetch: "npm:^2.6.1" nyc: "npm:^15.1.0" @@ -28023,19 +28062,19 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^2.0.0, nanoid@npm:^2.1.6": +"nanoid@npm:^2.0.0": version: 2.1.11 resolution: "nanoid@npm:2.1.11" checksum: 10/cf2a2eedcf9d8893a4687f11743ccf8381f047bc2b3d3887a23721bbef8fe64c5759b9cba6eb945e40efeb4a7e7379b3417e4dc5f6cc03050322d2c24a7ff69b languageName: node linkType: hard -"nanoid@npm:^3.1.31, nanoid@npm:^3.3.7": - version: 3.3.7 - resolution: "nanoid@npm:3.3.7" +"nanoid@npm:^3.1.31, nanoid@npm:^3.3.7, nanoid@npm:^3.3.8": + version: 3.3.8 + resolution: "nanoid@npm:3.3.8" bin: nanoid: bin/nanoid.cjs - checksum: 10/ac1eb60f615b272bccb0e2b9cd933720dad30bf9708424f691b8113826bb91aca7e9d14ef5d9415a6ba15c266b37817256f58d8ce980c82b0ba3185352565679 + checksum: 10/2d1766606cf0d6f47b6f0fdab91761bb81609b2e3d367027aff45e6ee7006f660fb7e7781f4a34799fe6734f1268eeed2e37a5fdee809ade0c2d4eb11b0f9c40 languageName: node linkType: hard