1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

Update gas limit on token allowance change (#18524)

* Update gas limit on token allowance change

* Fix unit test case

* lint: remove unused var txParams

* fix

* fix

* fix e2e

* fix e2e

* fix e2e

* fix e2e

* fix e2e

* fix build

* fix build

* fix build

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com>
Co-authored-by: legobt <6wbvkn0j@anonaddy.me>
This commit is contained in:
Jyoti Puri 2023-04-19 04:53:45 +05:30 committed by GitHub
parent 39f6042e65
commit 5892acab81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 228 additions and 6 deletions

View File

@ -16,7 +16,8 @@
"name": null
}
},
"warning": null
"warning": null,
"customTokenAmount": "10"
},
"history": {
"mostRecentOverviewPage": "/mostRecentOverviewPage"

View File

@ -0,0 +1,107 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CustomSpendingCap should match snapshot 1`] = `
<div>
<div
class="box custom-spending-cap box--padding-top-2 box--padding-right-6 box--padding-left-6 box--display-flex box--gap-2 box--flex-direction-column box--align-items-flex-start box--background-color-background-alternative box--rounded-sm"
>
<div
class="box custom-spending-cap__input box--display-block box--flex-direction-row box--justify-content-center"
>
<label
for="custom-spending-cap"
>
<div
class="form-field"
>
<div
class="box box--flex-direction-row"
>
<div
class="form-field__heading"
>
<div
class="box form-field__heading-title box--display-flex box--flex-direction-row box--align-items-baseline"
>
<h6
class="box box--margin-top-1 box--margin-bottom-1 box--display-inline-block box--flex-direction-row typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
>
Custom spending cap
</h6>
<div
class="box box--display-inline-block box--flex-direction-row"
>
<div>
<div
aria-describedby="tippy-tooltip-1"
class=""
data-original-title="null"
data-tooltipped=""
style="display: inline;"
tabindex="0"
>
<span
class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-inherit"
style="mask-image: url('./images/icons/question.svg');"
/>
</div>
</div>
</div>
</div>
<div
class="box form-field__heading-detail box--margin-bottom-2 box--flex-direction-row box--text-align-end"
>
<button
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
>
Use default
</button>
</div>
</div>
<input
class="form-field__input"
data-testid="custom-spending-cap-input"
id="custom-spending-cap"
placeholder="Enter a number"
type="text"
value="10"
/>
</div>
</div>
<div
class="box custom-spending-cap__max box--margin-left-auto box--padding-right-4 box--padding-bottom-2 box--flex-direction-row box--text-align-end box--width-max"
>
<button
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
>
Max
</button>
</div>
<div
class="box custom-spending-cap__description box--flex-direction-row"
>
<h6
class="box mm-text mm-text--body-sm box--padding-top-2 box--padding-bottom-2 box--flex-direction-row box--color-text-default"
>
<span>
This allows the third party to spend
<h6
class="box mm-text custom-spending-cap__input-value-and-token-name mm-text--body-sm-bold box--flex-direction-row box--color-text-default"
>
10
TST
</h6>
from your current balance.
</span>
</h6>
</div>
</label>
</div>
</div>
</div>
`;

View File

@ -3,6 +3,8 @@ import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import BigNumber from 'bignumber.js';
import { addHexPrefix } from 'ethereumjs-util';
import { I18nContext } from '../../../contexts/i18n';
import Box from '../../ui/box';
import FormField from '../../ui/form-field';
@ -23,24 +25,31 @@ import {
import { getCustomTokenAmount } from '../../../selectors';
import { setCustomTokenAmount } from '../../../ducks/app/app';
import { calcTokenAmount } from '../../../../shared/lib/transactions-controller-utils';
import { hexToDecimal } from '../../../../shared/modules/conversion.utils';
import {
MAX_TOKEN_ALLOWANCE_AMOUNT,
NUM_W_OPT_DECIMAL_COMMA_OR_DOT_REGEX,
DECIMAL_REGEX,
} from '../../../../shared/constants/tokens';
import { Numeric } from '../../../../shared/modules/Numeric';
import { estimateGas } from '../../../store/actions';
import { getCustomTxParamsData } from '../../../pages/confirm-approve/confirm-approve.util';
import { useGasFeeContext } from '../../../contexts/gasFee';
import { CustomSpendingCapTooltip } from './custom-spending-cap-tooltip';
export default function CustomSpendingCap({
txParams,
tokenName,
currentTokenBalance,
dappProposedValue,
siteOrigin,
passTheErrorText,
decimals,
setInputChangeInProgress,
}) {
const t = useContext(I18nContext);
const dispatch = useDispatch();
const { updateTransaction } = useGasFeeContext();
const inputRef = useRef(null);
const value = useSelector(getCustomTokenAmount);
@ -97,12 +106,17 @@ export default function CustomSpendingCap({
getInputTextLogic(value).description,
);
const handleChange = (valueInput) => {
const handleChange = async (valueInput) => {
if (!txParams) {
return;
}
setInputChangeInProgress(true);
let spendingCapError = '';
const inputTextLogic = getInputTextLogic(valueInput);
const inputTextLogicDescription = inputTextLogic.description;
const match = DECIMAL_REGEX.exec(replaceCommaToDot(valueInput));
if (match?.[1]?.length > decimals) {
setInputChangeInProgress(false);
return;
}
@ -128,6 +142,28 @@ export default function CustomSpendingCap({
}
dispatch(setCustomTokenAmount(String(valueInput)));
try {
const newData = getCustomTxParamsData(txParams.data, {
customPermissionAmount: valueInput,
decimals,
});
const { from, to, value: txValue } = txParams;
const estimatedGasLimit = await estimateGas({
from,
to,
value: txValue,
data: newData,
});
if (estimatedGasLimit) {
await updateTransaction({
gasLimit: hexToDecimal(addHexPrefix(estimatedGasLimit)),
});
}
} catch (exp) {
console.error('Error in trying to update gas limit', exp);
}
setInputChangeInProgress(false);
};
useEffect(() => {
@ -278,6 +314,10 @@ export default function CustomSpendingCap({
}
CustomSpendingCap.propTypes = {
/**
* Transaction params
*/
txParams: PropTypes.object.isRequired,
/**
* Displayed the token name currently tracked in description related to the input state
*/
@ -302,4 +342,8 @@ CustomSpendingCap.propTypes = {
* Number of decimals
*/
decimals: PropTypes.string,
/**
* Updating input state to changing
*/
setInputChangeInProgress: PropTypes.func.isRequired,
};

View File

@ -0,0 +1,61 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import userEvent from '@testing-library/user-event';
import mockState from '../../../../test/data/mock-state.json';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import * as Actions from '../../../store/actions';
import * as GasFeeContext from '../../../contexts/gasFee';
import CustomSpendingCap from './custom-spending-cap';
const props = {
txParams: {
data: '0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
from: '0x8eeee1781fd885ff5ddef7789486676961873d12',
gas: '0xb41b',
maxFeePerGas: '0x4a817c800',
maxPriorityFeePerGas: '0x4a817c800',
to: '0x665933d73375e385bef40abcccea8b4cccc32d4c',
value: '0x0',
},
tokenName: 'TST',
currentTokenBalance: '10',
dappProposedValue: '7',
siteOrigin: 'https://metamask.github.io',
decimals: '4',
passTheErrorText: () => undefined,
setInputChangeInProgress: () => undefined,
};
describe('CustomSpendingCap', () => {
const store = configureMockStore([thunk])(mockState);
it('should match snapshot', () => {
const { container } = renderWithProvider(
<CustomSpendingCap {...props} />,
store,
);
expect(container).toMatchSnapshot();
});
it('should change in token allowance amount should call functions to update gas limit', async () => {
const user = userEvent.setup();
const spyEstimateGas = jest
.spyOn(Actions, 'estimateGas')
.mockReturnValue(Promise.resolve('1770'));
const updateTransactionMock = jest.fn();
jest
.spyOn(GasFeeContext, 'useGasFeeContext')
.mockImplementation(() => ({ updateTransaction: updateTransactionMock }));
const { getByRole } = renderWithProvider(
<CustomSpendingCap {...props} />,
store,
);
await user.type(getByRole('textbox'), '5');
expect(spyEstimateGas).toHaveBeenCalledTimes(1);
expect(updateTransactionMock).toHaveBeenCalledTimes(1);
});
});

View File

@ -105,6 +105,7 @@ export default function TokenAllowance({
const thisOriginIsAllowedToSkipFirstPage = ALLOWED_HOSTS.includes(hostname);
const [showContractDetails, setShowContractDetails] = useState(false);
const [inputChangeInProgress, setInputChangeInProgress] = useState(false);
const [showFullTxDetails, setShowFullTxDetails] = useState(false);
const [isFirstPage, setIsFirstPage] = useState(
dappProposedTokenAmount !== '0' && !thisOriginIsAllowedToSkipFirstPage,
@ -402,12 +403,14 @@ export default function TokenAllowance({
<Box margin={[4, 4, 3, 4]}>
{isFirstPage ? (
<CustomSpendingCap
txParams={txData?.txParams}
tokenName={tokenSymbol}
currentTokenBalance={currentTokenBalance}
dappProposedValue={dappProposedTokenAmount}
siteOrigin={origin}
passTheErrorText={(value) => setErrorText(value)}
decimals={decimals}
setInputChangeInProgress={setInputChangeInProgress}
/>
) : (
<ReviewSpendingCap
@ -525,7 +528,9 @@ export default function TokenAllowance({
submitText={isFirstPage ? t('next') : t('approveButtonText')}
onCancel={() => handleReject()}
onSubmit={() => (isFirstPage ? handleNextClick() : handleApprove())}
disabled={disableNextButton || disableApproveButton}
disabled={
inputChangeInProgress || disableNextButton || disableApproveButton
}
>
{unapprovedTxCount > 1 && (
<Button

View File

@ -1,6 +1,6 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { fireEvent } from '@testing-library/react';
import { act, fireEvent } from '@testing-library/react';
import { renderWithProvider } from '../../../test/lib/render-helpers';
import { KeyringType } from '../../../shared/constants/keyring';
import TokenAllowance from './token-allowance';
@ -96,12 +96,14 @@ jest.mock('../../store/actions', () => ({
updatePreviousGasParams: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
createTransactionEventFragment: jest.fn(),
updateCustomNonce: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
estimateGas: jest.fn().mockImplementation(() => Promise.resolve()),
}));
jest.mock('../../contexts/gasFee', () => ({
useGasFeeContext: () => ({
maxPriorityFeePerGas: '0.1',
maxFeePerGas: '0.1',
updateTransaction: jest.fn(),
}),
}));
@ -215,8 +217,10 @@ describe('TokenAllowancePage', () => {
store,
);
act(() => {
const useDefault = getByText('Use default');
fireEvent.click(useDefault);
});
const input = getByTestId('custom-spending-cap-input');
expect(input.value).toBe('1');