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:
parent
658ceb90d1
commit
2ff289e271
9
app/_locales/en/messages.json
generated
9
app/_locales/en/messages.json
generated
@ -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"
|
||||
},
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -23,6 +23,7 @@
|
||||
"mostRecentOverviewPage": "/mostRecentOverviewPage"
|
||||
},
|
||||
"metamask": {
|
||||
"use4ByteResolution": true,
|
||||
"ipfsGateway": "dweb.link",
|
||||
"dismissSeedBackUpReminder": false,
|
||||
"usePhishDetect": true,
|
||||
|
114
test/e2e/tests/4byte-directory.spec.js
Normal file
114
test/e2e/tests/4byte-directory.spec.js
Normal 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(),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
@ -47,6 +47,7 @@ const initialState = {
|
||||
firstTimeFlowType: null,
|
||||
completedOnboarding: false,
|
||||
knownMethodData: {},
|
||||
use4ByteResolution: true,
|
||||
participateInMetaMetrics: null,
|
||||
nextNonce: null,
|
||||
conversionRate: null,
|
||||
|
@ -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'),
|
||||
|
@ -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', () => {
|
||||
|
@ -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 });
|
||||
|
@ -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' }],
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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}
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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>
|
||||
|
@ -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));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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({
|
||||
|
@ -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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user