1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Fix speed-up/cancel: don't update existing transaction data (#14355)

* Fix speed-up/cancel: don't update existing transaction data

* Move retryTxMeta state management to useGasFeeInputs.js

* Handle initial retryTxMeta set if no transaction is passed to useGasFeeInputs

* Ensure previousGas is use on retry transaction if it is available in useGasFeeInputs

* Remove update transaction mock and correctly test gas fee increase scenarios now that updateTransaction used in cancel-speedup is defined on the front end
This commit is contained in:
Dan J Miller 2022-04-14 13:20:48 -02:30 committed by ryanml
parent 54ac00027b
commit 78f68b3dab
4 changed files with 116 additions and 12 deletions

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { act, screen } from '@testing-library/react'; import { act, screen } from '@testing-library/react';
import BigNumber from 'bignumber.js';
import { import {
EDIT_GAS_MODES, EDIT_GAS_MODES,
@ -10,9 +11,47 @@ import mockEstimates from '../../../../test/data/mock-estimates.json';
import mockState from '../../../../test/data/mock-state.json'; import mockState from '../../../../test/data/mock-state.json';
import { GasFeeContextProvider } from '../../../contexts/gasFee'; import { GasFeeContextProvider } from '../../../contexts/gasFee';
import configureStore from '../../../store/store'; import configureStore from '../../../store/store';
import {
hexWEIToDecETH,
decGWEIToHexWEI,
} from '../../../helpers/utils/conversions.util';
import CancelSpeedupPopover from './cancel-speedup-popover'; import CancelSpeedupPopover from './cancel-speedup-popover';
const MAXFEEPERGAS_ABOVE_MOCK_MEDIUM_HEX = '0x174876e800';
const MAXGASCOST_ABOVE_MOCK_MEDIUM_BN = new BigNumber(
MAXFEEPERGAS_ABOVE_MOCK_MEDIUM_HEX,
16,
).times(21000, 10);
const MAXGASCOST_ABOVE_MOCK_MEDIUM_BN_PLUS_TEN_PCT_HEX = MAXGASCOST_ABOVE_MOCK_MEDIUM_BN.times(
1.1,
10,
).toString(16);
const EXPECTED_ETH_FEE_1 = hexWEIToDecETH(
MAXGASCOST_ABOVE_MOCK_MEDIUM_BN_PLUS_TEN_PCT_HEX,
);
const MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_DEC_GWEI =
mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET].gasFeeEstimates.medium
.suggestedMaxFeePerGas;
const MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_BN_WEI = new BigNumber(
decGWEIToHexWEI(MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_DEC_GWEI),
16,
);
const MAXFEEPERGAS_BELOW_MOCK_MEDIUM_HEX = MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_BN_WEI.div(
10,
10,
).toString(16);
const EXPECTED_ETH_FEE_2 = hexWEIToDecETH(
MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_BN_WEI.times(21000, 10).toString(16),
);
const MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_HEX_WEI = MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_BN_WEI.toString(
16,
);
jest.mock('../../../store/actions', () => ({ jest.mock('../../../store/actions', () => ({
disconnectGasFeeEstimatePoller: jest.fn(), disconnectGasFeeEstimatePoller: jest.fn(),
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()), getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
@ -21,7 +60,6 @@ jest.mock('../../../store/actions', () => ({
.mockImplementation(() => Promise.resolve()), .mockImplementation(() => Promise.resolve()),
addPollingTokenToAppState: jest.fn(), addPollingTokenToAppState: jest.fn(),
removePollingTokenFromAppState: jest.fn(), removePollingTokenFromAppState: jest.fn(),
updateTransaction: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
updateTransactionGasFees: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }), updateTransactionGasFees: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
updatePreviousGasParams: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }), updatePreviousGasParams: () => ({ type: 'UPDATE_TRANSACTION_PARAMS' }),
createTransactionEventFragment: jest.fn(), createTransactionEventFragment: jest.fn(),
@ -34,7 +72,10 @@ jest.mock('../../../contexts/transaction-modal', () => ({
}), }),
})); }));
const render = (props) => { const render = (
props,
maxFeePerGas = MOCK_SUGGESTED_MEDIUM_MAXFEEPERGAS_HEX_WEI,
) => {
const store = configureStore({ const store = configureStore({
metamask: { metamask: {
...mockState.metamask, ...mockState.metamask,
@ -56,7 +97,7 @@ const render = (props) => {
userFeeLevel: 'tenPercentIncreased', userFeeLevel: 'tenPercentIncreased',
txParams: { txParams: {
gas: '0x5208', gas: '0x5208',
maxFeePerGas: '0x59682f10', maxFeePerGas,
maxPriorityFeePerGas: '0x59682f00', maxPriorityFeePerGas: '0x59682f00',
}, },
}} }}
@ -80,12 +121,32 @@ describe('CancelSpeedupPopover', () => {
expect(screen.queryByText('🚀Speed Up')).toBeInTheDocument(); expect(screen.queryByText('🚀Speed Up')).toBeInTheDocument();
}); });
it('should show correct gas values', async () => { it('should show correct gas values, increased by 10%, when initial initial gas value is above estimated medium', async () => {
await act(async () => await act(async () =>
render({ render(
editGasMode: EDIT_GAS_MODES.SPEED_UP, {
}), editGasMode: EDIT_GAS_MODES.SPEED_UP,
},
MAXFEEPERGAS_ABOVE_MOCK_MEDIUM_HEX,
),
); );
expect(screen.queryAllByTitle('0.0000315 ETH').length).toBeGreaterThan(0); expect(
screen.queryAllByTitle(`${EXPECTED_ETH_FEE_1} ETH`).length,
).toBeGreaterThan(0);
});
it('should show correct gas values, set to the estimated medium, when initial initial gas value is below estimated medium', async () => {
await act(async () =>
render(
{
editGasMode: EDIT_GAS_MODES.SPEED_UP,
},
`0x${MAXFEEPERGAS_BELOW_MOCK_MEDIUM_HEX}`,
),
);
expect(
screen.queryAllByTitle(`${EXPECTED_ETH_FEE_2} ETH`).length,
).toBeGreaterThan(0);
}); });
}); });

