Description
References
- Follow-up from Fix
getRestricted
method: aligns runtime and type-level handling of omitted or empty inputs #4013
Description
The issue is observed when calling the ControllerMessenger
class method getRestricted
.
Repro steps
- The allowlists are omitted in both the
getRestricted
generic and function params. - The returned restricted controller-messenger is passed in directly as the
messenger
option in the controller constructor.
Following these steps prevents the Allowed{Action,Event}
generic defaults (never
) from being applied. Allowed{Action,Event}
are inferred as their generic constraints instead (NotNamespacedBy<...>
).
Currently known fixes
- Explicitly passing in the allowlists either as generic or function params.
- [unexpected behavior] Assigning the restricted messenger to a variable first before passing it into the controller constructor.
Objective
- Find a fix that is internal to
getRestricted
orRestrictedControllerMessenger
, and doesn't require the consumer to take actions at the call site. - As a fallback option, add an entry to changelog that describes this issue and conveys in detail the steps consumers must take to use
getRestricted
without experiencing unexpected behavior.
Details
Error
Example A
Error: Type 'RestrictedControllerMessenger<"NetworkController", NetworkControllerGetStateAction | NetworkControllerGetProviderConfigAction | ... 19 more ... | TransactionControllerGetStateAction, NetworkControllerStateChangeEvent | ... 20 more ... | TransactionControllerUnapprovedTransactionAddedEvent, "ApprovalController:getSta...' is not assignable to type 'NetworkControllerMessenger'.
Types of property '#allowedActions' are incompatible.
Type '("ApprovalController:getState" | "ApprovalController:clearRequests" | "ApprovalController:addRequest" | "ApprovalController:hasRequest" | "ApprovalController:acceptRequest" | ... 7 more ... | "TransactionController:getState")[]' is not assignable to type 'never[]'.
Type 'string' is not assignable to type 'never'.ts(2322)
NotNamespacedBy
,NarrowToAllowed
are working correctly.- There doesn't seem to be anything wrong with the generic params of
getRestricted
orRestrictedControllerMessenger
constructor.
const unrestrictedMessenger: UnrestrictedControllerMessenger =
new ControllerMessenger();
const networkController = new NetworkController({
messenger: unrestrictedMessenger.getRestricted({
name: 'NetworkController',
}),
Example B
No error, but full allowlist is inferred despite being omitted from generic + function arguments.
In this case, the code is relying on this bug to implement a SelectedNetworkControllerMessenger
while conveniently sidestepping the need to supply allowlists.
This is dangerous behavior (especially at runtime) that should not be possible. The code should be breaking from the lack of explicitly supplied function-param allowlists.
- From
SelectedNetworkMiddleware.test.ts
:
🚫 (method) ControllerMessenger<SelectedNetworkControllerActions | AllowedActions, SelectedNetworkControllerStateChangeEvent | AllowedEvents>.getRestricted<"SelectedNetworkController", "NetworkController:getNetworkClientById" | "NetworkController:getState" | "PermissionController:hasPermissions" | "PermissionController:getSubjectNames", "NetworkController:stateChange" | "PermissionController:stateChange">({ name, allowedActions, allowedEvents, }: {
...;
}): RestrictedControllerMessenger<...>
const buildMessenger = () => {
return new ControllerMessenger<
SelectedNetworkControllerActions | AllowedActions,
SelectedNetworkControllerEvents | AllowedEvents
>();
};
...
const messenger = buildMessenger();
const middleware = createSelectedNetworkMiddleware(
messenger.getRestricted({
name: 'SelectedNetworkController',
}),
);
✅ const restrictedMessenger: RestrictedControllerMessenger<"SelectedNetworkController", SelectedNetworkControllerGetSelectedNetworkStateAction | SelectedNetworkControllerGetNetworkClientIdForDomainAction | SelectedNetworkControllerSetNetworkClientIdForDomainAction, SelectedNetworkControllerStateChangeEvent, never, never>
...
const restrictedMessenger = messenger.getRestricted({
name: 'SelectedNetworkController',
});
const middleware = createSelectedNetworkMiddleware(restrictedMessenger);
Example C
Same behavior as Example B once this diff is applied:
diff --git a/packages/gas-fee-controller/src/GasFeeController.test.ts b/packages/gas-fee-controller/src/GasFeeController.test.ts
index d198f4b98..62a692751 100644
--- a/packages/gas-fee-controller/src/GasFeeController.test.ts
+++ b/packages/gas-fee-controller/src/GasFeeController.test.ts
@@ -66,14 +66,10 @@ const setupNetworkController = async ({
state: Partial<NetworkState>;
clock: sinon.SinonFakeTimers;
}) => {
- const restrictedMessenger = unrestrictedMessenger.getRestricted({
- name: 'NetworkController',
- allowedActions: [],
- allowedEvents: [],
- });
-
const networkController = new NetworkController({
- messenger: restrictedMessenger,
+ messenger: unrestrictedMessenger.getRestricted({
+ name: 'NetworkController',
+ }),
state,
infuraProjectId: '123',
trackMetaMetricsEvent: jest.fn(),
Fixes
- Explicitly pass
never
as generic params
const unrestrictedMessenger: UnrestrictedControllerMessenger =
new ControllerMessenger();
- const networkControllerMessenger = unrestrictedMessenger.getRestricted({
- name: 'NetworkController',
- });
const networkController = new NetworkController({
- messenger: networkControllerMessenger,
+ messenger: unrestrictedMessenger.getRestricted<
+ 'NetworkController',
+ never,
+ never
+ >({
+ name: 'NetworkController',
+ }),
- Explicitly pass empty array as function params
const unrestrictedMessenger: UnrestrictedControllerMessenger =
new ControllerMessenger();
- const networkControllerMessenger = unrestrictedMessenger.getRestricted({
- name: 'NetworkController',
- });
const networkController = new NetworkController({
- messenger: networkControllerMessenger,
+ messenger: unrestrictedMessenger.getRestricted({
+ name: 'NetworkController',
+ allowedActions: [],
+ allowedEvents: [],
+ }),
- Assign restricted messenger to its own variable first before passing into controller constructor
const unrestrictedMessenger: UnrestrictedControllerMessenger =
new ControllerMessenger();
+ const networkControllerMessenger = unrestrictedMessenger.getRestricted({
+ name: 'NetworkController',
+ });
const networkController = new NetworkController({
- messenger: unrestrictedMessenger.getRestricted({
- name: 'NetworkController',
- }),
+ messenger: networkControllerMessenger,