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

EIP-1559 - Implement form validation for EIP-1559 (#11540)

* Restructure advanced gas form errors

* Use shared constant for gas errors

* Add validation for fields too low

* Add warnings for high max fee and max priority fee

* Fix lint

* Fix priority fee high warning string
This commit is contained in:
David Walsh 2021-07-20 14:34:32 -05:00 committed by GitHub
parent 594025a198
commit 6d92759853
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 144 additions and 47 deletions

View File

@ -653,24 +653,36 @@
"editGasHigh": { "editGasHigh": {
"message": "High" "message": "High"
}, },
"editGasLimitOutOfBounds": {
"message": "Gas limit must be greater than 20999 and less than 7920027"
},
"editGasLimitTooltip": { "editGasLimitTooltip": {
"message": "Gas limit is the maximum units of gas you are willing to use. Units of gas are a multiplier to “Max priority fee” and “Max fee”." "message": "Gas limit is the maximum units of gas you are willing to use. Units of gas are a multiplier to “Max priority fee” and “Max fee”."
}, },
"editGasLow": { "editGasLow": {
"message": "Low" "message": "Low"
}, },
"editGasMaxFeeHigh": {
"message": "Max fee is higher than necessary"
},
"editGasMaxFeeLow": { "editGasMaxFeeLow": {
"message": "Max fee too low for network conditions" "message": "Max fee too low for network conditions"
}, },
"editGasMaxFeeTooltip": { "editGasMaxFeeTooltip": {
"message": "The max fee is the most youll pay (base fee + priority fee)." "message": "The max fee is the most youll pay (base fee + priority fee)."
}, },
"editGasMaxPriorityFeeHigh": {
"message": "Max priority fee is higher than necessary. You may pay more than needed."
},
"editGasMaxPriorityFeeLow": { "editGasMaxPriorityFeeLow": {
"message": "Max priority fee too low for network conditions" "message": "Max priority fee too low for network conditions"
}, },
"editGasMaxPriorityFeeTooltip": { "editGasMaxPriorityFeeTooltip": {
"message": "Max priority fee (aka “miner tip”) goes directly to miners and incentivizes them to prioritize your transaction. Youll most often pay your max setting" "message": "Max priority fee (aka “miner tip”) goes directly to miners and incentivizes them to prioritize your transaction. Youll most often pay your max setting"
}, },
"editGasMaxPriorityFeeZeroError": {
"message": "Max priority fee must be at least 1 GWEI"
},
"editGasMedium": { "editGasMedium": {
"message": "Medium" "message": "Medium"
}, },
@ -683,6 +695,9 @@
"editGasTooLow": { "editGasTooLow": {
"message": "Unknown processing time" "message": "Unknown processing time"
}, },
"editGasTooLowTooltip": {
"message": "Your max fee is low for current market conditions. We dont know when (or if) your transaction will be processed."
},
"editGasTotalBannerSubtitle": { "editGasTotalBannerSubtitle": {
"message": "Up to $1 ($2)", "message": "Up to $1 ($2)",
"display": "$1 represents a fiat value" "display": "$1 represents a fiat value"

View File

@ -13,6 +13,7 @@ import {
GAS_ESTIMATE_TYPES, GAS_ESTIMATE_TYPES,
GAS_RECOMMENDATIONS, GAS_RECOMMENDATIONS,
} from '../../../../shared/constants/gas'; } from '../../../../shared/constants/gas';
import { getGasFormErrorText } from '../../../helpers/constants/gas';
const DEFAULT_ESTIMATES_LEVEL = 'medium'; const DEFAULT_ESTIMATES_LEVEL = 'medium';
@ -31,8 +32,7 @@ export default function AdvancedGasControls({
setGasPrice, setGasPrice,
maxPriorityFeeFiat, maxPriorityFeeFiat,
maxFeeFiat, maxFeeFiat,
maxPriorityFeeError, gasErrors,
maxFeeError,
}) { }) {
const t = useContext(I18nContext); const t = useContext(I18nContext);
@ -63,6 +63,11 @@ export default function AdvancedGasControls({
<div className="advanced-gas-controls"> <div className="advanced-gas-controls">
<FormField <FormField
titleText={t('gasLimit')} titleText={t('gasLimit')}
error={
gasErrors?.gasLimit
? getGasFormErrorText(gasErrors.gasLimit, t)
: null
}
onChange={setGasLimit} onChange={setGasLimit}
tooltipText={t('editGasLimitTooltip')} tooltipText={t('editGasLimitTooltip')}
value={gasLimit} value={gasLimit}
@ -106,7 +111,11 @@ export default function AdvancedGasControls({
</> </>
) )
} }
error={maxPriorityFeeError} error={
gasErrors?.maxPriorityFee
? getGasFormErrorText(gasErrors.maxPriorityFee, t)
: null
}
/> />
<FormField <FormField
titleText={t('maxFee')} titleText={t('maxFee')}
@ -143,7 +152,11 @@ export default function AdvancedGasControls({
</> </>
) )
} }
error={maxFeeError} error={
gasErrors?.maxFee
? getGasFormErrorText(gasErrors.maxFee, t)
: null
}
/> />
</> </>
) : ( ) : (
@ -216,6 +229,5 @@ AdvancedGasControls.propTypes = {
setGasPrice: PropTypes.func, setGasPrice: PropTypes.func,
maxPriorityFeeFiat: PropTypes.string, maxPriorityFeeFiat: PropTypes.string,
maxFeeFiat: PropTypes.string, maxFeeFiat: PropTypes.string,
maxPriorityFeeError: PropTypes.string, gasErrors: PropTypes.object,
maxFeeError: PropTypes.string,
}; };

