Skip to content

Commit

Permalink
feat: ERC20 Revoke Allowance
Browse files Browse the repository at this point in the history
  • Loading branch information
pedronfigueiredo committed Sep 5, 2024
1 parent 2e3e57d commit 2c95417
Show file tree
Hide file tree
Showing 16 changed files with 383 additions and 107 deletions.
6 changes: 6 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
Expand Up @@ -87,7 +87,7 @@ describe('Confirmation Redesign ERC20 Approve Component', function () {
});
});

async function mocked4Bytes(mockServer: MockttpServer) {
export async function mocked4BytesApprove(mockServer: MockttpServer) {
return await mockServer
.forGet('https://www.4byte.directory/api/v1/signatures/')
.withQuery({ hex_signature: '0x095ea7b3' })
Expand All @@ -111,7 +111,7 @@ async function mocked4Bytes(mockServer: MockttpServer) {
}

async function mocks(server: MockttpServer) {
return [await mocked4Bytes(server)];
return [await mocked4BytesApprove(server)];
}

export async function importTST(driver: Driver) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { Driver } from '../../../webdriver/driver';
import { scrollAndConfirmAndAssertConfirm } from '../helpers';
import { openDAppWithContract, TestSuiteArguments } from './shared';
import { mocked4BytesApprove } from './erc20-approve-redesign.spec';

const {
defaultGanacheOptions,
Expand Down Expand Up @@ -155,7 +156,7 @@ describe('Confirmation Redesign ERC20 Increase Allowance', function () {
});
});

async function mocked4BytesIncreaseAllowance(mockServer: MockttpServer) {
export async function mocked4BytesIncreaseAllowance(mockServer: MockttpServer) {
return await mockServer
.forGet('https://www.4byte.directory/api/v1/signatures/')
.withQuery({ hex_signature: '0x39509351' })
Expand Down Expand Up @@ -205,7 +206,7 @@ async function editSpendingCap(driver: Driver, newSpendingCap: string) {
await driver.delay(veryLargeDelayMs * 2);
}

async function assertChangedSpendingCap(
export async function assertChangedSpendingCap(
driver: Driver,
newSpendingCap: string,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
import { MockttpServer } from 'mockttp';
import { WINDOW_TITLES } from '../../../helpers';
import { Driver } from '../../../webdriver/driver';
import { scrollAndConfirmAndAssertConfirm } from '../helpers';
import { mocked4BytesApprove } from './erc20-approve-redesign.spec';
import {
assertChangedSpendingCap,
editSpendingCap,
} from './increase-token-allowance-redesign.spec';
import { openDAppWithContract, TestSuiteArguments } from './shared';

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

describe('Confirmation Redesign ERC20 Revoke Allowance', function () {
const smartContract = SMART_CONTRACTS.HST;

describe('Submit an revoke 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,
testSpecificMock: mocks,
title: this.test?.fullTitle(),
},
async ({ driver, contractRegistry }: TestSuiteArguments) => {
await openDAppWithContract(driver, contractRegistry, smartContract);

await createERC20ApproveTransaction(driver);

const NEW_SPENDING_CAP = '0';
await editSpendingCap(driver, NEW_SPENDING_CAP);

await driver.waitForSelector({
css: 'h2',
text: 'Remove permission',
});

await scrollAndConfirmAndAssertConfirm(driver);

await assertChangedSpendingCap(driver, NEW_SPENDING_CAP);
},
);
});

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,
testSpecificMock: mocks,
title: this.test?.fullTitle(),
},
async ({ driver, contractRegistry }: TestSuiteArguments) => {
await openDAppWithContract(driver, contractRegistry, smartContract);

await createERC20ApproveTransaction(driver);

const NEW_SPENDING_CAP = '0';
await editSpendingCap(driver, NEW_SPENDING_CAP);

await driver.waitForSelector({
css: 'h2',
text: 'Remove permission',
});

await scrollAndConfirmAndAssertConfirm(driver);

await assertChangedSpendingCap(driver, NEW_SPENDING_CAP);
},
);
});
});
});

async function mocks(server: MockttpServer) {
return [await mocked4BytesApprove(server)];
}

async function createERC20ApproveTransaction(driver: Driver) {
await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
await driver.clickElement('#approveTokens');
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NameType } from '@metamask/name-controller';
import { TransactionMeta } from '@metamask/transaction-controller';
import React from 'react';
import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row';
import Name from '../../../../../../../components/app/name';
import { Box, Text } from '../../../../../../../components/component-library';
import Tooltip from '../../../../../../../components/ui/tooltip';
Expand Down Expand Up @@ -65,33 +66,38 @@ export const ApproveStaticSimulation = () => {
);

const simulationElements = (
<Box display={Display.Flex}>
<Box
display={Display.Inline}
marginInlineEnd={1}
minWidth={BlockSize.Zero}
>
{spendingCap === UNLIMITED_MSG ? (
<Tooltip title={formattedSpendingCap}>{formattedTokenText}</Tooltip>
) : (
formattedTokenText
)}
<ConfirmInfoRow
label={isNFT ? t('simulationApproveHeading') : t('spendingCap')}
>
<Box style={{ marginLeft: 'auto', maxWidth: '100%' }}>
<Box display={Display.Flex}>
<Box
display={Display.Inline}
marginInlineEnd={1}
minWidth={BlockSize.Zero}
>
{spendingCap === UNLIMITED_MSG ? (
<Tooltip title={formattedSpendingCap}>
{formattedTokenText}
</Tooltip>
) : (
formattedTokenText
)}
</Box>
<Name
value={transactionMeta.txParams.to as string}
type={NameType.ETHEREUM_ADDRESS}
/>
</Box>
</Box>
<Name
value={transactionMeta.txParams.to as string}
type={NameType.ETHEREUM_ADDRESS}
/>
</Box>
</ConfirmInfoRow>
);

return (
<StaticSimulation
title={t('simulationDetailsTitle')}
titleTooltip={t('simulationDetailsTitleTooltip')}
description={t('simulationDetailsApproveDesc')}
simulationHeading={
isNFT ? t('simulationApproveHeading') : t('spendingCap')
}
simulationElements={simulationElements}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import thunk from 'redux-thunk';
import { getMockApproveConfirmState } from '../../../../../../../test/data/confirmations/helper';
import { renderWithConfirmContextProvider } from '../../../../../../../test/lib/confirmations/render-helpers';
import ApproveInfo from './approve';
import { SpendingCapProvider } from './spending-cap-context';

jest.mock('../../../../../../store/actions', () => ({
...jest.requireActual('../../../../../../store/actions'),
Expand Down Expand Up @@ -69,7 +70,9 @@ describe('<ApproveInfo />', () => {
const mockStore = configureMockStore(middleware)(state);

const { container } = renderWithConfirmContextProvider(
<ApproveInfo />,
<SpendingCapProvider>
<ApproveInfo />
</SpendingCapProvider>,
mockStore,
);

Expand Down
25 changes: 20 additions & 5 deletions ui/pages/confirmations/components/confirm/info/approve/approve.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { TransactionMeta } from '@metamask/transaction-controller';
import {
TransactionMeta,
TransactionType,
} from '@metamask/transaction-controller';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useConfirmContext } from '../../../../context/confirm';
Expand All @@ -9,6 +12,9 @@ import { ApproveDetails } from './approve-details/approve-details';
import { ApproveStaticSimulation } from './approve-static-simulation/approve-static-simulation';
import { EditSpendingCapModal } from './edit-spending-cap-modal/edit-spending-cap-modal';
import { useIsNFT } from './hooks/use-is-nft';
import { RevokeDetails } from './revoke-details/revoke-details';
import { RevokeStaticSimulation } from './revoke-static-simulation/revoke-static-simulation';
import { useSpendingCapContext } from './spending-cap-context';
import { SpendingCap } from './spending-cap/spending-cap';

const ApproveInfo = () => {
Expand All @@ -24,7 +30,8 @@ const ApproveInfo = () => {

const [isOpenEditSpendingCapModal, setIsOpenEditSpendingCapModal] =
useState(false);
const [customSpendingCap, setCustomSpendingCap] = useState('');

const { customSpendingCap, setCustomSpendingCap } = useSpendingCapContext();

const setCustomSpendingCapCandidate = (newValue: string) => {
const value = parseInt(newValue, 10);
Expand All @@ -36,11 +43,19 @@ const ApproveInfo = () => {
return null;
}

const showRevokeScreen =
customSpendingCap === '0' &&
transactionMeta.type === TransactionType.tokenMethodApprove;

return (
<>
<ApproveStaticSimulation />
<ApproveDetails />
{!isNFT && (
{showRevokeScreen ? (
<RevokeStaticSimulation />
) : (
<ApproveStaticSimulation />
)}
{showRevokeScreen ? <RevokeDetails /> : <ApproveDetails />}
{!isNFT && !showRevokeScreen && (
<SpendingCap
setIsOpenEditSpendingCapModal={setIsOpenEditSpendingCapModal}
customSpendingCap={customSpendingCap}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useState } from 'react';

export const useCustomSpendingCap = () => {
const [customSpendingCap, setCustomSpendingCap] = useState('');

return { customSpendingCap, setCustomSpendingCap };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { ConfirmInfoSection } from '../../../../../../../components/app/confirm/info/row/section';
import { OriginRow } from '../../shared/transaction-details/transaction-details';

export const RevokeDetails = () => {
return (
<ConfirmInfoSection>
<OriginRow />
</ConfirmInfoSection>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { NameType } from '@metamask/name-controller';
import { TransactionMeta } from '@metamask/transaction-controller';
import React from 'react';
import { ConfirmInfoRow } from '../../../../../../../components/app/confirm/info/row';
import Name from '../../../../../../../components/app/name';
import { Box } from '../../../../../../../components/component-library';
import { Display } from '../../../../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../../../../hooks/useI18nContext';
import { useConfirmContext } from '../../../../../context/confirm';
import StaticSimulation from '../../shared/static-simulation/static-simulation';

export const RevokeStaticSimulation = () => {
const t = useI18nContext();

const { currentConfirmation: transactionMeta } = useConfirmContext() as {
currentConfirmation: TransactionMeta;
};

const simulationElements = (
<>
<ConfirmInfoRow label={t('spendingCap')}>
<Box style={{ marginLeft: 'auto', maxWidth: '100%' }}>
<Box display={Display.Flex}>
<Name
value={transactionMeta.txParams.to as string}
type={NameType.ETHEREUM_ADDRESS}
/>
</Box>
</Box>
</ConfirmInfoRow>

<ConfirmInfoRow label={t('spender')}>
<Box style={{ marginLeft: 'auto', maxWidth: '100%' }}>
<Box display={Display.Flex}>
<Name
value={transactionMeta.txParams.from as string}
type={NameType.ETHEREUM_ADDRESS}
/>
</Box>
</Box>
</ConfirmInfoRow>
</>
);

return (
<StaticSimulation
title={t('simulationDetailsTitle')}
titleTooltip={t('simulationDetailsTitleTooltip')}
description={t('revokeSimulationDetailsDesc')}
simulationElements={simulationElements}
/>
);
};
Loading

0 comments on commit 2c95417

Please sign in to comment.