1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-21 17:37:01 +01:00

Add Opt-out Settings toggle for 4byte contract method names resolution (#20098)

* Adding 4byte toggle to settings UI and preferences

* Adding 4byte toggle to advanced settings tab

* adding use4ByteResolution privacy logic to getContractMethodData & getMethodDataAsync, removing unused useMethodData hook, adding clearKnownMethodData

* add 4byte setting to onboarding advanced option

* more test changes

* adding e2e for 4byte setting toggle

* test and copy changes, snap updates

* removing 4byte from advanced section

* adding settings constant and fixing refs

* removing clearKnownMethodData, adding flag to selector, test fixes

* e2e refactor, selectors refactor

* adding tests

* Fix jest tests, remove unwanted forceUpdateMetamaskState

* Fix jest tests

* lint:fix

* settingsRefs fixes

---------

Co-authored-by: David Walsh <davidwalsh83@gmail.com>
This commit is contained in:
Victor Thomas 2023-08-04 13:28:37 -04:00 committed by GitHub
parent 658ceb90d1
commit 2ff289e271
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 404 additions and 27 deletions

View File

@ -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"
},

View File

@ -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
*

View File

@ -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();

View File

@ -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,

View File

@ -23,6 +23,7 @@
"mostRecentOverviewPage": "/mostRecentOverviewPage"
},
"metamask": {
"use4ByteResolution": true,
"ipfsGateway": "dweb.link",
"dismissSeedBackUpReminder": false,
"usePhishDetect": true,

View File

@ -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(),
);
},
);
});
});

View File

@ -47,6 +47,7 @@ const initialState = {
firstTimeFlowType: null,
completedOnboarding: false,
knownMethodData: {},
use4ByteResolution: true,
participateInMetaMetrics: null,
nextNonce: null,
conversionRate: null,

View File

@ -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'),

View File

@ -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', () => {

View File

@ -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 });

View File

@ -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' }],
});

View File

@ -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) {

View File

@ -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() {
</a>,
])}
/>
<Setting
value={turnOn4ByteResolution}
setValue={setTurnOn4ByteResolution}
title={t('use4ByteResolution')}
description={t('use4ByteResolutionDescription')}
/>
<Setting
value={turnOnTokenDetection}
setValue={setTurnOnTokenDetection}

View File

