A Metamask fork with Infura removed and default networks editable
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
ciphermask/ui/hooks/useGasFeeInputs.test.js

363 lines
11 KiB

import { act, renderHook } from '@testing-library/react-hooks';
import { useSelector } from 'react-redux';
import { GAS_ESTIMATE_TYPES } from '../../shared/constants/gas';
import { multiplyCurrencies } from '../../shared/modules/conversion.utils';
import { TRANSACTION_ENVELOPE_TYPES } from '../../shared/constants/transaction';
import {
getConversionRate,
getNativeCurrency,
} from '../ducks/metamask/metamask';
import {
checkNetworkAndAccountSupports1559,
getCurrentCurrency,
getShouldShowFiat,
txDataSelector,
getSelectedAccount,
} from '../selectors';
import { ETH, PRIMARY } from '../helpers/constants/common';
import { useGasFeeEstimates } from './useGasFeeEstimates';
import { useGasFeeInputs } from './useGasFeeInputs';
import { useUserPreferencedCurrency } from './useUserPreferencedCurrency';
jest.mock('./useUserPreferencedCurrency', () => ({
useUserPreferencedCurrency: jest.fn(),
}));
jest.mock('./useGasFeeEstimates', () => ({
useGasFeeEstimates: jest.fn(),
}));
jest.mock('react-redux', () => {
const actual = jest.requireActual('react-redux');
return {
...actual,
useSelector: jest.fn(),
};
});
// Why this number?
// 20 gwei * 21000 gasLimit = 420,000 gwei
// 420,000 gwei is 0.00042 ETH
// 0.00042 ETH * 100000 = $42
const MOCK_ETH_USD_CONVERSION_RATE = 100000;
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 HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE = {
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
gasFeeEstimates: {
low: {
minWaitTimeEstimate: 180000,
maxWaitTimeEstimate: 300000,
suggestedMaxPriorityFeePerGas: '3',
suggestedMaxFeePerGas: '53000',
},
medium: {
minWaitTimeEstimate: 15000,
maxWaitTimeEstimate: 60000,
suggestedMaxPriorityFeePerGas: '7',
suggestedMaxFeePerGas: '70000',
},
high: {
minWaitTimeEstimate: 0,
maxWaitTimeEstimate: 15000,
suggestedMaxPriorityFeePerGas: '10',
suggestedMaxFeePerGas: '100000',
},
estimatedBaseFee: '50000',
},
estimatedGasFeeTimeBounds: {},
};
const generateUseSelectorRouter = ({
checkNetworkAndAccountSupports1559Response,
} = {}) => (selector) => {
if (selector === getConversionRate) {
return MOCK_ETH_USD_CONVERSION_RATE;
}
if (selector === getNativeCurrency) {
return ETH;
}
if (selector === getCurrentCurrency) {
return 'USD';
}
if (selector === getShouldShowFiat) {
return true;
}
if (selector === txDataSelector) {
return {
txParams: {
value: '0x5555',
},
};
}
if (selector === getSelectedAccount) {
return {
balance: '0x440aa47cc2556',
};
}
if (selector === checkNetworkAndAccountSupports1559) {
return checkNetworkAndAccountSupports1559Response;
}
return undefined;
};
function getTotalCostInETH(gwei, gasLimit) {
return multiplyCurrencies(gwei, gasLimit, {
fromDenomination: 'GWEI',
toDenomination: 'ETH',
multiplicandBase: 10,
multiplierBase: 10,
});
}
describe('useGasFeeInputs', () => {
beforeEach(() => {
jest.clearAllMocks();
useUserPreferencedCurrency.mockImplementation((type) => {
if (type === PRIMARY) {
return { currency: ETH, numberOfDecimals: 6 };
}
return { currency: 'USD', numberOfDecimals: 2 };
});
});
describe('when using gasPrice API for estimation', () => {
beforeEach(() => {
useGasFeeEstimates.mockImplementation(
() => LEGACY_GAS_ESTIMATE_RETURN_VALUE,
);
useSelector.mockImplementation(generateUseSelectorRouter());
});
it('passes through the raw estimate values from useGasFeeEstimates', () => {
const { result } = renderHook(() => useGasFeeInputs());
expect(result.current.gasFeeEstimates).toMatchObject(
LEGACY_GAS_ESTIMATE_RETURN_VALUE.gasFeeEstimates,
);
expect(result.current.gasEstimateType).toBe(
LEGACY_GAS_ESTIMATE_RETURN_VALUE.gasEstimateType,
);
expect(result.current.estimatedGasFeeTimeBounds).toMatchObject({});
});
it('returns gasPrice appropriately, and "0" for EIP1559 fields', () => {
const { result } = renderHook(() => useGasFeeInputs());
expect(result.current.gasPrice).toBe(
LEGACY_GAS_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium,
);
expect(result.current.maxFeePerGas).toBe('0');
expect(result.current.maxPriorityFeePerGas).toBe('0');
});
it('updates values when user modifies gasPrice', () => {
useSelector.mockImplementation(
generateUseSelectorRouter({
checkNetworkAndAccountSupports1559Response: false,
}),
);
const { result } = renderHook(() => useGasFeeInputs());
expect(result.current.gasPrice).toBe(
LEGACY_GAS_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium,
);
let totalEthGasFee = getTotalCostInETH(
LEGACY_GAS_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium,
result.current.gasLimit,
);
let totalFiat = (
Number(totalEthGasFee) * MOCK_ETH_USD_CONVERSION_RATE
).toFixed(2);
expect(result.current.estimatedMaximumNative).toBe(
`${totalEthGasFee} ETH`,
);
expect(result.current.estimatedMaximumFiat).toBe(`$${totalFiat}`);
expect(result.current.estimatedMinimumFiat).toBe(`$${totalFiat}`);
act(() => {
result.current.setGasPrice('30');
});
totalEthGasFee = getTotalCostInETH('30', result.current.gasLimit);
totalFiat = (
Number(totalEthGasFee) * MOCK_ETH_USD_CONVERSION_RATE
).toFixed(2);
expect(result.current.gasPrice).toBe('30');
expect(result.current.estimatedMaximumNative).toBe(
`${totalEthGasFee} ETH`,
);
expect(result.current.estimatedMaximumFiat).toBe(`$${totalFiat}`);
expect(result.current.estimatedMinimumFiat).toBe(`$${totalFiat}`);
});
});
describe('when transaction is type-0', () => {
beforeEach(() => {
useGasFeeEstimates.mockImplementation(
() => FEE_MARKET_ESTIMATE_RETURN_VALUE,
);
useSelector.mockImplementation(generateUseSelectorRouter());
});
it('returns gasPrice appropriately, and "0" for EIP1559 fields', () => {
const { result } = renderHook(() =>
useGasFeeInputs('medium', {
txParams: {
value: '3782DACE9D90000',
gasLimit: '0x5028',
gasPrice: '0x5028',
type: TRANSACTION_ENVELOPE_TYPES.LEGACY,
},
}),
);
expect(result.current.gasPrice).toBe(0.00002052);
expect(result.current.maxFeePerGas).toBe('0');
expect(result.current.maxPriorityFeePerGas).toBe('0');
expect(result.current.hasBlockingGasErrors).toBeUndefined();
});
});
describe('when using EIP 1559 API for estimation', () => {
beforeEach(() => {
useGasFeeEstimates.mockImplementation(
() => FEE_MARKET_ESTIMATE_RETURN_VALUE,
);
useSelector.mockImplementation(generateUseSelectorRouter());
});
it('passes through the raw estimate values from useGasFeeEstimates', () => {
const { result } = renderHook(() => useGasFeeInputs());
expect(result.current.gasFeeEstimates).toMatchObject(
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates,
);
expect(result.current.gasEstimateType).toBe(
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasEstimateType,
);
expect(result.current.estimatedGasFeeTimeBounds).toMatchObject({});
});
it('returns EIP-1559 fields appropriately, and "0" for gasPrice fields', () => {
const { result } = renderHook(() => useGasFeeInputs());
expect(result.current.gasPrice).toBe('0');
expect(result.current.maxFeePerGas).toBe(
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium
.suggestedMaxFeePerGas,
);
expect(result.current.maxPriorityFeePerGas).toBe(
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium
.suggestedMaxPriorityFeePerGas,
);
});
it('updates values when user modifies maxFeePerGas', () => {
useSelector.mockImplementation(
generateUseSelectorRouter({
checkNetworkAndAccountSupports1559Response: true,
}),
);
const { result } = renderHook(() =>
useGasFeeInputs(null, { txParams: {}, userFeeLevel: 'medium' }),
);
expect(result.current.maxFeePerGas).toBe(
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium
.suggestedMaxFeePerGas,
);
let totalEthGasFee = getTotalCostInETH(
FEE_MARKET_ESTIMATE_RETURN_VALUE.gasFeeEstimates.medium
.suggestedMaxFeePerGas,
result.current.gasLimit,
);
let totalMaxFiat = (
Number(totalEthGasFee) * MOCK_ETH_USD_CONVERSION_RATE
).toFixed(2);
expect(result.current.estimatedMaximumNative).toBe(
`${totalEthGasFee} ETH`,
);
expect(result.current.estimatedMaximumFiat).toBe(`$${totalMaxFiat}`);
// TODO: test minimum fiat too
// expect(result.current.estimatedMinimumFiat).toBe(`$${totalMaxFiat}`);
act(() => {
result.current.setMaxFeePerGas('90');
});
totalEthGasFee = getTotalCostInETH('90', result.current.gasLimit);
totalMaxFiat = (
Number(totalEthGasFee) * MOCK_ETH_USD_CONVERSION_RATE
).toFixed(2);
expect(result.current.maxFeePerGas).toBe('90');
expect(result.current.estimatedMaximumNative).toBe(
`${totalEthGasFee} ETH`,
);
expect(result.current.estimatedMaximumFiat).toBe(`$${totalMaxFiat}`);
// TODO: test minimum fiat too
// expect(result.current.estimatedMinimumFiat).toBe(`$${totalMaxFiat}`);
});
});
describe('when balance is sufficient for minimum transaction cost', () => {
beforeEach(() => {
useGasFeeEstimates.mockImplementation(
() => FEE_MARKET_ESTIMATE_RETURN_VALUE,
);
useSelector.mockImplementation(generateUseSelectorRouter());
});
it('should return false', () => {
const { result } = renderHook(() => useGasFeeInputs());
expect(result.current.balanceError).toBe(false);
});
});
describe('when balance is insufficient for minimum transaction cost', () => {
beforeEach(() => {
useGasFeeEstimates.mockImplementation(
() => HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE,
);
useSelector.mockImplementation(
generateUseSelectorRouter({
checkNetworkAndAccountSupports1559Response: true,
}),
);
});
it('should return true', () => {
const { result } = renderHook(() =>
useGasFeeInputs(null, {
userFeeLevel: 'medium',
txParams: { gas: '0x5208' },
}),
);
expect(result.current.balanceError).toBe(true);
});
});
});