Add gas utils shared module (#11452)
parent
b1e2005a73
commit
9a6c222632
@ -0,0 +1,129 @@ |
|||||||
|
import { addHexPrefix } from 'ethereumjs-util'; |
||||||
|
import { |
||||||
|
addCurrencies, |
||||||
|
conversionGreaterThan, |
||||||
|
multiplyCurrencies, |
||||||
|
} from './conversion.utils'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Accepts an options bag containing gas fee parameters in hex format and |
||||||
|
* returns a gasTotal parameter representing the maximum amount of wei the |
||||||
|
* transaction will cost. |
||||||
|
* |
||||||
|
* @param {object} options - gas fee parameters object |
||||||
|
* @param {string} [options.gasLimit] - the maximum amount of gas to allow this |
||||||
|
* transaction to consume. Value is a hex string |
||||||
|
* @param {string} [options.gasPrice] - The fee in wei to pay per gas used. |
||||||
|
* gasPrice is only set on Legacy type transactions. Value is hex string |
||||||
|
* @param {string} [options.maxFeePerGas] - The maximum fee in wei to pay per |
||||||
|
* gas used. maxFeePerGas is introduced in EIP 1559 and represents the max |
||||||
|
* total a user will pay per gas. Actual cost is determined by baseFeePerGas |
||||||
|
* on the block + maxPriorityFeePerGas. Value is hex string |
||||||
|
* @returns {string} - The maximum total cost of transaction in hex wei string |
||||||
|
*/ |
||||||
|
export function getMaximumGasTotalInHexWei({ |
||||||
|
gasLimit = '0x0', |
||||||
|
gasPrice, |
||||||
|
maxFeePerGas, |
||||||
|
} = {}) { |
||||||
|
if (maxFeePerGas) { |
||||||
|
return addHexPrefix( |
||||||
|
multiplyCurrencies(gasLimit, maxFeePerGas, { |
||||||
|
toNumericBase: 'hex', |
||||||
|
multiplicandBase: 16, |
||||||
|
multiplierBase: 16, |
||||||
|
}), |
||||||
|
); |
||||||
|
} |
||||||
|
if (!gasPrice) { |
||||||
|
throw new Error( |
||||||
|
'getMaximumGasTotalInHexWei requires gasPrice be provided to calculate legacy gas total', |
||||||
|
); |
||||||
|
} |
||||||
|
return addHexPrefix( |
||||||
|
multiplyCurrencies(gasLimit, gasPrice, { |
||||||
|
toNumericBase: 'hex', |
||||||
|
multiplicandBase: 16, |
||||||
|
multiplierBase: 16, |
||||||
|
}), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Accepts an options bag containing gas fee parameters in hex format and |
||||||
|
* returns a gasTotal parameter representing the minimum amount of wei the |
||||||
|
* transaction will cost. For gasPrice types this is the same as max. |
||||||
|
* |
||||||
|
* @param {object} options - gas fee parameters object |
||||||
|
* @param {string} [options.gasLimit] - the maximum amount of gas to allow this |
||||||
|
* transaction to consume. Value is a hex string |
||||||
|
* @param {string} [options.gasPrice] - The fee in wei to pay per gas used. |
||||||
|
* gasPrice is only set on Legacy type transactions. Value is hex string |
||||||
|
* @param {string} [options.maxFeePerGas] - The maximum fee in wei to pay per |
||||||
|
* gas used. maxFeePerGas is introduced in EIP 1559 and represents the max |
||||||
|
* total a user will pay per gas. Actual cost is determined by baseFeePerGas |
||||||
|
* on the block + maxPriorityFeePerGas. Value is hex string |
||||||
|
* @param {string} [options.maxPriorityFeePerGas] - The maximum fee in wei to |
||||||
|
* pay a miner to include this transaction. |
||||||
|
* @param {string} [options.baseFeePerGas] - The estimated block baseFeePerGas |
||||||
|
* that will be burned. Introduced in EIP 1559. Value in hex wei. |
||||||
|
* @returns {string} - The minimum total cost of transaction in hex wei string |
||||||
|
*/ |
||||||
|
export function getMinimumGasTotalInHexWei({ |
||||||
|
gasLimit = '0x0', |
||||||
|
gasPrice, |
||||||
|
maxPriorityFeePerGas, |
||||||
|
maxFeePerGas, |
||||||
|
baseFeePerGas, |
||||||
|
} = {}) { |
||||||
|
const isEIP1559Estimate = Boolean( |
||||||
|
maxFeePerGas || maxPriorityFeePerGas || baseFeePerGas, |
||||||
|
); |
||||||
|
if (isEIP1559Estimate && gasPrice) { |
||||||
|
throw new Error( |
||||||
|
`getMinimumGasTotalInHexWei expects either gasPrice OR the EIP-1559 gas fields, but both were provided`, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (isEIP1559Estimate === false && !gasPrice) { |
||||||
|
throw new Error( |
||||||
|
`getMinimumGasTotalInHexWei expects either gasPrice OR the EIP-1559 gas fields, but neither were provided`, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (isEIP1559Estimate && !baseFeePerGas) { |
||||||
|
throw new Error( |
||||||
|
`getMinimumGasTotalInHexWei requires baseFeePerGas be provided when calculating EIP-1559 totals`, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (isEIP1559Estimate && (!maxFeePerGas || !maxPriorityFeePerGas)) { |
||||||
|
throw new Error( |
||||||
|
`getMinimumGasTotalInHexWei requires maxFeePerGas and maxPriorityFeePerGas be provided when calculating EIP-1559 totals`, |
||||||
|
); |
||||||
|
} |
||||||
|
if (isEIP1559Estimate === false) { |
||||||
|
return getMaximumGasTotalInHexWei({ gasLimit, gasPrice }); |
||||||
|
} |
||||||
|
const minimumFeePerGas = addCurrencies(baseFeePerGas, maxPriorityFeePerGas, { |
||||||
|
toNumericBase: 'hex', |
||||||
|
aBase: 16, |
||||||
|
bBase: 16, |
||||||
|
}); |
||||||
|
|
||||||
|
if ( |
||||||
|
conversionGreaterThan( |
||||||
|
{ value: minimumFeePerGas, fromNumericBase: 'hex' }, |
||||||
|
{ value: maxFeePerGas, fromNumericBase: 'hex' }, |
||||||
|
) |
||||||
|
) { |
||||||
|
return getMaximumGasTotalInHexWei({ gasLimit, maxFeePerGas }); |
||||||
|
} |
||||||
|
return addHexPrefix( |
||||||
|
multiplyCurrencies(gasLimit, minimumFeePerGas, { |
||||||
|
toNumericBase: 'hex', |
||||||
|
multiplicandBase: 16, |
||||||
|
multiplierBase: 16, |
||||||
|
}), |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,131 @@ |
|||||||
|
const { addHexPrefix } = require('ethereumjs-util'); |
||||||
|
const { conversionUtil } = require('./conversion.utils'); |
||||||
|
const { |
||||||
|
getMaximumGasTotalInHexWei, |
||||||
|
getMinimumGasTotalInHexWei, |
||||||
|
} = require('./gas.utils'); |
||||||
|
|
||||||
|
const feesToTest = [10, 24, 90]; |
||||||
|
const tipsToTest = [2, 10, 50]; |
||||||
|
const baseFeesToTest = [8, 12, 24]; |
||||||
|
const gasLimitsToTest = [21000, 100000]; |
||||||
|
|
||||||
|
describe('gas utils', () => { |
||||||
|
describe('when using EIP 1559 fields', () => { |
||||||
|
describe('getMaximumGasTotalInHexWei', () => { |
||||||
|
feesToTest.forEach((maxFeePerGas) => { |
||||||
|
describe(`when maxFeePerGas is ${maxFeePerGas}`, () => { |
||||||
|
gasLimitsToTest.forEach((gasLimit) => { |
||||||
|
const expectedResult = (gasLimit * maxFeePerGas).toString(); |
||||||
|
const gasLimitHex = addHexPrefix(gasLimit.toString(16)); |
||||||
|
const result = conversionUtil( |
||||||
|
getMaximumGasTotalInHexWei({ |
||||||
|
gasLimit: gasLimitHex, |
||||||
|
maxFeePerGas: addHexPrefix(maxFeePerGas.toString(16)), |
||||||
|
}), |
||||||
|
{ fromNumericBase: 'hex', toNumericBase: 'dec' }, |
||||||
|
); |
||||||
|
it(`returns ${expectedResult} when provided gasLimit: ${gasLimit}`, () => { |
||||||
|
expect(result).toStrictEqual(expectedResult); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('getMinimumGasTotalInHexWei', () => { |
||||||
|
feesToTest.forEach((maxFeePerGas) => { |
||||||
|
tipsToTest.forEach((maxPriorityFeePerGas) => { |
||||||
|
baseFeesToTest.forEach((baseFeePerGas) => { |
||||||
|
describe(`when baseFee is ${baseFeePerGas}, maxFeePerGas is ${maxFeePerGas} and tip is ${maxPriorityFeePerGas}`, () => { |
||||||
|
const maximum = maxFeePerGas; |
||||||
|
const minimum = baseFeePerGas + maxPriorityFeePerGas; |
||||||
|
const expectedEffectiveGasPrice = |
||||||
|
minimum < maximum ? minimum : maximum; |
||||||
|
const results = gasLimitsToTest.map((gasLimit) => { |
||||||
|
const gasLimitHex = addHexPrefix(gasLimit.toString(16)); |
||||||
|
const result = conversionUtil( |
||||||
|
getMinimumGasTotalInHexWei({ |
||||||
|
gasLimit: gasLimitHex, |
||||||
|
maxFeePerGas: addHexPrefix(maxFeePerGas.toString(16)), |
||||||
|
maxPriorityFeePerGas: addHexPrefix( |
||||||
|
maxPriorityFeePerGas.toString(16), |
||||||
|
), |
||||||
|
baseFeePerGas: addHexPrefix(baseFeePerGas.toString(16)), |
||||||
|
}), |
||||||
|
{ fromNumericBase: 'hex', toNumericBase: 'dec' }, |
||||||
|
); |
||||||
|
return { result, gasLimit }; |
||||||
|
}); |
||||||
|
it(`should use an effective gasPrice of ${expectedEffectiveGasPrice}`, () => { |
||||||
|
expect( |
||||||
|
results.every(({ result, gasLimit }) => { |
||||||
|
const effectiveGasPrice = Number(result) / gasLimit; |
||||||
|
return effectiveGasPrice === expectedEffectiveGasPrice; |
||||||
|
}), |
||||||
|
).toBe(true); |
||||||
|
}); |
||||||
|
results.forEach(({ result, gasLimit }) => { |
||||||
|
const expectedResult = ( |
||||||
|
expectedEffectiveGasPrice * gasLimit |
||||||
|
).toString(); |
||||||
|
it(`returns ${expectedResult} when provided gasLimit: ${gasLimit}`, () => { |
||||||
|
expect(result).toStrictEqual(expectedResult); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('when using legacy fields', () => { |
||||||
|
describe('getMaximumGasTotalInHexWei', () => { |
||||||
|
feesToTest.forEach((gasPrice) => { |
||||||
|
describe(`when gasPrice is ${gasPrice}`, () => { |
||||||
|
gasLimitsToTest.forEach((gasLimit) => { |
||||||
|
const expectedResult = (gasLimit * gasPrice).toString(); |
||||||
|
const gasLimitHex = addHexPrefix(gasLimit.toString(16)); |
||||||
|
it(`returns ${expectedResult} when provided gasLimit of ${gasLimit}`, () => { |
||||||
|
expect( |
||||||
|
conversionUtil( |
||||||
|
getMaximumGasTotalInHexWei({ |
||||||
|
gasLimit: gasLimitHex, |
||||||
|
gasPrice: addHexPrefix(gasPrice.toString(16)), |
||||||
|
}), |
||||||
|
{ fromNumericBase: 'hex', toNumericBase: 'dec' }, |
||||||
|
), |
||||||
|
).toStrictEqual(expectedResult); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('getMinimumGasTotalInHexWei', () => { |
||||||
|
feesToTest.forEach((gasPrice) => { |
||||||
|
describe(`when gasPrice is ${gasPrice}`, () => { |
||||||
|
gasLimitsToTest.forEach((gasLimit) => { |
||||||
|
const expectedResult = (gasLimit * gasPrice).toString(); |
||||||
|
const gasLimitHex = addHexPrefix(gasLimit.toString(16)); |
||||||
|
it(`returns ${expectedResult} when provided gasLimit of ${gasLimit}`, () => { |
||||||
|
expect( |
||||||
|
conversionUtil( |
||||||
|
getMinimumGasTotalInHexWei({ |
||||||
|
gasLimit: gasLimitHex, |
||||||
|
gasPrice: addHexPrefix(gasPrice.toString(16)), |
||||||
|
}), |
||||||
|
{ |
||||||
|
fromNumericBase: 'hex', |
||||||
|
toNumericBase: 'dec', |
||||||
|
}, |
||||||
|
), |
||||||
|
).toStrictEqual(expectedResult); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue