1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00
metamask-extension/shared/modules/conversion.utils.js
Brad Decker e0953d9f68
Update send and confirm state management, and tx controller gas defaults, for EIP1559 ()
wip

Documentation improvements for send slice support of EIP1559

Remove console.log in send duck

Property lookup safety improvement in selectors/confirm-transaction

Add code accidentally removed in rebase

Update addTxGasDefaults and _getDefaultGasFees to work with new estimate types, and ensure we correctly handle gas price estimates when on EIP1559 networks ()

* Fix typo

Remove console.log in send duck

* Update addTxGasDefaults and _getDefaultGasFees to work correctly with all new gas fee estimate types

* Don't show gas timing support when not on eip1559 compatible network

* Hide gas timing component on transaction screen when on a non-1559 network

* Improve comments, tests and edge case handling

* Ensure eip1559 fees are applied and updated correctly when eip1559 estimate api fails

* Lint fix

Co-authored-by: Brad Decker <git@braddecker.dev>

Remove console.log

Handle possible gasEstimateType undefined

Remove unnecessary nonce field position change in confirm-page-container-content__details
2021-07-30 22:15:18 -02:30

293 lines
8.5 KiB
JavaScript

/* Currency Conversion Utility
* This utility function can be used for converting currency related values within metamask.
* The caller should be able to pass it a value, along with information about the value's
* numeric base, denomination and currency, and the desired numeric base, denomination and
* currency. It should return a single value.
*
* @param {(number | string | BN)} value - The value to convert.
* @param {Object} [options] - Options to specify details of the conversion
* @param {string} [options.fromCurrency = 'ETH' | 'USD'] - The currency of the passed value
* @param {string} [options.toCurrency = 'ETH' | 'USD'] - The desired currency of the result
* @param {string} [options.fromNumericBase = 'hex' | 'dec' | 'BN'] - The numeric basic of the passed value.
* @param {string} [options.toNumericBase = 'hex' | 'dec' | 'BN'] - The desired numeric basic of the result.
* @param {string} [options.fromDenomination = 'WEI'] - The denomination of the passed value
* @param {string} [options.numberOfDecimals] - The desired number of decimals in the result
* @param {string} [options.roundDown] - The desired number of decimals to round down to
* @param {number} [options.conversionRate] - The rate to use to make the fromCurrency -> toCurrency conversion
* @returns {(number | string | BN)}
*
* The utility passes value along with the options as a single object to the `converter` function.
* `converter` conditional modifies the supplied `value` property, depending
* on the accompanying options.
*/
import BigNumber from 'bignumber.js';
import { stripHexPrefix, BN } from 'ethereumjs-util';
// Big Number Constants
const BIG_NUMBER_WEI_MULTIPLIER = new BigNumber('1000000000000000000');
const BIG_NUMBER_GWEI_MULTIPLIER = new BigNumber('1000000000');
const BIG_NUMBER_ETH_MULTIPLIER = new BigNumber('1');
// Setter Maps
const toBigNumber = {
hex: (n) => new BigNumber(stripHexPrefix(n), 16),
dec: (n) => new BigNumber(String(n), 10),
BN: (n) => new BigNumber(n.toString(16), 16),
};
const toNormalizedDenomination = {
WEI: (bigNumber) => bigNumber.div(BIG_NUMBER_WEI_MULTIPLIER),
GWEI: (bigNumber) => bigNumber.div(BIG_NUMBER_GWEI_MULTIPLIER),
ETH: (bigNumber) => bigNumber.div(BIG_NUMBER_ETH_MULTIPLIER),
};
const toSpecifiedDenomination = {
WEI: (bigNumber) => bigNumber.times(BIG_NUMBER_WEI_MULTIPLIER).round(),
GWEI: (bigNumber) => bigNumber.times(BIG_NUMBER_GWEI_MULTIPLIER).round(9),
ETH: (bigNumber) => bigNumber.times(BIG_NUMBER_ETH_MULTIPLIER).round(9),
};
const baseChange = {
hex: (n) => n.toString(16),
dec: (n) => new BigNumber(n).toString(10),
BN: (n) => new BN(n.toString(16)),
};
// Utility function for checking base types
const isValidBase = (base) => {
return Number.isInteger(base) && base > 1;
};
/**
* Defines the base type of numeric value
* @typedef {('hex' | 'dec' | 'BN')} NumericBase
*/
/**
* Defines which type of denomination a value is in
* @typedef {('WEI' | 'GWEI' | 'ETH')} EthDenomination
*/
/**
* Utility method to convert a value between denominations, formats and currencies.
* @param {Object} input
* @param {string | BigNumber} input.value
* @param {NumericBase} input.fromNumericBase
* @param {EthDenomination} [input.fromDenomination]
* @param {string} [input.fromCurrency]
* @param {NumericBase} input.toNumericBase
* @param {EthDenomination} [input.toDenomination]
* @param {string} [input.toCurrency]
* @param {number} [input.numberOfDecimals]
* @param {number} [input.conversionRate]
* @param {boolean} [input.invertConversionRate]
* @param {string} [input.roundDown]
*/
const converter = ({
value,
fromNumericBase,
fromDenomination,
fromCurrency,
toNumericBase,
toDenomination,
toCurrency,
numberOfDecimals,
conversionRate,
invertConversionRate,
roundDown,
}) => {
let convertedValue = fromNumericBase
? toBigNumber[fromNumericBase](value)
: value;
if (fromDenomination) {
convertedValue = toNormalizedDenomination[fromDenomination](convertedValue);
}
if (fromCurrency !== toCurrency) {
if (conversionRate === null || conversionRate === undefined) {
throw new Error(
`Converting from ${fromCurrency} to ${toCurrency} requires a conversionRate, but one was not provided`,
);
}
let rate = toBigNumber.dec(conversionRate);
if (invertConversionRate) {
rate = new BigNumber(1.0).div(conversionRate);
}
convertedValue = convertedValue.times(rate);
}
if (toDenomination) {
convertedValue = toSpecifiedDenomination[toDenomination](convertedValue);
}
if (numberOfDecimals) {
convertedValue = convertedValue.round(
numberOfDecimals,
BigNumber.ROUND_HALF_DOWN,
);
}
if (roundDown) {
convertedValue = convertedValue.round(roundDown, BigNumber.ROUND_DOWN);
}
if (toNumericBase) {
convertedValue = baseChange[toNumericBase](convertedValue);
}
return convertedValue;
};
const conversionUtil = (
value,
{
fromCurrency = null,
toCurrency = fromCurrency,
fromNumericBase,
toNumericBase,
fromDenomination,
toDenomination,
numberOfDecimals,
conversionRate,
invertConversionRate,
},
) => {
if (fromCurrency !== toCurrency && !conversionRate) {
return 0;
}
return converter({
fromCurrency,
toCurrency,
fromNumericBase,
toNumericBase,
fromDenomination,
toDenomination,
numberOfDecimals,
conversionRate,
invertConversionRate,
value: value || '0',
});
};
const getBigNumber = (value, base) => {
if (!isValidBase(base)) {
throw new Error('Must specificy valid base');
}
// We don't include 'number' here, because BigNumber will throw if passed
// a number primitive it considers unsafe.
if (typeof value === 'string' || value instanceof BigNumber) {
return new BigNumber(value, base);
}
return new BigNumber(String(value), base);
};
const addCurrencies = (a, b, options = {}) => {
const { aBase, bBase, ...conversionOptions } = options;
if (!isValidBase(aBase) || !isValidBase(bBase)) {
throw new Error('Must specify valid aBase and bBase');
}
const value = getBigNumber(a, aBase).add(getBigNumber(b, bBase));
return converter({
value,
...conversionOptions,
});
};
const subtractCurrencies = (a, b, options = {}) => {
const { aBase, bBase, ...conversionOptions } = options;
if (!isValidBase(aBase) || !isValidBase(bBase)) {
throw new Error('Must specify valid aBase and bBase');
}
const value = getBigNumber(a, aBase).minus(getBigNumber(b, bBase));
return converter({
value,
...conversionOptions,
});
};
const multiplyCurrencies = (a, b, options = {}) => {
const { multiplicandBase, multiplierBase, ...conversionOptions } = options;
if (!isValidBase(multiplicandBase) || !isValidBase(multiplierBase)) {
throw new Error('Must specify valid multiplicandBase and multiplierBase');
}
const value = getBigNumber(a, multiplicandBase).times(
getBigNumber(b, multiplierBase),
);
return converter({
value,
...conversionOptions,
});
};
const conversionGreaterThan = ({ ...firstProps }, { ...secondProps }) => {
const firstValue = converter({ ...firstProps });
const secondValue = converter({ ...secondProps });
return firstValue.gt(secondValue);
};
const conversionLessThan = ({ ...firstProps }, { ...secondProps }) => {
const firstValue = converter({ ...firstProps });
const secondValue = converter({ ...secondProps });
return firstValue.lt(secondValue);
};
const conversionMax = ({ ...firstProps }, { ...secondProps }) => {
const firstIsGreater = conversionGreaterThan(
{ ...firstProps },
{ ...secondProps },
);
return firstIsGreater ? firstProps.value : secondProps.value;
};
const conversionGTE = ({ ...firstProps }, { ...secondProps }) => {
const firstValue = converter({ ...firstProps });
const secondValue = converter({ ...secondProps });
return firstValue.greaterThanOrEqualTo(secondValue);
};
const conversionLTE = ({ ...firstProps }, { ...secondProps }) => {
const firstValue = converter({ ...firstProps });
const secondValue = converter({ ...secondProps });
return firstValue.lessThanOrEqualTo(secondValue);
};
const toNegative = (n, options = {}) => {
return multiplyCurrencies(n, -1, options);
};
function decGWEIToHexWEI(decGWEI) {
return conversionUtil(decGWEI, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
fromDenomination: 'GWEI',
toDenomination: 'WEI',
});
}
export {
conversionUtil,
addCurrencies,
multiplyCurrencies,
conversionGreaterThan,
conversionLessThan,
conversionGTE,
conversionLTE,
conversionMax,
toNegative,
subtractCurrencies,
decGWEIToHexWEI,
};