mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-26 04:20:53 +01:00
Extracting out error and warning code from useGasFeeInputs hook (#12283)
Extracting out error and warning code from useGasFeeInputs hook
This commit is contained in:
parent
71f91568db
commit
707ae7d652
267
ui/hooks/useGasFeeErrors.js
Normal file
267
ui/hooks/useGasFeeErrors.js
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { GAS_ESTIMATE_TYPES, GAS_LIMITS } from '../../shared/constants/gas';
|
||||||
|
import {
|
||||||
|
conversionLessThan,
|
||||||
|
conversionGreaterThan,
|
||||||
|
} from '../../shared/modules/conversion.utils';
|
||||||
|
import {
|
||||||
|
checkNetworkAndAccountSupports1559,
|
||||||
|
getSelectedAccount,
|
||||||
|
} from '../selectors';
|
||||||
|
import { addHexes } from '../helpers/utils/conversions.util';
|
||||||
|
import { isLegacyTransaction } from '../helpers/utils/transactions.util';
|
||||||
|
import {
|
||||||
|
bnGreaterThan,
|
||||||
|
bnLessThan,
|
||||||
|
bnLessThanEqualTo,
|
||||||
|
} from '../helpers/utils/util';
|
||||||
|
import { GAS_FORM_ERRORS } from '../helpers/constants/gas';
|
||||||
|
|
||||||
|
const HIGH_FEE_WARNING_MULTIPLIER = 1.5;
|
||||||
|
|
||||||
|
const validateGasLimit = (gasLimit, minimumGasLimit) => {
|
||||||
|
const gasLimitTooLow = conversionLessThan(
|
||||||
|
{ value: gasLimit, fromNumericBase: 'dec' },
|
||||||
|
{ value: minimumGasLimit || GAS_LIMITS.SIMPLE, fromNumericBase: 'hex' },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (gasLimitTooLow) return GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS;
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateMaxPriorityFee = (maxPriorityFeePerGasToUse, supportsEIP1559) => {
|
||||||
|
if (supportsEIP1559 && bnLessThanEqualTo(maxPriorityFeePerGasToUse, 0)) {
|
||||||
|
return GAS_FORM_ERRORS.MAX_PRIORITY_FEE_BELOW_MINIMUM;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateMaxFee = (
|
||||||
|
maxFeePerGasToUse,
|
||||||
|
maxPriorityFeeError,
|
||||||
|
maxPriorityFeePerGasToUse,
|
||||||
|
supportsEIP1559,
|
||||||
|
) => {
|
||||||
|
if (maxPriorityFeeError) return undefined;
|
||||||
|
if (
|
||||||
|
supportsEIP1559 &&
|
||||||
|
bnGreaterThan(maxPriorityFeePerGasToUse, maxFeePerGasToUse)
|
||||||
|
) {
|
||||||
|
return GAS_FORM_ERRORS.MAX_FEE_IMBALANCE;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateGasPrice = (
|
||||||
|
isFeeMarketGasEstimate,
|
||||||
|
gasPriceToUse,
|
||||||
|
supportsEIP1559,
|
||||||
|
transaction,
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
(!supportsEIP1559 || transaction?.txParams?.gasPrice) &&
|
||||||
|
!isFeeMarketGasEstimate &&
|
||||||
|
bnLessThanEqualTo(gasPriceToUse, 0)
|
||||||
|
) {
|
||||||
|
return GAS_FORM_ERRORS.GAS_PRICE_TOO_LOW;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMaxPriorityFeeWarning = (
|
||||||
|
gasFeeEstimates,
|
||||||
|
isFeeMarketGasEstimate,
|
||||||
|
isGasEstimatesLoading,
|
||||||
|
maxPriorityFeePerGasToUse,
|
||||||
|
supportsEIP1559,
|
||||||
|
) => {
|
||||||
|
if (!supportsEIP1559 || !isFeeMarketGasEstimate || isGasEstimatesLoading)
|
||||||
|
return undefined;
|
||||||
|
if (
|
||||||
|
bnLessThan(
|
||||||
|
maxPriorityFeePerGasToUse,
|
||||||
|
gasFeeEstimates?.low?.suggestedMaxPriorityFeePerGas,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return GAS_FORM_ERRORS.MAX_PRIORITY_FEE_TOO_LOW;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
gasFeeEstimates?.high &&
|
||||||
|
bnGreaterThan(
|
||||||
|
maxPriorityFeePerGasToUse,
|
||||||
|
gasFeeEstimates.high.suggestedMaxPriorityFeePerGas *
|
||||||
|
HIGH_FEE_WARNING_MULTIPLIER,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return GAS_FORM_ERRORS.MAX_PRIORITY_FEE_HIGH_WARNING;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMaxFeeWarning = (
|
||||||
|
gasFeeEstimates,
|
||||||
|
isGasEstimatesLoading,
|
||||||
|
isFeeMarketGasEstimate,
|
||||||
|
maxFeeError,
|
||||||
|
maxPriorityFeeError,
|
||||||
|
maxFeePerGasToUse,
|
||||||
|
supportsEIP1559,
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
maxPriorityFeeError ||
|
||||||
|
maxFeeError ||
|
||||||
|
!isFeeMarketGasEstimate ||
|
||||||
|
!supportsEIP1559
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!isGasEstimatesLoading &&
|
||||||
|
bnLessThan(maxFeePerGasToUse, gasFeeEstimates?.low?.suggestedMaxFeePerGas)
|
||||||
|
) {
|
||||||
|
return GAS_FORM_ERRORS.MAX_FEE_TOO_LOW;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
gasFeeEstimates?.high &&
|
||||||
|
bnGreaterThan(
|
||||||
|
maxFeePerGasToUse,
|
||||||
|
gasFeeEstimates.high.suggestedMaxFeePerGas * HIGH_FEE_WARNING_MULTIPLIER,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return GAS_FORM_ERRORS.MAX_FEE_HIGH_WARNING;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBalanceError = (minimumCostInHexWei, transaction, ethBalance) => {
|
||||||
|
const minimumTxCostInHexWei = addHexes(
|
||||||
|
minimumCostInHexWei,
|
||||||
|
transaction?.txParams?.value || '0x0',
|
||||||
|
);
|
||||||
|
|
||||||
|
return conversionGreaterThan(
|
||||||
|
{ value: minimumTxCostInHexWei, fromNumericBase: 'hex' },
|
||||||
|
{ value: ethBalance, fromNumericBase: 'hex' },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} GasFeeErrorsReturnType
|
||||||
|
* @property {Object} [gasErrors] - combined map of errors and warnings.
|
||||||
|
* @property {boolean} [hasGasErrors] - true if there are errors that can block submission.
|
||||||
|
* @property {Object} gasWarnings - map of gas warnings for EIP-1559 fields.
|
||||||
|
* @property {boolean} [balanceError] - true if user balance is less than transaction value.
|
||||||
|
* @property {boolean} [estimatesUnavailableWarning] - true if supportsEIP1559 is true and
|
||||||
|
* estimate is not of type fee-market.
|
||||||
|
*/
|
||||||
|
export function useGasFeeErrors({
|
||||||
|
transaction,
|
||||||
|
gasEstimateType,
|
||||||
|
gasFeeEstimates,
|
||||||
|
gasLimit,
|
||||||
|
gasPriceToUse,
|
||||||
|
isGasEstimatesLoading,
|
||||||
|
maxPriorityFeePerGasToUse,
|
||||||
|
maxFeePerGasToUse,
|
||||||
|
minimumCostInHexWei,
|
||||||
|
minimumGasLimit,
|
||||||
|
}) {
|
||||||
|
const supportsEIP1559 =
|
||||||
|
useSelector(checkNetworkAndAccountSupports1559) &&
|
||||||
|
!isLegacyTransaction(transaction?.txParams);
|
||||||
|
|
||||||
|
const isFeeMarketGasEstimate =
|
||||||
|
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET;
|
||||||
|
|
||||||
|
// Get all errors
|
||||||
|
const gasLimitError = validateGasLimit(gasLimit, minimumGasLimit);
|
||||||
|
|
||||||
|
const maxPriorityFeeError = validateMaxPriorityFee(
|
||||||
|
maxPriorityFeePerGasToUse,
|
||||||
|
supportsEIP1559,
|
||||||
|
);
|
||||||
|
|
||||||
|
const maxFeeError = validateMaxFee(
|
||||||
|
maxFeePerGasToUse,
|
||||||
|
maxPriorityFeeError,
|
||||||
|
maxPriorityFeePerGasToUse,
|
||||||
|
supportsEIP1559,
|
||||||
|
);
|
||||||
|
|
||||||
|
const gasPriceError = validateGasPrice(
|
||||||
|
isFeeMarketGasEstimate,
|
||||||
|
gasPriceToUse,
|
||||||
|
supportsEIP1559,
|
||||||
|
transaction,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get all warnings
|
||||||
|
const maxPriorityFeeWarning = getMaxPriorityFeeWarning(
|
||||||
|
gasFeeEstimates,
|
||||||
|
isFeeMarketGasEstimate,
|
||||||
|
isGasEstimatesLoading,
|
||||||
|
maxPriorityFeePerGasToUse,
|
||||||
|
supportsEIP1559,
|
||||||
|
);
|
||||||
|
|
||||||
|
const maxFeeWarning = getMaxFeeWarning(
|
||||||
|
gasFeeEstimates,
|
||||||
|
isGasEstimatesLoading,
|
||||||
|
isFeeMarketGasEstimate,
|
||||||
|
maxFeeError,
|
||||||
|
maxPriorityFeeError,
|
||||||
|
maxFeePerGasToUse,
|
||||||
|
supportsEIP1559,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Separating errors from warnings so we can know which value problems
|
||||||
|
// are blocking or simply useful information for the users
|
||||||
|
|
||||||
|
const gasErrors = useMemo(() => {
|
||||||
|
const errors = {};
|
||||||
|
if (gasLimitError) errors.gasLimit = gasLimitError;
|
||||||
|
if (maxPriorityFeeError) errors.maxPriorityFee = maxPriorityFeeError;
|
||||||
|
if (maxFeeError) errors.maxFee = maxFeeError;
|
||||||
|
if (gasPriceError) errors.gasPrice = gasPriceError;
|
||||||
|
return errors;
|
||||||
|
}, [gasLimitError, maxPriorityFeeError, maxFeeError, gasPriceError]);
|
||||||
|
|
||||||
|
const gasWarnings = useMemo(() => {
|
||||||
|
const warnings = {};
|
||||||
|
if (maxPriorityFeeWarning) warnings.maxPriorityFee = maxPriorityFeeWarning;
|
||||||
|
if (maxFeeWarning) warnings.maxFee = maxFeeWarning;
|
||||||
|
return warnings;
|
||||||
|
}, [maxPriorityFeeWarning, maxFeeWarning]);
|
||||||
|
|
||||||
|
const estimatesUnavailableWarning =
|
||||||
|
supportsEIP1559 && !isFeeMarketGasEstimate;
|
||||||
|
|
||||||
|
// Determine if we have any errors which should block submission
|
||||||
|
const hasGasErrors = Boolean(Object.keys(gasErrors).length);
|
||||||
|
|
||||||
|
// Combine 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 = useMemo(
|
||||||
|
() => ({
|
||||||
|
...gasWarnings,
|
||||||
|
...gasErrors,
|
||||||
|
}),
|
||||||
|
[gasErrors, gasWarnings],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { balance: ethBalance } = useSelector(getSelectedAccount);
|
||||||
|
const balanceError = getBalanceError(
|
||||||
|
minimumCostInHexWei,
|
||||||
|
transaction,
|
||||||
|
ethBalance,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
gasErrors: errorsAndWarnings,
|
||||||
|
hasGasErrors,
|
||||||
|
gasWarnings,
|
||||||
|
balanceError,
|
||||||
|
estimatesUnavailableWarning,
|
||||||
|
};
|
||||||
|
}
|
364
ui/hooks/useGasFeeErrors.test.js
Normal file
364
ui/hooks/useGasFeeErrors.test.js
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { GAS_ESTIMATE_TYPES } from '../../shared/constants/gas';
|
||||||
|
import { GAS_FORM_ERRORS } from '../helpers/constants/gas';
|
||||||
|
import {
|
||||||
|
checkNetworkAndAccountSupports1559,
|
||||||
|
getSelectedAccount,
|
||||||
|
} from '../selectors';
|
||||||
|
|
||||||
|
import { useGasFeeErrors } from './useGasFeeErrors';
|
||||||
|
|
||||||
|
jest.mock('./useGasFeeEstimates', () => ({
|
||||||
|
useGasFeeEstimates: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('react-redux', () => {
|
||||||
|
const actual = jest.requireActual('react-redux');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useSelector: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const LEGACY_GAS_ESTIMATE_RETURN_VALUE = {
|
||||||
|
gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,
|
||||||
|
gasFeeEstimates: {
|
||||||
|
low: '10',
|
||||||
|
medium: '20',
|
||||||
|
high: '30',
|
||||||
|
},
|
||||||
|
estimatedGasFeeTimeBounds: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const FEE_MARKET_ESTIMATE_RETURN_VALUE = {
|
||||||
|
gasEstimateType: GAS_ESTIMATE_TYPES.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: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateUseSelectorRouter = ({
|
||||||
|
checkNetworkAndAccountSupports1559Response,
|
||||||
|
} = {}) => (selector) => {
|
||||||
|
if (selector === getSelectedAccount) {
|
||||||
|
return {
|
||||||
|
balance: '0x440aa47cc2556',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (selector === checkNetworkAndAccountSupports1559) {
|
||||||
|
return checkNetworkAndAccountSupports1559Response;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const configureEIP1559 = () => {
|
||||||
|
useSelector.mockImplementation(
|
||||||
|
generateUseSelectorRouter({
|
||||||
|
checkNetworkAndAccountSupports1559Response: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const configureLegacy = () => {
|
||||||
|
useSelector.mockImplementation(
|
||||||
|
generateUseSelectorRouter({
|
||||||
|
checkNetworkAndAccountSupports1559Response: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderUseGasFeeErrorsHook = (props) => {
|
||||||
|
return renderHook(() =>
|
||||||
|
useGasFeeErrors({
|
||||||
|
transaction: { txParams: { type: '0x2', value: '100' } },
|
||||||
|
gasLimit: '21000',
|
||||||
|
gasPriceToUse: '10',
|
||||||
|
maxPriorityFeePerGasToUse: '10',
|
||||||
|
maxFeePerGasToUse: '100',
|
||||||
|
minimumCostInHexWei: '0x5208',
|
||||||
|
minimumGasLimit: '0x5208',
|
||||||
|
...FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('useGasFeeErrors', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('gasLimit validation', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configureLegacy();
|
||||||
|
});
|
||||||
|
it('does not returns gasLimitError if gasLimit is not below minimum', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook(
|
||||||
|
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||||
|
);
|
||||||
|
expect(result.current.gasErrors.gasLimit).toBeUndefined();
|
||||||
|
expect(result.current.hasGasErrors).toBe(false);
|
||||||
|
});
|
||||||
|
it('returns gasLimitError if gasLimit is below minimum', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
gasLimit: '100',
|
||||||
|
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||||
|
});
|
||||||
|
expect(result.current.gasErrors.gasLimit).toBe(
|
||||||
|
GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS,
|
||||||
|
);
|
||||||
|
expect(result.current.hasGasErrors).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('maxPriorityFee validation', () => {
|
||||||
|
describe('EIP1559 compliant estimates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configureEIP1559();
|
||||||
|
});
|
||||||
|
it('does not return maxPriorityFeeError if maxPriorityFee is not 0', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook();
|
||||||
|
expect(result.current.gasErrors.maxPriorityFee).toBeUndefined();
|
||||||
|
expect(result.current.hasGasErrors).toBe(false);
|
||||||
|
});
|
||||||
|
it('return maxPriorityFeeError if maxPriorityFee is 0', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
maxPriorityFeePerGasToUse: '0',
|
||||||
|
});
|
||||||
|
expect(result.current.gasErrors.maxPriorityFee).toBe(
|
||||||
|
GAS_FORM_ERRORS.MAX_PRIORITY_FEE_BELOW_MINIMUM,
|
||||||
|
);
|
||||||
|
expect(result.current.hasGasErrors).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Legacy estimates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configureLegacy();
|
||||||
|
});
|
||||||
|
it('does not return maxPriorityFeeError if maxPriorityFee is 0', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook(
|
||||||
|
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||||
|
);
|
||||||
|
expect(result.current.gasErrors.maxPriorityFee).toBeUndefined();
|
||||||
|
expect(result.current.hasGasErrors).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('maxFee validation', () => {
|
||||||
|
describe('EIP1559 compliant estimates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configureEIP1559();
|
||||||
|
});
|
||||||
|
it('does not return maxFeeError if maxFee is greater than maxPriorityFee', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook();
|
||||||
|
expect(result.current.gasErrors.maxFee).toBeUndefined();
|
||||||
|
expect(result.current.hasGasErrors).toBe(false);
|
||||||
|
});
|
||||||
|
it('return maxFeeError if maxFee is less than maxPriorityFee', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
maxFeePerGasToUse: '1',
|
||||||
|
maxPriorityFeePerGasToUse: '10',
|
||||||
|
});
|
||||||
|
expect(result.current.gasErrors.maxFee).toBe(
|
||||||
|
GAS_FORM_ERRORS.MAX_FEE_IMBALANCE,
|
||||||
|
);
|
||||||
|
expect(result.current.hasGasErrors).toBe(true);
|
||||||
|
});
|
||||||
|
it('does not return MAX_FEE_IMBALANCE error if maxPriorityFeePerGasToUse is 0', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
maxFeePerGasToUse: '1',
|
||||||
|
maxPriorityFeePerGasToUse: '0',
|
||||||
|
});
|
||||||
|
expect(result.current.gasErrors.maxFee).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Legacy estimates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configureLegacy();
|
||||||
|
});
|
||||||
|
it('does not return maxFeeError if maxFee is less than maxPriorityFee', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
maxFeePerGasToUse: '1',
|
||||||
|
maxPriorityFeePerGasToUse: '10',
|
||||||
|
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||||
|
});
|
||||||
|
expect(result.current.gasErrors.maxFee).toBeUndefined();
|
||||||
|
expect(result.current.hasGasErrors).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('gasPrice validation', () => {
|
||||||
|
describe('EIP1559 compliant estimates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configureEIP1559();
|
||||||
|
});
|
||||||
|
it('does not return gasPriceError if gasPrice is 0', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({ gasPriceToUse: '0' });
|
||||||
|
expect(result.current.gasErrors.gasPrice).toBeUndefined();
|
||||||
|
expect(result.current.hasGasErrors).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Legacy estimates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configureLegacy();
|
||||||
|
});
|
||||||
|
it('returns gasPriceError if gasPrice is 0', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
gasPriceToUse: '0',
|
||||||
|
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||||
|
});
|
||||||
|
expect(result.current.gasErrors.gasPrice).toBe(
|
||||||
|
GAS_FORM_ERRORS.GAS_PRICE_TOO_LOW,
|
||||||
|
);
|
||||||
|
expect(result.current.hasGasErrors).toBe(true);
|
||||||
|
});
|
||||||
|
it('does not return gasPriceError if gasPrice is > 0', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook(
|
||||||
|
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||||
|
);
|
||||||
|
expect(result.current.gasErrors.gasPrice).toBeUndefined();
|
||||||
|
expect(result.current.hasGasErrors).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('maxPriorityFee warning', () => {
|
||||||
|
describe('EIP1559 compliant estimates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configureEIP1559();
|
||||||
|
});
|
||||||
|
it('does not return maxPriorityFeeWarning if maxPriorityFee is > suggestedMaxPriorityFeePerGas', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook();
|
||||||
|
expect(result.current.gasWarnings.maxPriorityFee).toBeUndefined();
|
||||||
|
});
|
||||||
|
it('return maxPriorityFeeWarning if maxPriorityFee is < suggestedMaxPriorityFeePerGas', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
maxPriorityFeePerGasToUse: '1',
|
||||||
|
});
|
||||||
|
expect(result.current.gasWarnings.maxPriorityFee).toBe(
|
||||||
|
GAS_FORM_ERRORS.MAX_PRIORITY_FEE_TOO_LOW,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('return maxPriorityFeeWarning if maxPriorityFee is > gasFeeEstimates.high.suggestedMaxPriorityFeePerGas', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
maxPriorityFeePerGasToUse: '100',
|
||||||
|
});
|
||||||
|
expect(result.current.gasWarnings.maxPriorityFee).toBe(
|
||||||
|
GAS_FORM_ERRORS.MAX_PRIORITY_FEE_HIGH_WARNING,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Legacy estimates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configureLegacy();
|
||||||
|
});
|
||||||
|
it('does not return maxPriorityFeeWarning if maxPriorityFee is < gasFeeEstimates.low.suggestedMaxPriorityFeePerGas', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
maxPriorityFeePerGasToUse: '1',
|
||||||
|
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||||
|
});
|
||||||
|
expect(result.current.gasWarnings.maxPriorityFee).toBeUndefined();
|
||||||
|
expect(result.current.hasGasErrors).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('maxFee warning', () => {
|
||||||
|
describe('EIP1559 compliant estimates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configureEIP1559();
|
||||||
|
});
|
||||||
|
it('does not return maxFeeWarning if maxFee is > suggestedMaxFeePerGas', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook();
|
||||||
|
expect(result.current.gasWarnings.maxFee).toBeUndefined();
|
||||||
|
});
|
||||||
|
it('return maxFeeWarning if maxFee is < suggestedMaxFeePerGas', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
maxFeePerGasToUse: '20',
|
||||||
|
});
|
||||||
|
expect(result.current.gasWarnings.maxFee).toBe(
|
||||||
|
GAS_FORM_ERRORS.MAX_FEE_TOO_LOW,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('return maxFeeWarning if gasFeeEstimates are high and maxFee is > suggestedMaxFeePerGas', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
maxFeePerGasToUse: '1000',
|
||||||
|
});
|
||||||
|
expect(result.current.gasWarnings.maxFee).toBe(
|
||||||
|
GAS_FORM_ERRORS.MAX_FEE_HIGH_WARNING,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Legacy estimates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configureLegacy();
|
||||||
|
});
|
||||||
|
it('does not return maxFeeWarning if maxFee is < suggestedMaxFeePerGas', () => {
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
maxFeePerGasToUse: '1',
|
||||||
|
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||||
|
});
|
||||||
|
expect(result.current.gasWarnings.maxFee).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Balance Error', () => {
|
||||||
|
it('is false if balance is greater than transaction value', () => {
|
||||||
|
configureEIP1559();
|
||||||
|
const { result } = renderUseGasFeeErrorsHook();
|
||||||
|
expect(result.current.balanceError).toBe(false);
|
||||||
|
});
|
||||||
|
it('is true if balance is less than transaction value', () => {
|
||||||
|
configureLegacy();
|
||||||
|
const { result } = renderUseGasFeeErrorsHook({
|
||||||
|
transaction: { txParams: { type: '0x2', value: '0x440aa47cc2556' } },
|
||||||
|
...LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||||
|
});
|
||||||
|
expect(result.current.balanceError).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('estimatesUnavailableWarning', () => {
|
||||||
|
it('is false if supportsEIP1559 and gasEstimateType is fee-market', () => {
|
||||||
|
configureEIP1559();
|
||||||
|
const { result } = renderUseGasFeeErrorsHook();
|
||||||
|
expect(result.current.estimatesUnavailableWarning).toBe(false);
|
||||||
|
});
|
||||||
|
it('is true if supportsEIP1559 and gasEstimateType is not fee-market', () => {
|
||||||
|
useSelector.mockImplementation(
|
||||||
|
generateUseSelectorRouter({
|
||||||
|
checkNetworkAndAccountSupports1559Response: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const { result } = renderUseGasFeeErrorsHook(
|
||||||
|
LEGACY_GAS_ESTIMATE_RETURN_VALUE,
|
||||||
|
);
|
||||||
|
expect(result.current.estimatesUnavailableWarning).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -2,16 +2,8 @@ import { addHexPrefix } from 'ethereumjs-util';
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import {
|
import { GAS_ESTIMATE_TYPES, EDIT_GAS_MODES } from '../../shared/constants/gas';
|
||||||
GAS_ESTIMATE_TYPES,
|
import { multiplyCurrencies } from '../../shared/modules/conversion.utils';
|
||||||
EDIT_GAS_MODES,
|
|
||||||
GAS_LIMITS,
|
|
||||||
} from '../../shared/constants/gas';
|
|
||||||
import {
|
|
||||||
multiplyCurrencies,
|
|
||||||
conversionLessThan,
|
|
||||||
conversionGreaterThan,
|
|
||||||
} from '../../shared/modules/conversion.utils';
|
|
||||||
import {
|
import {
|
||||||
getMaximumGasTotalInHexWei,
|
getMaximumGasTotalInHexWei,
|
||||||
getMinimumGasTotalInHexWei,
|
getMinimumGasTotalInHexWei,
|
||||||
@ -20,7 +12,6 @@ import { PRIMARY, SECONDARY } from '../helpers/constants/common';
|
|||||||
import {
|
import {
|
||||||
checkNetworkAndAccountSupports1559,
|
checkNetworkAndAccountSupports1559,
|
||||||
getShouldShowFiat,
|
getShouldShowFiat,
|
||||||
getSelectedAccount,
|
|
||||||
getAdvancedInlineGasShown,
|
getAdvancedInlineGasShown,
|
||||||
} from '../selectors';
|
} from '../selectors';
|
||||||
|
|
||||||
@ -29,21 +20,14 @@ import {
|
|||||||
decGWEIToHexWEI,
|
decGWEIToHexWEI,
|
||||||
decimalToHex,
|
decimalToHex,
|
||||||
hexToDecimal,
|
hexToDecimal,
|
||||||
addHexes,
|
|
||||||
} from '../helpers/utils/conversions.util';
|
} from '../helpers/utils/conversions.util';
|
||||||
import {
|
|
||||||
bnGreaterThan,
|
|
||||||
bnLessThan,
|
|
||||||
bnLessThanEqualTo,
|
|
||||||
} from '../helpers/utils/util';
|
|
||||||
import { GAS_FORM_ERRORS } from '../helpers/constants/gas';
|
import { GAS_FORM_ERRORS } from '../helpers/constants/gas';
|
||||||
import { isLegacyTransaction } from '../helpers/utils/transactions.util';
|
import { isLegacyTransaction } from '../helpers/utils/transactions.util';
|
||||||
|
|
||||||
import { useCurrencyDisplay } from './useCurrencyDisplay';
|
import { useCurrencyDisplay } from './useCurrencyDisplay';
|
||||||
import { useGasFeeEstimates } from './useGasFeeEstimates';
|
import { useGasFeeEstimates } from './useGasFeeEstimates';
|
||||||
import { useUserPreferencedCurrency } from './useUserPreferencedCurrency';
|
import { useUserPreferencedCurrency } from './useUserPreferencedCurrency';
|
||||||
|
import { useGasFeeErrors } from './useGasFeeErrors';
|
||||||
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
|
||||||
@ -155,7 +139,6 @@ export function useGasFeeInputs(
|
|||||||
minimumGasLimit = '0x5208',
|
minimumGasLimit = '0x5208',
|
||||||
editGasMode,
|
editGasMode,
|
||||||
) {
|
) {
|
||||||
const { balance: ethBalance } = useSelector(getSelectedAccount);
|
|
||||||
const supportsEIP1559 =
|
const supportsEIP1559 =
|
||||||
useSelector(checkNetworkAndAccountSupports1559) &&
|
useSelector(checkNetworkAndAccountSupports1559) &&
|
||||||
!isLegacyTransaction(transaction?.txParams);
|
!isLegacyTransaction(transaction?.txParams);
|
||||||
@ -380,102 +363,24 @@ export function useGasFeeInputs(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let estimatesUnavailableWarning = null;
|
const {
|
||||||
|
gasErrors,
|
||||||
// Separating errors from warnings so we can know which value problems
|
hasGasErrors,
|
||||||
// are blocking or simply useful information for the users
|
gasWarnings,
|
||||||
const gasErrors = {};
|
balanceError,
|
||||||
const gasWarnings = {};
|
estimatesUnavailableWarning,
|
||||||
|
} = useGasFeeErrors({
|
||||||
const gasLimitTooLow = conversionLessThan(
|
transaction,
|
||||||
{ value: gasLimit, fromNumericBase: 'dec' },
|
gasEstimateType,
|
||||||
{ value: minimumGasLimit || GAS_LIMITS.SIMPLE, fromNumericBase: 'hex' },
|
gasFeeEstimates,
|
||||||
);
|
gasLimit,
|
||||||
|
gasPriceToUse,
|
||||||
if (gasLimitTooLow) {
|
isGasEstimatesLoading,
|
||||||
gasErrors.gasLimit = GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS;
|
maxPriorityFeePerGasToUse,
|
||||||
}
|
maxFeePerGasToUse,
|
||||||
|
|
||||||
// This ensures these are applied when the api fails to return a fee market type
|
|
||||||
// It is okay if these errors get overwritten below, as those overwrites can only
|
|
||||||
// happen when the estimate api is live.
|
|
||||||
if (supportsEIP1559) {
|
|
||||||
if (bnLessThanEqualTo(maxPriorityFeePerGasToUse, 0)) {
|
|
||||||
gasErrors.maxPriorityFee = GAS_FORM_ERRORS.MAX_PRIORITY_FEE_BELOW_MINIMUM;
|
|
||||||
} else if (bnGreaterThan(maxPriorityFeePerGasToUse, maxFeePerGasToUse)) {
|
|
||||||
gasErrors.maxFee = GAS_FORM_ERRORS.MAX_FEE_IMBALANCE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supportsEIP1559 && gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {
|
|
||||||
if (bnLessThanEqualTo(maxPriorityFeePerGasToUse, 0)) {
|
|
||||||
gasErrors.maxPriorityFee = GAS_FORM_ERRORS.MAX_PRIORITY_FEE_BELOW_MINIMUM;
|
|
||||||
} else if (
|
|
||||||
!isGasEstimatesLoading &&
|
|
||||||
bnLessThan(
|
|
||||||
maxPriorityFeePerGasToUse,
|
|
||||||
gasFeeEstimates?.low?.suggestedMaxPriorityFeePerGas,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
gasWarnings.maxPriorityFee = GAS_FORM_ERRORS.MAX_PRIORITY_FEE_TOO_LOW;
|
|
||||||
} else if (bnGreaterThan(maxPriorityFeePerGasToUse, maxFeePerGasToUse)) {
|
|
||||||
gasErrors.maxFee = GAS_FORM_ERRORS.MAX_FEE_IMBALANCE;
|
|
||||||
} else if (
|
|
||||||
gasFeeEstimates?.high &&
|
|
||||||
bnGreaterThan(
|
|
||||||
maxPriorityFeePerGasToUse,
|
|
||||||
gasFeeEstimates.high.suggestedMaxPriorityFeePerGas *
|
|
||||||
HIGH_FEE_WARNING_MULTIPLIER,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
gasWarnings.maxPriorityFee =
|
|
||||||
GAS_FORM_ERRORS.MAX_PRIORITY_FEE_HIGH_WARNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isGasEstimatesLoading &&
|
|
||||||
bnLessThan(maxFeePerGasToUse, gasFeeEstimates?.low?.suggestedMaxFeePerGas)
|
|
||||||
) {
|
|
||||||
gasWarnings.maxFee = GAS_FORM_ERRORS.MAX_FEE_TOO_LOW;
|
|
||||||
} else if (
|
|
||||||
gasFeeEstimates?.high &&
|
|
||||||
bnGreaterThan(
|
|
||||||
maxFeePerGasToUse,
|
|
||||||
gasFeeEstimates.high.suggestedMaxFeePerGas *
|
|
||||||
HIGH_FEE_WARNING_MULTIPLIER,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
gasWarnings.maxFee = GAS_FORM_ERRORS.MAX_FEE_HIGH_WARNING;
|
|
||||||
}
|
|
||||||
} else if (supportsEIP1559) {
|
|
||||||
estimatesUnavailableWarning = true;
|
|
||||||
} else if (
|
|
||||||
(!supportsEIP1559 || transaction?.txParams?.gasPrice) &&
|
|
||||||
bnLessThanEqualTo(gasPriceToUse, 0)
|
|
||||||
) {
|
|
||||||
gasErrors.gasPrice = GAS_FORM_ERRORS.GAS_PRICE_TOO_LOW;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = {
|
|
||||||
...gasWarnings,
|
|
||||||
...gasErrors,
|
|
||||||
};
|
|
||||||
|
|
||||||
const minimumTxCostInHexWei = addHexes(
|
|
||||||
minimumCostInHexWei,
|
minimumCostInHexWei,
|
||||||
transaction?.txParams?.value || '0x0',
|
minimumGasLimit,
|
||||||
);
|
});
|
||||||
|
|
||||||
const balanceError = conversionGreaterThan(
|
|
||||||
{ value: minimumTxCostInHexWei, fromNumericBase: 'hex' },
|
|
||||||
{ value: ethBalance, fromNumericBase: 'hex' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleGasLimitOutOfBoundError = useCallback(() => {
|
const handleGasLimitOutOfBoundError = useCallback(() => {
|
||||||
if (gasErrors.gasLimit === GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS) {
|
if (gasErrors.gasLimit === GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS) {
|
||||||
@ -521,9 +426,6 @@ export function useGasFeeInputs(
|
|||||||
gasFeeEstimates,
|
gasFeeEstimates,
|
||||||
gasEstimateType,
|
gasEstimateType,
|
||||||
estimatedGasFeeTimeBounds,
|
estimatedGasFeeTimeBounds,
|
||||||
gasErrors: errorsAndWarnings,
|
|
||||||
hasGasErrors: hasBlockingGasErrors,
|
|
||||||
gasWarnings,
|
|
||||||
onManualChange: () => {
|
onManualChange: () => {
|
||||||
setInternalEstimateToUse('custom');
|
setInternalEstimateToUse('custom');
|
||||||
handleGasLimitOutOfBoundError();
|
handleGasLimitOutOfBoundError();
|
||||||
@ -534,8 +436,11 @@ export function useGasFeeInputs(
|
|||||||
setMaxPriorityFeePerGas(maxPriorityFeePerGasToUse);
|
setMaxPriorityFeePerGas(maxPriorityFeePerGasToUse);
|
||||||
setGasPriceHasBeenManuallySet(true);
|
setGasPriceHasBeenManuallySet(true);
|
||||||
},
|
},
|
||||||
|
estimatedBaseFee: gasSettings.baseFeePerGas,
|
||||||
|
gasErrors,
|
||||||
|
hasGasErrors,
|
||||||
|
gasWarnings,
|
||||||
balanceError,
|
balanceError,
|
||||||
estimatesUnavailableWarning,
|
estimatesUnavailableWarning,
|
||||||
estimatedBaseFee: gasSettings.baseFeePerGas,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user