From d78a1818ab49af02c55b1975de9dc47029df4514 Mon Sep 17 00:00:00 2001 From: Vinicius Stevam <45455812+vinistevam@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:01:06 +0100 Subject: [PATCH] fix: disable the confirm button when there is a blocking alert (#27347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR aims to disable the confirm button when there is a blocking alert. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27347?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/27147 ## **Manual testing steps** 1. Go to test dapp 2. Don't have funds 3. Trigger malicious mint erc20 on Sepolia 4. Click on Review alerts or 1. Go to test dapp 2. Click Sign In With Ethereum Bad Domain 3. Click Confirm 4. See the friction modal ## **Screenshots/Recordings** [Screencast from 23-09-2024 17:57:22.webm](https://github.com/user-attachments/assets/4c00284c-93d3-4b1b-9553-39eda67b7924) ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .../alerts/insufficient-funds.spec.ts | 5 +- .../components/confirm/footer/footer.test.tsx | 84 ++++++++++++++----- .../components/confirm/footer/footer.tsx | 29 ++++++- 3 files changed, 89 insertions(+), 29 deletions(-) diff --git a/test/e2e/tests/confirmations/alerts/insufficient-funds.spec.ts b/test/e2e/tests/confirmations/alerts/insufficient-funds.spec.ts index f8d70b8e6568..9c74714c82e6 100644 --- a/test/e2e/tests/confirmations/alerts/insufficient-funds.spec.ts +++ b/test/e2e/tests/confirmations/alerts/insufficient-funds.spec.ts @@ -7,7 +7,6 @@ import { WINDOW_TITLES, } from '../../../helpers'; import { SMART_CONTRACTS } from '../../../seeder/smart-contracts'; -import { scrollAndConfirmAndAssertConfirm } from '../helpers'; import { TestSuiteArguments, openDAppWithContract, @@ -48,8 +47,6 @@ describe('Alert for insufficient funds @no-mmi', function () { await verifyAlertForInsufficientBalance(driver); - await scrollAndConfirmAndAssertConfirm(driver); - await verifyConfirmationIsDisabled(driver); }, ); @@ -58,7 +55,7 @@ describe('Alert for insufficient funds @no-mmi', function () { async function verifyConfirmationIsDisabled(driver: Driver) { const confirmButton = await driver.findElement( - '[data-testid="confirm-alert-modal-submit-button"]', + '[data-testid="confirm-footer-button"]', ); assert.equal(await confirmButton.isEnabled(), false); } diff --git a/ui/pages/confirmations/components/confirm/footer/footer.test.tsx b/ui/pages/confirmations/components/confirm/footer/footer.test.tsx index 2c735a508422..014c583737ff 100644 --- a/ui/pages/confirmations/components/confirm/footer/footer.test.tsx +++ b/ui/pages/confirmations/components/confirm/footer/footer.test.tsx @@ -27,6 +27,7 @@ import { Severity } from '../../../../../helpers/constants/design-system'; import { SignatureRequestType } from '../../../types/confirm'; import * as confirmContext from '../../../context/confirm'; +import { Alert } from '../../../../../ducks/confirm-alerts/confirm-alerts'; import Footer from './footer'; jest.mock('react-redux', () => ({ @@ -247,7 +248,8 @@ describe('ConfirmFooter', () => { const OWNER_ID_MOCK = '123'; const KEY_ALERT_KEY_MOCK = 'Key'; const ALERT_MESSAGE_MOCK = 'Alert 1'; - const alertsMock = [ + + const alertsMock: Alert[] = [ { key: KEY_ALERT_KEY_MOCK, field: KEY_ALERT_KEY_MOCK, @@ -257,32 +259,63 @@ describe('ConfirmFooter', () => { alertDetails: ['Detail 1', 'Detail 2'], }, ]; - const stateWithAlertsMock = getMockPersonalSignConfirmStateForRequest( - { - ...unapprovedPersonalSignMsg, - id: OWNER_ID_MOCK, - msgParams: { - from: '0xc42edfcc21ed14dda456aa0756c153f7985d8813', - }, - } as SignatureRequestType, - { - confirmAlerts: { - alerts: { [OWNER_ID_MOCK]: alertsMock }, - confirmed: { - [OWNER_ID_MOCK]: { [KEY_ALERT_KEY_MOCK]: false }, + + const createStateWithAlerts = ( + alerts: Alert[], + confirmed: Record, + ) => { + return getMockPersonalSignConfirmStateForRequest( + { + ...unapprovedPersonalSignMsg, + id: OWNER_ID_MOCK, + msgParams: { + from: '0xc42edfcc21ed14dda456aa0756c153f7985d8813', }, + } as SignatureRequestType, + { + confirmAlerts: { + alerts: { [OWNER_ID_MOCK]: alerts }, + confirmed: { [OWNER_ID_MOCK]: confirmed }, + }, + metamask: {}, }, - metamask: {}, - }, - ); - it('renders the review alerts button when there are unconfirmed alerts', () => { - const { getByText } = render(stateWithAlertsMock); - expect(getByText('Confirm')).toBeInTheDocument(); + ); + }; + + const stateWithAlertsMock = createStateWithAlerts(alertsMock, { + [KEY_ALERT_KEY_MOCK]: false, }); - it('renders the confirm button when there are no unconfirmed alerts', () => { - const { getByText } = render(); - expect(getByText('Confirm')).toBeInTheDocument(); + it('renders the "review alerts" button when there are unconfirmed alerts', () => { + const stateWithMultipleDangerAlerts = createStateWithAlerts( + [ + alertsMock[0], + { + ...alertsMock[0], + key: 'From', + }, + ], + { [KEY_ALERT_KEY_MOCK]: false }, + ); + const { getByText } = render(stateWithMultipleDangerAlerts); + expect(getByText('Review alerts')).toBeInTheDocument(); + }); + + it('renders the "review alerts" button disabled when there are blocking alerts', () => { + const stateWithMultipleDangerAlerts = createStateWithAlerts( + [ + alertsMock[0], + { + ...alertsMock[0], + key: 'From', + isBlocking: true, + }, + ], + { [KEY_ALERT_KEY_MOCK]: false }, + ); + const { getByText } = render(stateWithMultipleDangerAlerts); + expect(getByText('Review alerts')).toBeInTheDocument(); + expect(getByText('Review alerts')).toBeDisabled(); }); it('sets the alert modal visible when the review alerts button is clicked', () => { @@ -290,5 +323,10 @@ describe('ConfirmFooter', () => { fireEvent.click(getByTestId('confirm-footer-button')); expect(getByTestId('confirm-alert-modal-submit-button')).toBeDefined(); }); + + it('renders the "confirm" button when there are no alerts', () => { + const { getByText } = render(); + expect(getByText('Confirm')).toBeInTheDocument(); + }); }); }); diff --git a/ui/pages/confirmations/components/confirm/footer/footer.tsx b/ui/pages/confirmations/components/confirm/footer/footer.tsx index c1666b6f238d..67f3d133afb7 100644 --- a/ui/pages/confirmations/components/confirm/footer/footer.tsx +++ b/ui/pages/confirmations/components/confirm/footer/footer.tsx @@ -37,6 +37,7 @@ import { import { useConfirmContext } from '../../../context/confirm'; import { getConfirmationSender } from '../utils'; import { MetaMetricsEventLocation } from '../../../../../../shared/constants/metametrics'; +import { Severity } from '../../../../../helpers/constants/design-system'; export type OnCancelHandler = ({ location, @@ -44,6 +45,22 @@ export type OnCancelHandler = ({ location: MetaMetricsEventLocation; }) => void; +function getButtonDisabledState( + hasUnconfirmedDangerAlerts: boolean, + hasBlockingAlerts: boolean, + disabled: boolean, +) { + if (hasBlockingAlerts) { + return true; + } + + if (hasUnconfirmedDangerAlerts) { + return false; + } + + return disabled; +} + const ConfirmButton = ({ alertOwnerId = '', disabled, @@ -63,6 +80,10 @@ const ConfirmButton = ({ const { dangerAlerts, hasDangerAlerts, hasUnconfirmedDangerAlerts } = useAlerts(alertOwnerId); + const hasDangerBlockingAlerts = dangerAlerts.some( + (alert) => alert.severity === Severity.Danger && alert.isBlocking, + ); + const handleCloseConfirmModal = useCallback(() => { setConfirmModalVisible(false); }, []); @@ -86,12 +107,16 @@ const ConfirmButton = ({ block danger data-testid="confirm-footer-button" - disabled={hasUnconfirmedDangerAlerts ? false : disabled} + disabled={getButtonDisabledState( + hasUnconfirmedDangerAlerts, + hasDangerBlockingAlerts, + disabled, + )} onClick={handleOpenConfirmModal} size={ButtonSize.Lg} startIconName={IconName.Danger} > - {dangerAlerts?.length > 1 ? t('reviewAlerts') : t('confirm')} + {dangerAlerts?.length > 0 ? t('reviewAlerts') : t('confirm')} ) : (