Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a71c93e
First draft of gas fee controller
danjm Jun 14, 2021
c4a2606
Second draft of gas fee controller
danjm Jun 16, 2021
7886180
Third draft of gas fee controller
danjm Jun 17, 2021
2be94cb
Delete use of legacy gas fee
danjm Jun 18, 2021
ba8e275
Track whether polling should be instantiated or stopped with tokens
danjm Jun 18, 2021
179dee6
Network controller getEIP1559Compatibility can be called whenever a n…
danjm Jun 18, 2021
55d3607
update tests (#495)
rickycodes Jun 18, 2021
16075b0
Fetch estimate using eth_gasPrice if on a custom network
danjm Jun 21, 2021
b70d323
getGasFeeEstimatesAndStartPolling returns poll token, and a new one i…
danjm Jun 21, 2021
0a8a248
Add getTimeEstimate to GasFee controller
danjm Jun 21, 2021
cb88478
export GasFeeController
rickycodes Jun 21, 2021
1ccd62a
Add public method for calling and returning gas estimates without pol…
danjm Jun 23, 2021
dbf9f6c
Fix return of fetchLegacyGasPriceEstimate
danjm Jun 23, 2021
16b313b
Proper error handling and fallback for _fetchGasFeeEstimateData
danjm Jun 23, 2021
a782c9d
Include estimated time bounds in gas fee state
danjm Jun 23, 2021
32b2930
rename
rickycodes Jun 23, 2021
97e9e8f
Add GasFeeController.test.ts
rickycodes Jun 23, 2021
47f3659
remove TODO
rickycodes Jun 24, 2021
9971e68
Add result token length test
rickycodes Jun 24, 2021
91a2cd2
Add estimates property test
rickycodes Jun 24, 2021
0dec6e7
Add should fail to re-initialize test
rickycodes Jun 24, 2021
e4e962e
remove console.log
rickycodes Jun 24, 2021
afdc385
do not modify state directly
rickycodes Jun 24, 2021
fd4bd64
Use before/afterEach and fix messenger
rickycodes Jun 24, 2021
b1c5631
check gasFeeEstimates properties
rickycodes Jun 24, 2021
3646d62
check that state is empty to start
rickycodes Jun 24, 2021
51dad0d
Add one additional property check
rickycodes Jun 24, 2021
4ac4ffe
Adding TokenListController to fetch the token list from token service…
NiranjanaBinoy Jun 28, 2021
c1ca513
address feedback
rickycodes Jun 30, 2021
0458679
add mock server for eip1559
rickycodes Jun 15, 2021
728c039
get tests working again
rickycodes Jun 30, 2021
cd90799
Use heroku endpoint
rickycodes Jun 30, 2021
8d570bb
Handle fetch correctly in gasfeecontroller unit tests, using nock
danjm Jun 30, 2021
2e2b1a7
gasFee controller calculateTimeEstimate handles decimals, by way of u…
danjm Jun 30, 2021
1516d87
Lint fix
danjm Jun 30, 2021
6eac11c
Fix dec to gwi (#504)
brad-decker Jul 1, 2021
3df6baf
use ethjs-unit for unit conversions (#506)
brad-decker Jul 1, 2021
117d898
Add metaswaps API and normalize all gas fee units to dec gwei (#507)
brad-decker Jul 1, 2021
b4eebac
update types to be more identifiable (#508)
brad-decker Jul 2, 2021
6fbf024
use LegacyGasFeeEstimate type
brad-decker Jul 2, 2021
5d2908e
handle hex prefixed
brad-decker Jul 5, 2021
283fc88
baseFee -> baseFeePerGas
brad-decker Jul 5, 2021
cf82e70
make more configurable (#513)
brad-decker Jul 6, 2021
8d04cc5
use optional chaining
brad-decker Jul 6, 2021
41cdace
update test case wording
brad-decker Jul 7, 2021
3a42d98
use testdata instead of programmatic testing
brad-decker Jul 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
make more configurable (#513)
  • Loading branch information
brad-decker committed Jul 7, 2021
commit cf82e705f090e247ffc454bfd424effb456a844f
65 changes: 55 additions & 10 deletions src/gas/GasFeeController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/<chain_id>';
const TEST_LEGACY_FEE_API = 'https://test/<chain_id>';

const name = 'GasFeeController';

Expand All @@ -30,8 +30,9 @@ function getRestrictedMessenger() {

describe('GasFeeController', () => {
let gasFeeController: GasFeeController;
let getIsMainnet: jest.Mock<boolean>;
let getCurrentNetworkLegacyGasAPICompatibility: jest.Mock<boolean>;
let getIsEIP1559Compatible: jest.Mock<Promise<boolean>>;
let getChainId: jest.Mock<`0x${string}` | `${number}` | number>;

beforeAll(() => {
nock.disableNetConnect();
Expand All @@ -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('<chain_id>', '1'))
.get(/.+/u)
.reply(200, {
low: {
Expand All @@ -71,7 +75,7 @@ describe('GasFeeController', () => {
})
.persist();

nock(EXTERNAL_GAS_PRICES_API_URL)
nock(TEST_LEGACY_FEE_API.replace('<chain_id>', '0x1'))
.get(/.+/u)
.reply(200, {
SafeGasPrice: '22',
Expand All @@ -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 ???
});
});
Expand Down Expand Up @@ -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();
Expand All @@ -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');
Expand Down
44 changes: 36 additions & 8 deletions src/gas/GasFeeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
Expand Down Expand Up @@ -197,6 +201,10 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {

private pollTokens: Set<string>;

private legacyAPIEndpoint: string;

private EIP1559APIEndpoint: string;

private fetchGasEstimates;

private fetchEthGasPriceEstimate;
Expand All @@ -205,9 +213,11 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {

private getCurrentNetworkEIP1559Compatibility;

private getCurrentNetworkLegacyGasAPICompatibility;

private getCurrentAccountEIP1559Compatibility;

private getIsMainnet;
private getChainId;

private ethQuery: any;

Expand All @@ -224,9 +234,12 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {
fetchLegacyGasPriceEstimates = defaultFetchLegacyGasPriceEstimates,
getCurrentNetworkEIP1559Compatibility,
getCurrentAccountEIP1559Compatibility,
getIsMainnet,
getChainId,
getCurrentNetworkLegacyGasAPICompatibility,
getProvider,
onNetworkStateChange,
legacyAPIEndpoint = LEGACY_GAS_PRICES_API_URL,
EIP1559APIEndpoint = GAS_FEE_API,
}: {
interval?: number;
messenger: RestrictedControllerMessenger<
Expand All @@ -241,10 +254,13 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {
fetchEthGasPriceEstimate?: typeof defaultFetchEthGasPriceEstimate;
fetchLegacyGasPriceEstimates?: typeof defaultFetchLegacyGasPriceEstimates;
getCurrentNetworkEIP1559Compatibility: () => Promise<boolean>;
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,
Expand All @@ -258,8 +274,11 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {
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);
Expand Down Expand Up @@ -294,7 +313,12 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {
*/
async _fetchGasFeeEstimateData(): Promise<GasFeeState | undefined> {
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) {
Expand All @@ -310,7 +334,9 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {

try {
if (isEIP1559Compatible) {
const estimates = await this.fetchGasEstimates();
const estimates = await this.fetchGasEstimates(
this.EIP1559APIEndpoint.replace('<chain_id>', `${chainId}`),
);
const {
suggestedMaxPriorityFeePerGas,
suggestedMaxFeePerGas,
Expand All @@ -324,8 +350,10 @@ export class GasFeeController extends BaseController<typeof name, GasFeeState> {
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('<chain_id>', `${chainId}`),
);
newState = {
gasFeeEstimates: estimates,
estimatedGasFeeTimeBounds: {},
Expand Down
11 changes: 5 additions & 6 deletions src/gas/gas-util.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
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',
ProposeGasPrice: '25',
FastGasPrice: '30',
})
.persist();
const result = await fetchLegacyGasPriceEstimates();
const result = await fetchLegacyGasPriceEstimates(
'https://not-a-real-url/',
);
expect(result).toMatchObject({
high: '30',
medium: '25',
Expand Down
15 changes: 7 additions & 8 deletions src/gas/gas-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<GasFeeEstimates> {
return await handleFetch(GAS_FEE_API);
export async function fetchGasEstimates(url: string): Promise<GasFeeEstimates> {
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<LegacyGasPriceEstimate> {
const result = await handleFetch(EXTERNAL_GAS_PRICES_API_URL, {
referrer: EXTERNAL_GAS_PRICES_API_URL,
export async function fetchLegacyGasPriceEstimates(
url: string,
): Promise<LegacyGasPriceEstimate> {
const result = await handleFetch(url, {
referrer: url,
referrerPolicy: 'no-referrer-when-downgrade',
method: 'GET',
mode: 'cors',
Expand Down