Skip to content

Commit

Permalink
feat: Redesign Approve confirmation
Browse files Browse the repository at this point in the history
  • Loading branch information
pedronfigueiredo committed Aug 16, 2024
1 parent 7916145 commit ea70ebc
Show file tree
Hide file tree
Showing 13 changed files with 437 additions and 9 deletions.
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.

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 contract interaction 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 contract interaction 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
6 changes: 3 additions & 3 deletions test/integration/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ export const createMockImplementation = <T,>(requests: Record<string, T>) => {
};
};

export function mock4byte() {
export function mock4byte(hexSignature: string) {
const mockEndpoint = nock('https://www.4byte.directory:443', {
encodedQueryParams: true,
})
.persist()
.get('/api/v1/signatures/')
.query({ hex_signature: '0x3b4b1381' })
.query({ hex_signature: hexSignature })
.reply(200, {
results: [
{
id: 235447,
created_at: '2021-09-14T02:07:09.805000Z',
text_signature: 'mintNFTs(uint256)',
hex_signature: '0x3b4b1381',
hex_signature: hexSignature,
bytes_signature: ';K\u0013 ',
},
],
Expand Down
Loading

0 comments on commit ea70ebc

Please sign in to comment.