diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 3a80d75ae..a8f43027c 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -3914,6 +3914,9 @@ "skipAccountSecurityDetails": { "message": "I understand that until I back up my Secret Recovery Phrase, I may lose my accounts and all of their assets." }, + "smartContracts": { + "message": "Smart contracts" + }, "smartSwap": { "message": "Smart swap" }, @@ -5207,6 +5210,12 @@ "urlExistsErrorMsg": { "message": "This URL is currently used by the $1 network." }, + "use4ByteResolution": { + "message": "Decode smart contracts" + }, + "use4ByteResolutionDescription": { + "message": "To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contact that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared." + }, "useMultiAccountBalanceChecker": { "message": "Batch account balance requests" }, diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 5b591cb44..6e223825e 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -39,6 +39,7 @@ export default class PreferencesController { // set to false will be using the static list from contract-metadata useTokenDetection: false, useNftDetection: false, + use4ByteResolution: true, useCurrencyRateCheck: true, openSeaEnabled: false, ///: BEGIN:ONLY_INCLUDE_IN(blockaid) @@ -169,6 +170,15 @@ export default class PreferencesController { this.store.updateState({ useNftDetection }); } + /** + * Setter for the `use4ByteResolution` property + * + * @param {boolean} use4ByteResolution - (Privacy) Whether or not the user prefers to have smart contract name details resolved with 4byte.directory + */ + setUse4ByteResolution(use4ByteResolution) { + this.store.updateState({ use4ByteResolution }); + } + /** * Setter for the `useCurrencyRateCheck` property * diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index af740eddc..55caf5d7a 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -233,6 +233,25 @@ describe('preferences controller', function () { }); }); + describe('setUse4ByteResolution', function () { + it('should default to true', function () { + const state = preferencesController.store.getState(); + assert.equal(state.use4ByteResolution, true); + }); + + it('should set the use4ByteResolution property in state', function () { + assert.equal( + preferencesController.store.getState().use4ByteResolution, + true, + ); + preferencesController.setUse4ByteResolution(false); + assert.equal( + preferencesController.store.getState().use4ByteResolution, + false, + ); + }); + }); + describe('setOpenSeaEnabled', function () { it('should default to false', function () { const state = preferencesController.store.getState(); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index bc865aa67..93777335a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2230,6 +2230,9 @@ export default class MetamaskController extends EventEmitter { setUseNftDetection: preferencesController.setUseNftDetection.bind( preferencesController, ), + setUse4ByteResolution: preferencesController.setUse4ByteResolution.bind( + preferencesController, + ), setUseCurrencyRateCheck: preferencesController.setUseCurrencyRateCheck.bind( preferencesController, diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 1095058d0..69db1fc05 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -23,6 +23,7 @@ "mostRecentOverviewPage": "/mostRecentOverviewPage" }, "metamask": { + "use4ByteResolution": true, "ipfsGateway": "dweb.link", "dismissSeedBackUpReminder": false, "usePhishDetect": true, diff --git a/test/e2e/tests/4byte-directory.spec.js b/test/e2e/tests/4byte-directory.spec.js new file mode 100644 index 000000000..a8a5f07c7 --- /dev/null +++ b/test/e2e/tests/4byte-directory.spec.js @@ -0,0 +1,114 @@ +const { strict: assert } = require('assert'); +const FixtureBuilder = require('../fixture-builder'); +const { + withFixtures, + openDapp, + unlockWallet, + largeDelayMs, + veryLargeDelayMs, + WINDOW_TITLES, +} = require('../helpers'); +const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); + +describe('4byte setting', function () { + it('makes a call to 4byte when the setting is on', async function () { + const smartContract = SMART_CONTRACTS.PIGGYBANK; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + smartContract, + title: this.test.title, + }, + async ({ driver, contractRegistry }) => { + const contractAddress = await contractRegistry.getContractAddress( + smartContract, + ); + await driver.navigate(); + await unlockWallet(driver); + + // deploy contract + await openDapp(driver, contractAddress); + + // wait for deployed contract, calls and confirms a contract method where ETH is sent + await driver.delay(largeDelayMs); + await driver.clickElement('#depositButton'); + + await driver.waitForSelector({ + css: 'span', + text: 'Deposit initiated', + }); + + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + const actionElement = await driver.waitForSelector({ + css: '.confirm-page-container-summary__action__name', + text: 'Deposit', + }); + assert.equal(await actionElement.getText(), 'DEPOSIT'); + }, + ); + }); + + it('does not try to get contract method name from 4byte when the setting is off', async function () { + const smartContract = SMART_CONTRACTS.PIGGYBANK; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + smartContract, + title: this.test.title, + }, + async ({ driver, contractRegistry }) => { + const contractAddress = await contractRegistry.getContractAddress( + smartContract, + ); + await driver.navigate(); + await unlockWallet(driver); + + // goes to the settings screen + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); + await driver.clickElement({ text: 'Settings', tag: 'div' }); + await driver.clickElement({ text: 'Security & privacy', tag: 'div' }); + + // turns off 4Byte Directory contract method name resolution + await driver.clickElement( + '[data-testid="4byte-resolution-container"] .toggle-button', + ); + + // deploy contract + await openDapp(driver, contractAddress); + + // wait for deployed contract, calls and confirms a contract method where ETH is sent + await driver.findClickableElement('#depositButton'); + await driver.clickElement('#depositButton'); + + await driver.waitForSelector({ + css: 'span', + text: 'Deposit initiated', + }); + + await driver.waitUntilXWindowHandles(3); + await driver.switchToWindowWithTitle(WINDOW_TITLES.Notification); + const contractInteraction = 'Contract interaction'; + const actionElement = await driver.waitForSelector({ + css: '.confirm-page-container-summary__action__name', + text: contractInteraction, + }); + // We add a delay here to wait for any potential UI changes + await driver.delay(veryLargeDelayMs); + // css text-transform: uppercase is applied to the text + assert.equal( + await actionElement.getText(), + contractInteraction.toUpperCase(), + ); + }, + ); + }); +}); diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index 2ba6b365a..8f6ae2004 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -47,6 +47,7 @@ const initialState = { firstTimeFlowType: null, completedOnboarding: false, knownMethodData: {}, + use4ByteResolution: true, participateInMetaMetrics: null, nextNonce: null, conversionRate: null, diff --git a/ui/helpers/constants/settings.js b/ui/helpers/constants/settings.js index 1aedec0d1..4a879d614 100644 --- a/ui/helpers/constants/settings.js +++ b/ui/helpers/constants/settings.js @@ -163,6 +163,13 @@ export const SETTINGS_CONSTANTS = [ route: `${SECURITY_ROUTE}#phishing-detection`, icon: 'fa fa-lock', }, + { + tabMessage: (t) => t('securityAndPrivacy'), + sectionMessage: (t) => t('use4ByteResolution'), + descriptionMessage: (t) => t('use4ByteResolutionDescription'), + route: `${SECURITY_ROUTE}#decode-smart-contracts`, + icon: 'fa fa-lock', + }, { tabMessage: (t) => t('securityAndPrivacy'), sectionMessage: (t) => t('participateInMetaMetrics'), diff --git a/ui/helpers/utils/settings-search.test.js b/ui/helpers/utils/settings-search.test.js index efc42ff48..638453b53 100644 --- a/ui/helpers/utils/settings-search.test.js +++ b/ui/helpers/utils/settings-search.test.js @@ -165,7 +165,7 @@ describe('Settings Search Utils', () => { it('should get good security & privacy section number', () => { expect( getNumberOfSettingsInSection(t, t('securityAndPrivacy')), - ).toStrictEqual(12); + ).toStrictEqual(13); }); it('should get good alerts section number', () => { diff --git a/ui/helpers/utils/transactions.util.js b/ui/helpers/utils/transactions.util.js index 0bf5e791d..cb9d98fe9 100644 --- a/ui/helpers/utils/transactions.util.js +++ b/ui/helpers/utils/transactions.util.js @@ -49,14 +49,18 @@ let registry; * Attempts to return the method data from the MethodRegistry library, the message registry library and the token abi, in that order of preference * * @param {string} fourBytePrefix - The prefix from the method code associated with the data + * @param {boolean} allow4ByteRequests - Whether or not to allow 4byte.directory requests, toggled by the user in privacy settings * @returns {object} */ -export async function getMethodDataAsync(fourBytePrefix) { +export async function getMethodDataAsync(fourBytePrefix, allow4ByteRequests) { try { - const fourByteSig = await getMethodFrom4Byte(fourBytePrefix).catch((e) => { - log.error(e); - return null; - }); + let fourByteSig = null; + if (allow4ByteRequests) { + fourByteSig = await getMethodFrom4Byte(fourBytePrefix).catch((e) => { + log.error(e); + return null; + }); + } if (!registry) { registry = new MethodRegistry({ provider: global.ethereumProvider }); diff --git a/ui/helpers/utils/transactions.util.test.js b/ui/helpers/utils/transactions.util.test.js index cdf2b9810..bbc9c3107 100644 --- a/ui/helpers/utils/transactions.util.test.js +++ b/ui/helpers/utils/transactions.util.test.js @@ -62,7 +62,7 @@ describe('Transactions utils', () => { global.ethereumProvider = new HttpProvider( 'https://mainnet.infura.io/v3/341eacb578dd44a1a049cbc5f6fd4035', ); - it('returns a valid signature for setApprovalForAll', async () => { + it('returns a valid signature for setApprovalForAll when use4ByteResolution privacy setting is ON', async () => { nock('https://www.4byte.directory:443', { encodedQueryParams: true }) .get('/api/v1/signatures/') .query({ hex_signature: '0xa22cb465' }) @@ -87,7 +87,7 @@ describe('Transactions utils', () => { }, ], }); - expect(await utils.getMethodDataAsync('0xa22cb465')).toStrictEqual({ + expect(await utils.getMethodDataAsync('0xa22cb465', true)).toStrictEqual({ name: 'Set Approval For All', params: [{ type: 'address' }, { type: 'bool' }], }); diff --git a/ui/pages/confirm-transaction/confirm-transaction.component.js b/ui/pages/confirm-transaction/confirm-transaction.component.js index 9d26ed3f6..b570ea059 100644 --- a/ui/pages/confirm-transaction/confirm-transaction.component.js +++ b/ui/pages/confirm-transaction/confirm-transaction.component.js @@ -33,6 +33,7 @@ import { usePrevious } from '../../hooks/usePrevious'; import { unconfirmedTransactionsListSelector, unconfirmedTransactionsHashSelector, + use4ByteResolutionSelector, } from '../../selectors'; import { disconnectGasFeeEstimatePoller, @@ -71,6 +72,7 @@ const ConfirmTransaction = () => { unconfirmedTxsSorted, ]); const [transaction, setTransaction] = useState(getTransaction); + const use4ByteResolution = useSelector(use4ByteResolutionSelector); useEffect(() => { const tx = getTransaction(); @@ -127,7 +129,7 @@ const ConfirmTransaction = () => { const { txParams: { data } = {}, origin } = transaction; if (origin !== ORIGIN_METAMASK) { - dispatch(getContractMethodData(data)); + dispatch(getContractMethodData(data, use4ByteResolution)); } const txId = transactionId || paramsTransactionId; @@ -154,7 +156,7 @@ const ConfirmTransaction = () => { dispatch(clearConfirmTransaction()); dispatch(setTransactionToConfirm(paramsTransactionId)); if (origin !== ORIGIN_METAMASK) { - dispatch(getContractMethodData(data)); + dispatch(getContractMethodData(data, use4ByteResolution)); } } else if (prevTransactionId && !transactionId && !totalUnapproved) { dispatch(setDefaultHomeActiveTabName('activity')).then(() => { @@ -178,6 +180,7 @@ const ConfirmTransaction = () => { totalUnapproved, transaction, transactionId, + use4ByteResolution, ]); if (isValidTokenMethod && isValidTransactionId) { diff --git a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js index 18be5590c..4fa1d3538 100644 --- a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js +++ b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js @@ -36,6 +36,7 @@ import { setUseCurrencyRateCheck, setUseMultiAccountBalanceChecker, setUsePhishDetect, + setUse4ByteResolution, setUseTokenDetection, setUseAddressBarEnsResolution, showModal, @@ -48,6 +49,7 @@ export default function PrivacySettings() { const dispatch = useDispatch(); const history = useHistory(); const [usePhishingDetection, setUsePhishingDetection] = useState(true); + const [turnOn4ByteResolution, setTurnOn4ByteResolution] = useState(true); const [turnOnTokenDetection, setTurnOnTokenDetection] = useState(true); const [turnOnCurrencyRateCheck, setTurnOnCurrencyRateCheck] = useState(true); const [showIncomingTransactions, setShowIncomingTransactions] = @@ -68,6 +70,7 @@ export default function PrivacySettings() { setFeatureFlag('showIncomingTransactions', showIncomingTransactions), ); dispatch(setUsePhishDetect(usePhishingDetection)); + dispatch(setUse4ByteResolution(turnOn4ByteResolution)); dispatch(setUseTokenDetection(turnOnTokenDetection)); dispatch( setUseMultiAccountBalanceChecker(isMultiAccountBalanceCheckerEnabled), @@ -171,6 +174,12 @@ export default function PrivacySettings() { , ])} /> + { const store = configureMockStore([thunk])(mockStore); const setFeatureFlagStub = jest.fn(); const setUsePhishDetectStub = jest.fn(); + const setUse4ByteResolutionStub = jest.fn(); const setUseTokenDetectionStub = jest.fn(); const setUseCurrencyRateCheckStub = jest.fn(); const setIpfsGatewayStub = jest.fn(); @@ -33,6 +34,7 @@ describe('Privacy Settings Onboarding View', () => { setBackgroundConnection({ setFeatureFlag: setFeatureFlagStub, setUsePhishDetect: setUsePhishDetectStub, + setUse4ByteResolution: setUse4ByteResolutionStub, setUseTokenDetection: setUseTokenDetectionStub, setUseCurrencyRateCheck: setUseCurrencyRateCheckStub, setIpfsGateway: setIpfsGatewayStub, @@ -49,6 +51,7 @@ describe('Privacy Settings Onboarding View', () => { // All settings are initialized toggled to true expect(setFeatureFlagStub).toHaveBeenCalledTimes(0); expect(setUsePhishDetectStub).toHaveBeenCalledTimes(0); + expect(setUse4ByteResolutionStub).toHaveBeenCalledTimes(0); expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(0); expect(setUseMultiAccountBalanceCheckerStub).toHaveBeenCalledTimes(0); expect(setUseCurrencyRateCheckStub).toHaveBeenCalledTimes(0); @@ -64,10 +67,12 @@ describe('Privacy Settings Onboarding View', () => { fireEvent.click(toggles[3]); fireEvent.click(toggles[4]); fireEvent.click(toggles[5]); + fireEvent.click(toggles[6]); fireEvent.click(submitButton); expect(setFeatureFlagStub).toHaveBeenCalledTimes(1); expect(setUsePhishDetectStub).toHaveBeenCalledTimes(1); + expect(setUse4ByteResolutionStub).toHaveBeenCalledTimes(1); expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(1); expect(setUseMultiAccountBalanceCheckerStub).toHaveBeenCalledTimes(1); expect(setUseCurrencyRateCheckStub).toHaveBeenCalledTimes(1); @@ -75,6 +80,7 @@ describe('Privacy Settings Onboarding View', () => { expect(setFeatureFlagStub.mock.calls[0][1]).toStrictEqual(false); expect(setUsePhishDetectStub.mock.calls[0][0]).toStrictEqual(false); + expect(setUse4ByteResolutionStub.mock.calls[0][0]).toStrictEqual(false); expect(setUseTokenDetectionStub.mock.calls[0][0]).toStrictEqual(false); expect(setUseMultiAccountBalanceCheckerStub.mock.calls[0][0]).toStrictEqual( false, @@ -91,9 +97,11 @@ describe('Privacy Settings Onboarding View', () => { fireEvent.click(toggles[3]); fireEvent.click(toggles[4]); fireEvent.click(toggles[5]); + fireEvent.click(toggles[6]); fireEvent.click(submitButton); expect(setFeatureFlagStub).toHaveBeenCalledTimes(2); expect(setUsePhishDetectStub).toHaveBeenCalledTimes(2); + expect(setUse4ByteResolutionStub).toHaveBeenCalledTimes(2); expect(setUseTokenDetectionStub).toHaveBeenCalledTimes(2); expect(setUseMultiAccountBalanceCheckerStub).toHaveBeenCalledTimes(2); expect(setUseCurrencyRateCheckStub).toHaveBeenCalledTimes(2); @@ -101,6 +109,7 @@ describe('Privacy Settings Onboarding View', () => { expect(setFeatureFlagStub.mock.calls[1][1]).toStrictEqual(true); expect(setUsePhishDetectStub.mock.calls[1][0]).toStrictEqual(true); + expect(setUse4ByteResolutionStub.mock.calls[1][0]).toStrictEqual(true); expect(setUseTokenDetectionStub.mock.calls[1][0]).toStrictEqual(true); expect(setUseMultiAccountBalanceCheckerStub.mock.calls[1][0]).toStrictEqual( true, diff --git a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap index cdec54e99..23b7eebf5 100644 --- a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap +++ b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap @@ -113,6 +113,87 @@ exports[`Security Tab should match snapshot 1`] = ` +
+ + Smart contracts + +
+
+
+
+ + Decode smart contracts + +
+ To improve user experience, we customize the activity tab with messages based on the smart contracts you interact with. MetaMask uses a service called 4byte.directory to decode data and show you a version of a smart contact that's easier to read. This helps reduce your chances of approving malicious smart contract actions, but can result in your IP address being shared. +
+
+
+
+
diff --git a/ui/pages/settings/security-tab/security-tab.component.js b/ui/pages/settings/security-tab/security-tab.component.js index 85ca2453f..00f75117d 100644 --- a/ui/pages/settings/security-tab/security-tab.component.js +++ b/ui/pages/settings/security-tab/security-tab.component.js @@ -61,6 +61,8 @@ export default class SecurityTab extends PureComponent { setShowIncomingTransactionsFeatureFlag: PropTypes.func.isRequired, setUsePhishDetect: PropTypes.func.isRequired, usePhishDetect: PropTypes.bool.isRequired, + setUse4ByteResolution: PropTypes.func.isRequired, + use4ByteResolution: PropTypes.bool.isRequired, useTokenDetection: PropTypes.bool.isRequired, setUseTokenDetection: PropTypes.func.isRequired, setIpfsGateway: PropTypes.func.isRequired, @@ -254,6 +256,34 @@ export default class SecurityTab extends PureComponent { ); } + renderUse4ByteResolutionToggle() { + const { t } = this.context; + const { use4ByteResolution, setUse4ByteResolution } = this.props; + return ( +
+
+ {t('use4ByteResolution')} +
+ {t('use4ByteResolutionDescription')} +
+
+
+
+ setUse4ByteResolution(!value)} + offLabel={t('off')} + onLabel={t('on')} + /> +
+
+
+ ); + } + renderMetaMetricsOptIn() { const { t } = this.context; const { participateInMetaMetrics, setParticipateInMetaMetrics } = @@ -261,7 +291,7 @@ export default class SecurityTab extends PureComponent { return ( @@ -495,7 +525,7 @@ export default class SecurityTab extends PureComponent { return (
- Alerts + + {this.context.t('alerts')} +
{this.renderPhishingDetectionToggle()}
- +
+ + {this.context.t('smartContracts')} + +
+
+ {this.renderUse4ByteResolutionToggle()} +
{this.context.t('transactions')} diff --git a/ui/pages/settings/security-tab/security-tab.container.js b/ui/pages/settings/security-tab/security-tab.container.js index d245a42e1..4e363df40 100644 --- a/ui/pages/settings/security-tab/security-tab.container.js +++ b/ui/pages/settings/security-tab/security-tab.container.js @@ -12,6 +12,7 @@ import { setUseAddressBarEnsResolution, setOpenSeaEnabled, setUseNftDetection, + setUse4ByteResolution, } from '../../../store/actions'; import SecurityTab from './security-tab.component'; @@ -31,6 +32,7 @@ const mapStateToProps = (state) => { useAddressBarEnsResolution, openSeaEnabled, useNftDetection, + use4ByteResolution, } = metamask; return { @@ -45,6 +47,7 @@ const mapStateToProps = (state) => { useAddressBarEnsResolution, openSeaEnabled, useNftDetection, + use4ByteResolution, }; }; @@ -69,6 +72,9 @@ const mapDispatchToProps = (dispatch) => { dispatch(setUseAddressBarEnsResolution(value)), setOpenSeaEnabled: (val) => dispatch(setOpenSeaEnabled(val)), setUseNftDetection: (val) => dispatch(setUseNftDetection(val)), + setUse4ByteResolution: (value) => { + return dispatch(setUse4ByteResolution(value)); + }, }; }; diff --git a/ui/pages/settings/security-tab/security-tab.test.js b/ui/pages/settings/security-tab/security-tab.test.js index 777f572ea..eb03f74fb 100644 --- a/ui/pages/settings/security-tab/security-tab.test.js +++ b/ui/pages/settings/security-tab/security-tab.test.js @@ -58,6 +58,10 @@ describe('Security Tab', () => { expect(await toggleCheckbox('usePhishingDetection', true)).toBe(true); }); + it('toggles 4byte resolution', async () => { + expect(await toggleCheckbox('4byte-resolution-container', true)).toBe(true); + }); + it('toggles balance and token price checker', async () => { expect(await toggleCheckbox('currencyRateCheckToggle', true)).toBe(true); }); diff --git a/ui/selectors/confirm-transaction.js b/ui/selectors/confirm-transaction.js index 349750192..70566ab9a 100644 --- a/ui/selectors/confirm-transaction.js +++ b/ui/selectors/confirm-transaction.js @@ -141,7 +141,8 @@ export const unconfirmedMessagesHashSelector = createSelector( }; }, ); - +export const use4ByteResolutionSelector = (state) => + state.metamask.use4ByteResolution; export const currentCurrencySelector = (state) => state.metamask.currentCurrency; export const conversionRateSelector = (state) => state.metamask.conversionRate; diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 8485c5d9f..0f05d28b6 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -698,9 +698,9 @@ export function getKnownMethodData(state, data) { } const prefixedData = addHexPrefix(data); const fourBytePrefix = prefixedData.slice(0, 10); - const { knownMethodData } = state.metamask; - - return knownMethodData && knownMethodData[fourBytePrefix]; + const { knownMethodData, use4ByteResolution } = state.metamask; + // If 4byte setting is off, we do not want to return the knownMethodData + return use4ByteResolution && knownMethodData?.[fourBytePrefix]; } export function getFeatureFlags(state) { diff --git a/ui/store/actions.test.js b/ui/store/actions.test.js index 91e2c2ef7..933ec6ce2 100644 --- a/ui/store/actions.test.js +++ b/ui/store/actions.test.js @@ -1756,6 +1756,44 @@ describe('Actions', () => { }); }); + describe('#setUse4ByteResolution', () => { + afterEach(() => { + sinon.restore(); + }); + + it('calls setUse4ByteResolution in background', async () => { + const store = mockStore(); + const setUse4ByteResolutionStub = sinon.stub().callsFake((_, cb) => cb()); + _setBackgroundConnection({ + setUse4ByteResolution: setUse4ByteResolutionStub, + }); + + await store.dispatch(actions.setUse4ByteResolution()); + expect(setUse4ByteResolutionStub.callCount).toStrictEqual(1); + }); + + it('errors when setUse4ByteResolution in background throws', async () => { + const store = mockStore(); + const setUse4ByteResolutionStub = sinon.stub().callsFake((_, cb) => { + cb(new Error('error')); + }); + + _setBackgroundConnection({ + setUse4ByteResolution: setUse4ByteResolutionStub, + }); + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', payload: undefined }, + { type: 'DISPLAY_WARNING', payload: 'error' }, + { type: 'HIDE_LOADING_INDICATION' }, + ]; + + await store.dispatch(actions.setUse4ByteResolution()); + + expect(store.getActions()).toStrictEqual(expectedActions); + }); + }); + describe('#updateCurrentLocale', () => { beforeEach(() => { sinon.stub(window, 'fetch').resolves({ diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 7bb50726a..b7751a7ed 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -2997,6 +2997,22 @@ export function setUseNftDetection( }; } +export function setUse4ByteResolution( + val: boolean, +): ThunkAction { + return async (dispatch: MetaMaskReduxDispatch) => { + dispatch(showLoadingIndication()); + log.debug(`background.setUse4ByteResolution`); + try { + await submitRequestToBackground('setUse4ByteResolution', [val]); + } catch (error) { + dispatch(displayWarning(error)); + } finally { + dispatch(hideLoadingIndication()); + } + }; +} + export function setUseCurrencyRateCheck( val: boolean, ): ThunkAction { @@ -3775,7 +3791,7 @@ export function getContractMethodData( if (fourBytePrefix.length < 10) { return {}; } - const { knownMethodData } = getState().metamask; + const { knownMethodData, use4ByteResolution } = getState().metamask; if ( knownMethodData?.[fourBytePrefix] && Object.keys(knownMethodData[fourBytePrefix]).length !== 0 @@ -3785,7 +3801,10 @@ export function getContractMethodData( log.debug(`loadingMethodData`); - const { name, params } = (await getMethodDataAsync(fourBytePrefix)) as { + const { name, params } = (await getMethodDataAsync( + fourBytePrefix, + use4ByteResolution, + )) as { name: string; params: unknown; };