From 23a859826f126fdfa01f2337fa93d40da77fb5ba Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Fri, 25 Jun 2021 11:24:00 -0500 Subject: [PATCH] add method to detect EIP 1559 support (#11369) --- app/scripts/controllers/detect-tokens.test.js | 3 + .../network/network-controller.test.js | 71 +++++++++++++++- app/scripts/controllers/network/network.js | 83 +++++++++++++++++++ app/scripts/controllers/preferences.test.js | 3 + app/scripts/metamask-controller.test.js | 5 ++ ui/ducks/metamask/metamask.js | 4 + 6 files changed, 168 insertions(+), 1 deletion(-) diff --git a/app/scripts/controllers/detect-tokens.test.js b/app/scripts/controllers/detect-tokens.test.js index 4d4578124..7bd788271 100644 --- a/app/scripts/controllers/detect-tokens.test.js +++ b/app/scripts/controllers/detect-tokens.test.js @@ -30,6 +30,9 @@ describe('DetectTokensController', function () { '0x7e57e2', '0xbc86727e770de68b1060c91f6bb6945c73e10388', ]); + sandbox + .stub(network, 'getLatestBlock') + .callsFake(() => Promise.resolve({})); sandbox .stub(preferences, '_detectIsERC721') .returns(Promise.resolve(false)); diff --git a/app/scripts/controllers/network/network-controller.test.js b/app/scripts/controllers/network/network-controller.test.js index 3417241f6..6c234e852 100644 --- a/app/scripts/controllers/network/network-controller.test.js +++ b/app/scripts/controllers/network/network-controller.test.js @@ -1,11 +1,13 @@ import { strict as assert } from 'assert'; import sinon from 'sinon'; import { getNetworkDisplayName } from './util'; -import NetworkController from './network'; +import NetworkController, { NETWORK_EVENTS } from './network'; describe('NetworkController', function () { describe('controller', function () { let networkController; + let getLatestBlockStub; + let setProviderTypeAndWait; const noop = () => undefined; const networkControllerProviderConfig = { getAccounts: noop, @@ -13,7 +15,21 @@ describe('NetworkController', function () { beforeEach(function () { networkController = new NetworkController(); + getLatestBlockStub = sinon + .stub(networkController, 'getLatestBlock') + .callsFake(() => Promise.resolve({})); networkController.setInfuraProjectId('foo'); + setProviderTypeAndWait = () => + new Promise((resolve) => { + networkController.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => { + resolve(); + }); + networkController.setProviderType('mainnet'); + }); + }); + + afterEach(function () { + getLatestBlockStub.reset(); }); describe('#provider', function () { @@ -67,6 +83,59 @@ describe('NetworkController', function () { ); }); }); + + describe('#getEIP1559Compatibility', function () { + it('should return false when baseFeePerGas is not in the block header', async function () { + networkController.initializeProvider(networkControllerProviderConfig); + const supportsEIP1559 = await networkController.getEIP1559Compatibility(); + assert.equal(supportsEIP1559, false); + }); + + it('should return true when baseFeePerGas is in block header', async function () { + networkController.initializeProvider(networkControllerProviderConfig); + getLatestBlockStub.callsFake(() => + Promise.resolve({ baseFeePerGas: '0xa ' }), + ); + const supportsEIP1559 = await networkController.getEIP1559Compatibility(); + assert.equal(supportsEIP1559, true); + }); + + it('should store EIP1559 support in state to reduce calls to getLatestBlock', async function () { + networkController.initializeProvider(networkControllerProviderConfig); + getLatestBlockStub.callsFake(() => + Promise.resolve({ baseFeePerGas: '0xa ' }), + ); + await networkController.getEIP1559Compatibility(); + const supportsEIP1559 = await networkController.getEIP1559Compatibility(); + assert.equal(getLatestBlockStub.calledOnce, true); + assert.equal(supportsEIP1559, true); + }); + + it('should clear stored EIP1559 support when changing networks', async function () { + networkController.initializeProvider(networkControllerProviderConfig); + networkController.consoleThis = true; + getLatestBlockStub.callsFake(() => + Promise.resolve({ baseFeePerGas: '0xa ' }), + ); + await networkController.getEIP1559Compatibility(); + assert.equal( + networkController.networkDetails.getState().EIPS[1559], + true, + ); + getLatestBlockStub.callsFake(() => Promise.resolve({})); + await setProviderTypeAndWait('mainnet'); + assert.equal( + networkController.networkDetails.getState().EIPS[1559], + undefined, + ); + await networkController.getEIP1559Compatibility(); + assert.equal( + networkController.networkDetails.getState().EIPS[1559], + false, + ); + assert.equal(getLatestBlockStub.calledTwice, true); + }); + }); }); describe('utils', function () { diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 1799f658f..2056cc154 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -51,6 +51,10 @@ const defaultProviderConfig = { ...defaultProviderConfigOpts, }; +const defaultNetworkDetailsState = { + EIPS: { 1559: undefined }, +}; + export const NETWORK_EVENTS = { // Fired after the actively selected network is changed NETWORK_DID_CHANGE: 'networkDidChange', @@ -74,10 +78,21 @@ export default class NetworkController extends EventEmitter { this.providerStore.getState(), ); this.networkStore = new ObservableStore('loading'); + // We need to keep track of a few details about the current network + // Ideally we'd merge this.networkStore with this new store, but doing so + // will require a decent sized refactor of how we're accessing network + // state. Currently this is only used for detecting EIP 1559 support but + // can be extended to track other network details. + this.networkDetails = new ObservableStore( + opts.networkDetails || { + ...defaultNetworkDetailsState, + }, + ); this.store = new ComposedStore({ provider: this.providerStore, previousProviderStore: this.previousProviderStore, network: this.networkStore, + networkDetails: this.networkDetails, }); // provider and block tracker @@ -120,6 +135,42 @@ export default class NetworkController extends EventEmitter { return { provider, blockTracker }; } + /** + * Method to return the latest block for the current network + * @returns {Object} Block header + */ + getLatestBlock() { + return new Promise((resolve, reject) => { + const { provider } = this.getProviderAndBlockTracker(); + const ethQuery = new EthQuery(provider); + ethQuery.sendAsync( + { method: 'eth_getBlockByNumber', params: ['latest', false] }, + (err, block) => { + if (err) { + return reject(err); + } + return resolve(block); + }, + ); + }); + } + + /** + * Method to check if the block header contains fields that indicate EIP 1559 + * support (baseFeePerGas). + * @returns {Promise} true if current network supports EIP 1559 + */ + async getEIP1559Compatibility() { + const { EIPS } = this.networkDetails.getState(); + if (EIPS[1559] !== undefined) { + return EIPS[1559]; + } + const latestBlock = await this.getLatestBlock(); + const supportsEIP1559 = latestBlock.baseFeePerGas !== undefined; + this.setNetworkEIPSupport(1559, supportsEIP1559); + return supportsEIP1559; + } + verifyNetwork() { // Check network when restoring connectivity: if (this.isNetworkLoading()) { @@ -135,6 +186,26 @@ export default class NetworkController extends EventEmitter { this.networkStore.putState(network); } + /** + * Set EIP support indication in the networkDetails store + * @param {number} EIPNumber - The number of the EIP to mark support for + * @param {boolean} isSupported - True if the EIP is supported + */ + setNetworkEIPSupport(EIPNumber, isSupported) { + this.networkDetails.updateState({ + EIPS: { + [EIPNumber]: isSupported, + }, + }); + } + + /** + * Reset EIP support to default (no support) + */ + clearNetworkDetails() { + this.networkDetails.putState({ ...defaultNetworkDetailsState }); + } + isNetworkLoading() { return this.getNetworkState() === 'loading'; } @@ -154,6 +225,8 @@ export default class NetworkController extends EventEmitter { 'NetworkController - lookupNetwork aborted due to missing chainId', ); this.setNetworkState('loading'); + // keep network details in sync with network state + this.clearNetworkDetails(); return; } @@ -174,10 +247,14 @@ export default class NetworkController extends EventEmitter { if (initialNetwork === currentNetwork) { if (err) { this.setNetworkState('loading'); + // keep network details in sync with network state + this.clearNetworkDetails(); return; } this.setNetworkState(networkVersion); + // look up EIP-1559 support + this.getEIP1559Compatibility(); } }); } @@ -298,9 +375,15 @@ export default class NetworkController extends EventEmitter { } _switchNetwork(opts) { + // Indicate to subscribers that network is about to change this.emit(NETWORK_EVENTS.NETWORK_WILL_CHANGE); + // Set loading state this.setNetworkState('loading'); + // Reset network details + this.clearNetworkDetails(); + // Configure the provider appropriately this._configureProvider(opts); + // Notify subscribers that network has changed this.emit(NETWORK_EVENTS.NETWORK_DID_CHANGE, opts.type); } diff --git a/app/scripts/controllers/preferences.test.js b/app/scripts/controllers/preferences.test.js index d5b993c8e..5e454d55a 100644 --- a/app/scripts/controllers/preferences.test.js +++ b/app/scripts/controllers/preferences.test.js @@ -30,6 +30,9 @@ describe('preferences controller', function () { network.initializeProvider(networkControllerProviderConfig); provider = network.getProviderAndBlockTracker().provider; + sandbox + .stub(network, 'getLatestBlock') + .callsFake(() => Promise.resolve({})); sandbox.stub(network, 'getCurrentChainId').callsFake(() => currentChainId); sandbox .stub(network, 'getProviderConfig') diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 87fed2aef..f75bf7b50 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -21,6 +21,11 @@ const firstTimeState = { rpcUrl: 'http://localhost:8545', chainId: '0x539', }, + networkDetails: { + EIPS: { + 1559: false, + }, + }, }, }; diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index 0047b6904..e72fb8ec7 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -238,3 +238,7 @@ export function getSendToAccounts(state) { export function getUnapprovedTxs(state) { return state.metamask.unapprovedTxs; } + +export function isEIP1559Network(state) { + return state.metamask.networkDetails.EIPS[1559] === true; +}