mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +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:
parent
ad11352199
commit
e091e77674
@ -16,7 +16,8 @@
|
|||||||
"name": null
|
"name": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"warning": null
|
"warning": null,
|
||||||
|
"customTokenAmount": "10"
|
||||||
},
|
},
|
||||||
"history": {
|
"history": {
|
||||||
"mostRecentOverviewPage": "/mostRecentOverviewPage"
|
"mostRecentOverviewPage": "/mostRecentOverviewPage"
|
||||||
|
@ -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>
|
||||||
|
`;
|
@ -3,6 +3,8 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { addHexPrefix } from 'ethereumjs-util';
|
||||||
|
|
||||||
import { I18nContext } from '../../../contexts/i18n';
|
import { I18nContext } from '../../../contexts/i18n';
|
||||||
import Box from '../../ui/box';
|
import Box from '../../ui/box';
|
||||||
import FormField from '../../ui/form-field';
|
import FormField from '../../ui/form-field';
|
||||||
@ -23,24 +25,31 @@ import {
|
|||||||
import { getCustomTokenAmount } from '../../../selectors';
|
import { getCustomTokenAmount } from '../../../selectors';
|
||||||
import { setCustomTokenAmount } from '../../../ducks/app/app';
|
import { setCustomTokenAmount } from '../../../ducks/app/app';
|
||||||
import { calcTokenAmount } from '../../../../shared/lib/transactions-controller-utils';
|
import { calcTokenAmount } from '../../../../shared/lib/transactions-controller-utils';
|
||||||
|
import { hexToDecimal } from '../../../../shared/modules/conversion.utils';
|
||||||
import {
|
import {
|
||||||
MAX_TOKEN_ALLOWANCE_AMOUNT,
|
MAX_TOKEN_ALLOWANCE_AMOUNT,
|
||||||
NUM_W_OPT_DECIMAL_COMMA_OR_DOT_REGEX,
|
NUM_W_OPT_DECIMAL_COMMA_OR_DOT_REGEX,
|
||||||
DECIMAL_REGEX,
|
DECIMAL_REGEX,
|
||||||
} from '../../../../shared/constants/tokens';
|
} from '../../../../shared/constants/tokens';
|
||||||
import { Numeric } from '../../../../shared/modules/Numeric';
|
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';
|
import { CustomSpendingCapTooltip } from './custom-spending-cap-tooltip';
|
||||||
|
|
||||||
export default function CustomSpendingCap({
|
export default function CustomSpendingCap({
|
||||||
|
txParams,
|
||||||
tokenName,
|
tokenName,
|
||||||
currentTokenBalance,
|
currentTokenBalance,
|
||||||
dappProposedValue,
|
dappProposedValue,
|
||||||
siteOrigin,
|
siteOrigin,
|
||||||
passTheErrorText,
|
passTheErrorText,
|
||||||
decimals,
|
decimals,
|
||||||
|
setInputChangeInProgress,
|
||||||
}) {
|
}) {
|
||||||
const t = useContext(I18nContext);
|
const t = useContext(I18nContext);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const { updateTransaction } = useGasFeeContext();
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
const value = useSelector(getCustomTokenAmount);
|
const value = useSelector(getCustomTokenAmount);
|
||||||
@ -97,12 +106,17 @@ export default function CustomSpendingCap({
|
|||||||
getInputTextLogic(value).description,
|
getInputTextLogic(value).description,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = (valueInput) => {
|
const handleChange = async (valueInput) => {
|
||||||
|
if (!txParams) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setInputChangeInProgress(true);
|
||||||
let spendingCapError = '';
|
let spendingCapError = '';
|
||||||
const inputTextLogic = getInputTextLogic(valueInput);
|
const inputTextLogic = getInputTextLogic(valueInput);
|
||||||
const inputTextLogicDescription = inputTextLogic.description;
|
const inputTextLogicDescription = inputTextLogic.description;
|
||||||
const match = DECIMAL_REGEX.exec(replaceCommaToDot(valueInput));
|
const match = DECIMAL_REGEX.exec(replaceCommaToDot(valueInput));
|
||||||
if (match?.[1]?.length > decimals) {
|
if (match?.[1]?.length > decimals) {
|
||||||
|
setInputChangeInProgress(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +142,28 @@ export default function CustomSpendingCap({
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch(setCustomTokenAmount(String(valueInput)));
|
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(() => {
|
useEffect(() => {
|
||||||
@ -278,6 +314,10 @@ export default function CustomSpendingCap({
|
|||||||
}
|
}
|
||||||
|
|
||||||
CustomSpendingCap.propTypes = {
|
CustomSpendingCap.propTypes = {
|
||||||
|
/**
|
||||||
|
* Transaction params
|
||||||
|
*/
|
||||||
|
txParams: PropTypes.object.isRequired,
|
||||||
/**
|
/**
|
||||||
* Displayed the token name currently tracked in description related to the input state
|
* Displayed the token name currently tracked in description related to the input state
|
||||||
*/
|
*/
|
||||||
@ -302,4 +342,8 @@ CustomSpendingCap.propTypes = {
|
|||||||
* Number of decimals
|
* Number of decimals
|
||||||
*/
|
*/
|
||||||
decimals: PropTypes.string,
|
decimals: PropTypes.string,
|
||||||
|
/**
|
||||||
|
* Updating input state to changing
|
||||||
|
*/
|
||||||
|
setInputChangeInProgress: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
@ -105,6 +105,7 @@ export default function TokenAllowance({
|
|||||||
const thisOriginIsAllowedToSkipFirstPage = ALLOWED_HOSTS.includes(hostname);
|
const thisOriginIsAllowedToSkipFirstPage = ALLOWED_HOSTS.includes(hostname);
|
||||||
|
|
||||||
const [showContractDetails, setShowContractDetails] = useState(false);
|
const [showContractDetails, setShowContractDetails] = useState(false);
|
||||||
|
const [inputChangeInProgress, setInputChangeInProgress] = useState(false);
|
||||||
const [showFullTxDetails, setShowFullTxDetails] = useState(false);
|
const [showFullTxDetails, setShowFullTxDetails] = useState(false);
|
||||||
const [isFirstPage, setIsFirstPage] = useState(
|
const [isFirstPage, setIsFirstPage] = useState(
|
||||||
dappProposedTokenAmount !== '0' && !thisOriginIsAllowedToSkipFirstPage,
|
dappProposedTokenAmount !== '0' && !thisOriginIsAllowedToSkipFirstPage,
|
||||||
@ -402,12 +403,14 @@ export default function TokenAllowance({
|
|||||||
<Box margin={[4, 4, 3, 4]}>
|
<Box margin={[4, 4, 3, 4]}>
|
||||||
{isFirstPage ? (
|
{isFirstPage ? (
|
||||||
<CustomSpendingCap
|
<CustomSpendingCap
|
||||||
|
txParams={txData?.txParams}
|
||||||
tokenName={tokenSymbol}
|
tokenName={tokenSymbol}
|
||||||
currentTokenBalance={currentTokenBalance}
|
currentTokenBalance={currentTokenBalance}
|
||||||
dappProposedValue={dappProposedTokenAmount}
|
dappProposedValue={dappProposedTokenAmount}
|
||||||
siteOrigin={origin}
|
siteOrigin={origin}
|
||||||
passTheErrorText={(value) => setErrorText(value)}
|
passTheErrorText={(value) => setErrorText(value)}
|
||||||
decimals={decimals}
|
decimals={decimals}
|
||||||
|
setInputChangeInProgress={setInputChangeInProgress}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ReviewSpendingCap
|
<ReviewSpendingCap
|
||||||
@ -525,7 +528,9 @@ export default function TokenAllowance({
|
|||||||
submitText={isFirstPage ? t('next') : t('approveButtonText')}
|
submitText={isFirstPage ? t('next') : t('approveButtonText')}
|
||||||
onCancel={() => handleReject()}
|
onCancel={() => handleReject()}
|
||||||
onSubmit={() => (isFirstPage ? handleNextClick() : handleApprove())}
|
onSubmit={() => (isFirstPage ? handleNextClick() : handleApprove())}
|
||||||
disabled={disableNextButton || disableApproveButton}
|
disabled={
|
||||||
|
inputChangeInProgress || disableNextButton || disableApproveButton
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{unapprovedTxCount > 1 && (
|
{unapprovedTxCount > 1 && (
|
||||||
<Button
|
<Button
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import configureMockStore from 'redux-mock-store';
|
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 { renderWithProvider } from '../../../test/lib/render-helpers';
|
||||||
import { KeyringType } from '../../../shared/constants/keyring';
|
import { KeyringType } from '../../../shared/constants/keyring';
|
||||||
import TokenAllowance from './token-allowance';
|
import TokenAllowance from './token-allowance';
|
||||||
@ -93,12 +93,14 @@ jest.mock('../../store/actions', () => ({
|
|||||||
updatePreviousGasParams: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
|
updatePreviousGasParams: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
|
||||||
createTransactionEventFragment: jest.fn(),
|
createTransactionEventFragment: jest.fn(),
|
||||||
updateCustomNonce: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
|
updateCustomNonce: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
|
||||||
|
estimateGas: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../contexts/gasFee', () => ({
|
jest.mock('../../contexts/gasFee', () => ({
|
||||||
useGasFeeContext: () => ({
|
useGasFeeContext: () => ({
|
||||||
maxPriorityFeePerGas: '0.1',
|
maxPriorityFeePerGas: '0.1',
|
||||||
maxFeePerGas: '0.1',
|
maxFeePerGas: '0.1',
|
||||||
|
updateTransaction: jest.fn(),
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -212,8 +214,10 @@ describe('TokenAllowancePage', () => {
|
|||||||
store,
|
store,
|
||||||
);
|
);
|
||||||
|
|
||||||
const useDefault = getByText('Use default');
|
act(() => {
|
||||||
fireEvent.click(useDefault);
|
const useDefault = getByText('Use default');
|
||||||
|
fireEvent.click(useDefault);
|
||||||
|
});
|
||||||
|
|
||||||
const input = getByTestId('custom-spending-cap-input');
|
const input = getByTestId('custom-spending-cap-input');
|
||||||
expect(input.value).toBe('1');
|
expect(input.value).toBe('1');
|
||||||
|
Loading…
Reference in New Issue
Block a user