Skip to content

Commit

Permalink
chore: Update @metamask/gas-fee-controller and peer deps (#28745)
Browse files Browse the repository at this point in the history
Update the `@metamask/gas-fee-controller` package to v21 to satisfy the
peer dependency on `@metamask/network-controller@^21.0.0`, and update
the `@metamask/user-operation-controller` to satisfy its peer dependency
upon `@metamask/gas-fee-controller`.

Note that an older version of `@metamask/gas-fee-controller` (v18)
remains in the dependency tree, but only because it's imported by
`@metamask/smart-transaction-controller` for type reasons. It has no
runtime impact on the application, so the associated peer dependency
warnings from this older release can be ignored. This will be eliminated
soon, in an upcoming PR.

The updated `@metamask/user-operation-controller` still does not have
its peer dependencies satisfied, but the problems are pre-existing. The
`@metamask/keyring-controller` and `@metamask/transaction-controller`
packages are head of where this package expects them to be. This is not
made worse by this PR though, and will be addressed in a future PR.

Changelogs:
- `@metamask/gas-fee-controller@21`:
https://github.com/MetaMask/core/blob/main/packages/gas-fee-controller/CHANGELOG.md#2100
  - One breaking change with an impact:
>The inherited AbstractPollingController method
startPollingByNetworkClientId has been renamed to startPolling
(MetaMask/core#4752)
- `@metamask/user-operation-controller@16`:
https://github.com/MetaMask/core/blob/main/packages/user-operation-controller/CHANGELOG.md#1600
- No breaking changes with an impact. It appeared at first that the
`startPollingByNetworkClientId` was breaking, but the user operation
controller had an override for that method so it was preserved ([see
here](https://github.com/MetaMask/core/pull/4752/files#diff-335af05ece636eb593b348e369dff139dfbfea49ad4e9ba3bb47b4909aab9aefR304))

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28745?quickstart=1)

Related to MetaMask/MetaMask-planning#3568

N/A

N/A

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

---------

Co-authored-by: MetaMask Bot <[email protected]>
  • Loading branch information
2 people authored and DDDDDanica committed Nov 27, 2024
1 parent 3fa28bf commit 9bddc8d
Show file tree
Hide file tree
Showing 17 changed files with 307 additions and 4 deletions.
5 changes: 4 additions & 1 deletion app/scripts/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,6 @@ export function setupController(
//
// MetaMask Controller
//

controller = new MetamaskController({
infuraProjectId: process.env.INFURA_PROJECT_ID,
// User confirmation callbacks:
Expand Down Expand Up @@ -885,12 +884,16 @@ export function setupController(
senderUrl?.origin === `chrome-extension://${browser.runtime.id}`;
}

controller.remoteFeatureFlagController.getRemoteFeatureFlags();

if (isMetaMaskInternalProcess) {
const portStream =
overrides?.getPortStream?.(remotePort) || new PortStream(remotePort);
// communication with popup
controller.isClientOpen = true;
controller.setupTrustedCommunication(portStream, remotePort.sender);
// initialize the request to fetch remote feature flags
controller.remoteFeatureFlagController.getRemoteFeatureFlags();

if (processName === ENVIRONMENT_TYPE_POPUP) {
openPopupCount += 1;
Expand Down
1 change: 1 addition & 0 deletions app/scripts/constants/sentry-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ export const SENTRY_BACKGROUND_STATE = {
useTransactionSimulations: true,
enableMV3TimestampSave: true,
},
FeatureFlagController: {},
NotificationServicesPushController: {
fcmToken: false,
},
Expand Down
64 changes: 64 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ import {
} from '@metamask/controller-utils';

import { AccountsController } from '@metamask/accounts-controller';
import {
RemoteFeatureFlagController,
ClientConfigApiService,
} from '@metamask/remote-feature-flag-controller';

///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
import {
Expand Down Expand Up @@ -243,6 +247,7 @@ import { endTrace, trace } from '../../shared/lib/trace';
// eslint-disable-next-line import/no-restricted-paths
import { isSnapId } from '../../ui/helpers/utils/snaps';
import { BridgeStatusAction } from '../../shared/types/bridge-status';
import { ENVIRONMENT } from '../../development/build/constants';
import fetchWithCache from '../../shared/lib/fetch-with-cache';
import { BalancesController as MultichainBalancesController } from './lib/accounts/BalancesController';
import {
Expand Down Expand Up @@ -395,6 +400,17 @@ const PHISHING_SAFELIST = 'metamask-phishing-safelist';
// OneKey devices can connect to Metamask using Trezor USB transport. They use a specific device minor version (99) to differentiate between genuine Trezor and OneKey devices.
export const ONE_KEY_VIA_TREZOR_MINOR_VERSION = 99;

const environmentMappingForRemoteFeatureFlag = {
[ENVIRONMENT.DEVELOPMENT]: 'dev',
[ENVIRONMENT.RELEASE_CANDIDATE]: 'rc',
[ENVIRONMENT.PRODUCTION]: 'prod',
};

const buildTypeMappingForRemoteFeatureFlag = {
flask: 'flask',
main: 'main',
};

export default class MetamaskController extends EventEmitter {
/**
* @param {object} opts
Expand Down Expand Up @@ -2339,6 +2355,41 @@ export default class MetamaskController extends EventEmitter {
clearPendingConfirmations.bind(this),
);

// RemoteFeatureFlagController has subscription for preferences changes
this.controllerMessenger.subscribe(
'PreferencesController:stateChange',
previousValueComparator((prevState, currState) => {
const { useExternalServices: prevUseExternalServices } = prevState;
const { useExternalServices: currUseExternalServices } = currState;

if (currUseExternalServices && !prevUseExternalServices) {
this.remoteFeatureFlagController.enable();
this.getRemoteFeatureFlags();
} else if (!currUseExternalServices && prevUseExternalServices) {
this.remoteFeatureFlagController.disable();
}
}, this.preferencesController.state),
);

// Initialize RemoteFeatureFlagController
this.remoteFeatureFlagController = new RemoteFeatureFlagController({
messenger: this.controllerMessenger.getRestricted({
name: 'RemoteFeatureFlagController',
allowedActions: ['PreferencesController:getState'],
allowedEvents: ['PreferencesController:stateChange'],
}),
disabled: !this.preferencesController.state.useExternalServices,
clientConfigApiService: new ClientConfigApiService({
fetch: globalThis.fetch.bind(globalThis),
config: {
client: 'extension',
distribution:
this._getConfigForRemoteFeatureFlagRequest().distribution,
environment: this._getConfigForRemoteFeatureFlagRequest().environment,
},
}),
});

this.metamaskMiddleware = createMetamaskMiddleware({
static: {
eth_syncing: false,
Expand Down Expand Up @@ -2505,6 +2556,7 @@ export default class MetamaskController extends EventEmitter {
NotificationServicesController: this.notificationServicesController,
NotificationServicesPushController:
this.notificationServicesPushController,
RemoteFeatureFlagController: this.remoteFeatureFlagController,
...resetOnRestartStore,
});

Expand Down Expand Up @@ -2560,6 +2612,7 @@ export default class MetamaskController extends EventEmitter {
QueuedRequestController: this.queuedRequestController,
NotificationServicesPushController:
this.notificationServicesPushController,
RemoteFeatureFlagController: this.remoteFeatureFlagController,
...resetOnRestartStore,
},
controllerMessenger: this.controllerMessenger,
Expand Down Expand Up @@ -7347,6 +7400,17 @@ export default class MetamaskController extends EventEmitter {
};
}

_getConfigForRemoteFeatureFlagRequest() {
const distribution =
buildTypeMappingForRemoteFeatureFlag[process.env.METAMASK_BUILD_TYPE] ||
'main';
const environment =
environmentMappingForRemoteFeatureFlag[
process.env.METAMASK_ENVIRONMENT
] || 'dev';
return { distribution, environment };
}

#checkTokenListPolling(currentState, previousState) {
const previousEnabled = this.#isTokenListPollingRequired(previousState);
const newEnabled = this.#isTokenListPollingRequired(currentState);
Expand Down
104 changes: 104 additions & 0 deletions app/scripts/metamask-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { flushPromises } from '../../test/lib/timer-helpers';
import { ETH_EOA_METHODS } from '../../shared/constants/eth-methods';
import { createMockInternalAccount } from '../../test/jest/mocks';
import { mockNetworkState } from '../../test/stub/networks';
import { ENVIRONMENT } from '../../development/build/constants';
import { SECOND } from '../../shared/constants/time';
import {
BalancesController as MultichainBalancesController,
Expand Down Expand Up @@ -2601,6 +2602,109 @@ describe('MetaMaskController', () => {
);
});
});

describe('RemoteFeatureFlagController', () => {
let localMetamaskController;
let mockClientConfigApiService;

beforeEach(() => {
mockClientConfigApiService = {
fetchRemoteFeatureFlags: jest
.fn()
.mockResolvedValue({ cachedData: [] }),
};

localMetamaskController = new MetaMaskController({
showUserConfirmation: noop,
initLangCode: 'en_US',
platform: {
showTransactionNotification: () => undefined,
getVersion: () => 'foo',
},
browser: browserPolyfillMock,
infuraProjectId: 'foo',
isFirstMetaMaskControllerSetup: true,
initState: {
...cloneDeep(firstTimeState),
PreferencesController: {
useExternalServices: false,
},
},
});
});

it('should initialize RemoteFeatureFlagController in disabled state when useExternalServices is false', async () => {
const { remoteFeatureFlagController, preferencesController } =
localMetamaskController;
// Check preferences state
expect(preferencesController.state.useExternalServices).toBe(false);

// Since disabled is true, getRemoteFeatureFlags should return empty array
const flags = await remoteFeatureFlagController.getRemoteFeatureFlags();
expect(flags).toStrictEqual([]);

// Check controller state
expect(remoteFeatureFlagController.state).toStrictEqual({
remoteFeatureFlags: [],
cacheTimestamp: 0,
});
});

it('should enable/disable feature flag fetching based on useExternalServices in PreferenceController', async () => {
const { remoteFeatureFlagController } = localMetamaskController;

// Initially disabled
let flags = await remoteFeatureFlagController.getRemoteFeatureFlags();
expect(flags).toStrictEqual([]);

// Enable external services
await simulatePreferencesChange({
useExternalServices: true,
});

// Mock the service to return some flags
const mockFlags = [{ 'test-flag': true }];
jest
.spyOn(remoteFeatureFlagController, 'getRemoteFeatureFlags')
.mockResolvedValueOnce(mockFlags);

// Should now fetch flags
flags = await remoteFeatureFlagController.getRemoteFeatureFlags();
expect(flags).toStrictEqual(mockFlags);

// Disable external services
await simulatePreferencesChange({
useExternalServices: false,
});

// Should return empty array again
flags = await remoteFeatureFlagController.getRemoteFeatureFlags();
expect(flags).toStrictEqual([]);
});
});

describe('_getConfigForRemoteFeatureFlagRequest', () => {
it('should return config in mapping', async () => {
const result =
await metamaskController._getConfigForRemoteFeatureFlagRequest();
expect(result).toStrictEqual({
distribution: 'main',
environment: 'dev',
});
});

it('should return config when not matching default mapping', async () => {
process.env.METAMASK_BUILD_TYPE = 'beta';
process.env.METAMASK_ENVIRONMENT = ENVIRONMENT.RELEASE_CANDIDATE;

const result =
await metamaskController._getConfigForRemoteFeatureFlagRequest();
expect(result).toStrictEqual({
distribution: 'main',
environment: 'rc',
});
});
});
});

describe('onFeatureFlagResponseReceived', () => {
Expand Down
10 changes: 10 additions & 0 deletions lavamoat/browserify/beta/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,16 @@
"semver": true
}
},
"@metamask/remote-feature-flag-controller": {
"globals": {
"console.error": true
},
"packages": {
"@metamask/base-controller": true,
"@metamask/utils": true,
"cockatiel": true
}
},
"@metamask/rpc-errors": {
"packages": {
"@metamask/rpc-errors>fast-safe-stringify": true,
Expand Down
10 changes: 10 additions & 0 deletions lavamoat/browserify/flask/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,16 @@
"semver": true
}
},
"@metamask/remote-feature-flag-controller": {
"globals": {
"console.error": true
},
"packages": {
"@metamask/base-controller": true,
"@metamask/utils": true,
"cockatiel": true
}
},
"@metamask/rpc-errors": {
"packages": {
"@metamask/rpc-errors>fast-safe-stringify": true,
Expand Down
10 changes: 10 additions & 0 deletions lavamoat/browserify/main/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,16 @@
"semver": true
}
},
"@metamask/remote-feature-flag-controller": {
"globals": {
"console.error": true
},
"packages": {
"@metamask/base-controller": true,
"@metamask/utils": true,
"cockatiel": true
}
},
"@metamask/rpc-errors": {
"packages": {
"@metamask/rpc-errors>fast-safe-stringify": true,
Expand Down
10 changes: 10 additions & 0 deletions lavamoat/browserify/mmi/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -2475,6 +2475,16 @@
"semver": true
}
},
"@metamask/remote-feature-flag-controller": {
"globals": {
"console.error": true
},
"packages": {
"@metamask/base-controller": true,
"@metamask/utils": true,
"cockatiel": true
}
},
"@metamask/rpc-errors": {
"packages": {
"@metamask/rpc-errors>fast-safe-stringify": true,
Expand Down
10 changes: 9 additions & 1 deletion lavamoat/build-system/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -2040,7 +2040,8 @@
"chokidar>normalize-path": true,
"chokidar>readdirp": true,
"del>is-glob": true,
"eslint>glob-parent": true
"eslint>glob-parent": true,
"tsx>fsevents": true
}
},
"chokidar>anymatch": {
Expand Down Expand Up @@ -8838,6 +8839,13 @@
"typescript": true
}
},
"tsx>fsevents": {
"globals": {
"console.assert": true,
"process.platform": true
},
"native": true
},
"typescript": {
"builtin": {
"buffer.Buffer": true,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@
"@metamask/providers": "^18.2.0",
"@metamask/queued-request-controller": "^7.0.1",
"@metamask/rate-limit-controller": "^6.0.0",
"@metamask/remote-feature-flag-controller": "file:../core/packages/remote-feature-flag-controller",
"@metamask/rpc-errors": "^7.0.0",
"@metamask/safe-event-emitter": "^3.1.1",
"@metamask/scure-bip39": "^2.0.3",
Expand Down
3 changes: 2 additions & 1 deletion privacy-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"cdn.segment.io",
"cdnjs.cloudflare.com",
"chainid.network",
"client-config.api.cx.metamask.io",
"client-side-detection.api.cx.metamask.io",
"configuration.dev.metamask-institutional.io",
"configuration.metamask-institutional.io",
Expand Down Expand Up @@ -71,4 +72,4 @@
"unresponsive-rpc.url",
"user-storage.api.cx.metamask.io",
"www.4byte.directory"
]
]
6 changes: 6 additions & 0 deletions test/e2e/fixture-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ class FixtureBuilder {
});
}

withUseBasicFunctionalityDisabled() {
return this.withPreferencesController({
useExternalServices: false,
});
}

withGasFeeController(data) {
merge(this.fixture.data.GasFeeController, data);
return this;
Expand Down
Loading

0 comments on commit 9bddc8d

Please sign in to comment.