From 63a778969cf229e29c8ac56a632de4fcb0ccd020 Mon Sep 17 00:00:00 2001 From: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> Date: Mon, 29 Jul 2024 19:03:03 +0800 Subject: [PATCH] chore: show snap error if it is dev (#299) * chore: show snap error if it is dev * chore: reset logger log level --------- Co-authored-by: khanti42 --- packages/starknet-snap/src/index.ts | 43 ++++--- packages/starknet-snap/test/src/index.test.ts | 112 +++++++++++++++++- 2 files changed, 137 insertions(+), 18 deletions(-) diff --git a/packages/starknet-snap/src/index.ts b/packages/starknet-snap/src/index.ts index 984035ff..93edbcd4 100644 --- a/packages/starknet-snap/src/index.ts +++ b/packages/starknet-snap/src/index.ts @@ -6,12 +6,12 @@ import type { Component, } from '@metamask/snaps-sdk'; import { - InternalError, panel, row, divider, text, copyable, + SnapError, } from '@metamask/snaps-sdk'; import { Mutex } from 'async-mutex'; import { ethers } from 'ethers'; @@ -61,7 +61,7 @@ import { STARKNET_TESTNET_NETWORK, } from './utils/constants'; import { getAddressKeyDeriver } from './utils/keyPair'; -import { logger } from './utils/logger'; +import { logger, LogLevel } from './utils/logger'; import { toJson } from './utils/serializer'; import { dappUrl, @@ -80,17 +80,17 @@ declare const snap; const saveMutex = new Mutex(); export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { + const requestParams = request?.params as unknown as ApiRequestParams; + const debugLevel = requestParams?.debugLevel; + logger.init(debugLevel as unknown as string); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + console.log(`debugLevel: ${logger.getLogLevel()}`); + + logger.log(`${request.method}:\nrequestParams: ${toJson(requestParams)}`); + try { - const requestParams = request?.params as unknown as ApiRequestParams; const isDev = Boolean(requestParams?.isDev); - const debugLevel = requestParams?.debugLevel; - logger.init(debugLevel as unknown as string); - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - console.log(`debugLevel: ${logger.getLogLevel()}`); - - logger.log(`${request.method}:\nrequestParams: ${toJson(requestParams)}`); - // Switch statement for methods not requiring state to speed things up a bit if (request.method === 'ping') { logger.log('pong'); return 'pong'; @@ -148,11 +148,13 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { switch (request.method) { case 'starkNet_createAccount': apiParams.keyDeriver = await getAddressKeyDeriver(snap); - return createAccount(apiParams as unknown as ApiParamsWithKeyDeriver); + return await createAccount( + apiParams as unknown as ApiParamsWithKeyDeriver, + ); case 'starkNet_createAccountLegacy': apiParams.keyDeriver = await getAddressKeyDeriver(snap); - return createAccount( + return await createAccount( apiParams as unknown as ApiParamsWithKeyDeriver, false, true, @@ -292,7 +294,16 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { throw new Error('Method not found.'); } } catch (error) { - throw new InternalError(error) as unknown as Error; + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + logger.error(`Error: ${error}`); + // We don't want to expose the error message to the user when it is a production build + if (logger.getLogLevel() === LogLevel.OFF) { + throw new SnapError( + 'Unable to execute the rpc request', + ) as unknown as Error; + } else { + throw new SnapError(error.message) as unknown as Error; + } } }; @@ -395,6 +406,10 @@ export const onHomePage: OnHomePageHandler = async () => { content: panel(panelItems), }; } catch (error) { - throw new InternalError(error) as unknown as Error; + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + logger.error(`Error: ${error}`); + throw new SnapError( + 'Unable to initialize Snap HomePage', + ) as unknown as Error; } }; diff --git a/packages/starknet-snap/test/src/index.test.ts b/packages/starknet-snap/test/src/index.test.ts index 52a9df97..327d1c46 100644 --- a/packages/starknet-snap/test/src/index.test.ts +++ b/packages/starknet-snap/test/src/index.test.ts @@ -1,6 +1,8 @@ import chai, { expect } from 'chai'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; +import { SnapError } from '@metamask/snaps-sdk'; + import { WalletMock } from '../wallet.mock.test'; import { getBip44EntropyStub, account1 } from '../constants.test'; import { SnapState } from '../../src/types/snapState'; @@ -11,12 +13,113 @@ import { STARKNET_SEPOLIA_TESTNET_NETWORK, } from '../../src/utils/constants'; import * as starknetUtils from '../../src/utils/starknetUtils'; -import { onHomePage } from '../../src'; +import * as createAccountApi from '../../src/createAccount'; +import * as keyPairUtils from '../../src/utils/keyPair'; +import * as logger from '../../src/utils/logger'; +import { onHomePage, onRpcRequest } from '../../src'; chai.use(sinonChai); const sandbox = sinon.createSandbox(); -describe('Test function: onHomePage', function () { +describe('onRpcRequest', function () { + const walletStub = new WalletMock(); + + const mockSnap = () => { + const globalAny: any = global; + globalAny.snap = walletStub; + }; + + afterEach(function () { + walletStub.reset(); + sandbox.restore(); + // Temp solution: Switch off logger after each test + logger.logger.init('off'); + }); + + it('processes request successfully', async function () { + mockSnap(); + const createAccountSpy = sandbox.stub(createAccountApi, 'createAccount'); + const keyPairSpy = sandbox.stub(keyPairUtils, 'getAddressKeyDeriver'); + + createAccountSpy.resolvesThis(); + keyPairSpy.resolvesThis(); + + await onRpcRequest({ + origin: 'http://localhost:3000', + request: { + method: 'starkNet_createAccount', + params: [], + jsonrpc: '2.0', + id: 1, + }, + }); + + expect(keyPairSpy).to.have.been.calledOnce; + expect(createAccountSpy).to.have.been.calledOnce; + }); + + it('throws `Unable to execute the rpc request` error if an error has thrown and `LogLevel` is `OFF`', async function () { + mockSnap(); + const createAccountSpy = sandbox.stub(createAccountApi, 'createAccount'); + const keyPairSpy = sandbox.stub(keyPairUtils, 'getAddressKeyDeriver'); + + createAccountSpy.rejects(new Error('Custom Error')); + keyPairSpy.resolvesThis(); + + let expectedError; + + try { + await onRpcRequest({ + origin: 'http://localhost:3000', + request: { + method: 'starkNet_createAccount', + params: [], + jsonrpc: '2.0', + id: 1, + }, + }); + } catch (error) { + expectedError = error; + } finally { + expect(expectedError).to.be.instanceOf(SnapError); + expect(expectedError.message).to.equal( + 'Unable to execute the rpc request', + ); + } + }); + + it('does not hide the error message if an error is thrown and `LogLevel` is not `OFF`', async function () { + mockSnap(); + const createAccountSpy = sandbox.stub(createAccountApi, 'createAccount'); + const keyPairSpy = sandbox.stub(keyPairUtils, 'getAddressKeyDeriver'); + + createAccountSpy.rejects(new Error('Custom Error')); + keyPairSpy.resolvesThis(); + + let expectedError; + + try { + await onRpcRequest({ + origin: 'http://localhost:3000', + request: { + method: 'starkNet_createAccount', + params: { + debugLevel: 'DEBUG', + }, + jsonrpc: '2.0', + id: 1, + }, + }); + } catch (error) { + expectedError = error; + } finally { + expect(expectedError).to.be.instanceOf(SnapError); + expect(expectedError.message).to.equal('Custom Error'); + } + }); +}); + +describe('onHomePage', function () { const walletStub = new WalletMock(); // eslint-disable-next-line no-restricted-globals, @typescript-eslint/no-explicit-any const globalAny: any = global; @@ -152,14 +255,15 @@ describe('Test function: onHomePage', function () { }); }); - it('throws error when state not found', async function () { + it('throws `Unable to initialize Snap HomePage` error when state not found', async function () { let error; try { await onHomePage(); } catch (err) { error = err; } finally { - expect(error).to.be.an('error'); + expect(error).to.be.instanceOf(SnapError); + expect(error.message).to.equal('Unable to initialize Snap HomePage'); } }); });