diff --git a/src/retryOnEmpty.test.ts b/src/retryOnEmpty.test.ts index cbda6489..b219b23c 100644 --- a/src/retryOnEmpty.test.ts +++ b/src/retryOnEmpty.test.ts @@ -2,6 +2,7 @@ import { providerFromEngine } from '@metamask/eth-json-rpc-provider'; import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; import { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import { errorCodes, rpcErrors } from '@metamask/rpc-errors'; import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils'; import { PollingBlockTracker } from 'eth-block-tracker'; @@ -627,6 +628,47 @@ describe('createRetryOnEmptyMiddleware', () => { ); }); }); + + describe('when provider return execution revert error', () => { + it('returns the same error to caller', async () => { + await withTestSetup( + { + configureMiddleware: ({ provider, blockTracker }) => { + return { + middlewareUnderTest: createRetryOnEmptyMiddleware({ + provider, + blockTracker, + }), + }; + }, + }, + async ({ engine, provider }) => { + const request: JsonRpcRequest = { + id: 123, + jsonrpc: '2.0', + method: 'eth_call', + params: buildMockParamsWithBlockParamAt(1, '100'), + }; + stubProviderRequests(provider, [ + buildStubForBlockNumberRequest(), + { + request, + response: () => { + throw rpcErrors.invalidInput('execution reverted'); + }, + }, + ]); + const promiseForResponse = engine.handle(request); + expect(await promiseForResponse).toMatchObject({ + error: expect.objectContaining({ + code: errorCodes.rpc.invalidInput, + message: 'execution reverted', + }), + }); + }, + ); + }); + }); }); /** diff --git a/src/retryOnEmpty.ts b/src/retryOnEmpty.ts index f4e717e4..b777f8c2 100644 --- a/src/retryOnEmpty.ts +++ b/src/retryOnEmpty.ts @@ -13,6 +13,7 @@ import pify from 'pify'; import { projectLogger, createModuleLogger } from './logging-utils'; import type { Block } from './types'; import { blockTagParamIndex } from './utils/cache'; +import { isExecutionRevertedError } from './utils/error'; import { timeout } from './utils/timeout'; // @@ -140,7 +141,10 @@ async function retry( for (let index = 0; index < maxRetries; index++) { try { return await asyncFn(); - } catch (err) { + } catch (err: unknown) { + if (isExecutionRevertedError(err)) { + throw err as unknown; + } log('(call %i) Request failed, waiting 1s to retry again...', index + 1); await timeout(1000); } diff --git a/src/utils/error.test.ts b/src/utils/error.test.ts new file mode 100644 index 00000000..d51eca67 --- /dev/null +++ b/src/utils/error.test.ts @@ -0,0 +1,36 @@ +import { errorCodes } from '@metamask/rpc-errors'; + +import { isExecutionRevertedError } from './error'; + +const executionRevertedError = { + code: errorCodes.rpc.invalidInput, + message: 'execution reverted', +}; + +describe('isExecutionRevertedError', () => { + it('return false if object is not valid JSON RPC error', async () => { + const result = isExecutionRevertedError({ test: 'dummy' }); + expect(result).toBe(false); + }); + + it('return false if error code is not same as errorCodes.rpc.invalidInput', async () => { + const result = isExecutionRevertedError({ + ...executionRevertedError, + code: 123, + }); + expect(result).toBe(false); + }); + + it('return false if error message is not "execution reverted"', async () => { + const result = isExecutionRevertedError({ + ...executionRevertedError, + message: 'test', + }); + expect(result).toBe(false); + }); + + it('return true for correct executionRevertedError', async () => { + const result = isExecutionRevertedError(executionRevertedError); + expect(result).toBe(true); + }); +}); diff --git a/src/utils/error.ts b/src/utils/error.ts new file mode 100644 index 00000000..dbc693c0 --- /dev/null +++ b/src/utils/error.ts @@ -0,0 +1,13 @@ +import { errorCodes } from '@metamask/rpc-errors'; +import { isJsonRpcError } from '@metamask/utils'; +import type { JsonRpcError } from '@metamask/utils'; + +export function isExecutionRevertedError( + error: unknown, +): error is JsonRpcError { + return ( + isJsonRpcError(error) && + error.code === errorCodes.rpc.invalidInput && + error.message === 'execution reverted' + ); +}