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

Legacy transactions to use old transaction flow (#12782)

This commit is contained in:
Jyoti Puri 2021-11-29 21:38:24 +05:30 committed by GitHub
parent e8b7fcf8dc
commit 0e0e7ac830
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 264 additions and 95 deletions

View File

@ -0,0 +1,27 @@
{
"fee-market": {
"gasEstimateType": "fee-market",
"gasFeeEstimates": {
"low": {
"minWaitTimeEstimate": 180000,
"maxWaitTimeEstimate": 300000,
"suggestedMaxPriorityFeePerGas": "3",
"suggestedMaxFeePerGas": "53"
},
"medium": {
"minWaitTimeEstimate": 15000,
"maxWaitTimeEstimate": 60000,
"suggestedMaxPriorityFeePerGas": "7",
"suggestedMaxFeePerGas": "70"
},
"high": {
"minWaitTimeEstimate": 0,
"maxWaitTimeEstimate": 15000,
"suggestedMaxPriorityFeePerGas": "10",
"suggestedMaxFeePerGas": "100"
},
"estimatedBaseFee": "50"
},
"estimatedGasFeeTimeBounds": {}
}
}

View File

@ -7,9 +7,6 @@ import ActionableMessage from '../../../ui/actionable-message/actionable-message
import { PageContainerFooter } from '../../../ui/page-container'; import { PageContainerFooter } from '../../../ui/page-container';
import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.'; import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.';
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2 = process.env.EIP_1559_V2;
export default class ConfirmPageContainerContent extends Component { export default class ConfirmPageContainerContent extends Component {
static contextTypes = { static contextTypes = {
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
@ -42,7 +39,8 @@ export default class ConfirmPageContainerContent extends Component {
hideUserAcknowledgedGasMissing: PropTypes.bool, hideUserAcknowledgedGasMissing: PropTypes.bool,
unapprovedTxCount: PropTypes.number, unapprovedTxCount: PropTypes.number,
rejectNText: PropTypes.string, rejectNText: PropTypes.string,
hideTitle: PropTypes.boolean, hideTitle: PropTypes.bool,
supportsEIP1559V2: PropTypes.bool,
}; };
renderContent() { renderContent() {
@ -101,6 +99,7 @@ export default class ConfirmPageContainerContent extends Component {
hideTitle, hideTitle,
setUserAcknowledgedGasMissing, setUserAcknowledgedGasMissing,
hideUserAcknowledgedGasMissing, hideUserAcknowledgedGasMissing,
supportsEIP1559V2,
} = this.props; } = this.props;
const primaryAction = hideUserAcknowledgedGasMissing const primaryAction = hideUserAcknowledgedGasMissing
@ -141,11 +140,13 @@ export default class ConfirmPageContainerContent extends Component {
hideTitle={hideTitle} hideTitle={hideTitle}
/> />
{this.renderContent()} {this.renderContent()}
{!EIP_1559_V2 && !hasSimulationError && (errorKey || errorMessage) && ( {!supportsEIP1559V2 &&
<div className="confirm-page-container-content__error-container"> !hasSimulationError &&
<ErrorMessage errorMessage={errorMessage} errorKey={errorKey} /> (errorKey || errorMessage) && (
</div> <div className="confirm-page-container-content__error-container">
)} <ErrorMessage errorMessage={errorMessage} errorKey={errorKey} />
</div>
)}
<PageContainerFooter <PageContainerFooter
onCancel={onCancel} onCancel={onCancel}
cancelText={cancelText} cancelText={cancelText}

View File

@ -41,6 +41,8 @@ describe('Confirm Page Container Content', () => {
}); });
it('render ConfirmPageContainer component with simulation error', async () => { it('render ConfirmPageContainer component with simulation error', async () => {
process.env.EIP_1559_V2 = false;
const { queryByText, getByText } = renderWithProvider( const { queryByText, getByText } = renderWithProvider(
<ConfirmPageContainerContent {...props} />, <ConfirmPageContainerContent {...props} />,
store, store,

View File

@ -3,9 +3,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import Identicon from '../../../../ui/identicon'; import Identicon from '../../../../ui/identicon';
import { useGasFeeContext } from '../../../../../contexts/gasFee';
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2 = process.env.EIP_1559_V2;
const ConfirmPageContainerSummary = (props) => { const ConfirmPageContainerSummary = (props) => {
const { const {
@ -21,6 +19,8 @@ const ConfirmPageContainerSummary = (props) => {
hideTitle, hideTitle,
} = props; } = props;
const { supportsEIP1559V2 } = useGasFeeContext();
return ( return (
<div className={classnames('confirm-page-container-summary', className)}> <div className={classnames('confirm-page-container-summary', className)}>
{origin === 'metamask' ? null : ( {origin === 'metamask' ? null : (
@ -48,7 +48,7 @@ const ConfirmPageContainerSummary = (props) => {
</div> </div>
) : null} ) : null}
</div> </div>
{!hideSubtitle && !EIP_1559_V2 && ( {!hideSubtitle && !supportsEIP1559V2 && (
<div className="confirm-page-container-summary__subtitle"> <div className="confirm-page-container-summary__subtitle">
{subtitleComponent} {subtitleComponent}
</div> </div>

View File

@ -1,24 +1,25 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import SenderToRecipient from '../../ui/sender-to-recipient';
import { PageContainerFooter } from '../../ui/page-container';
import EditGasPopover from '../edit-gas-popover';
import { EDIT_GAS_MODES } from '../../../../shared/constants/gas'; import { EDIT_GAS_MODES } from '../../../../shared/constants/gas';
import { GasFeeContextProvider } from '../../../contexts/gasFee'; import { GasFeeContextProvider } from '../../../contexts/gasFee';
import ErrorMessage from '../../ui/error-message';
import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction'; import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
import { PageContainerFooter } from '../../ui/page-container';
import Dialog from '../../ui/dialog'; import Dialog from '../../ui/dialog';
import ErrorMessage from '../../ui/error-message';
import SenderToRecipient from '../../ui/sender-to-recipient';
import AdvancedGasFeePopover from '../advanced-gas-fee-popover'; import AdvancedGasFeePopover from '../advanced-gas-fee-popover';
import EditGasFeePopover from '../edit-gas-fee-popover'; import EditGasFeePopover from '../edit-gas-fee-popover/edit-gas-fee-popover';
import EditGasPopover from '../edit-gas-popover';
import { import {
ConfirmPageContainerHeader, ConfirmPageContainerHeader,
ConfirmPageContainerContent, ConfirmPageContainerContent,
ConfirmPageContainerNavigation, ConfirmPageContainerNavigation,
} from '.'; } from '.';
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2 = process.env.EIP_1559_V2;
export default class ConfirmPageContainer extends Component { export default class ConfirmPageContainer extends Component {
static contextTypes = { static contextTypes = {
t: PropTypes.func, t: PropTypes.func,
@ -77,6 +78,7 @@ export default class ConfirmPageContainer extends Component {
showAddToAddressBookModal: PropTypes.func, showAddToAddressBookModal: PropTypes.func,
contact: PropTypes.object, contact: PropTypes.object,
isOwnedAccount: PropTypes.bool, isOwnedAccount: PropTypes.bool,
supportsEIP1559V2: PropTypes.bool,
}; };
render() { render() {
@ -127,6 +129,7 @@ export default class ConfirmPageContainer extends Component {
showAddToAddressBookModal, showAddToAddressBookModal,
contact = {}, contact = {},
isOwnedAccount, isOwnedAccount,
supportsEIP1559V2,
} = this.props; } = this.props;
const showAddToAddressDialog = const showAddToAddressDialog =
@ -208,6 +211,7 @@ export default class ConfirmPageContainer extends Component {
origin={origin} origin={origin}
ethGasPriceWarning={ethGasPriceWarning} ethGasPriceWarning={ethGasPriceWarning}
hideTitle={hideTitle} hideTitle={hideTitle}
supportsEIP1559V2={supportsEIP1559V2}
/> />
)} )}
{shouldDisplayWarning && ( {shouldDisplayWarning && (
@ -230,7 +234,7 @@ export default class ConfirmPageContainer extends Component {
)} )}
</PageContainerFooter> </PageContainerFooter>
)} )}
{editingGas && !EIP_1559_V2 && ( {editingGas && !supportsEIP1559V2 && (
<EditGasPopover <EditGasPopover
mode={EDIT_GAS_MODES.MODIFY_IN_PLACE} mode={EDIT_GAS_MODES.MODIFY_IN_PLACE}
onClose={handleCloseEditGas} onClose={handleCloseEditGas}

View File

@ -28,8 +28,6 @@ import { useGasFeeContext } from '../../../contexts/gasFee';
// Once we reach this second threshold, we switch to minutes as a unit // Once we reach this second threshold, we switch to minutes as a unit
const SECOND_CUTOFF = 90; const SECOND_CUTOFF = 90;
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2 = process.env.EIP_1559_V2;
// Shows "seconds" as unit of time if under SECOND_CUTOFF, otherwise "minutes" // Shows "seconds" as unit of time if under SECOND_CUTOFF, otherwise "minutes"
const toHumanReadableTime = (milliseconds = 1, t) => { const toHumanReadableTime = (milliseconds = 1, t) => {
@ -50,7 +48,7 @@ export default function GasTiming({
const [customEstimatedTime, setCustomEstimatedTime] = useState(null); const [customEstimatedTime, setCustomEstimatedTime] = useState(null);
const t = useContext(I18nContext); const t = useContext(I18nContext);
const { estimateUsed } = useGasFeeContext(); const { estimateUsed, supportsEIP1559V2 } = useGasFeeContext();
// If the user has chosen a value lower than the low gas fee estimate, // If the user has chosen a value lower than the low gas fee estimate,
// We'll need to use the useEffect hook below to make a call to calculate // We'll need to use the useEffect hook below to make a call to calculate
@ -97,7 +95,7 @@ export default function GasTiming({
]); ]);
let unknownProcessingTimeText; let unknownProcessingTimeText;
if (EIP_1559_V2) { if (supportsEIP1559V2) {
unknownProcessingTimeText = t('editGasTooLow'); unknownProcessingTimeText = t('editGasTooLow');
} else { } else {
unknownProcessingTimeText = ( unknownProcessingTimeText = (
@ -155,7 +153,7 @@ export default function GasTiming({
]); ]);
} }
} else { } else {
if (!EIP_1559_V2 || estimateUsed === 'low') { if (!supportsEIP1559V2 || estimateUsed === 'low') {
attitude = 'negative'; attitude = 'negative';
} }
// If the user has chosen a value less than our low estimate, // If the user has chosen a value less than our low estimate,
@ -176,7 +174,7 @@ export default function GasTiming({
} }
} }
// code below needs to cleaned-up once EIP_1559_V2 flag is removed // code below needs to cleaned-up once EIP_1559_V2 flag is removed
else if (EIP_1559_V2) { else if (supportsEIP1559V2) {
text = t('gasTimingNegative', [ text = t('gasTimingNegative', [
toHumanReadableTime(low.maxWaitTimeEstimate, t), toHumanReadableTime(low.maxWaitTimeEstimate, t),
]); ]);
@ -199,8 +197,8 @@ export default function GasTiming({
<Typography <Typography
variant={TYPOGRAPHY.H7} variant={TYPOGRAPHY.H7}
className={classNames('gas-timing', { className={classNames('gas-timing', {
[`gas-timing--${attitude}`]: attitude && !EIP_1559_V2, [`gas-timing--${attitude}`]: attitude && !supportsEIP1559V2,
[`gas-timing--${attitude}-V2`]: attitude && EIP_1559_V2, [`gas-timing--${attitude}-V2`]: attitude && supportsEIP1559V2,
})} })}
> >
{text} {text}

View File

@ -16,9 +16,6 @@ export default function TransactionDetail({
onEdit, onEdit,
userAcknowledgedGasMissing, userAcknowledgedGasMissing,
}) { }) {
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2 = process.env.EIP_1559_V2;
const t = useI18nContext(); const t = useI18nContext();
const { const {
gasLimit, gasLimit,
@ -26,11 +23,12 @@ export default function TransactionDetail({
estimateUsed, estimateUsed,
maxFeePerGas, maxFeePerGas,
maxPriorityFeePerGas, maxPriorityFeePerGas,
supportsEIP1559V2,
transaction, transaction,
} = useGasFeeContext(); } = useGasFeeContext();
const { openModal } = useTransactionModalContext(); const { openModal } = useTransactionModalContext();
if (EIP_1559_V2 && estimateUsed) { if (supportsEIP1559V2 && estimateUsed) {
const editEnabled = !hasSimulationError || userAcknowledgedGasMissing; const editEnabled = !hasSimulationError || userAcknowledgedGasMissing;
if (!editEnabled) return null; if (!editEnabled) return null;

View File

@ -1,9 +1,13 @@
import React from 'react'; import React from 'react';
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { ETH } from '../../../helpers/constants/common'; import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
import { TRANSACTION_ENVELOPE_TYPES } from '../../../../shared/constants/transaction';
import { GasFeeContextProvider } from '../../../contexts/gasFee'; import { GasFeeContextProvider } from '../../../contexts/gasFee';
import { renderWithProvider } from '../../../../test/jest'; import { renderWithProvider } from '../../../../test/jest';
import mockEstimates from '../../../../test/data/mock-estimates.json';
import mockState from '../../../../test/data/mock-state.json';
import configureStore from '../../../store/store'; import configureStore from '../../../store/store';
import TransactionDetail from './transaction-detail.component'; import TransactionDetail from './transaction-detail.component';
@ -19,20 +23,14 @@ jest.mock('../../../store/actions', () => ({
const render = ({ componentProps, contextProps } = {}) => { const render = ({ componentProps, contextProps } = {}) => {
const store = configureStore({ const store = configureStore({
metamask: { metamask: {
nativeCurrency: ETH, ...mockState.metamask,
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
provider: {},
cachedBalances: {},
accounts: { accounts: {
'0xAddress': { [mockState.metamask.selectedAddress]: {
address: '0xAddress', address: mockState.metamask.selectedAddress,
balance: '0x176e5b6f173ebe66', balance: '0x1F4',
}, },
}, },
selectedAddress: '0xAddress', gasFeeEstimates: mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET],
featureFlags: { advancedInlineGas: true },
}, },
}); });
@ -129,4 +127,17 @@ describe('TransactionDetail', () => {
expect(screen.queryByRole('button')).toBeInTheDocument(); expect(screen.queryByRole('button')).toBeInTheDocument();
expect(screen.queryByText('Low')).toBeInTheDocument(); expect(screen.queryByText('Low')).toBeInTheDocument();
}); });
it('should render edit link with text edit for legacy transactions', () => {
render({
contextProps: {
transaction: {
userFeeLevel: 'low',
txParams: { type: TRANSACTION_ENVELOPE_TYPES.LEGACY },
},
},
});
expect(screen.queryByText('🐢')).not.toBeInTheDocument();
expect(screen.queryByText('Edit')).toBeInTheDocument();
});
}); });

View File

@ -74,6 +74,10 @@ export function useGasFeeInputs(
minimumGasLimit = '0x5208', minimumGasLimit = '0x5208',
editGasMode = EDIT_GAS_MODES.MODIFY_IN_PLACE, editGasMode = EDIT_GAS_MODES.MODIFY_IN_PLACE,
) { ) {
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2_ENABLED =
process.env.EIP_1559_V2 === true || process.env.EIP_1559_V2 === 'true';
const supportsEIP1559 = const supportsEIP1559 =
useSelector(checkNetworkAndAccountSupports1559) && useSelector(checkNetworkAndAccountSupports1559) &&
!isLegacyTransaction(transaction?.txParams); !isLegacyTransaction(transaction?.txParams);
@ -304,6 +308,7 @@ export function useGasFeeInputs(
hasGasErrors, hasGasErrors,
hasSimulationError, hasSimulationError,
supportsEIP1559, supportsEIP1559,
supportsEIP1559V2: supportsEIP1559 && EIP_1559_V2_ENABLED,
updateTransactionUsingGasFeeEstimates, updateTransactionUsingGasFeeEstimates,
}; };
} }

View File

@ -316,4 +316,38 @@ describe('useGasFeeInputs', () => {
expect(result.current.estimatedMinimumFiat).toBe(''); expect(result.current.estimatedMinimumFiat).toBe('');
}); });
}); });
describe('supportsEIP1559V2', () => {
beforeEach(() => {
configureEIP1559();
useSelector.mockImplementation(
generateUseSelectorRouter({
checkNetworkAndAccountSupports1559Response: true,
}),
);
process.env.EIP_1559_V2 = true;
});
afterEach(() => {
process.env.EIP_1559_V2 = false;
});
it('return true for fee_market transaction type', () => {
const { result } = renderHook(() =>
useGasFeeInputs(null, {
txParams: { type: TRANSACTION_ENVELOPE_TYPES.FEE_MARKET },
}),
);
expect(result.current.supportsEIP1559V2).toBe(true);
});
it('return false for legacy transaction type', () => {
const { result } = renderHook(() =>
useGasFeeInputs(null, {
txParams: { type: TRANSACTION_ENVELOPE_TYPES.LEGACY },
}),
);
expect(result.current.supportsEIP1559V2).toBe(false);
});
});
}); });

View File

@ -26,7 +26,10 @@ import {
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
} from '../../../shared/constants/transaction'; } from '../../../shared/constants/transaction';
import { getMethodName } from '../../helpers/utils/metrics'; import { getMethodName } from '../../helpers/utils/metrics';
import { getTransactionTypeTitle } from '../../helpers/utils/transactions.util'; import {
getTransactionTypeTitle,
isLegacyTransaction,
} from '../../helpers/utils/transactions.util';
import { toBuffer } from '../../../shared/modules/buffer-utils'; import { toBuffer } from '../../../shared/modules/buffer-utils';
import { TransactionModalContextProvider } from '../../contexts/transaction-modal'; import { TransactionModalContextProvider } from '../../contexts/transaction-modal';
@ -57,7 +60,8 @@ import GasDetailsItem from './gas-details-item';
import TransactionAlerts from './transaction-alerts'; import TransactionAlerts from './transaction-alerts';
// eslint-disable-next-line prefer-destructuring // eslint-disable-next-line prefer-destructuring
const EIP_1559_V2 = process.env.EIP_1559_V2; const EIP_1559_V2_ENABLED =
process.env.EIP_1559_V2 === true || process.env.EIP_1559_V2 === 'true';
const renderHeartBeatIfNotInTest = () => const renderHeartBeatIfNotInTest = () =>
process.env.IN_TEST === 'true' ? null : <LoadingHeartBeat />; process.env.IN_TEST === 'true' ? null : <LoadingHeartBeat />;
@ -420,7 +424,9 @@ export default class ConfirmTransactionBase extends Component {
) : null; ) : null;
const renderGasDetailsItem = () => { const renderGasDetailsItem = () => {
return EIP_1559_V2 ? ( return EIP_1559_V2_ENABLED &&
supportsEIP1559 &&
!isLegacyTransaction(txData) ? (
<GasDetailsItem <GasDetailsItem
key="gas_details" key="gas_details"
hexMaximumTransactionFee={hexMaximumTransactionFee} hexMaximumTransactionFee={hexMaximumTransactionFee}
@ -571,14 +577,12 @@ export default class ConfirmTransactionBase extends Component {
return ( return (
<div className="confirm-page-container-content__details"> <div className="confirm-page-container-content__details">
{EIP_1559_V2 && ( <TransactionAlerts
<TransactionAlerts setUserAcknowledgedGasMissing={() =>
setUserAcknowledgedGasMissing={() => this.setUserAcknowledgedGasMissing()
this.setUserAcknowledgedGasMissing() }
} userAcknowledgedGasMissing={userAcknowledgedGasMissing}
userAcknowledgedGasMissing={userAcknowledgedGasMissing} />
/>
)}
<TransactionDetail <TransactionDetail
disabled={isDisabled()} disabled={isDisabled()}
userAcknowledgedGasMissing={userAcknowledgedGasMissing} userAcknowledgedGasMissing={userAcknowledgedGasMissing}
@ -949,6 +953,7 @@ export default class ConfirmTransactionBase extends Component {
gasFeeIsCustom, gasFeeIsCustom,
nativeCurrency, nativeCurrency,
hardwareWalletRequiresConnection, hardwareWalletRequiresConnection,
supportsEIP1559,
} = this.props; } = this.props;
const { const {
submitting, submitting,
@ -1046,6 +1051,11 @@ export default class ConfirmTransactionBase extends Component {
editingGas={editingGas} editingGas={editingGas}
handleCloseEditGas={() => this.handleCloseEditGas()} handleCloseEditGas={() => this.handleCloseEditGas()}
currentTransaction={txData} currentTransaction={txData}
supportsEIP1559V2={
EIP_1559_V2_ENABLED &&
supportsEIP1559 &&
!isLegacyTransaction(txData)
}
/> />
</TransactionModalContextProvider> </TransactionModalContextProvider>
); );

View File

@ -27,11 +27,10 @@ const GasDetailsItem = ({
maxFeePerGas, maxFeePerGas,
maxPriorityFeePerGas, maxPriorityFeePerGas,
userAcknowledgedGasMissing, userAcknowledgedGasMissing,
txData,
useNativeCurrencyAsPrimaryCurrency, useNativeCurrencyAsPrimaryCurrency,
}) => { }) => {
const t = useI18nContext(); const t = useI18nContext();
const { estimateUsed, hasSimulationError } = useGasFeeContext(); const { estimateUsed, hasSimulationError, transaction } = useGasFeeContext();
if (hasSimulationError && !userAcknowledgedGasMissing) return null; if (hasSimulationError && !userAcknowledgedGasMissing) return null;
@ -124,10 +123,10 @@ const GasDetailsItem = ({
subTitle={ subTitle={
<GasTiming <GasTiming
maxPriorityFeePerGas={hexWEIToDecGWEI( maxPriorityFeePerGas={hexWEIToDecGWEI(
maxPriorityFeePerGas || txData.txParams.maxPriorityFeePerGas, maxPriorityFeePerGas || transaction.txParams.maxPriorityFeePerGas,
)} )}
maxFeePerGas={hexWEIToDecGWEI( maxFeePerGas={hexWEIToDecGWEI(
maxFeePerGas || txData.txParams.maxFeePerGas, maxFeePerGas || transaction.txParams.maxFeePerGas,
)} )}
/> />
} }
@ -142,7 +141,6 @@ GasDetailsItem.propTypes = {
maxFeePerGas: PropTypes.string, maxFeePerGas: PropTypes.string,
maxPriorityFeePerGas: PropTypes.string, maxPriorityFeePerGas: PropTypes.string,
userAcknowledgedGasMissing: PropTypes.bool.isRequired, userAcknowledgedGasMissing: PropTypes.bool.isRequired,
txData: PropTypes.object,
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool, useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
}; };

View File

@ -37,12 +37,8 @@ const render = ({ componentProps, contextProps } = {}) => {
}); });
return renderWithProvider( return renderWithProvider(
<GasFeeContextProvider {...contextProps}> <GasFeeContextProvider transaction={{ txParams: {} }} {...contextProps}>
<GasDetailsItem <GasDetailsItem userAcknowledgedGasMissing={false} {...componentProps} />
txData={{ txParams: {} }}
userAcknowledgedGasMissing={false}
{...componentProps}
/>
</GasFeeContextProvider>, </GasFeeContextProvider>,
store, store,
); );
@ -60,14 +56,18 @@ describe('GasDetailsItem', () => {
}); });
it('should show warning icon if estimates are high', async () => { it('should show warning icon if estimates are high', async () => {
render({ contextProps: { defaultEstimateToUse: 'high' } }); render({
contextProps: { transaction: { txParams: {}, userFeeLevel: 'high' } },
});
await waitFor(() => { await waitFor(() => {
expect(screen.queryByText('⚠ Max fee:')).toBeInTheDocument(); expect(screen.queryByText('⚠ Max fee:')).toBeInTheDocument();
}); });
}); });
it('should not show warning icon if estimates are not high', async () => { it('should not show warning icon if estimates are not high', async () => {
render({ contextProps: { defaultEstimateToUse: 'low' } }); render({
contextProps: { transaction: { txParams: {}, userFeeLevel: 'low' } },
});
await waitFor(() => { await waitFor(() => {
expect(screen.queryByText('Max fee:')).toBeInTheDocument(); expect(screen.queryByText('Max fee:')).toBeInTheDocument();
}); });
@ -76,8 +76,11 @@ describe('GasDetailsItem', () => {
it('should return null if there is simulationError and user has not acknowledged gasMissing warning', () => { it('should return null if there is simulationError and user has not acknowledged gasMissing warning', () => {
const { container } = render({ const { container } = render({
contextProps: { contextProps: {
defaultEstimateToUse: 'low', transaction: {
transaction: { simulationFails: true }, txParams: {},
simulationFails: true,
userFeeLevel: 'low',
},
}, },
}); });
expect(container.innerHTML).toHaveLength(0); expect(container.innerHTML).toHaveLength(0);

View File

@ -16,10 +16,17 @@ const TransactionAlerts = ({
userAcknowledgedGasMissing, userAcknowledgedGasMissing,
setUserAcknowledgedGasMissing, setUserAcknowledgedGasMissing,
}) => { }) => {
const { balanceError, estimateUsed, hasSimulationError } = useGasFeeContext(); const {
balanceError,
estimateUsed,
hasSimulationError,
supportsEIP1559V2,
} = useGasFeeContext();
const pendingTransactions = useSelector(submittedPendingTransactionsSelector); const pendingTransactions = useSelector(submittedPendingTransactionsSelector);
const t = useI18nContext(); const t = useI18nContext();
if (!supportsEIP1559V2) return null;
return ( return (
<div className="transaction-alerts"> <div className="transaction-alerts">
{hasSimulationError && ( {hasSimulationError && (

View File

@ -1,9 +1,14 @@
import React from 'react'; import React from 'react';
import { screen } from '@testing-library/react'; import { fireEvent, screen } from '@testing-library/react';
import { ETH } from '../../../helpers/constants/common'; import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction'; import {
TRANSACTION_ENVELOPE_TYPES,
TRANSACTION_STATUSES,
} from '../../../../shared/constants/transaction';
import { renderWithProvider } from '../../../../test/lib/render-helpers'; import { renderWithProvider } from '../../../../test/lib/render-helpers';
import mockEstimates from '../../../../test/data/mock-estimates.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';
@ -17,44 +22,54 @@ jest.mock('../../../store/actions', () => ({
addPollingTokenToAppState: jest.fn(), addPollingTokenToAppState: jest.fn(),
})); }));
const render = ({ props, state } = {}) => { const render = ({ componentProps, transactionProps, state }) => {
const store = configureStore({ const store = configureStore({
metamask: { metamask: {
nativeCurrency: ETH, ...mockState.metamask,
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
provider: {},
cachedBalances: {},
accounts: { accounts: {
'0xAddress': { [mockState.metamask.selectedAddress]: {
address: '0xAddress', address: mockState.metamask.selectedAddress,
balance: '0x1F4', balance: '0x1F4',
}, },
}, },
selectedAddress: '0xAddress', gasFeeEstimates: mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET],
...state, ...state,
}, },
}); });
return renderWithProvider( return renderWithProvider(
<GasFeeContextProvider {...props}> <GasFeeContextProvider
<TransactionAlerts /> transaction={{
txParams: {
type: TRANSACTION_ENVELOPE_TYPES.FEE_MARKET,
},
...transactionProps,
}}
>
<TransactionAlerts {...componentProps} />
</GasFeeContextProvider>, </GasFeeContextProvider>,
store, store,
); );
}; };
describe('TransactionAlerts', () => { describe('TransactionAlerts', () => {
beforeEach(() => {
process.env.EIP_1559_V2 = true;
});
afterEach(() => {
process.env.EIP_1559_V2 = false;
});
it('should returning warning message for low gas estimate', () => { it('should returning warning message for low gas estimate', () => {
render({ props: { transaction: { userFeeLevel: 'low' } } }); render({ transactionProps: { userFeeLevel: 'low' } });
expect( expect(
document.getElementsByClassName('actionable-message--warning'), document.getElementsByClassName('actionable-message--warning'),
).toHaveLength(1); ).toHaveLength(1);
}); });
it('should return null for gas estimate other than low', () => { it('should return null for gas estimate other than low', () => {
render({ props: { transaction: { userFeeLevel: 'high' } } }); render({ transactionProps: { userFeeLevel: 'high' } });
expect( expect(
document.getElementsByClassName('actionable-message--warning'), document.getElementsByClassName('actionable-message--warning'),
).toHaveLength(0); ).toHaveLength(0);
@ -62,8 +77,9 @@ describe('TransactionAlerts', () => {
it('should not show insufficient balance message if transaction value is less than balance', () => { it('should not show insufficient balance message if transaction value is less than balance', () => {
render({ render({
props: { transactionProps: {
transaction: { userFeeLevel: 'high', txParams: { value: '0x64' } }, userFeeLevel: 'high',
txParams: { value: '0x64' },
}, },
}); });
expect(screen.queryByText('Insufficient funds.')).not.toBeInTheDocument(); expect(screen.queryByText('Insufficient funds.')).not.toBeInTheDocument();
@ -71,8 +87,9 @@ describe('TransactionAlerts', () => {
it('should show insufficient balance message if transaction value is more than balance', () => { it('should show insufficient balance message if transaction value is more than balance', () => {
render({ render({
props: { transactionProps: {
transaction: { userFeeLevel: 'high', txParams: { value: '0x5208' } }, userFeeLevel: 'high',
txParams: { value: '0x5208' },
}, },
}); });
expect(screen.queryByText('Insufficient funds.')).toBeInTheDocument(); expect(screen.queryByText('Insufficient funds.')).toBeInTheDocument();
@ -86,7 +103,7 @@ describe('TransactionAlerts', () => {
id: 0, id: 0,
time: 0, time: 0,
txParams: { txParams: {
from: '0xAddress', from: mockState.metamask.selectedAddress,
to: '0xRecipient', to: '0xRecipient',
}, },
status: TRANSACTION_STATUSES.SUBMITTED, status: TRANSACTION_STATUSES.SUBMITTED,
@ -98,4 +115,58 @@ describe('TransactionAlerts', () => {
screen.queryByText('You have (1) pending transaction.'), screen.queryByText('You have (1) pending transaction.'),
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
describe('SimulationError Message', () => {
it('should show simulation error message along with option to proceed anyway if transaction.simulationFails is true', () => {
render({ transactionProps: { simulationFails: true } });
expect(
screen.queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(
screen.queryByText('I want to proceed anyway'),
).toBeInTheDocument();
});
it('should not show options to acknowledge gas-missing warning if component prop userAcknowledgedGasMissing is already true', () => {
render({
componentProps: {
userAcknowledgedGasMissing: true,
},
transactionProps: { simulationFails: true },
});
expect(
screen.queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(
screen.queryByText('I want to proceed anyway'),
).not.toBeInTheDocument();
});
it('should call prop setUserAcknowledgedGasMissing if option to acknowledge gas-missing warning is clicked', () => {
const setUserAcknowledgedGasMissing = jest.fn();
render({
componentProps: {
setUserAcknowledgedGasMissing,
},
transactionProps: { simulationFails: true },
});
fireEvent.click(screen.queryByText('I want to proceed anyway'));
expect(setUserAcknowledgedGasMissing).toHaveBeenCalledTimes(1);
});
it('should return null for legacy transactions', () => {
const { container } = render({
transactionProps: {
txParams: {
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
},
},
});
expect(container.firstChild).toBeNull();
});
});
}); });