@ -21,6 +21,7 @@ describe('Privacy Settings Onboarding View', () => {
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,

View File

@ -113,6 +113,87 @@ exports[`Security Tab should match snapshot 1`] = `
</div>
</div>
</div>
<div>
<span
class="settings-page__security-tab-sub-header"
>
Smart contracts
</span>
</div>
<div
class="settings-page__content-padded"
>
<div
class="settings-page__content-row"
>
<div
class="settings-page__content-item"
>
<span>
Decode smart contracts
</span>
<div
class="settings-page__content-description"
>
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.
</div>
</div>
<div
class="settings-page__content-item"
>
<div
class="settings-page__content-item-col"
data-testid="4byte-resolution-container"
>
<label
class="toggle-button toggle-button--on"
tabindex="0"
>
<div
style="display: flex; width: 52px; align-items: center; justify-content: flex-start; position: relative; cursor: pointer; background-color: transparent; border: 0px; padding: 0px; user-select: none;"
>
<div
style="width: 40px; height: 24px; padding: 0px; border-radius: 26px; display: flex; align-items: center; justify-content: center; background-color: rgb(242, 244, 246);"
>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgb(250, 250, 250); margin-top: auto; margin-bottom: auto; line-height: 0; opacity: 1; width: 26px; height: 20px; left: 4px;"
/>
<div
style="font-size: 11px; display: flex; align-items: center; justify-content: center; font-family: 'Helvetica Neue', Helvetica, sans-serif; position: relative; color: rgba(255, 255, 255, 0.6); bottom: 0px; margin-top: auto; margin-bottom: auto; padding-right: 5px; line-height: 0; width: 26px; height: 20px; opacity: 0;"
/>
</div>
<div
style="position: absolute; height: 100%; top: 0px; left: 0px; display: flex; flex: 1; align-self: stretch; align-items: center; justify-content: flex-start;"
>
<div
style="width: 18px; height: 18px; display: flex; align-self: center; box-shadow: none; border-radius: 50%; box-sizing: border-box; position: relative; background-color: rgb(3, 125, 214); left: 18px;"
/>
</div>
<input
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px;"
type="checkbox"
value="true"
/>
</div>
<div
class="toggle-button__status"
>
<span
class="toggle-button__label-off"
>
Off
</span>
<span
class="toggle-button__label-on"
>
On
</span>
</div>
</label>
</div>
</div>
</div>
</div>
<span
class="settings-page__security-tab-sub-header"
>

View File

@ -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 (
<div ref={this.settingsRefs[3]} className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{t('use4ByteResolution')}</span>
<div className="settings-page__content-description">
{t('use4ByteResolutionDescription')}
</div>
</div>
<div className="settings-page__content-item">
<div
className="settings-page__content-item-col"
data-testid="4byte-resolution-container"
>
<ToggleButton
value={use4ByteResolution}
onToggle={(value) => setUse4ByteResolution(!value)}
offLabel={t('off')}
onLabel={t('on')}
/>
</div>
</div>
</div>
);
}
renderMetaMetricsOptIn() {
const { t } = this.context;
const { participateInMetaMetrics, setParticipateInMetaMetrics } =
@ -261,7 +291,7 @@ export default class SecurityTab extends PureComponent {
return (
<Box
ref={this.settingsRefs[3]}
ref={this.settingsRefs[4]}
className="settings-page__content-row"
display={Display.Flex}
flexDirection={FlexDirection.Row}
@ -294,7 +324,7 @@ export default class SecurityTab extends PureComponent {
return (
<Box
ref={this.settingsRefs[4]}
ref={this.settingsRefs[5]}
className="settings-page__content-row"
data-testid="advanced-setting-choose-your-network"
display={Display.Flex}
@ -384,7 +414,7 @@ export default class SecurityTab extends PureComponent {
};
return (
<Box
ref={this.settingsRefs[5]}
ref={this.settingsRefs[6]}
className="settings-page__content-row"
data-testid="setting-ipfs-gateway"
display={Display.Flex}
@ -428,7 +458,7 @@ export default class SecurityTab extends PureComponent {
display={Display.Flex}
flexDirection={FlexDirection.Row}
justifyContent={JustifyContent.spaceBetween}
ref={this.settingsRefs[9]}
ref={this.settingsRefs[10]}
marginTop={3}
id="ens-domains"
>
@ -495,7 +525,7 @@ export default class SecurityTab extends PureComponent {
return (
<Box
ref={this.settingsRefs[6]}
ref={this.settingsRefs[7]}
className="settings-page__content-row"
data-testid="advanced-setting-gas-fee-estimation"
display={Display.Flex}
@ -548,7 +578,7 @@ export default class SecurityTab extends PureComponent {
return (
<Box
ref={this.settingsRefs[7]}
ref={this.settingsRefs[8]}
className="settings-page__content-row"
display={Display.Flex}
flexDirection={FlexDirection.Row}
@ -589,7 +619,7 @@ export default class SecurityTab extends PureComponent {
return (
<Box
ref={this.settingsRefs[8]}
ref={this.settingsRefs[9]}
className="settings-page__content-row"
display={Display.Flex}
flexDirection={FlexDirection.Row}
@ -653,7 +683,7 @@ export default class SecurityTab extends PureComponent {
return (
<Box
ref={this.settingsRefs[10]}
ref={this.settingsRefs[11]}
className="settings-page__content-row"
display={Display.Flex}
flexDirection={FlexDirection.Row}
@ -705,7 +735,7 @@ export default class SecurityTab extends PureComponent {
} = this.props;
return (
<Box
ref={this.settingsRefs[11]}
ref={this.settingsRefs[12]}
className="settings-page__content-row"
display={Display.Flex}
flexDirection={FlexDirection.Row}
@ -770,12 +800,21 @@ export default class SecurityTab extends PureComponent {
{this.context.t('privacy')}
</span>
<div>
<span className="settings-page__security-tab-sub-header">Alerts</span>
<span className="settings-page__security-tab-sub-header">
{this.context.t('alerts')}
</span>
</div>
<div className="settings-page__content-padded">
{this.renderPhishingDetectionToggle()}
</div>
<div>
<span className="settings-page__security-tab-sub-header">
{this.context.t('smartContracts')}
</span>
</div>
<div className="settings-page__content-padded">
{this.renderUse4ByteResolutionToggle()}
</div>
<span className="settings-page__security-tab-sub-header">
{this.context.t('transactions')}
</span>

View File

@ -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));
},
};
};

View File

@ -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);
});

View File

@ -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;

View File

@ -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) {

View File

@ -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({

View File

@ -2997,6 +2997,22 @@ export function setUseNftDetection(
};
}
export function setUse4ByteResolution(
val: boolean,
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
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<void, MetaMaskReduxState, unknown, AnyAction> {
@ -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;
};