Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [cherrypick][V12.3.0] Redesign Approve confirmation (#26464) (1/3 permit simulation fix) #27186

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions test/data/confirmations/contract-interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,21 @@ export const genUnapprovedContractInteractionConfirmation = ({
userFeeLevel: 'medium',
verifiedOnBlockchain: false,
});

export const genUnapprovedApproveConfirmation = ({
address = CONTRACT_INTERACTION_SENDER_ADDRESS,
}: {
address?: Hex;
} = {}) => ({
...genUnapprovedContractInteractionConfirmation(),
txParams: {
from: address,
data: '0x095ea7b30000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000001',
gas: '0x16a92',
to: '0x076146c765189d51be3160a2140cf80bfc73ad68',
value: '0x0',
maxFeePerGas: '0x5b06b0c0d',
maxPriorityFeePerGas: '0x59682f00',
},
type: TransactionType.tokenMethodApprove,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
import { veryLargeDelayMs, WINDOW_TITLES } from '../../../helpers';
import { Driver } from '../../../webdriver/driver';
import { scrollAndConfirmAndAssertConfirm } from '../helpers';
import {
openDAppWithContract,
TestSuiteArguments,
toggleAdvancedDetails,
} from './shared';

const {
defaultGanacheOptions,
defaultGanacheOptionsForType2Transactions,
withFixtures,
} = require('../../../helpers');
const FixtureBuilder = require('../../../fixture-builder');
const { SMART_CONTRACTS } = require('../../../seeder/smart-contracts');

describe('Confirmation Redesign ERC721 Approve Component', function () {
const smartContract = SMART_CONTRACTS.NFTS;

describe('Submit an Approve transaction @no-mmi', function () {
it('Sends a type 0 transaction (Legacy)', async function () {
await withFixtures(
{
dapp: true,
fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToTestDapp()
.withPreferencesController({
preferences: {
redesignedConfirmationsEnabled: true,
isRedesignedConfirmationsDeveloperEnabled: true,
},
})
.build(),
ganacheOptions: defaultGanacheOptions,
smartContract,
title: this.test?.fullTitle(),
},
async ({ driver, contractRegistry }: TestSuiteArguments) => {
await openDAppWithContract(driver, contractRegistry, smartContract);

await createMintTransaction(driver);
await confirmMintTransaction(driver);

await createApproveTransaction(driver);

await assertApproveDetails(driver);
await confirmApproveTransaction(driver);
},
);
});

it('Sends a type 2 transaction (EIP1559)', async function () {
await withFixtures(
{
dapp: true,
fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToTestDapp()
.withPreferencesController({
preferences: {
redesignedConfirmationsEnabled: true,
isRedesignedConfirmationsDeveloperEnabled: true,
},
})
.build(),
ganacheOptions: defaultGanacheOptionsForType2Transactions,
smartContract,
title: this.test?.fullTitle(),
},
async ({ driver, contractRegistry }: TestSuiteArguments) => {
await openDAppWithContract(driver, contractRegistry, smartContract);

await createMintTransaction(driver);
await confirmMintTransaction(driver);

await createApproveTransaction(driver);
await assertApproveDetails(driver);
await confirmApproveTransaction(driver);
},
);
});
});
});

async function createMintTransaction(driver: Driver) {
await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
await driver.clickElement('#mintButton');
}

export async function confirmMintTransaction(driver: Driver) {
await driver.waitUntilXWindowHandles(3);

await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);

await driver.waitForSelector({
css: 'h2',
text: 'Transaction request',
});

await scrollAndConfirmAndAssertConfirm(driver);
}

async function createApproveTransaction(driver: Driver) {
await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
await driver.clickElement('#approveButton');
}

async function assertApproveDetails(driver: Driver) {
await driver.delay(veryLargeDelayMs);
await driver.waitUntilXWindowHandles(3);
await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);

await driver.waitForSelector({
css: 'h2',
text: 'Allowance request',
});

await driver.waitForSelector({
css: 'p',
text: 'This site wants permission to withdraw your NFTs',
});

await toggleAdvancedDetails(driver);

await driver.waitForSelector({
css: 'p',
text: 'Request from',
});

await driver.waitForSelector({
css: 'p',
text: 'Interacting with',
});

await driver.waitForSelector({
css: 'p',
text: 'Method',
});
}

async function confirmApproveTransaction(driver: Driver) {
await scrollAndConfirmAndAssertConfirm(driver);

await driver.delay(veryLargeDelayMs);
await driver.waitUntilXWindowHandles(2);
await driver.switchToWindowWithTitle(WINDOW_TITLES.ExtensionInFullScreenView);

await driver.clickElement({ text: 'Activity', tag: 'button' });
await driver.waitForSelector(
'.transaction-list__completed-transactions .activity-list-item:nth-of-type(1)',
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ describe('Contract Interaction Confirmation', () => {
beforeEach(() => {
jest.resetAllMocks();
setupSubmitRequestToBackgroundMocks();
mock4byte();
const MINT_NFT_HEX_SIG = '0x3b4b1381';
mock4byte(MINT_NFT_HEX_SIG);
});

afterEach(() => {
Expand Down
138 changes: 138 additions & 0 deletions test/integration/confirmations/transactions/erc721-approve.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { ApprovalType } from '@metamask/controller-utils';
import { waitFor } from '@testing-library/react';
import nock from 'nock';
import * as backgroundConnection from '../../../../ui/store/background-connection';
import { integrationTestRender } from '../../../lib/render-helpers';
import mockMetaMaskState from '../../data/integration-init-state.json';
import { createMockImplementation, mock4byte } from '../../helpers';
import { getUnapprovedApproveTransaction } from './transactionDataHelpers';

jest.mock('../../../../ui/store/background-connection', () => ({
...jest.requireActual('../../../../ui/store/background-connection'),
submitRequestToBackground: jest.fn(),
}));

const mockedBackgroundConnection = jest.mocked(backgroundConnection);

const backgroundConnectionMocked = {
onNotification: jest.fn(),
};
export const pendingTransactionId = '48a75190-45ca-11ef-9001-f3886ec2397c';
export const pendingTransactionTime = new Date().getTime();

const getMetaMaskStateWithUnapprovedApproveTransaction = (
accountAddress: string,
) => {
return {
...mockMetaMaskState,
preferences: {
...mockMetaMaskState.preferences,
redesignedConfirmationsEnabled: true,
},
pendingApprovals: {
[pendingTransactionId]: {
id: pendingTransactionId,
origin: 'origin',
time: pendingTransactionTime,
type: ApprovalType.Transaction,
requestData: {
txId: pendingTransactionId,
},
requestState: null,
expectsResult: false,
},
},
pendingApprovalCount: 1,
knownMethodData: {
'0x3b4b1381': {
name: 'Mint NFTs',
params: [
{
type: 'uint256',
},
],
},
},
transactions: [
getUnapprovedApproveTransaction(
accountAddress,
pendingTransactionId,
pendingTransactionTime,
),
],
};
};

const advancedDetailsMockedRequests = {
getGasFeeTimeEstimate: {
lowerTimeBound: new Date().getTime(),
upperTimeBound: new Date().getTime(),
},
getNextNonce: '9',
decodeTransactionData: {
data: [
{
name: 'approve',
params: [
{
type: 'address',
value: '0x2e0D7E8c45221FcA00d74a3609A0f7097035d09B',
},
{
type: 'uint256',
value: 1,
},
],
},
],
source: 'FourByte',
},
};

const setupSubmitRequestToBackgroundMocks = (
mockRequests?: Record<string, unknown>,
) => {
mockedBackgroundConnection.submitRequestToBackground.mockImplementation(
createMockImplementation({
...advancedDetailsMockedRequests,
...(mockRequests ?? {}),
}),
);
};

describe('ERC721 Approve Confirmation', () => {
beforeEach(() => {
jest.resetAllMocks();
setupSubmitRequestToBackgroundMocks();
const APPROVE_NFT_HEX_SIG = '0x095ea7b3';
mock4byte(APPROVE_NFT_HEX_SIG);
});

afterEach(() => {
nock.cleanAll();
});

it('displays approve details with correct data', async () => {
const account =
mockMetaMaskState.internalAccounts.accounts[
mockMetaMaskState.internalAccounts
.selectedAccount as keyof typeof mockMetaMaskState.internalAccounts.accounts
];

const mockedMetaMaskState =
getMetaMaskStateWithUnapprovedApproveTransaction(account.address);

const { getByText } = await integrationTestRender({
preloadedState: mockedMetaMaskState,
backgroundConnection: backgroundConnectionMocked,
});

await waitFor(() => {
expect(getByText('Allowance request')).toBeInTheDocument();
});

await waitFor(() => {
expect(getByText('Request from')).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,30 @@ export const getUnapprovedTransaction = (
};
};

export const getUnapprovedApproveTransaction = (
accountAddress: string,
pendingTransactionId: string,
pendingTransactionTime: number,
) => {
return {
...getUnapprovedTransaction(
accountAddress,
pendingTransactionId,
pendingTransactionTime,
),
txParams: {
from: accountAddress,
data: '0x095ea7b30000000000000000000000002e0d7e8c45221fca00d74a3609a0f7097035d09b0000000000000000000000000000000000000000000000000000000000000001',
gas: '0x16a92',
to: '0x076146c765189d51be3160a2140cf80bfc73ad68',
value: '0x0',
maxFeePerGas: '0x5b06b0c0d',
maxPriorityFeePerGas: '0x59682f00',
},
type: TransactionType.tokenMethodApprove,
};
};

export const getMaliciousUnapprovedTransaction = (
accountAddress: string,
pendingTransactionId: string,
Expand Down
Loading
Loading