From cc471dc7a58146b3b52ad9ac851790a2b211bfd5 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Tue, 6 Jul 2021 15:25:15 -0500 Subject: [PATCH] make more configurable (#513) --- src/gas/GasFeeController.test.ts | 65 +++++++++++++++++++++++++++----- src/gas/GasFeeController.ts | 44 +++++++++++++++++---- src/gas/gas-util.test.ts | 11 +++--- src/gas/gas-util.ts | 15 ++++---- 4 files changed, 103 insertions(+), 32 deletions(-) diff --git a/src/gas/GasFeeController.test.ts b/src/gas/GasFeeController.test.ts index 9946f49d7e..51f13d26fe 100644 --- a/src/gas/GasFeeController.test.ts +++ b/src/gas/GasFeeController.test.ts @@ -7,9 +7,9 @@ import { GasFeeStateChange, LegacyGasPriceEstimate, } from './GasFeeController'; -import { EXTERNAL_GAS_PRICES_API_URL } from './gas-util'; -const GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/'; +const TEST_GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/'; +const TEST_LEGACY_FEE_API = 'https://test/'; const name = 'GasFeeController'; @@ -30,8 +30,9 @@ function getRestrictedMessenger() { describe('GasFeeController', () => { let gasFeeController: GasFeeController; - let getIsMainnet: jest.Mock; + let getCurrentNetworkLegacyGasAPICompatibility: jest.Mock; let getIsEIP1559Compatible: jest.Mock>; + let getChainId: jest.Mock<`0x${string}` | `${number}` | number>; beforeAll(() => { nock.disableNetConnect(); @@ -42,11 +43,14 @@ describe('GasFeeController', () => { }); beforeEach(() => { - getIsMainnet = jest.fn().mockImplementation(() => false); + getChainId = jest.fn().mockImplementation(() => '0x1'); + getCurrentNetworkLegacyGasAPICompatibility = jest + .fn() + .mockImplementation(() => false); getIsEIP1559Compatible = jest .fn() .mockImplementation(() => Promise.resolve(true)); - nock(GAS_FEE_API) + nock(TEST_GAS_FEE_API.replace('', '1')) .get(/.+/u) .reply(200, { low: { @@ -71,7 +75,7 @@ describe('GasFeeController', () => { }) .persist(); - nock(EXTERNAL_GAS_PRICES_API_URL) + nock(TEST_LEGACY_FEE_API.replace('', '0x1')) .get(/.+/u) .reply(200, { SafeGasPrice: '22', @@ -84,8 +88,11 @@ describe('GasFeeController', () => { interval: 10000, messenger: getRestrictedMessenger(), getProvider: () => stub(), + getChainId, + legacyAPIEndpoint: TEST_LEGACY_FEE_API, + EIP1559APIEndpoint: TEST_GAS_FEE_API, onNetworkStateChange: () => stub(), - getIsMainnet, + getCurrentNetworkLegacyGasAPICompatibility, getCurrentNetworkEIP1559Compatibility: getIsEIP1559Compatible, // change this for networkController.state.properties.isEIP1559Compatible ??? }); }); @@ -113,9 +120,47 @@ describe('GasFeeController', () => { ); }); - describe('when on mainnet before london', () => { + describe('when on any network supporting legacy gas estimation api', () => { it('should _fetchGasFeeEstimateData', async () => { - getIsMainnet.mockImplementation(() => true); + getCurrentNetworkLegacyGasAPICompatibility.mockImplementation(() => true); + getIsEIP1559Compatible.mockImplementation(() => Promise.resolve(false)); + expect(gasFeeController.state.gasFeeEstimates).toStrictEqual({}); + const estimates = await gasFeeController._fetchGasFeeEstimateData(); + expect(estimates).toHaveProperty('gasFeeEstimates'); + expect( + (gasFeeController.state.gasFeeEstimates as LegacyGasPriceEstimate).high, + ).toBe('30'); + }); + }); + + describe('getChainId', () => { + it('should work with a number input', async () => { + getChainId.mockImplementation(() => 1); + getCurrentNetworkLegacyGasAPICompatibility.mockImplementation(() => true); + getIsEIP1559Compatible.mockImplementation(() => Promise.resolve(false)); + expect(gasFeeController.state.gasFeeEstimates).toStrictEqual({}); + const estimates = await gasFeeController._fetchGasFeeEstimateData(); + expect(estimates).toHaveProperty('gasFeeEstimates'); + expect( + (gasFeeController.state.gasFeeEstimates as LegacyGasPriceEstimate).high, + ).toBe('30'); + }); + + it('should work with a hexstring input', async () => { + getChainId.mockImplementation(() => '0x1'); + getCurrentNetworkLegacyGasAPICompatibility.mockImplementation(() => true); + getIsEIP1559Compatible.mockImplementation(() => Promise.resolve(false)); + expect(gasFeeController.state.gasFeeEstimates).toStrictEqual({}); + const estimates = await gasFeeController._fetchGasFeeEstimateData(); + expect(estimates).toHaveProperty('gasFeeEstimates'); + expect( + (gasFeeController.state.gasFeeEstimates as LegacyGasPriceEstimate).high, + ).toBe('30'); + }); + + it('should work with a numeric string input', async () => { + getChainId.mockImplementation(() => '1'); + getCurrentNetworkLegacyGasAPICompatibility.mockImplementation(() => true); getIsEIP1559Compatible.mockImplementation(() => Promise.resolve(false)); expect(gasFeeController.state.gasFeeEstimates).toStrictEqual({}); const estimates = await gasFeeController._fetchGasFeeEstimateData(); @@ -128,7 +173,7 @@ describe('GasFeeController', () => { describe('when on any network supporting EIP-1559', () => { it('should _fetchGasFeeEstimateData', async () => { - getIsMainnet.mockImplementation(() => true); + getCurrentNetworkLegacyGasAPICompatibility.mockImplementation(() => true); expect(gasFeeController.state.gasFeeEstimates).toStrictEqual({}); const estimates = await gasFeeController._fetchGasFeeEstimateData(); expect(estimates).toHaveProperty('gasFeeEstimates'); diff --git a/src/gas/GasFeeController.ts b/src/gas/GasFeeController.ts index f6d226ab7b..f1e6d1e403 100644 --- a/src/gas/GasFeeController.ts +++ b/src/gas/GasFeeController.ts @@ -2,6 +2,7 @@ import type { Patch } from 'immer'; import EthQuery from 'eth-query'; import { v1 as random } from 'uuid'; +import { isHexString } from 'ethereumjs-util'; import { BaseController } from '../BaseControllerV2'; import { safelyExecute } from '../util'; import type { RestrictedControllerMessenger } from '../ControllerMessenger'; @@ -16,6 +17,9 @@ import { calculateTimeEstimate, } from './gas-util'; +const GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/'; +export const LEGACY_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`; + export type unknownString = 'unknown'; // Fee Market describes the way gas is set after the london hardfork, and was @@ -197,6 +201,10 @@ export class GasFeeController extends BaseController { private pollTokens: Set; + private legacyAPIEndpoint: string; + + private EIP1559APIEndpoint: string; + private fetchGasEstimates; private fetchEthGasPriceEstimate; @@ -205,9 +213,11 @@ export class GasFeeController extends BaseController { private getCurrentNetworkEIP1559Compatibility; + private getCurrentNetworkLegacyGasAPICompatibility; + private getCurrentAccountEIP1559Compatibility; - private getIsMainnet; + private getChainId; private ethQuery: any; @@ -224,9 +234,12 @@ export class GasFeeController extends BaseController { fetchLegacyGasPriceEstimates = defaultFetchLegacyGasPriceEstimates, getCurrentNetworkEIP1559Compatibility, getCurrentAccountEIP1559Compatibility, - getIsMainnet, + getChainId, + getCurrentNetworkLegacyGasAPICompatibility, getProvider, onNetworkStateChange, + legacyAPIEndpoint = LEGACY_GAS_PRICES_API_URL, + EIP1559APIEndpoint = GAS_FEE_API, }: { interval?: number; messenger: RestrictedControllerMessenger< @@ -241,10 +254,13 @@ export class GasFeeController extends BaseController { fetchEthGasPriceEstimate?: typeof defaultFetchEthGasPriceEstimate; fetchLegacyGasPriceEstimates?: typeof defaultFetchLegacyGasPriceEstimates; getCurrentNetworkEIP1559Compatibility: () => Promise; + getCurrentNetworkLegacyGasAPICompatibility: () => boolean; getCurrentAccountEIP1559Compatibility?: () => boolean; - getIsMainnet: () => boolean; + getChainId: () => `0x${string}` | `${number}` | number; getProvider: () => NetworkController['provider']; onNetworkStateChange: (listener: (state: NetworkState) => void) => void; + legacyAPIEndpoint?: string; + EIP1559APIEndpoint?: string; }) { super({ name, @@ -258,8 +274,11 @@ export class GasFeeController extends BaseController { this.fetchLegacyGasPriceEstimates = fetchLegacyGasPriceEstimates; this.pollTokens = new Set(); this.getCurrentNetworkEIP1559Compatibility = getCurrentNetworkEIP1559Compatibility; + this.getCurrentNetworkLegacyGasAPICompatibility = getCurrentNetworkLegacyGasAPICompatibility; this.getCurrentAccountEIP1559Compatibility = getCurrentAccountEIP1559Compatibility; - this.getIsMainnet = getIsMainnet; + this.EIP1559APIEndpoint = EIP1559APIEndpoint; + this.legacyAPIEndpoint = legacyAPIEndpoint; + this.getChainId = getChainId; const provider = getProvider(); this.ethQuery = new EthQuery(provider); @@ -294,7 +313,12 @@ export class GasFeeController extends BaseController { */ async _fetchGasFeeEstimateData(): Promise { let isEIP1559Compatible; - const isMainnet = this.getIsMainnet(); + const isLegacyGasAPICompatible = this.getCurrentNetworkLegacyGasAPICompatibility(); + + let chainId = this.getChainId(); + if (typeof chainId === 'string' && isHexString(chainId)) { + chainId = parseInt(chainId, 16); + } try { isEIP1559Compatible = await this.getEIP1559Compatibility(); } catch (e) { @@ -310,7 +334,9 @@ export class GasFeeController extends BaseController { try { if (isEIP1559Compatible) { - const estimates = await this.fetchGasEstimates(); + const estimates = await this.fetchGasEstimates( + this.EIP1559APIEndpoint.replace('', `${chainId}`), + ); const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas, @@ -324,8 +350,10 @@ export class GasFeeController extends BaseController { estimatedGasFeeTimeBounds, gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET, }; - } else if (isMainnet) { - const estimates = await this.fetchLegacyGasPriceEstimates(); + } else if (isLegacyGasAPICompatible) { + const estimates = await this.fetchLegacyGasPriceEstimates( + this.legacyAPIEndpoint.replace('', `${chainId}`), + ); newState = { gasFeeEstimates: estimates, estimatedGasFeeTimeBounds: {}, diff --git a/src/gas/gas-util.test.ts b/src/gas/gas-util.test.ts index 15323f1f20..cff089d42b 100644 --- a/src/gas/gas-util.test.ts +++ b/src/gas/gas-util.test.ts @@ -1,13 +1,10 @@ import nock from 'nock'; -import { - EXTERNAL_GAS_PRICES_API_URL, - fetchLegacyGasPriceEstimates, -} from './gas-util'; +import { fetchLegacyGasPriceEstimates } from './gas-util'; describe('gas utils', () => { describe('fetchLegacyGasPriceEstimates', () => { it('should fetch external gasPrices and return high/medium/low', async () => { - const scope = nock(EXTERNAL_GAS_PRICES_API_URL) + const scope = nock('https://not-a-real-url/') .get(/.+/u) .reply(200, { SafeGasPrice: '22', @@ -15,7 +12,9 @@ describe('gas utils', () => { FastGasPrice: '30', }) .persist(); - const result = await fetchLegacyGasPriceEstimates(); + const result = await fetchLegacyGasPriceEstimates( + 'https://not-a-real-url/', + ); expect(result).toMatchObject({ high: '30', medium: '25', diff --git a/src/gas/gas-util.ts b/src/gas/gas-util.ts index 6667e3a735..e2ca7346c3 100644 --- a/src/gas/gas-util.ts +++ b/src/gas/gas-util.ts @@ -8,20 +8,19 @@ import { LegacyGasPriceEstimate, } from './GasFeeController'; -const GAS_FEE_API = 'https://mock-gas-server.herokuapp.com/'; -export const EXTERNAL_GAS_PRICES_API_URL = `https://api.metaswap.codefi.network/gasPrices`; - -export async function fetchGasEstimates(): Promise { - return await handleFetch(GAS_FEE_API); +export async function fetchGasEstimates(url: string): Promise { + return await handleFetch(url); } /** * Hit the legacy MetaSwaps gasPrices estimate api and return the low, medium * high values from that API. */ -export async function fetchLegacyGasPriceEstimates(): Promise { - const result = await handleFetch(EXTERNAL_GAS_PRICES_API_URL, { - referrer: EXTERNAL_GAS_PRICES_API_URL, +export async function fetchLegacyGasPriceEstimates( + url: string, +): Promise { + const result = await handleFetch(url, { + referrer: url, referrerPolicy: 'no-referrer-when-downgrade', method: 'GET', mode: 'cors',