View File

@ -16,10 +16,12 @@
align-self: center; align-self: center;
} }
&__row-error, .form-field__row--error .form-field__heading-title h6 {
&__row--error h6 { color: $error-1;
color: $error-1 !important;
padding-top: 6px; & path {
fill: $error-1;
}
} }
h6 { h6 {
@ -27,8 +29,7 @@
margin-inline-end: 6px; margin-inline-end: 6px;
} }
i { path {
color: #dadada; fill: #dadada;
font-size: $font-size-h7;
} }
} }

View File

@ -48,14 +48,13 @@ export default function EditGasDisplay({
setEstimateToUse, setEstimateToUse,
estimatedMinimumFiat, estimatedMinimumFiat,
estimatedMaximumFiat, estimatedMaximumFiat,
isMaxFeeError, hasGasErrors,
isMaxPriorityFeeError,
isGasTooLow,
dappSuggestedGasFeeAcknowledged, dappSuggestedGasFeeAcknowledged,
setDappSuggestedGasFeeAcknowledged, setDappSuggestedGasFeeAcknowledged,
showAdvancedForm, showAdvancedForm,
setShowAdvancedForm, setShowAdvancedForm,
warning, warning,
gasErrors,
}) { }) {
const t = useContext(I18nContext); const t = useContext(I18nContext);
@ -126,14 +125,19 @@ export default function EditGasDisplay({
{t('gasDisplayAcknowledgeDappButtonText')} {t('gasDisplayAcknowledgeDappButtonText')}
</Button> </Button>
)} )}
{isGasTooLow && ( {hasGasErrors && (
<div className="edit-gas-display__error"> <div className="edit-gas-display__error">
<Typography <Typography
color={COLORS.ERROR1} color={COLORS.ERROR1}
variant={TYPOGRAPHY.H7} variant={TYPOGRAPHY.H7}
align={TEXT_ALIGN.CENTER} align={TEXT_ALIGN.CENTER}
fontWeight={FONT_WEIGHT.BOLD}
> >
{t('editGasTooLow')} {t('editGasTooLow')}{' '}
<InfoTooltip
position="top"
contentText={t('editGasTooLowTooltip')}
/>
</Typography> </Typography>
</div> </div>
)} )}
@ -194,10 +198,7 @@ export default function EditGasDisplay({
setGasPrice={setGasPrice} setGasPrice={setGasPrice}
maxPriorityFeeFiat={maxPriorityFeePerGasFiat} maxPriorityFeeFiat={maxPriorityFeePerGasFiat}
maxFeeFiat={maxFeePerGasFiat} maxFeeFiat={maxFeePerGasFiat}
maxPriorityFeeError={ gasErrors={gasErrors}
isMaxPriorityFeeError ? t('editGasMaxPriorityFeeLow') : null
}
maxFeeError={isMaxFeeError ? t('editGasMaxFeeLow') : null}
/> />
)} )}
</div> </div>
@ -236,13 +237,12 @@ EditGasDisplay.propTypes = {
setEstimateToUse: PropTypes.func, setEstimateToUse: PropTypes.func,
estimatedMinimumFiat: PropTypes.string, estimatedMinimumFiat: PropTypes.string,
estimatedMaximumFiat: PropTypes.string, estimatedMaximumFiat: PropTypes.string,
isMaxFeeError: PropTypes.boolean, hasGasErrors: PropTypes.boolean,
isMaxPriorityFeeError: PropTypes.boolean,
isGasTooLow: PropTypes.boolean,
dappSuggestedGasFeeAcknowledged: PropTypes.boolean, dappSuggestedGasFeeAcknowledged: PropTypes.boolean,
setDappSuggestedGasFeeAcknowledged: PropTypes.func, setDappSuggestedGasFeeAcknowledged: PropTypes.func,
showAdvancedForm: PropTypes.bool, showAdvancedForm: PropTypes.bool,
setShowAdvancedForm: PropTypes.func, setShowAdvancedForm: PropTypes.func,
warning: PropTypes.string, warning: PropTypes.string,
transaction: PropTypes.object, transaction: PropTypes.object,
gasErrors: PropTypes.object,
}; };

