Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert the ENS controller to the BaseController v2 API #1134

Merged
merged 2 commits into from
Mar 17, 2023
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
104 changes: 86 additions & 18 deletions packages/ens-controller/src/EnsController.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ControllerMessenger } from '@metamask/base-controller';
import { toChecksumHexAddress } from '@metamask/controller-utils';
import { EnsController } from './EnsController';

Expand All @@ -11,14 +12,33 @@ const address1Checksum = toChecksumHexAddress(address1);
const address2Checksum = toChecksumHexAddress(address2);
const address3Checksum = toChecksumHexAddress(address3);

const name = 'EnsController';

/**
* Constructs a restricted controller messenger.
*
* @returns A restricted controller messenger.
*/
function getMessenger() {
return new ControllerMessenger().getRestricted<typeof name, never, never>({
name,
});
}

describe('EnsController', () => {
it('should set default state', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.state).toStrictEqual({ ensEntries: {} });
});

it('should add a new ENS entry and return true', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, address1)).toStrictEqual(true);
expect(controller.state).toStrictEqual({
ensEntries: {
Expand All @@ -34,7 +54,10 @@ describe('EnsController', () => {
});

it('should add a new ENS entry with null address and return true', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, null)).toStrictEqual(true);
expect(controller.state).toStrictEqual({
ensEntries: {
Expand All @@ -50,7 +73,10 @@ describe('EnsController', () => {
});

it('should update an ENS entry and return true', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, address1)).toStrictEqual(true);
expect(controller.set('1', name1, address2)).toStrictEqual(true);
expect(controller.state).toStrictEqual({
Expand All @@ -67,7 +93,10 @@ describe('EnsController', () => {
});

it('should update an ENS entry with null address and return true', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, address1)).toStrictEqual(true);
expect(controller.set('1', name1, null)).toStrictEqual(true);
expect(controller.state).toStrictEqual({
Expand All @@ -84,7 +113,10 @@ describe('EnsController', () => {
});

it('should not update an ENS entry if the address is the same (valid address) and return false', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, address1)).toStrictEqual(true);
expect(controller.set('1', name1, address1)).toStrictEqual(false);
expect(controller.state).toStrictEqual({
Expand All @@ -101,7 +133,10 @@ describe('EnsController', () => {
});

it('should not update an ENS entry if the address is the same (null) and return false', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, null)).toStrictEqual(true);
expect(controller.set('1', name1, null)).toStrictEqual(false);
expect(controller.state).toStrictEqual({
Expand All @@ -118,7 +153,10 @@ describe('EnsController', () => {
});

it('should add multiple ENS entries and update without side effects', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, address1)).toStrictEqual(true);
expect(controller.set('1', name2, address2)).toStrictEqual(true);
expect(controller.set('2', name1, address1)).toStrictEqual(true);
Expand Down Expand Up @@ -149,7 +187,10 @@ describe('EnsController', () => {
});

it('should get ENS entry by chainId and ensName', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, address1)).toStrictEqual(true);
expect(controller.get('1', name1)).toStrictEqual({
address: address1Checksum,
Expand All @@ -159,19 +200,28 @@ describe('EnsController', () => {
});

it('should return null when getting nonexistent name', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, address1)).toStrictEqual(true);
expect(controller.get('1', name2)).toBeNull();
});

it('should return null when getting nonexistent chainId', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, address1)).toStrictEqual(true);
expect(controller.get('2', name1)).toBeNull();
});

it('should throw on attempt to set invalid ENS entry: chainId', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(() => {
controller.set('a', name1, address1);
}).toThrow(
Expand All @@ -181,15 +231,21 @@ describe('EnsController', () => {
});

it('should throw on attempt to set invalid ENS entry: ENS name', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(() => {
controller.set('1', 'foo.eth', address1);
}).toThrow('Invalid ENS name: foo.eth');
expect(controller.state).toStrictEqual({ ensEntries: {} });
});

it('should throw on attempt to set invalid ENS entry: address', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(() => {
controller.set('1', name1, 'foo');
}).toThrow(
Expand All @@ -199,14 +255,20 @@ describe('EnsController', () => {
});

it('should remove an ENS entry and return true', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, address1)).toStrictEqual(true);
expect(controller.delete('1', name1)).toStrictEqual(true);
expect(controller.state).toStrictEqual({ ensEntries: {} });
});

it('should return false if an ENS entry was NOT deleted', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
controller.set('1', name1, address1);
expect(controller.delete('1', 'bar')).toStrictEqual(false);
expect(controller.delete('2', 'bar')).toStrictEqual(false);
Expand All @@ -224,7 +286,10 @@ describe('EnsController', () => {
});

it('should add multiple ENS entries and remove without side effects', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, address1)).toStrictEqual(true);
expect(controller.set('1', name2, address2)).toStrictEqual(true);
expect(controller.set('2', name1, address1)).toStrictEqual(true);
Expand All @@ -250,7 +315,10 @@ describe('EnsController', () => {
});

it('should clear all ENS entries', () => {
const controller = new EnsController();
const messenger = getMessenger();
const controller = new EnsController({
messenger,
});
expect(controller.set('1', name1, address1)).toStrictEqual(true);
expect(controller.set('1', name2, address2)).toStrictEqual(true);
expect(controller.set('2', name1, address1)).toStrictEqual(true);
Expand Down
100 changes: 66 additions & 34 deletions packages/ens-controller/src/EnsController.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import {
BaseController,
BaseConfig,
BaseState,
BaseControllerV2,
RestrictedControllerMessenger,
} from '@metamask/base-controller';
import {
normalizeEnsName,
isValidHexAddress,
toChecksumHexAddress,
} from '@metamask/controller-utils';

const name = 'EnsController';

/**
* @type EnsEntry
*
Expand All @@ -17,51 +18,83 @@ import {
* @property ensName - The ENS name
* @property address - Hex address with the ENS name, or null
*/
export interface EnsEntry {
export type EnsEntry = {
chainId: string;
ensName: string;
address: string | null;
}
};

/**
* @type EnsState
* @type EnsControllerState
*
* ENS controller state
* @property ensEntries - Object of ENS entry objects
*/
export interface EnsState extends BaseState {
ensEntries: { [chainId: string]: { [ensName: string]: EnsEntry } };
}
export type EnsControllerState = {
ensEntries: {
[chainId: string]: {
[ensName: string]: EnsEntry;
};
};
};

export type EnsControllerMessenger = RestrictedControllerMessenger<
typeof name,
never,
never,
never,
never
>;

const metadata = {
ensEntries: { persist: true, anonymous: false },
};

const defaultState = {
ensEntries: {},
};

/**
* Controller that manages a list ENS names and their resolved addresses
* by chainId. A null address indicates an unresolved ENS name.
*/
export class EnsController extends BaseController<BaseConfig, EnsState> {
/**
* Name of this controller used during composition
*/
override name = 'EnsController';

export class EnsController extends BaseControllerV2<
typeof name,
EnsControllerState,
EnsControllerMessenger
> {
/**
* Creates an EnsController instance.
*
* @param config - Initial options used to configure this controller.
* @param state - Initial state to set on this controller.
* @param options - Constructor options.
* @param options.messenger - A reference to the messaging system.
* @param options.state - Initial state to set on this controller.
*/
constructor(config?: Partial<BaseConfig>, state?: Partial<EnsState>) {
super(config, state);

this.defaultState = { ensEntries: {} };

this.initialize();
constructor({
messenger,
state,
}: {
messenger: EnsControllerMessenger;
state?: Partial<EnsControllerState>;
}) {
super({
name,
metadata,
messenger,
state: {
...defaultState,
...state,
},
});
}

/**
* Remove all chain Ids and ENS entries from state.
*/
clear() {
this.update({ ensEntries: {} });
this.update((state) => {
state.ensEntries = {};
});
}

/**
Expand All @@ -81,14 +114,13 @@ export class EnsController extends BaseController<BaseConfig, EnsState> {
return false;
}

const ensEntries = Object.assign({}, this.state.ensEntries);
delete ensEntries[chainId][normalizedEnsName];
this.update((state) => {
delete state.ensEntries[chainId][normalizedEnsName];

if (Object.keys(ensEntries[chainId]).length === 0) {
delete ensEntries[chainId];
}

this.update({ ensEntries });
if (Object.keys(state.ensEntries[chainId]).length === 0) {
delete state.ensEntries[chainId];
}
});
return true;
}

Expand Down Expand Up @@ -146,8 +178,8 @@ export class EnsController extends BaseController<BaseConfig, EnsState> {
return false;
}

this.update({
ensEntries: {
this.update((state) => {
state.ensEntries = {
...this.state.ensEntries,
[chainId]: {
...this.state.ensEntries[chainId],
Expand All @@ -157,7 +189,7 @@ export class EnsController extends BaseController<BaseConfig, EnsState> {
ensName: normalizedEnsName,
},
},
},
};
});
return true;
}
Expand Down