Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ app/core/OAuthService @MetaMask/web3auth
app/components/Views/Onboarding @MetaMask/web3auth
app/reducers/onboarding @MetaMask/web3auth

# Delegation team
app/core/Engine/controllers/gator-permissions-controller @MetaMask/delegation
app/core/Engine/messengers/gator-permissions-controller-messenger @MetaMask/delegation
# QA Team - E2E Framework
e2e/api-mocking/ @MetaMask/qa
e2e/fixtures/ @MetaMask/qa
Expand Down
13 changes: 13 additions & 0 deletions app/core/Engine/Engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ describe('Engine', () => {
expect(engine.context).toHaveProperty('DeFiPositionsController');
expect(engine.context).toHaveProperty('NetworkEnablementController');
expect(engine.context).toHaveProperty('PerpsController');
expect(engine.context).toHaveProperty('GatorPermissionsController');
});

it('calling Engine.init twice returns the same instance', () => {
Expand Down Expand Up @@ -205,6 +206,18 @@ describe('Engine', () => {
previousMigrationVersion: 0, // This will be managed by the controller
currentMigrationVersion,
},
GatorPermissionsController: {
gatorPermissionsMapSerialized: JSON.stringify({
'native-token-stream': {},
'native-token-periodic': {},
'erc20-token-stream': {},
'erc20-token-periodic': {},
other: {},
}),
gatorPermissionsProviderSnapId: 'npm:@metamask/gator-permissions-snap',
isFetchingGatorPermissions: false,
isGatorPermissionsEnabled: false,
},
};

expect(initialBackgroundState).toStrictEqual(expectedState);
Expand Down
8 changes: 8 additions & 0 deletions app/core/Engine/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,10 @@ import { seedlessOnboardingControllerInit } from './controllers/seedless-onboard
import { perpsControllerInit } from './controllers/perps-controller';
import { selectUseTokenDetection } from '../../selectors/preferencesController';
import { rewardsControllerInit } from './controllers/rewards-controller';
import { GatorPermissionsControllerInit } from './controllers/gator-permissions-controller';
import { RewardsDataService } from './controllers/rewards-controller/services/rewards-data-service';
import { selectAssetsAccountApiBalancesEnabled } from '../../selectors/featureFlagController/assetsAccountApiBalances';
import type { GatorPermissionsController } from '@metamask/gator-permissions-controller';

const NON_EMPTY = 'NON_EMPTY';

Expand Down Expand Up @@ -296,6 +298,7 @@ export class Engine {

accountsController: AccountsController;
gasFeeController: GasFeeController;
gatorPermissionsController: GatorPermissionsController;
keyringController: KeyringController;
smartTransactionsController: SmartTransactionsController;
transactionController: TransactionController;
Expand Down Expand Up @@ -1152,6 +1155,7 @@ export class Engine {
AppMetadataController: appMetadataControllerInit,
ApprovalController: ApprovalControllerInit,
GasFeeController: GasFeeControllerInit,
GatorPermissionsController: GatorPermissionsControllerInit,
TransactionController: TransactionControllerInit,
SignatureController: SignatureControllerInit,
CurrencyRateController: currencyRateControllerInit,
Expand Down Expand Up @@ -1198,6 +1202,8 @@ export class Engine {
controllersByName.SeedlessOnboardingController;
const perpsController = controllersByName.PerpsController;
const rewardsController = controllersByName.RewardsController;
const gatorPermissionsController =
controllersByName.GatorPermissionsController;

// Initialize and store RewardsDataService
this.rewardsDataService = new RewardsDataService({
Expand All @@ -1213,6 +1219,7 @@ export class Engine {
// Backwards compatibility for existing references
this.accountsController = accountsController;
this.gasFeeController = gasFeeController;
this.gatorPermissionsController = gatorPermissionsController;
this.transactionController = transactionController;

const multichainNetworkController =
Expand Down Expand Up @@ -1517,6 +1524,7 @@ export class Engine {
fetchEstimatedMultiLayerL1Fee,
}),
GasFeeController: this.gasFeeController,
GatorPermissionsController: gatorPermissionsController,
ApprovalController: approvalController,
PermissionController: permissionController,
RemoteFeatureFlagController: remoteFeatureFlagController,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { GatorPermissionsController } from '@metamask/gator-permissions-controller';
import { buildControllerInitRequestMock } from '../../utils/test-utils';
import type { ControllerInitRequest } from '../../types';
import { isGatorPermissionsFeatureEnabled } from '../../../../util/environment';
import {
getGatorPermissionsControllerMessenger,
GatorPermissionsControllerMessenger,
} from '../../messengers/gator-permissions-controller-messenger/gator-permissions-controller-messenger';
import { GatorPermissionsControllerInit } from './gator-permissions-controller-init';
import { ExtendedControllerMessenger } from '../../../ExtendedControllerMessenger';

jest.mock('@metamask/gator-permissions-controller');
jest.mock('../../../../util/environment');

function buildInitRequestMock(): jest.Mocked<
ControllerInitRequest<GatorPermissionsControllerMessenger>
> {
const baseControllerMessenger = new ExtendedControllerMessenger();

return {
...buildControllerInitRequestMock(baseControllerMessenger),
controllerMessenger: getGatorPermissionsControllerMessenger(
baseControllerMessenger,
),
initMessenger: undefined,
};
}

describe('GatorPermissionsControllerInit', () => {
const MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID = 'npm:mock-snap-id';
const GatorPermissionsControllerClassMock = jest.mocked(
GatorPermissionsController,
);
const originalGatorPermissionProviderSnapId =
process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID;

beforeEach(() => {
jest.resetAllMocks();
jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(true);
process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID =
MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID;
});

afterEach(() => {
if (originalGatorPermissionProviderSnapId) {
process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID =
originalGatorPermissionProviderSnapId;
} else {
delete process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID;
}
});

it('returns controller instance', () => {
const requestMock = buildInitRequestMock();
expect(
GatorPermissionsControllerInit(requestMock).controller,
).toBeInstanceOf(GatorPermissionsController);
});

it('initializes with correct messenger and state(gator permissions feature enabled)', () => {
const requestMock = buildInitRequestMock();
jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(true);
GatorPermissionsControllerInit(requestMock);

expect(GatorPermissionsControllerClassMock).toHaveBeenCalledWith({
messenger: requestMock.controllerMessenger,
state: {
isGatorPermissionsEnabled: true,
gatorPermissionsProviderSnapId: MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID,
...requestMock.persistedState.GatorPermissionsController,
},
});
});

it('initializes with correct messenger and state(gator permissions feature disabled)', () => {
const requestMock = buildInitRequestMock();
jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(false);
GatorPermissionsControllerInit(requestMock);

expect(GatorPermissionsControllerClassMock).toHaveBeenCalledWith({
messenger: requestMock.controllerMessenger,
state: {
isGatorPermissionsEnabled: false,
gatorPermissionsProviderSnapId: MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID,
...requestMock.persistedState.GatorPermissionsController,
},
});
});

it('handles undefined persistedState.GatorPermissionsController', () => {
const requestMock = buildInitRequestMock();
requestMock.persistedState.GatorPermissionsController = undefined;
jest.mocked(isGatorPermissionsFeatureEnabled).mockReturnValue(true);

GatorPermissionsControllerInit(requestMock);

expect(GatorPermissionsControllerClassMock).toHaveBeenCalledWith({
messenger: requestMock.controllerMessenger,
state: {
isGatorPermissionsEnabled: true,
gatorPermissionsProviderSnapId: MOCK_GATOR_PERMISSIONS_PROVIDER_SNAP_ID,
},
});
});

it('resolves the default when GATOR_PERMISSIONS_PROVIDER_SNAP_ID is not specified', () => {
const requestMock = buildInitRequestMock();

delete process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID;

GatorPermissionsControllerInit(requestMock);

expect(GatorPermissionsControllerClassMock).toHaveBeenCalledWith({
messenger: requestMock.controllerMessenger,
state: {
isGatorPermissionsEnabled: true,
},
});

const calledWithState =
GatorPermissionsControllerClassMock.mock.calls[0][0].state;

expect(calledWithState).toEqual({
isGatorPermissionsEnabled: true,
});

// GatorPermissionsController requires that the key does not exist if the snap id is not specified
expect(
Object.prototype.hasOwnProperty.call(
calledWithState,
'gatorPermissionsProviderSnapId',
),
).toBe(false);
});

describe('GATOR_PERMISSIONS_PROVIDER_SNAP_ID incorrectly specified', () => {
['', ' ', 'invalid-snap-id'].forEach((invalidSnapId) => {
it(`throws when provided invalid GATOR_PERMISSIONS_PROVIDER_SNAP_ID: ${invalidSnapId}`, () => {
const requestMock = buildInitRequestMock();

process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID =
invalidSnapId as string;

expect(() => GatorPermissionsControllerInit(requestMock)).toThrow(
'GATOR_PERMISSIONS_PROVIDER_SNAP_ID must be set to a valid snap id',
);

expect(GatorPermissionsControllerClassMock).not.toHaveBeenCalled();
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { ControllerInitFunction } from '../../types';
import type { GatorPermissionsControllerMessenger } from '../../messengers/gator-permissions-controller-messenger/gator-permissions-controller-messenger';
import {
GatorPermissionsController,
GatorPermissionsControllerState,
} from '@metamask/gator-permissions-controller';
import { isSnapId } from '@metamask/snaps-utils';
import { isGatorPermissionsFeatureEnabled } from '../../../../util/environment';

const generateDefaultGatorPermissionsControllerState =
(): Partial<GatorPermissionsControllerState> => {
const gatorPermissionsProviderSnapId =
process.env.GATOR_PERMISSIONS_PROVIDER_SNAP_ID;

// if GATOR_PERMISSIONS_PROVIDER_SNAP_ID is not specified, GatorPermissionsController will initialize it's default
if (
gatorPermissionsProviderSnapId !== undefined &&
!isSnapId(gatorPermissionsProviderSnapId)
) {
throw new Error(
'GATOR_PERMISSIONS_PROVIDER_SNAP_ID must be set to a valid snap id',
);
}

const isGatorPermissionsEnabled = isGatorPermissionsFeatureEnabled();

const state: Partial<GatorPermissionsControllerState> = {
isGatorPermissionsEnabled,
};

if (gatorPermissionsProviderSnapId) {
state.gatorPermissionsProviderSnapId = gatorPermissionsProviderSnapId;
}

return state;
};

export const GatorPermissionsControllerInit: ControllerInitFunction<
GatorPermissionsController,
GatorPermissionsControllerMessenger
> = (request) => {
const { controllerMessenger: messenger, persistedState } = request;

const controller = new GatorPermissionsController({
messenger,
state: {
...generateDefaultGatorPermissionsControllerState(),
...persistedState.GatorPermissionsController,
},
});

return {
controller,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './gator-permissions-controller-init';
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { GatorPermissionsControllerMessenger } from '@metamask/gator-permissions-controller';
import { BaseControllerMessenger } from '../../types';

export type { GatorPermissionsControllerMessenger };

export function getGatorPermissionsControllerMessenger(
messenger: BaseControllerMessenger,
): GatorPermissionsControllerMessenger {
return messenger.getRestricted({
name: 'GatorPermissionsController',
allowedActions: [],
allowedEvents: [],
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './gator-permissions-controller-messenger';
5 changes: 5 additions & 0 deletions app/core/Engine/messengers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { getBridgeControllerMessenger } from './bridge-controller-messenger';
import { getBridgeStatusControllerMessenger } from './bridge-status-controller-messenger';
import { getMultichainAccountServiceMessenger } from './multichain-account-service-messenger/multichain-account-service-messenger';
import { getRewardsControllerMessenger } from './rewards-controller-messenger';
import { getGatorPermissionsControllerMessenger } from './gator-permissions-controller-messenger';
/**
* The messengers for the controllers that have been.
*/
Expand Down Expand Up @@ -166,4 +167,8 @@ export const CONTROLLER_MESSENGERS = {
getMessenger: getRewardsControllerMessenger,
getInitMessenger: noop,
},
GatorPermissionsController: {
getMessenger: getGatorPermissionsControllerMessenger,
getInitMessenger: noop,
},
} as const;
9 changes: 8 additions & 1 deletion app/core/Engine/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ import {
MultichainAccountServiceActions,
MultichainAccountServiceEvents,
} from '@metamask/multichain-account-service';
import {
GatorPermissionsController,
GatorPermissionsControllerState,
} from '@metamask/gator-permissions-controller';

/**
* Controllers that area always instantiated
Expand Down Expand Up @@ -555,6 +559,7 @@ export type Controllers = {
RewardsController: RewardsController;
RewardsDataService: RewardsDataService;
SeedlessOnboardingController: SeedlessOnboardingController<EncryptionKey>;
GatorPermissionsController: GatorPermissionsController;
};

/**
Expand Down Expand Up @@ -624,6 +629,7 @@ export type EngineState = {
PerpsController: PerpsControllerState;
RewardsController: RewardsControllerState;
SeedlessOnboardingController: SeedlessOnboardingControllerState;
GatorPermissionsController: GatorPermissionsControllerState;
};

/** Controller names */
Expand Down Expand Up @@ -686,7 +692,8 @@ export type ControllersToInitialize =
| 'BridgeController'
| 'BridgeStatusController'
| 'NetworkEnablementController'
| 'RewardsController';
| 'RewardsController'
| 'GatorPermissionsController';

/**
* Callback that returns a controller messenger for a specific controller.
Expand Down
Loading
Loading