View File

@ -21,6 +21,14 @@
} }
} }
&__error .info-tooltip {
display: inline-block;
path {
fill: $error-1;
}
}
&__dapp-acknowledgement-warning { &__dapp-acknowledgement-warning {
margin-bottom: 20px; margin-bottom: 20px;
} }

View File

@ -72,9 +72,8 @@ export default function EditGasPopover({
setEstimateToUse, setEstimateToUse,
estimatedMinimumFiat, estimatedMinimumFiat,
estimatedMaximumFiat, estimatedMaximumFiat,
isMaxFeeError, hasGasErrors,
isMaxPriorityFeeError, gasErrors,
isGasTooLow,
} = useGasFeeInputs(defaultEstimateToUse); } = useGasFeeInputs(defaultEstimateToUse);
/** /**
@ -173,12 +172,7 @@ export default function EditGasPopover({
<Button <Button
type="primary" type="primary"
onClick={onSubmit} onClick={onSubmit}
disabled={ disabled={hasGasErrors || isGasEstimatesLoading}
isMaxFeeError ||
isMaxPriorityFeeError ||
isGasTooLow ||
isGasEstimatesLoading
}
> >
{footerButtonText} {footerButtonText}
</Button> </Button>
@ -217,12 +211,11 @@ export default function EditGasPopover({
setEstimateToUse={setEstimateToUse} setEstimateToUse={setEstimateToUse}
estimatedMinimumFiat={estimatedMinimumFiat} estimatedMinimumFiat={estimatedMinimumFiat}
estimatedMaximumFiat={estimatedMaximumFiat} estimatedMaximumFiat={estimatedMaximumFiat}
isMaxFeeError={isMaxFeeError} hasGasErrors={hasGasErrors}
isMaxPriorityFeeError={isMaxPriorityFeeError}
isGasTooLow={isGasTooLow}
onEducationClick={() => setShowEducationContent(true)} onEducationClick={() => setShowEducationContent(true)}
mode={mode} mode={mode}
transaction={transaction} transaction={transaction}
gasErrors={gasErrors}
{...editGasDisplayProps} {...editGasDisplayProps}
/> />
)} )}

View File

@ -0,0 +1,27 @@
export const GAS_FORM_ERRORS = {
GAS_LIMIT_OUT_OF_BOUNDS: 'editGasLimitOutOfBounds',
MAX_PRIORITY_FEE_TOO_LOW: 'editGasMaxPriorityFeeLow',
MAX_FEE_TOO_LOW: 'editGasMaxFeeLow',
MAX_PRIORITY_FEE_ZERO: 'editGasMaxPriorityFeeZeroError',
MAX_PRIORITY_FEE_HIGH_WARNING: 'editGasMaxPriorityFeeHigh',
MAX_FEE_HIGH_WARNING: 'editGasMaxFeeHigh',
};
export function getGasFormErrorText(type, t) {
switch (type) {
case GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS:
return t('editGasLimitOutOfBounds');
case GAS_FORM_ERRORS.MAX_PRIORITY_FEE_TOO_LOW:
return t('editGasMaxPriorityFeeLow');
case GAS_FORM_ERRORS.MAX_FEE_TOO_LOW:
return t('editGasMaxFeeLow');
case GAS_FORM_ERRORS.MAX_PRIORITY_FEE_ZERO:
return t('editGasMaxPriorityFeeZeroError');
case GAS_FORM_ERRORS.MAX_PRIORITY_FEE_HIGH_WARNING:
return t('editGasMaxPriorityFeeHigh');
case GAS_FORM_ERRORS.MAX_FEE_HIGH_WARNING:
return t('editGasMaxFeeHigh');
default:
return '';
}
}

View File

@ -13,10 +13,13 @@ import {
decimalToHex, decimalToHex,
} from '../helpers/utils/conversions.util'; } from '../helpers/utils/conversions.util';
import { getShouldShowFiat } from '../selectors'; import { getShouldShowFiat } from '../selectors';
import { GAS_FORM_ERRORS } from '../helpers/constants/gas';
import { useCurrencyDisplay } from './useCurrencyDisplay'; import { useCurrencyDisplay } from './useCurrencyDisplay';
import { useGasFeeEstimates } from './useGasFeeEstimates'; import { useGasFeeEstimates } from './useGasFeeEstimates';
import { useUserPreferencedCurrency } from './useUserPreferencedCurrency'; import { useUserPreferencedCurrency } from './useUserPreferencedCurrency';
const HIGH_FEE_WARNING_MULTIPLIER = 1.5;
/** /**
* Opaque string type representing a decimal (base 10) number in GWEI * Opaque string type representing a decimal (base 10) number in GWEI
* @typedef {`${number}`} DecGweiString * @typedef {`${number}`} DecGweiString
@ -271,39 +274,75 @@ export function useGasFeeInputs(defaultEstimateToUse = 'medium') {
}, },
); );
let isMaxPriorityFeeError = false; // Separating errors from warnings so we can know which value problems
let isMaxFeeError = false; // are blocking or simply useful information for the users
const gasErrors = {};
const gasWarnings = {};
if (gasLimit < 21000 || gasLimit > 7920027) {
gasErrors.gasLimit = GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS;
}
switch (gasEstimateType) { switch (gasEstimateType) {
case GAS_ESTIMATE_TYPES.FEE_MARKET: case GAS_ESTIMATE_TYPES.FEE_MARKET:
isMaxPriorityFeeError = if (maxPriorityFeePerGasToUse < 1) {
gasErrors.maxPriorityFee = GAS_FORM_ERRORS.MAX_PRIORITY_FEE_ZERO;
} else if (
!isGasEstimatesLoading && !isGasEstimatesLoading &&
maxPriorityFeePerGasToUse < maxPriorityFeePerGasToUse <
gasFeeEstimates?.low?.suggestedMaxPriorityFeePerGas; gasFeeEstimates?.low?.suggestedMaxPriorityFeePerGas
isMaxFeeError = ) {
gasErrors.maxPriorityFee = GAS_FORM_ERRORS.MAX_PRIORITY_FEE_TOO_LOW;
} else if (
gasFeeEstimates?.high &&
maxPriorityFeePerGasToUse >
gasFeeEstimates.high.suggestedMaxPriorityFeePerGas *
HIGH_FEE_WARNING_MULTIPLIER
) {
gasWarnings.maxPriorityFee =
GAS_FORM_ERRORS.MAX_PRIORITY_FEE_HIGH_WARNING;
}
if (
!isGasEstimatesLoading && !isGasEstimatesLoading &&
maxFeePerGasToUse < gasFeeEstimates?.low?.suggestedMaxFeePerGas; maxFeePerGasToUse < gasFeeEstimates?.low?.suggestedMaxFeePerGas
) {
gasErrors.maxFee = GAS_FORM_ERRORS.MAX_FEE_TOO_LOW;
} else if (
gasFeeEstimates?.high &&
maxFeePerGasToUse >
gasFeeEstimates.high.suggestedMaxFeePerGas *
HIGH_FEE_WARNING_MULTIPLIER
) {
gasWarnings.maxFee = GAS_FORM_ERRORS.MAX_FEE_HIGH_WARNING;
}
break; break;
default: default:
break; break;
} }
const isGasTooLow = Boolean(isMaxPriorityFeeError || isMaxFeeError); // Determine if we have any errors which should block submission
const hasBlockingGasErrors = Boolean(Object.keys(gasErrors).length);
// Now that we've determined errors that block submission, we can pool the warnings
// and errors into one object for easier use within the UI. This object should have
// no effect on whether or not the user can submit the form
const errorsAndWarnings = {
...gasErrors,
...gasWarnings,
};
return { return {
maxFeePerGas: maxFeePerGasToUse, maxFeePerGas: maxFeePerGasToUse,
maxFeePerGasFiat: showFiat ? maxFeePerGasFiat : '', maxFeePerGasFiat: showFiat ? maxFeePerGasFiat : '',
setMaxFeePerGas, setMaxFeePerGas,
isMaxFeeError,
maxPriorityFeePerGas: maxPriorityFeePerGasToUse, maxPriorityFeePerGas: maxPriorityFeePerGasToUse,
maxPriorityFeePerGasFiat: showFiat ? maxPriorityFeePerGasFiat : '', maxPriorityFeePerGasFiat: showFiat ? maxPriorityFeePerGasFiat : '',
setMaxPriorityFeePerGas, setMaxPriorityFeePerGas,
isMaxPriorityFeeError,
gasPrice: gasPriceToUse, gasPrice: gasPriceToUse,
setGasPrice, setGasPrice,
gasLimit, gasLimit,
setGasLimit, setGasLimit,
isGasTooLow,
estimateToUse, estimateToUse,
setEstimateToUse, setEstimateToUse,
estimatedMinimumFiat: showFiat ? estimatedMinimumFiat : '', estimatedMinimumFiat: showFiat ? estimatedMinimumFiat : '',
@ -313,5 +352,7 @@ export function useGasFeeInputs(defaultEstimateToUse = 'medium') {
gasFeeEstimates, gasFeeEstimates,
gasEstimateType, gasEstimateType,
estimatedGasFeeTimeBounds, estimatedGasFeeTimeBounds,
gasErrors: errorsAndWarnings,
hasGasErrors: hasBlockingGasErrors,
}; };
} }