View File

@ -1,7 +1,10 @@
import { constant, times, uniq, zip } from 'lodash'; import { constant, times, uniq, zip } from 'lodash';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { addHexPrefix } from 'ethereumjs-util'; import { addHexPrefix } from 'ethereumjs-util';
import { GAS_RECOMMENDATIONS } from '../../../shared/constants/gas'; import {
GAS_RECOMMENDATIONS,
EDIT_GAS_MODES,
} from '../../../shared/constants/gas';
import { multiplyCurrencies } from '../../../shared/modules/conversion.utils'; import { multiplyCurrencies } from '../../../shared/modules/conversion.utils';
import { import {
bnGreaterThan, bnGreaterThan,
@ -106,3 +109,16 @@ export function formatGasFeeOrFeeRange(
return `${formattedRange} GWEI`; return `${formattedRange} GWEI`;
} }
/**
* Helper method for determining whether an edit gas mode is either a speed up or cancel transaction
*
* @param {string | undefined} editGasMode - One of 'speed-up', 'cancel', 'modify-in-place', or 'swaps'
* @returns boolean
*/
export function editGasModeIsSpeedUpOrCancel(editGasMode) {
return (
editGasMode === EDIT_GAS_MODES.CANCEL ||
editGasMode === EDIT_GAS_MODES.SPEED_UP
);
}

View File

@ -17,6 +17,7 @@ import { hexToDecimal } from '../../helpers/utils/conversions.util';
import { isLegacyTransaction } from '../../helpers/utils/transactions.util'; import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
import { useGasFeeEstimates } from '../useGasFeeEstimates'; import { useGasFeeEstimates } from '../useGasFeeEstimates';
import { editGasModeIsSpeedUpOrCancel } from '../../helpers/utils/gas';
import { useGasFeeErrors } from './useGasFeeErrors'; import { useGasFeeErrors } from './useGasFeeErrors';
import { useGasPriceInput } from './useGasPriceInput'; import { useGasPriceInput } from './useGasPriceInput';
import { useMaxFeePerGasInput } from './useMaxFeePerGasInput'; import { useMaxFeePerGasInput } from './useMaxFeePerGasInput';
@ -81,7 +82,7 @@ import { useTransactionFunctions } from './useTransactionFunctions';
* *
* @param {EstimateLevel} [defaultEstimateToUse] - which estimate * @param {EstimateLevel} [defaultEstimateToUse] - which estimate
* level to default the 'estimateToUse' state variable to. * level to default the 'estimateToUse' state variable to.
* @param {object} [transaction] * @param {object} [_transaction]
* @param {string} [minimumGasLimit] * @param {string} [minimumGasLimit]
* @param {EDIT_GAS_MODES[keyof EDIT_GAS_MODES]} editGasMode * @param {EDIT_GAS_MODES[keyof EDIT_GAS_MODES]} editGasMode
* @returns {GasFeeInputReturnType & import( * @returns {GasFeeInputReturnType & import(
@ -90,10 +91,28 @@ import { useTransactionFunctions } from './useTransactionFunctions';
*/ */
export function useGasFeeInputs( export function useGasFeeInputs(
defaultEstimateToUse = GAS_RECOMMENDATIONS.MEDIUM, defaultEstimateToUse = GAS_RECOMMENDATIONS.MEDIUM,
transaction, _transaction,
minimumGasLimit = '0x5208', minimumGasLimit = '0x5208',
editGasMode = EDIT_GAS_MODES.MODIFY_IN_PLACE, editGasMode = EDIT_GAS_MODES.MODIFY_IN_PLACE,
) { ) {
const initialRetryTxMeta = {
txParams: _transaction?.txParams,
id: _transaction?.id,
userFeeLevel: _transaction?.userFeeLevel,
originalGasEstimate: _transaction?.originalGasEstimate,
userEditedGasLimit: _transaction?.userEditedGasLimit,
};
if (_transaction?.previousGas) {
initialRetryTxMeta.previousGas = _transaction?.previousGas;
}
const [retryTxMeta, setRetryTxMeta] = useState(initialRetryTxMeta);
const transaction = editGasModeIsSpeedUpOrCancel(editGasMode)
? retryTxMeta
: _transaction;
const eip1559V2Enabled = useSelector(getEIP1559V2Enabled); const eip1559V2Enabled = useSelector(getEIP1559V2Enabled);
const supportsEIP1559 = const supportsEIP1559 =
@ -272,6 +291,7 @@ export function useGasFeeInputs(
maxPriorityFeePerGas, maxPriorityFeePerGas,
minimumGasLimit, minimumGasLimit,
transaction, transaction,
setRetryTxMeta,
}); });
// When a user selects an estimate level, it will wipe out what they have // When a user selects an estimate level, it will wipe out what they have

View File

@ -6,7 +6,10 @@ import {
decimalToHex, decimalToHex,
decGWEIToHexWEI, decGWEIToHexWEI,
} from '../../helpers/utils/conversions.util'; } from '../../helpers/utils/conversions.util';
import { addTenPercentAndRound } from '../../helpers/utils/gas'; import {
addTenPercentAndRound,
editGasModeIsSpeedUpOrCancel,
} from '../../helpers/utils/gas';
import { import {
createCancelTransaction, createCancelTransaction,
createSpeedUpTransaction, createSpeedUpTransaction,
@ -24,6 +27,7 @@ export const useTransactionFunctions = ({
gasLimit: gasLimitValue, gasLimit: gasLimitValue,
maxPriorityFeePerGas: maxPriorityFeePerGasValue, maxPriorityFeePerGas: maxPriorityFeePerGasValue,
transaction, transaction,
setRetryTxMeta,
}) => { }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -87,6 +91,8 @@ export const useTransactionFunctions = ({
updateSwapsUserFeeLevel(estimateUsed || PRIORITY_LEVELS.CUSTOM), updateSwapsUserFeeLevel(estimateUsed || PRIORITY_LEVELS.CUSTOM),
); );
dispatch(updateCustomSwapsEIP1559GasParams(newGasSettings)); dispatch(updateCustomSwapsEIP1559GasParams(newGasSettings));
} else if (editGasModeIsSpeedUpOrCancel(editGasMode)) {
setRetryTxMeta(updatedTxMeta);
} else { } else {
newGasSettings.userEditedGasLimit = updatedTxMeta.userEditedGasLimit; newGasSettings.userEditedGasLimit = updatedTxMeta.userEditedGasLimit;
newGasSettings.userFeeLevel = updatedTxMeta.userFeeLevel; newGasSettings.userFeeLevel = updatedTxMeta.userFeeLevel;
@ -110,6 +116,7 @@ export const useTransactionFunctions = ({
getTxMeta, getTxMeta,
maxPriorityFeePerGasValue, maxPriorityFeePerGasValue,
transaction, transaction,
setRetryTxMeta,
], ],
); );