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

chore: remove eth-method-registry package #5203

Closed
Closed
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
7 changes: 7 additions & 0 deletions packages/transaction-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Replaced `eth-method-registry` dependency with `@ethersproject/abi` for ABI method lookups ([#5203](https://github.com/MetaMask/core/pull/5203))
- The `MethodDataHelper` class now uses ethers.js `Interface` class to parse and lookup method signatures
- Method data is now derived directly from the ABI interface
- The structure of returned method data remains compatible with previous implementation

## [43.0.0]

### Added
Expand Down
1 change: 0 additions & 1 deletion packages/transaction-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
"@metamask/utils": "^11.0.1",
"async-mutex": "^0.5.0",
"bn.js": "^5.2.1",
"eth-method-registry": "^4.0.0",
"fast-json-patch": "^3.1.1",
"lodash": "^4.17.21",
"uuid": "^8.3.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,6 @@ export class TransactionController extends BaseController<
);

this.#methodDataHelper = new MethodDataHelper({
getProvider: (networkClientId) => this.#getProvider({ networkClientId }),
getState: () => this.state.methodData,
});

Expand Down
71 changes: 71 additions & 0 deletions packages/transaction-controller/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,74 @@ export const ABI_SIMULATION_ERC721_LEGACY = [
type: 'event',
},
];

export const METHOD_REGISTRY_ABI = [
{
constant: false,
inputs: [{ name: '_new', type: 'address' }],
name: 'setOwner',
outputs: [],
payable: false,
type: 'function',
},
{
constant: true,
inputs: [],
name: 'totalSignatures',
outputs: [{ name: '', type: 'uint256' }],
payable: false,
type: 'function',
},
{
constant: true,
inputs: [],
name: 'owner',
outputs: [{ name: '', type: 'address' }],
payable: false,
type: 'function',
},
{
constant: false,
inputs: [],
name: 'drain',
outputs: [],
payable: false,
type: 'function',
},
{
constant: true,
inputs: [{ name: '', type: 'bytes4' }],
name: 'entries',
outputs: [{ name: '', type: 'string' }],
payable: false,
type: 'function',
},
{
constant: false,
inputs: [{ name: '_method', type: 'string' }],
name: 'register',
outputs: [{ name: '', type: 'bool' }],
payable: false,
type: 'function',
},
{ inputs: [], type: 'constructor' },
{
anonymous: false,
inputs: [
{ indexed: true, name: 'creator', type: 'address' },
{ indexed: true, name: 'signature', type: 'bytes4' },
{ indexed: false, name: 'method', type: 'string' },
],
name: 'Registered',
type: 'event',
},
{
anonymous: false,
inputs: [
{ indexed: true, name: 'old', type: 'address' },
{ indexed: true, name: 'current', type: 'address' },
],
name: 'NewOwner',
type: 'event',
},
];
129 changes: 56 additions & 73 deletions packages/transaction-controller/src/helpers/MethodDataHelper.test.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,59 @@
import { MethodRegistry } from 'eth-method-registry';

import type { MethodData } from '../TransactionController';
import { MethodDataHelper } from './MethodDataHelper';

jest.mock('eth-method-registry');
import type { MethodData } from '../TransactionController';

const FOUR_BYTE_PREFIX_MOCK = '0x12345678';
const NETWORK_CLIENT_ID_MOCK = 'testNetworkClientId';
const SIGNATURE_MOCK = 'testMethod(uint256,uint256)';

const METHOD_DATA_MOCK: MethodData = {
registryMethod: SIGNATURE_MOCK,
parsedRegistryMethod: {
name: 'testMethod',
args: [{ type: 'uint256' }, { type: 'uint256' }],
},
};

/**
* Creates a mock MethodRegistry instance.
* @returns The mocked MethodRegistry instance.
*/
function createMethodRegistryMock() {
return {
lookup: jest.fn(),
parse: jest.fn(),
} as unknown as jest.Mocked<MethodRegistry>;
}

