Skip to content

Commit

Permalink
Display a warning and gas fee component for token allowance and NFT f…
Browse files Browse the repository at this point in the history
…low when transaction is expected to fail (#17437)

Co-authored-by: Pedro Figueiredo <pedro.figueiredo@consensys.net>
Co-authored-by: Dan J Miller <danjm.com@gmail.com>
Co-authored-by: Brad Decker <bhdecker84@gmail.com>
  • Loading branch information
4 people authored Feb 1, 2023
1 parent fd81945 commit 308151c
Show file tree
Hide file tree
Showing 12 changed files with 369 additions and 42 deletions.
29 changes: 24 additions & 5 deletions ui/components/app/approve-content-card/approve-content-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export default function ApproveContentCard({
isSetApproveForAll,
isApprovalOrRejection,
data,
userAcknowledgedGasMissing,
renderSimulationFailureWarning,
}) {
const t = useContext(I18nContext);

Expand Down Expand Up @@ -91,9 +93,14 @@ export default function ApproveContentCard({
</Button>
</Box>
)}
{showEdit && showAdvanceGasFeeOptions && supportsEIP1559 && (
<EditGasFeeButton />
)}
{showEdit &&
showAdvanceGasFeeOptions &&
supportsEIP1559 &&
!renderSimulationFailureWarning && (
<EditGasFeeButton
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
)}
</Box>
)}
<Box
Expand All @@ -102,8 +109,12 @@ export default function ApproveContentCard({
className="approve-content-card-container__card-content"
>
{renderTransactionDetailsContent &&
(!isMultiLayerFeeNetwork && supportsEIP1559 ? (
<GasDetailsItem />
(!isMultiLayerFeeNetwork &&
supportsEIP1559 &&
!renderSimulationFailureWarning ? (
<GasDetailsItem
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
) : (
<Box
display={DISPLAY.FLEX}
Expand Down Expand Up @@ -301,4 +312,12 @@ ApproveContentCard.propTypes = {
* Current transaction data
*/
data: PropTypes.string,
/**
* User acknowledge gas is missing or not
*/
userAcknowledgedGasMissing: PropTypes.bool,
/**
* Render simulation failure warning
*/
renderSimulationFailureWarning: PropTypes.bool,
};
17 changes: 4 additions & 13 deletions ui/components/app/transaction-alerts/transaction-alerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { submittedPendingTransactionsSelector } from '../../../selectors';
import { useGasFeeContext } from '../../../contexts/gasFee';
import { useI18nContext } from '../../../hooks/useI18nContext';
import ActionableMessage from '../../ui/actionable-message/actionable-message';
import SimulationErrorMessage from '../../ui/simulation-error-message';
import Typography from '../../ui/typography';
import { TYPOGRAPHY } from '../../../helpers/constants/design-system';
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
Expand All @@ -23,19 +24,9 @@ const TransactionAlerts = ({
return (
<div className="transaction-alerts">
{supportsEIP1559 && hasSimulationError && (
<ActionableMessage
message={t('simulationErrorMessageV2')}
useIcon
iconFillColor="var(--color-error-default)"
type="danger"
primaryActionV2={
userAcknowledgedGasMissing === true
? undefined
: {
label: t('proceedWithTransaction'),
onClick: setUserAcknowledgedGasMissing,
}
}
<SimulationErrorMessage
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
setUserAcknowledgedGasMissing={setUserAcknowledgedGasMissing}
/>
)}
{supportsEIP1559 && pendingTransactions?.length > 0 && (
Expand Down
1 change: 1 addition & 0 deletions ui/components/ui/simulation-error-message/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './simulation-error-message';
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import ActionableMessage from '../actionable-message';
import { I18nContext } from '../../../../.storybook/i18n';

export default function SimulationErrorMessage({
userAcknowledgedGasMissing = false,
setUserAcknowledgedGasMissing,
}) {
const t = useContext(I18nContext);

return (
<ActionableMessage
message={t('simulationErrorMessageV2')}
useIcon
iconFillColor="var(--color-error-default)"
type="danger"
primaryActionV2={
userAcknowledgedGasMissing === true
? undefined
: {
label: t('proceedWithTransaction'),
onClick: setUserAcknowledgedGasMissing,
}
}
/>
);
}

SimulationErrorMessage.propTypes = {
userAcknowledgedGasMissing: PropTypes.bool,
setUserAcknowledgedGasMissing: PropTypes.func,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { fireEvent } from '@testing-library/react';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import SimulationErrorMessage from './simulation-error-message';

describe('Simulation Error Message', () => {
const store = configureMockStore()({});
let props = {};

beforeEach(() => {
props = {
userAcknowledgedGasMissing: false,
setUserAcknowledgedGasMissing: jest.fn(),
};
});

it('should render SimulationErrorMessage component with I want to procced anyway link', () => {
const { queryByText } = renderWithProvider(
<SimulationErrorMessage {...props} />,
store,
);

expect(
queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(queryByText('I want to proceed anyway')).toBeInTheDocument();
});

it('should render SimulationErrorMessage component without I want to procced anyway link', () => {
props.userAcknowledgedGasMissing = true;
const { queryByText } = renderWithProvider(
<SimulationErrorMessage {...props} />,
store,
);

expect(
queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(queryByText('I want to proceed anyway')).not.toBeInTheDocument();
});

it('should render SimulationErrorMessage component with I want to proceed anyway and fire that event', () => {
props.userAcknowledgedGasMissing = false;
const { queryByText, getByText } = renderWithProvider(
<SimulationErrorMessage {...props} />,
store,
);

expect(
queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(queryByText('I want to proceed anyway')).toBeInTheDocument();

const proceedAnywayLink = getByText('I want to proceed anyway');
fireEvent.click(proceedAnywayLink);
expect(props.setUserAcknowledgedGasMissing).toHaveBeenCalledTimes(1);
});
});
18 changes: 18 additions & 0 deletions ui/hooks/useSimulationFailureWarning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useSelector } from 'react-redux';
import { txDataSelector } from '../selectors';

/**
* Returns the simulation failure warning if a simulaiton error
* is present and user didn't acknowledge gas missing
*
* @param {boolean} userAcknowledgedGasMissing - Whether the user acknowledge gas missing
* @returns {boolean} simulation failure warning
*/

export function useSimulationFailureWarning(userAcknowledgedGasMissing) {
const txData = useSelector(txDataSelector) || {};
const hasSimulationError = Boolean(txData.simulationFails);
const renderSimulationFailureWarning =
hasSimulationError && !userAcknowledgedGasMissing;
return renderSimulationFailureWarning;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { formatCurrency } from '../../../helpers/utils/confirm-tx.util';
import Typography from '../../../components/ui/typography';
import Box from '../../../components/ui/box';
import Button from '../../../components/ui/button';
import SimulationErrorMessage from '../../../components/ui/simulation-error-message';
import EditGasFeeButton from '../../../components/app/edit-gas-fee-button';
import MultiLayerFeeMessage from '../../../components/app/multilayer-fee-message';
import {
Expand Down Expand Up @@ -65,12 +66,15 @@ export default class ConfirmApproveContent extends Component {
isSetApproveForAll: PropTypes.bool,
isApprovalOrRejection: PropTypes.bool,
userAddress: PropTypes.string,
userAcknowledgedGasMissing: PropTypes.bool,
setUserAcknowledgedGasMissing: PropTypes.func,
renderSimulationFailureWarning: PropTypes.bool,
};

state = {
showFullTxDetails: false,
copied: false,
setshowContractDetails: false,
setShowContractDetails: false,
};

renderApproveContentCard({
Expand All @@ -84,7 +88,11 @@ export default class ConfirmApproveContent extends Component {
footer,
noBorder,
}) {
const { supportsEIP1559 } = this.props;
const {
supportsEIP1559,
renderSimulationFailureWarning,
userAcknowledgedGasMissing,
} = this.props;
const { t } = this.context;
return (
<div
Expand Down Expand Up @@ -116,9 +124,14 @@ export default class ConfirmApproveContent extends Component {
</Button>
</Box>
)}
{showEdit && showAdvanceGasFeeOptions && supportsEIP1559 && (
<EditGasFeeButton />
)}
{showEdit &&
showAdvanceGasFeeOptions &&
supportsEIP1559 &&
!renderSimulationFailureWarning && (
<EditGasFeeButton
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
)}
</div>
)}
<div className="confirm-approve-content__card-content">{content}</div>
Expand All @@ -139,9 +152,19 @@ export default class ConfirmApproveContent extends Component {
txData,
isMultiLayerFeeNetwork,
supportsEIP1559,
userAcknowledgedGasMissing,
renderSimulationFailureWarning,
} = this.props;
if (!isMultiLayerFeeNetwork && supportsEIP1559) {
return <GasDetailsItem />;
if (
!isMultiLayerFeeNetwork &&
supportsEIP1559 &&
!renderSimulationFailureWarning
) {
return (
<GasDetailsItem
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
);
}
return (
<div className="confirm-approve-content__transaction-details-content">
Expand Down Expand Up @@ -491,8 +514,11 @@ export default class ConfirmApproveContent extends Component {
tokenId,
tokenAddress,
assetName,
userAcknowledgedGasMissing,
setUserAcknowledgedGasMissing,
renderSimulationFailureWarning,
} = this.props;
const { showFullTxDetails, setshowContractDetails } = this.state;
const { showFullTxDetails, setShowContractDetails } = this.state;

return (
<div
Expand Down Expand Up @@ -539,13 +565,13 @@ export default class ConfirmApproveContent extends Component {
<Button
type="link"
className="confirm-approve-content__verify-contract-details"
onClick={() => this.setState({ setshowContractDetails: true })}
onClick={() => this.setState({ setShowContractDetails: true })}
>
{t('verifyContractDetails')}
</Button>
{setshowContractDetails && (
{setShowContractDetails && (
<ContractDetailsModal
onClose={() => this.setState({ setshowContractDetails: false })}
onClose={() => this.setState({ setShowContractDetails: false })}
tokenName={tokenSymbol}
tokenAddress={tokenAddress}
toAddress={toAddress}
Expand All @@ -558,6 +584,21 @@ export default class ConfirmApproveContent extends Component {
)}
</Box>
<div className="confirm-approve-content__card-wrapper">
{renderSimulationFailureWarning && (
<Box
paddingTop={0}
paddingRight={6}
paddingBottom={4}
paddingLeft={6}
>
<SimulationErrorMessage
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
setUserAcknowledgedGasMissing={() =>
setUserAcknowledgedGasMissing(true)
}
/>
</Box>
)}
{this.renderApproveContentCard({
symbol: <i className="fa fa-tag" />,
title: t('transactionFee'),
Expand Down
Loading

0 comments on commit 308151c

Please sign in to comment.