diff --git a/src/gas/GasFeeController.ts b/src/gas/GasFeeController.ts index 1cdd281c66d..583a0d7345f 100644 --- a/src/gas/GasFeeController.ts +++ b/src/gas/GasFeeController.ts @@ -615,7 +615,7 @@ export class GasFeeController extends BaseController< numberOfBlocks: 100, percentiles: [50], }); - const sortedPriorityFees: number[] = feeHistory.blocks + const sortedPriorityFees = feeHistory.blocks .map((block) => block.priorityFeesByPercentile.get(50)) .filter(isNumber) .sort((a, b) => a - b); diff --git a/src/gas/fetchFeeHistory.test.ts b/src/gas/fetchFeeHistory.test.ts index 899e108411a..4fda530b8eb 100644 --- a/src/gas/fetchFeeHistory.test.ts +++ b/src/gas/fetchFeeHistory.test.ts @@ -1,14 +1,10 @@ import { fetchFeeHistory } from './fetchFeeHistory'; type EthFeeHistoryResponse = { - jsonrpc: string; - id: number; - result: { - oldestBlock: string; - baseFeePerGas: string[]; - gasUsedRatio: number[]; - reward: string[][]; - }; + oldestBlock: string; + baseFeePerGas: string[]; + gasUsedRatio: number[]; + reward: string[][]; }; describe('fetchRecentFeeHistory', () => { @@ -31,37 +27,33 @@ describe('fetchRecentFeeHistory', () => { // "params": ["0x5", "latest", [10, 20, 30]] // }' https://mainnet.infura.io/v3/ const ethQuery = buildEthQuery({ - jsonrpc: '2.0', - id: 1, - result: { - oldestBlock: '0xcb1939', - // Note that this array contains 6 items when we requested 5. Per - // , - // baseFeePerGas will always include an extra item which is the calculated base fee for the - // next (future) block. - baseFeePerGas: [ - '0x16eb46a3bb', - '0x14cd6f0628', - '0x1763700ef2', - '0x1477020d14', - '0x129c9eb46b', - '0x134002f480', - ], - gasUsedRatio: [ - 0.13060046666666666, - 0.9972395333333334, - 0, - 0.13780313333333333, - 0.6371707333333333, - ], - reward: [ - ['0x59682f00', '0x59682f00', '0x59682f00'], - ['0x540ae480', '0x59682f00', '0x59682f00'], - ['0x0', '0x0', '0x0'], - ['0x3b9aca00', '0x3b9aca00', '0x3b9aca00'], - ['0x59682f00', '0x59682f00', '0x59682f00'], - ], - }, + oldestBlock: '0xcb1939', + // Note that this array contains 6 items when we requested 5. Per + // , + // baseFeePerGas will always include an extra item which is the calculated base fee for the + // next (future) block. + baseFeePerGas: [ + '0x16eb46a3bb', + '0x14cd6f0628', + '0x1763700ef2', + '0x1477020d14', + '0x129c9eb46b', + '0x134002f480', + ], + gasUsedRatio: [ + 0.13060046666666666, + 0.9972395333333334, + 0, + 0.13780313333333333, + 0.6371707333333333, + ], + reward: [ + ['0x59682f00', '0x59682f00', '0x59682f00'], + ['0x540ae480', '0x59682f00', '0x59682f00'], + ['0x0', '0x0', '0x0'], + ['0x3b9aca00', '0x3b9aca00', '0x3b9aca00'], + ['0x59682f00', '0x59682f00', '0x59682f00'], + ], }); const feeHistory = await fetchFeeHistory({ @@ -124,14 +116,10 @@ describe('fetchRecentFeeHistory', () => { it('should handle an "empty" response from eth_feeHistory', async () => { const ethQuery = buildEthQuery({ - jsonrpc: '2.0', - id: 1, - result: { - oldestBlock: '0x0', - baseFeePerGas: [], - gasUsedRatio: [], - reward: [], - }, + oldestBlock: '0x0', + baseFeePerGas: [], + gasUsedRatio: [], + reward: [], }); const feeHistory = await fetchFeeHistory({ diff --git a/src/gas/fetchFeeHistory.ts b/src/gas/fetchFeeHistory.ts index 6266bfc7d9f..a1c808bd2eb 100644 --- a/src/gas/fetchFeeHistory.ts +++ b/src/gas/fetchFeeHistory.ts @@ -37,26 +37,33 @@ type BlockFeeHistory = { }; type EthFeeHistoryResponse = { - jsonrpc: string; - id: number; - result: { - oldestBlock: HexString; - baseFeePerGas: HexString[]; - gasUsedRatio: number[]; - reward: HexString[][]; - }; + oldestBlock: HexString; + baseFeePerGas: HexString[]; + gasUsedRatio: number[]; + reward: HexString[][]; }; /** * Converts a hexadecimal string to a decimal number. * - * @param hex - A string encoding a hexadecimal number (with a "0x") prefix. + * @param hex - A string encoding a hexadecimal number (with a "0x" prefix). * @returns The number in decimal. */ function hexToDec(hex: string): number { return parseInt(new BN(stripHexPrefix(hex), 16).toString(10), 10); } +/** + * Converts a decimal number to a hexadecimal string. + * + * @param dec - A number in decimal. + * @returns A string encoding that number in hexadecimal (with a "0x" prefix). + */ +function decToHex(dec: number): string { + const hexString = new BN(dec.toString(), 10).toString(16); + return `0x${hexString}`; +} + /** * Uses eth_feeHistory (an EIP-1559 feature) to obtain information about gas and priority fees from * a range of blocks that appeared on a network, starting from a particular block and working @@ -97,19 +104,19 @@ export async function fetchFeeHistory({ numberOfBlocks: number; percentiles: number[]; }): Promise { - const { result }: EthFeeHistoryResponse = await query( + const response: EthFeeHistoryResponse = await query( ethQuery, 'eth_feeHistory', - [{ params: [numberOfBlocks, endBlock, percentiles] }], + [decToHex(numberOfBlocks), endBlock, percentiles], ); - const startBlockId = result.oldestBlock; + const startBlockId = response.oldestBlock; let blocks: BlockFeeHistory[] = []; if ( - result.baseFeePerGas.length > 0 && - result.gasUsedRatio.length > 0 && - result.reward.length > 0 + response.baseFeePerGas.length > 0 && + response.gasUsedRatio.length > 0 && + response.reward.length > 0 ) { blocks = zipEqualSizedTuples({ tuples: [ @@ -117,9 +124,9 @@ export async function fetchFeeHistory({ // , // baseFeePerGas will always include an extra item which is the calculated base fee for the // next (future) block. We don't care about this, so chop it off. - result.baseFeePerGas.slice(0, numberOfBlocks), - result.gasUsedRatio, - result.reward, + response.baseFeePerGas.slice(0, numberOfBlocks), + response.gasUsedRatio, + response.reward, ], numberOfColumnsPerTuple: numberOfBlocks, }).map(([baseFeePerGasAsHex, gasUsedRatio, priorityFeesAsHex]) => { diff --git a/src/util.test.ts b/src/util.test.ts index 1561274e70c..baa5ba4a1ca 100644 --- a/src/util.test.ts +++ b/src/util.test.ts @@ -1,8 +1,6 @@ import 'isomorphic-fetch'; import { BN } from 'ethereumjs-util'; import nock from 'nock'; -import HttpProvider from 'ethjs-provider-http'; -import EthQuery from 'eth-query'; import * as util from './util'; import { Transaction, @@ -20,50 +18,6 @@ const GAS_PRICE = 'gasPrice'; const FAIL = 'lol'; const PASS = '0x1'; -const mockFlags: { [key: string]: any } = { - estimateGas: null, - gasPrice: null, -}; -const PROVIDER = new HttpProvider( - 'https://ropsten.infura.io/v3/341eacb578dd44a1a049cbc5f6fd4035', -); - -jest.mock('eth-query', () => - jest.fn().mockImplementation(() => { - return { - estimateGas: (_transaction: any, callback: any) => { - callback(undefined, '0x0'); - }, - gasPrice: (callback: any) => { - if (mockFlags.gasPrice) { - callback(new Error(mockFlags.gasPrice)); - return; - } - callback(undefined, '0x0'); - }, - getBlockByNumber: ( - _blocknumber: any, - _fetchTxs: boolean, - callback: any, - ) => { - callback(undefined, { gasLimit: '0x0' }); - }, - getCode: (_to: any, callback: any) => { - callback(undefined, '0x0'); - }, - getTransactionByHash: (_hash: any, callback: any) => { - callback(undefined, { blockNumber: '0x1' }); - }, - getTransactionCount: (_from: any, _to: any, callback: any) => { - callback(undefined, '0x0'); - }, - sendRawTransaction: (_transaction: any, callback: any) => { - callback(undefined, '1337'); - }, - }; - }), -); - describe('util', () => { beforeEach(() => { nock.cleanAll(); @@ -931,18 +885,52 @@ describe('util', () => { }); describe('query', () => { - it('should query and resolve', async () => { - const ethQuery = new EthQuery(PROVIDER); - const gasPrice = await util.query(ethQuery, 'gasPrice', []); - expect(gasPrice).toStrictEqual('0x0'); + describe('when the given method exists directly on the EthQuery', () => { + it('should call the method on the EthQuery and, if it is successful, return a promise that resolves to the result', async () => { + const ethQuery = { + getBlockByHash: (blockId: any, cb: any) => cb(null, { id: blockId }), + }; + const result = await util.query(ethQuery, 'getBlockByHash', ['0x1234']); + expect(result).toStrictEqual({ id: '0x1234' }); + }); + + it('should call the method on the EthQuery and, if it errors, return a promise that is rejected with the error', async () => { + const ethQuery = { + getBlockByHash: (_blockId: any, cb: any) => + cb(new Error('uh oh'), null), + }; + await expect( + util.query(ethQuery, 'getBlockByHash', ['0x1234']), + ).rejects.toThrow('uh oh'); + }); }); - it('should query and reject if error', async () => { - const ethQuery = new EthQuery(PROVIDER); - mockFlags.gasPrice = 'Uh oh'; - await expect(util.query(ethQuery, 'gasPrice', [])).rejects.toThrow( - 'Uh oh', - ); + describe('when the given method does not exist directly on the EthQuery', () => { + it('should use sendAsync to call the RPC endpoint and, if it is successful, return a promise that resolves to the result', async () => { + const ethQuery = { + sendAsync: ({ method, params }: any, cb: any) => { + if (method === 'eth_getBlockByHash') { + return cb(null, { id: params[0] }); + } + throw new Error(`Unsupported method ${method}`); + }, + }; + const result = await util.query(ethQuery, 'eth_getBlockByHash', [ + '0x1234', + ]); + expect(result).toStrictEqual({ id: '0x1234' }); + }); + + it('should use sendAsync to call the RPC endpoint and, if it errors, return a promise that is rejected with the error', async () => { + const ethQuery = { + sendAsync: (_args: any, cb: any) => { + cb(new Error('uh oh'), null); + }, + }; + await expect( + util.query(ethQuery, 'eth_getBlockByHash', ['0x1234']), + ).rejects.toThrow('uh oh'); + }); }); }); diff --git a/src/util.ts b/src/util.ts index 5b8e0af5dd2..24584a406b7 100644 --- a/src/util.ts +++ b/src/util.ts @@ -687,13 +687,19 @@ export function query( args: any[] = [], ): Promise { return new Promise((resolve, reject) => { - ethQuery[method](...args, (error: Error, result: any) => { + const cb = (error: Error, result: any) => { if (error) { reject(error); return; } resolve(result); - }); + }; + + if (typeof ethQuery[method] === 'function') { + ethQuery[method](...args, cb); + } else { + ethQuery.sendAsync({ method, params: args }, cb); + } }); }