Skip to content

Commit

Permalink
Adds confirmation screen for 'increaseAllowance'
Browse files Browse the repository at this point in the history
  • Loading branch information
pedronfigueiredo committed Mar 25, 2024
1 parent fc0e886 commit 4335463
Show file tree
Hide file tree
Showing 14 changed files with 361 additions and 6 deletions.
4 changes: 4 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.

1 change: 1 addition & 0 deletions app/scripts/lib/transaction/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,7 @@ async function buildEventFragmentProperties({
TransactionType.tokenMethodSetApprovalForAll,
TransactionType.tokenMethodTransfer,
TransactionType.tokenMethodTransferFrom,
TransactionType.tokenMethodIncreaseAllowance,
TransactionType.smart,
TransactionType.swap,
TransactionType.swapApproval,
Expand Down
17 changes: 16 additions & 1 deletion shared/modules/transaction.utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { isHexString } from 'ethereumjs-util';
import { Interface } from '@ethersproject/abi';
import { abiERC721, abiERC20, abiERC1155 } from '@metamask/metamask-eth-abis';
import {
abiERC721,
abiERC20,
abiERC1155,
USDC_ABI,
} from '@metamask/metamask-eth-abis';
import type EthQuery from '@metamask/eth-query';
import log from 'loglevel';
import {
Expand All @@ -18,6 +23,7 @@ const INFERRABLE_TRANSACTION_TYPES: TransactionType[] = [
TransactionType.tokenMethodSetApprovalForAll,
TransactionType.tokenMethodTransfer,
TransactionType.tokenMethodTransferFrom,
TransactionType.tokenMethodIncreaseAllowance,
TransactionType.contractInteraction,
TransactionType.simpleSend,
];
Expand All @@ -32,6 +38,7 @@ type InferTransactionTypeResult = {
const erc20Interface = new Interface(abiERC20);
const erc721Interface = new Interface(abiERC721);
const erc1155Interface = new Interface(abiERC1155);
const USDCInterface = new Interface(USDC_ABI);

/**
* Determines if the maxFeePerGas and maxPriorityFeePerGas fields are supplied
Expand Down Expand Up @@ -116,6 +123,12 @@ export function parseStandardTokenTransactionData(data: string) {
// ignore and return undefined
}

try {
return USDCInterface.parseTransaction({ data });
} catch {
// ignore and return undefined
}

return undefined;
}

Expand Down Expand Up @@ -169,6 +182,7 @@ export async function determineTransactionType(
TransactionType.tokenMethodSetApprovalForAll,
TransactionType.tokenMethodTransfer,
TransactionType.tokenMethodTransferFrom,
TransactionType.tokenMethodIncreaseAllowance,
TransactionType.tokenMethodSafeTransferFrom,
].find((methodName) => isEqualCaseInsensitive(methodName, name));
return {
Expand Down Expand Up @@ -227,6 +241,7 @@ export async function determineTransactionAssetType(
TransactionType.tokenMethodSetApprovalForAll,
TransactionType.tokenMethodTransfer,
TransactionType.tokenMethodTransferFrom,
TransactionType.tokenMethodIncreaseAllowance,
].find((methodName) => methodName === inferrableType);

if (
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,9 @@ const PRIVATE_KEY =
const PRIVATE_KEY_TWO =
'0xa444f52ea41e3a39586d7069cb8e8233e9f6b9dea9cbb700cce69ae860661cc8';

const ACCOUNT_1 = '0x5cfe73b6021e818b776b421b1c4db2474086a7e1';
const ACCOUNT_2 = '0x09781764c08de8ca82e156bbf156a3ca217c7950';

const defaultGanacheOptions = {
accounts: [{ secretKey: PRIVATE_KEY, balance: convertETHToHexGwei(25) }],
};
Expand Down Expand Up @@ -1090,6 +1093,8 @@ module.exports = {
TEST_SEED_PHRASE_TWO,
PRIVATE_KEY,
PRIVATE_KEY_TWO,
ACCOUNT_1,
ACCOUNT_2,
getWindowHandles,
convertToHexValue,
tinyDelayMs,
Expand Down
277 changes: 277 additions & 0 deletions test/e2e/tests/tokens/increase-token-allowance.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
const FixtureBuilder = require('../../fixture-builder');
const {
defaultGanacheOptions,
openDapp,
sendTransaction,
unlockWallet,
withFixtures,
ACCOUNT_1,
ACCOUNT_2,
WINDOW_TITLES,
} = require('../../helpers');
const { SMART_CONTRACTS } = require('../../seeder/smart-contracts');

describe('Increase Token Allowance', function () {
const smartContract = SMART_CONTRACTS.HST;

it('increases token spending cap to allow other accounts to transfer tokens @no-mmi', async function () {
await withFixtures(
{
dapp: true,
fixtures: new FixtureBuilder()
.withPermissionControllerConnectedToTestDapp()
.build(),
ganacheOptions: defaultGanacheOptions,
smartContract,
title: this.test.fullTitle(),
},
async ({ driver, contractRegistry }) => {
const ACCOUNT_1_NAME = 'Account 1';
const ACCOUNT_2_NAME = '2nd Account';

const initialSpendingCap = '1';
const additionalSpendingCap = '1';

await unlockWallet(driver);

const contractAddress = await contractRegistry.getContractAddress(
smartContract,
);
await openDapp(driver, contractAddress);

await deployTokenContract(driver);
await approveTokenSpendingCapTo(driver, ACCOUNT_2, initialSpendingCap);

await sendTransaction(driver, ACCOUNT_2, '1');
await addAccount(driver, ACCOUNT_2_NAME);

await triggerTransferFromTokens(driver, ACCOUNT_1, ACCOUNT_2);
// 'Transfer From Tokens' on the test dApp attempts to transfer 1.5 TST.
// Since this is higher than the 'initialSpendingCap', it should fail.
await pollForTokenAddressesError(
driver,
'reverted ERC20: insufficient allowance',
);

await switchToAccountWithName(driver, ACCOUNT_1_NAME);

await increaseTokenAllowance(driver, additionalSpendingCap);

await switchToAccountWithName(driver, ACCOUNT_2_NAME);
await triggerTransferFromTokens(driver, ACCOUNT_1, ACCOUNT_2);
await confirmTransferFromTokensSuccess(driver);
},
);
});

async function deployTokenContract(driver) {
await driver.findClickableElement('#deployButton');
}

async function approveTokenSpendingCapTo(
driver,
accountToApproveFor,
initialSpendingCap,
) {
await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);

let approveToFillEl = await driver.findElement('[id="approveTo"]');
await approveToFillEl.clear();
await approveToFillEl.fill(accountToApproveFor);

await driver.clickElement({ text: 'Approve Tokens', tag: 'button' });

await driver.switchToWindowWithTitle(
WINDOW_TITLES.ExtensionInFullScreenView,
);
await driver.clickElement({ tag: 'button', text: 'Activity' });

const pendingTransactions = await driver.findElements(
'.transaction-list__pending-transactions .activity-list-item',
);
pendingTransactions[0].click();

let setSpendingCap = await driver.findElement(
'[data-testid="custom-spending-cap-input"]',
);
await setSpendingCap.fill(initialSpendingCap);

await driver.clickElement({
tag: 'button',
text: 'Next',
});
driver.waitForSelector({
css: '.box--display-flex > h6',
text: `10 TST`,
});
await driver.waitForSelector({
text: `${initialSpendingCap} TST`,
css: '.mm-box > h6',
});
await driver.clickElement({
tag: 'button',
text: 'Approve',
});

await driver.waitForSelector({
css: '.transaction-list__completed-transactions .activity-list-item [data-testid="activity-list-item-action"]',
text: 'Approve TST spending cap',
});
}

async function addAccount(driver, newAccountName) {
await driver.clickElement('[data-testid="account-menu-icon"]');
await driver.clickElement(
'[data-testid="multichain-account-menu-popover-action-button"]',
);
await driver.clickElement(
'[data-testid="multichain-account-menu-popover-add-account"]',
);

await driver.fill('[placeholder="Account 2"]', newAccountName);
await driver.clickElement({ text: 'Create', tag: 'button' });
await driver.findElement({
css: '[data-testid="account-menu-icon"]',
text: newAccountName,
});
}

async function triggerTransferFromTokens(
driver,
senderAccount,
recipientAccount,
) {
await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
let transferFromSenderInputEl = await driver.findElement(
'[id="transferFromSenderInput"]',
);
await transferFromSenderInputEl.clear();
await transferFromSenderInputEl.fill(senderAccount);

let transferFromRecipientInputEl = await driver.findElement(
'[id="transferFromRecipientInput"]',
);
await transferFromRecipientInputEl.clear();
await transferFromRecipientInputEl.fill(recipientAccount);

await driver.clickElement({
text: 'Transfer From Tokens',
tag: 'button',
});
}

async function pollForTokenAddressesError(
driver,
errorMessagePart,
timeout = driver.timeout,
) {
const pollInterval = 500;
let elapsedTime = 0;

await new Promise((resolve, reject) => {
const pollInsufficientAllowanceError = setInterval(async () => {
try {
const tokenAddressesElement = await driver.findElement(
'#tokenAddresses',
);
const tokenAddressesMsgText = await tokenAddressesElement.getText();
const isErrorThrown =
tokenAddressesMsgText.includes(errorMessagePart);

if (isErrorThrown) {
// Condition satisfied, stopping poll.
clearInterval(pollInsufficientAllowanceError);
resolve();
} else {
elapsedTime += pollInterval;
if (elapsedTime >= timeout) {
// Timeout reached, stopping poll.
clearInterval(pollInsufficientAllowanceError);
reject(
new Error(
`Did not throw '${errorMessagePart}' error as expected. Timeout reached, stopping poll.`,
),
);
}
}
} catch (error) {
clearInterval(pollInsufficientAllowanceError);
reject(error);
}
}, pollInterval);
});
}

async function switchToAccountWithName(driver, accountName) {
await driver.switchToWindowWithTitle(
WINDOW_TITLES.ExtensionInFullScreenView,
);
await driver.clickElement('[data-testid="account-menu-icon"]');

await driver.findElement({
css: `.multichain-account-list-item .multichain-account-list-item__account-name__button`,
text: accountName,
});

await driver.clickElement({
css: `.multichain-account-list-item .multichain-account-list-item__account-name__button`,
text: accountName,
});
}

async function increaseTokenAllowance(driver, finalSpendingCap) {
await driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp);
await driver.clickElement({
text: 'Increase Token Allowance',
tag: 'button',
});

await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
let setSpendingCap = await driver.findElement(
'[data-testid="custom-spending-cap-input"]',
);
await setSpendingCap.fill(finalSpendingCap);

await driver.clickElement({
tag: 'button',
text: 'Next',
});
driver.waitForSelector({
css: '.box--display-flex > h6',
text: `10 TST`,
});
await driver.waitForSelector({
text: `${finalSpendingCap} TST`,
css: '.mm-box > h6',
});
await driver.clickElement({
tag: 'button',
text: 'Approve',
});

await driver.switchToWindowWithTitle(
WINDOW_TITLES.ExtensionInFullScreenView,
);
await driver.clickElement({ tag: 'button', text: 'Activity' });
await driver.waitForSelector({
css: '.transaction-list__completed-transactions .activity-list-item [data-testid="activity-list-item-action"]',
text: 'Increase TST spending cap',
});
}

async function confirmTransferFromTokensSuccess(driver) {
await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
await driver.waitForSelector({ text: '1.5 TST', tag: 'h1' });
await driver.clickElement({ text: 'Confirm', tag: 'button' });

await driver.switchToWindowWithTitle(
WINDOW_TITLES.ExtensionInFullScreenView,
);
await driver.clickElement({ tag: 'button', text: 'Activity' });

await driver.waitForSelector({
css: '.transaction-list__completed-transactions .activity-list-item [data-testid="activity-list-item-action"]',
text: 'Send TST',
});
}
});
4 changes: 4 additions & 0 deletions ui/helpers/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const CONFIRM_SET_APPROVAL_FOR_ALL_PATH = '/set-approval-for-all';
const CONFIRM_TRANSFER_FROM_PATH = '/transfer-from';
const CONFIRM_SAFE_TRANSFER_FROM_PATH = '/safe-transfer-from';
const CONFIRM_TOKEN_METHOD_PATH = '/token-method';
const CONFIRM_INCREASE_ALLOWANCE_PATH = '/increase-allowance';
const SIGNATURE_REQUEST_PATH = '/signature-request';
const DECRYPT_MESSAGE_REQUEST_PATH = '/decrypt-message-request';
const ENCRYPTION_PUBLIC_KEY_REQUEST_PATH = '/encryption-public-key-request';
Expand Down Expand Up @@ -178,6 +179,8 @@ const PATH_NAME_MAP = {
'Confirm Approve Transaction Page',
[`${CONFIRM_TRANSACTION_ROUTE}/:id${CONFIRM_SET_APPROVAL_FOR_ALL_PATH}`]:
'Confirm Set Approval For All Transaction Page',
[`${CONFIRM_TRANSACTION_ROUTE}/:id${CONFIRM_INCREASE_ALLOWANCE_PATH}`]:
'Confirm Increase Allowance Transaction Page',
[`${CONFIRM_TRANSACTION_ROUTE}/:id${CONFIRM_TRANSFER_FROM_PATH}`]:
'Confirm Transfer From Transaction Page',
[`${CONFIRM_TRANSACTION_ROUTE}/:id${CONFIRM_SAFE_TRANSFER_FROM_PATH}`]:
Expand Down Expand Up @@ -226,6 +229,7 @@ export {
CONFIRM_TRANSFER_FROM_PATH,
CONFIRM_SAFE_TRANSFER_FROM_PATH,
CONFIRM_TOKEN_METHOD_PATH,
CONFIRM_INCREASE_ALLOWANCE_PATH,
SIGNATURE_REQUEST_PATH,
DECRYPT_MESSAGE_REQUEST_PATH,
ENCRYPTION_PUBLIC_KEY_REQUEST_PATH,
Expand Down
Loading

0 comments on commit 4335463

Please sign in to comment.