Skip to content

Commit

Permalink
Add proxy for ethereum global (MetaMask#1087)
Browse files Browse the repository at this point in the history
* Add proxy for ethereum global

* Test access to ethereum public props
  • Loading branch information
FrederikBolding authored Dec 19, 2022
1 parent 8966739 commit ddffcf5
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 6 deletions.
8 changes: 4 additions & 4 deletions packages/snaps-execution-environments/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ module.exports = deepmerge(baseConfig, {
coveragePathIgnorePatterns: ['./src/index.ts', '.ava.test.ts'],
coverageThreshold: {
global: {
branches: 83.68,
functions: 92.19,
lines: 87.11,
statements: 87.22,
branches: 83.93,
functions: 92.25,
lines: 87.07,
statements: 87.18,
},
},
testEnvironment: '<rootDir>/jest.environment.js',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,111 @@ describe('BaseSnapExecutor', () => {
});
});

it('allows direct access to ethereum public properties', async () => {
const CODE = `
module.exports.onRpcRequest = () => {
const listener = () => undefined;
ethereum.on('accountsChanged', listener);
ethereum.removeListener('accountsChanged', listener);
return ethereum.request({ method: 'eth_blockNumber', params: [] }) };
`;
const executor = new TestSnapExecutor();

await executor.executeSnap(1, FAKE_SNAP_NAME, CODE, ['ethereum']);

expect(await executor.readCommand()).toStrictEqual({
jsonrpc: '2.0',
id: 1,
result: 'OK',
});

await executor.writeCommand({
jsonrpc: '2.0',
id: 2,
method: 'snapRpc',
params: [
FAKE_SNAP_NAME,
ON_RPC_REQUEST,
FAKE_ORIGIN,
{ jsonrpc: '2.0', method: '', params: [] },
],
});

expect(await executor.readCommand()).toStrictEqual({
jsonrpc: '2.0',
method: 'OutboundRequest',
});

const blockNumRequest = await executor.readRpc();
expect(blockNumRequest).toStrictEqual({
name: 'metamask-provider',
data: {
id: expect.any(Number),
jsonrpc: '2.0',
method: 'eth_blockNumber',
params: [],
},
});

await executor.writeRpc({
name: 'metamask-provider',
data: {
jsonrpc: '2.0',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: blockNumRequest.data.id!,
result: '0xa70e77',
},
});

expect(await executor.readCommand()).toStrictEqual({
jsonrpc: '2.0',
method: 'OutboundResponse',
});

expect(await executor.readCommand()).toStrictEqual({
id: 2,
jsonrpc: '2.0',
result: '0xa70e77',
});
});

it("doesn't allow direct access to ethereum internals", async () => {
const CODE = `
module.exports.onRpcRequest = () => ethereum._rpcEngine.handle({ method: 'snap_confirm', params: [] });
`;
const executor = new TestSnapExecutor();

await executor.executeSnap(1, FAKE_SNAP_NAME, CODE, ['ethereum']);

expect(await executor.readCommand()).toStrictEqual({
jsonrpc: '2.0',
id: 1,
result: 'OK',
});

await executor.writeCommand({
jsonrpc: '2.0',
id: 2,
method: 'snapRpc',
params: [
FAKE_SNAP_NAME,
ON_RPC_REQUEST,
FAKE_ORIGIN,
{ jsonrpc: '2.0', method: '', params: [] },
],
});

expect(await executor.readCommand()).toStrictEqual({
jsonrpc: '2.0',
error: {
code: -32603,
message: "Cannot read properties of undefined (reading 'handle')",
data: expect.any(Object),
},
id: 2,
});
});

it('only allows certain methods in snap API', async () => {
const CODE = `
module.exports.onRpcRequest = () => snap.request({ method: 'eth_blockNumber', params: [] });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ export class BaseSnapExecutor {
private createEIP1193Provider(provider: StreamProvider): StreamProvider {
const originalRequest = provider.request.bind(provider);

provider.request = async (args) => {
const request = async (args: RequestArguments) => {
assert(
!args.method.startsWith('snap_'),
ethErrors.rpc.methodNotFound({
Expand All @@ -428,7 +428,20 @@ export class BaseSnapExecutor {
}
};

return provider;
// To harden and limit access to internals, we use a proxy.
const proxy = new Proxy(provider, {
get(target, prop: keyof StreamProvider) {
if (prop === 'request') {
return request;
} else if (['on', 'removeListener'].includes(prop)) {
return target[prop];
}

return undefined;
},
});

return proxy;
}

/**
Expand Down

0 comments on commit ddffcf5

Please sign in to comment.