describe('MethodDataHelper', () => {
const methodRegistryClassMock = jest.mocked(MethodRegistry);

beforeEach(() => {
jest.resetAllMocks();
});

describe('lookup', () => {
it('returns method data from cache', async () => {
const cachedMethodData: MethodData = {
registryMethod: 'cached()',
parsedRegistryMethod: {
name: 'cached',
args: [],
},
};

const methodDataHelper = new MethodDataHelper({
getProvider: jest.fn(),
getState: () => ({ [FOUR_BYTE_PREFIX_MOCK]: METHOD_DATA_MOCK }),
getState: () => ({ [FOUR_BYTE_PREFIX_MOCK]: cachedMethodData }),
});

const result = await methodDataHelper.lookup(
FOUR_BYTE_PREFIX_MOCK,
NETWORK_CLIENT_ID_MOCK,
);

expect(result).toStrictEqual(METHOD_DATA_MOCK);
expect(result).toStrictEqual(cachedMethodData);
});

it('returns method data from registry lookup', async () => {
const methodRegistryMock = createMethodRegistryMock();
methodRegistryMock.lookup.mockResolvedValueOnce(SIGNATURE_MOCK);
methodRegistryMock.parse.mockReturnValueOnce(
METHOD_DATA_MOCK.parsedRegistryMethod,
);

methodRegistryClassMock.mockReturnValueOnce(methodRegistryMock);

it('correctly parses a known method signature', async () => {
const methodDataHelper = new MethodDataHelper({
getProvider: jest.fn(),
getState: () => ({}),
});

const fourBytePrefix = '0x13af4035';

const result = await methodDataHelper.lookup(
FOUR_BYTE_PREFIX_MOCK,
fourBytePrefix,
NETWORK_CLIENT_ID_MOCK,
);

expect(result).toStrictEqual(METHOD_DATA_MOCK);
expect(result).toStrictEqual({
registryMethod: 'setOwner(address)',
parsedRegistryMethod: {
name: 'setOwner',
args: [{ type: 'address' }],
},
});
});

it('returns empty method data if not found in registry', async () => {
const methodRegistryMock = createMethodRegistryMock();
methodRegistryMock.lookup.mockResolvedValueOnce(undefined);

methodRegistryClassMock.mockReturnValueOnce(methodRegistryMock);

it('returns empty result for unknown method signature', async () => {
const methodDataHelper = new MethodDataHelper({
getProvider: jest.fn(),
getState: () => ({}),
});

Expand All @@ -94,16 +68,25 @@ describe('MethodDataHelper', () => {
});
});

it('creates registry instance for each unique network client ID', async () => {
const getProviderMock = jest.fn();
it('creates interface instance for each unique network client ID', async () => {
const mockInterface = jest.fn().mockImplementation(() => ({
getFunction: jest.fn().mockImplementation(() => {
throw new Error('Function not found');
}),
}));

jest.doMock('@ethersproject/abi', () => ({
Interface: mockInterface,
}));

const methodRegistryMock = createMethodRegistryMock();
methodRegistryMock.lookup.mockResolvedValueOnce(undefined);
// Clear the module cache
jest.resetModules();

methodRegistryClassMock.mockReturnValueOnce(methodRegistryMock);
// Re-import dependencies after mocking
// eslint-disable-next-line @typescript-eslint/no-require-imports, n/global-require, @typescript-eslint/no-shadow
const { MethodDataHelper } = require('./MethodDataHelper');

const methodDataHelper = new MethodDataHelper({
getProvider: getProviderMock,
getState: () => ({}),
});

Expand All @@ -122,36 +105,36 @@ describe('MethodDataHelper', () => {
'anotherNetworkClientId',
);

expect(methodRegistryClassMock).toHaveBeenCalledTimes(2);
expect(getProviderMock).toHaveBeenCalledTimes(2);
});

it('emits event when method data is fetched', async () => {
const methodRegistryMock = createMethodRegistryMock();
methodRegistryMock.lookup.mockResolvedValueOnce(SIGNATURE_MOCK);
methodRegistryMock.parse.mockReturnValueOnce(
METHOD_DATA_MOCK.parsedRegistryMethod,
await methodDataHelper.lookup(
FOUR_BYTE_PREFIX_MOCK,
'anotherNetworkClientId',
);

methodRegistryClassMock.mockReturnValueOnce(methodRegistryMock);
expect(mockInterface).toHaveBeenCalledTimes(2);

jest.unmock('@ethersproject/abi');
});

it('emits an update event for new lookups', async () => {
const methodDataHelper = new MethodDataHelper({
getProvider: jest.fn(),
getState: () => ({}),
});

const updateListener = jest.fn();
methodDataHelper.hub.on('update', updateListener);

await methodDataHelper.lookup(
FOUR_BYTE_PREFIX_MOCK,
NETWORK_CLIENT_ID_MOCK,
);
const fourBytePrefix = '0x13af4035';
await methodDataHelper.lookup(fourBytePrefix, NETWORK_CLIENT_ID_MOCK);

expect(updateListener).toHaveBeenCalledTimes(1);
expect(updateListener).toHaveBeenCalledWith({
fourBytePrefix: FOUR_BYTE_PREFIX_MOCK,
methodData: METHOD_DATA_MOCK,
fourBytePrefix,
methodData: {
registryMethod: 'setOwner(address)',
parsedRegistryMethod: {
name: 'setOwner',
args: [{ type: 'address' }],
},
},
});
});
});
Expand Down
Loading
Loading