diff --git a/package.json b/package.json index 319d1509..4787dbaf 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "test:watch": "jest --watch" }, "dependencies": { - "@metamask/eth-block-tracker": "^10.0.0", - "@metamask/eth-json-rpc-provider": "^4.0.0", + "@metamask/eth-block-tracker": "^11.0.0", + "@metamask/eth-json-rpc-provider": "^4.1.0", "@metamask/eth-sig-util": "^7.0.0", "@metamask/json-rpc-engine": "^9.0.0", "@metamask/rpc-errors": "^6.0.0", diff --git a/src/block-cache.test.ts b/src/block-cache.test.ts index 34744593..07f2373c 100644 --- a/src/block-cache.test.ts +++ b/src/block-cache.test.ts @@ -23,11 +23,7 @@ function createTestSetup() { describe('block cache', () => { it('should cache a request and only hit the provider once', async () => { const { engine, provider, blockTracker } = createTestSetup(); - const spy = jest - .spyOn(provider, 'sendAsync') - .mockImplementation((req, cb) => { - cb(undefined, { id: req.id, result: '0x0', jsonrpc: '2.0' }); - }); + const requestSpy = jest.spyOn(provider, 'request').mockResolvedValue('0x0'); let hitCount = 0; const hitCountMiddleware = createHitTrackerMiddleware(); @@ -56,6 +52,6 @@ describe('block cache', () => { expect(hitCount).toBe(1); expect(response.result).toBe('0x0'); expect(response2.result).toBe('0x0'); - expect(spy).toHaveBeenCalled(); + expect(requestSpy).toHaveBeenCalled(); }); }); diff --git a/src/block-ref.test.ts b/src/block-ref.test.ts index 2d5f8947..7dea2d7d 100644 --- a/src/block-ref.test.ts +++ b/src/block-ref.test.ts @@ -10,7 +10,7 @@ import { stubProviderRequests, buildStubForBlockNumberRequest, buildStubForGenericRequest, - buildFinalMiddlewareWithDefaultResponse, + buildFinalMiddlewareWithDefaultResult, buildMockParamsWithoutBlockParamAt, expectProviderRequestNotToHaveBeenMade, } from '../test/util/helpers'; @@ -106,11 +106,7 @@ describe('createBlockRefMiddleware', () => { '0x100', ), }, - response: (req) => ({ - id: req.id, - jsonrpc: '2.0' as const, - result: 'something', - }), + result: async () => 'something', }), ]); @@ -120,14 +116,13 @@ describe('createBlockRefMiddleware', () => { id: 1, jsonrpc: '2.0', result: 'something', - error: undefined, }); }, ); }); it('does not proceed to the next middleware after making a request through the provider', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -161,11 +156,7 @@ describe('createBlockRefMiddleware', () => { '0x100', ), }, - response: (req) => ({ - id: req.id, - jsonrpc: '2.0' as const, - result: 'something', - }), + result: async () => 'something', }), ]); @@ -207,11 +198,7 @@ describe('createBlockRefMiddleware', () => { '0x100', ), }, - response: (req) => ({ - id: req.id, - jsonrpc: '2.0' as const, - result: 'something', - }), + result: async () => 'something', }), ]); @@ -221,14 +208,13 @@ describe('createBlockRefMiddleware', () => { id: 1, jsonrpc: '2.0', result: 'something', - error: undefined, }); }, ); }); it('does not proceed to the next middleware after making a request through the provider', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -259,11 +245,7 @@ describe('createBlockRefMiddleware', () => { '0x100', ), }, - response: (req) => ({ - id: req.id, - jsonrpc: '2.0' as const, - result: 'something', - }), + result: async () => 'something', }), ]); @@ -279,7 +261,7 @@ describe('createBlockRefMiddleware', () => { 'if the block param is something other than "latest", like %o', (blockParam) => { it('does not make a direct request through the provider', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -303,19 +285,19 @@ describe('createBlockRefMiddleware', () => { blockParam, ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest('0x100'), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); it('proceeds to the next middleware', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -366,7 +348,7 @@ describe('createBlockRefMiddleware', () => { describe('when the RPC method does not take a block parameter', () => { it('does not make a direct request through the provider', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -387,19 +369,19 @@ describe('createBlockRefMiddleware', () => { method: 'a_non_block_param_method', params: ['some value', '0x200'], }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest('0x100'), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); it('proceeds to the next middleware', async () => { - const finalMiddleware = buildFinalMiddlewareWithDefaultResponse(); + const finalMiddleware = buildFinalMiddlewareWithDefaultResult(); await withTestSetup( { @@ -465,7 +447,7 @@ async function withTestSetup( const { middlewareUnderTest, - otherMiddleware = [buildFinalMiddlewareWithDefaultResponse()], + otherMiddleware = [buildFinalMiddlewareWithDefaultResult()], } = configureMiddleware({ engine, provider, blockTracker }); for (const middleware of [middlewareUnderTest, ...otherMiddleware]) { diff --git a/src/block-ref.ts b/src/block-ref.ts index 949031c0..1348b8cc 100644 --- a/src/block-ref.ts +++ b/src/block-ref.ts @@ -2,13 +2,8 @@ import type { PollingBlockTracker } from '@metamask/eth-block-tracker'; import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; -import type { - Json, - JsonRpcParams, - PendingJsonRpcResponse, -} from '@metamask/utils'; +import type { Json, JsonRpcParams } from '@metamask/utils'; import { klona } from 'klona/full'; -import pify from 'pify'; import { projectLogger, createModuleLogger } from './logging-utils'; import type { Block } from './types'; @@ -68,12 +63,8 @@ export function createBlockRefMiddleware({ // perform child request log('Performing another request %o', childRequest); - const childRes: PendingJsonRpcResponse = await pify( - provider.sendAsync, - ).call(provider, childRequest); - // copy child response onto original response - res.result = childRes.result; - res.error = childRes.error; + // copy child result onto original response + res.result = await provider.request(childRequest); return undefined; }); diff --git a/src/providerAsMiddleware.ts b/src/providerAsMiddleware.ts index 70f620aa..c41e4262 100644 --- a/src/providerAsMiddleware.ts +++ b/src/providerAsMiddleware.ts @@ -1,5 +1,8 @@ import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; -import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; +import { + createAsyncMiddleware, + type JsonRpcMiddleware, +} from '@metamask/json-rpc-engine'; import type { Json, JsonRpcParams, @@ -9,21 +12,9 @@ import type { export function providerAsMiddleware( provider: SafeEventEmitterProvider, ): JsonRpcMiddleware { - return (req, res, _next, end) => { - // send request to provider - provider.sendAsync( - req, - (err: unknown, providerRes: PendingJsonRpcResponse) => { - // forward any error - if (err instanceof Error) { - return end(err); - } - // copy provider response onto original response - Object.assign(res, providerRes); - return end(); - }, - ); - }; + return createAsyncMiddleware(async (req, res) => { + res.result = await provider.request(req); + }); } export function ethersProviderAsMiddleware( diff --git a/src/retryOnEmpty.test.ts b/src/retryOnEmpty.test.ts index 27fefb3c..af541f29 100644 --- a/src/retryOnEmpty.test.ts +++ b/src/retryOnEmpty.test.ts @@ -3,13 +3,13 @@ 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 { errorCodes, providerErrors, rpcErrors } from '@metamask/rpc-errors'; import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils'; import { createRetryOnEmptyMiddleware } from '.'; import type { ProviderRequestStub } from '../test/util/helpers'; import { - buildFinalMiddlewareWithDefaultResponse, + buildFinalMiddlewareWithDefaultResult, buildMockParamsWithBlockParamAt, buildMockParamsWithoutBlockParamAt, buildSimpleFinalMiddleware, @@ -142,22 +142,18 @@ describe('createRetryOnEmptyMiddleware', () => { blockNumber, ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest(blockNumber), stubRequestThatFailsThenFinallySucceeds({ request, numberOfTimesToFail: 9, - successfulResponse: (req) => ({ - id: req.id, - jsonrpc: '2.0', - result: 'something', - }), + successfulResult: async () => 'something', }), ]); const promiseForResponse = engine.handle(request); await waitForRequestToBeRetried({ - sendAsyncSpy, + requestSpy, request, numberOfTimes: 10, }); @@ -166,7 +162,6 @@ describe('createRetryOnEmptyMiddleware', () => { id: 1, jsonrpc: '2.0', result: 'something', - error: undefined, }); }, ); @@ -195,19 +190,12 @@ describe('createRetryOnEmptyMiddleware', () => { blockNumber, ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest(blockNumber), stubGenericRequest({ request, - response: (req) => { - return { - id: req.id, - jsonrpc: '2.0', - error: { - code: -1, - message: 'oops', - }, - }; + result: () => { + throw providerErrors.custom({ code: -1, message: 'oops' }); }, remainAfterUse: true, }), @@ -215,7 +203,7 @@ describe('createRetryOnEmptyMiddleware', () => { const promiseForResponse = engine.handle(request); await waitForRequestToBeRetried({ - sendAsyncSpy, + requestSpy, request, numberOfTimes: 10, }); @@ -263,13 +251,7 @@ describe('createRetryOnEmptyMiddleware', () => { buildStubForBlockNumberRequest(blockNumber), stubGenericRequest({ request, - response: (req) => { - return { - id: req.id, - jsonrpc: '2.0', - result: 'success', - }; - }, + result: async () => 'success', }), ]); @@ -303,13 +285,13 @@ describe('createRetryOnEmptyMiddleware', () => { '0x100', ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest('0x0'), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); @@ -376,13 +358,13 @@ describe('createRetryOnEmptyMiddleware', () => { blockParam, ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest('0x0'), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); @@ -450,13 +432,13 @@ describe('createRetryOnEmptyMiddleware', () => { blockParam, ), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest(), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); @@ -519,13 +501,13 @@ describe('createRetryOnEmptyMiddleware', () => { method, params: buildMockParamsWithoutBlockParamAt(blockParamIndex), }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest(), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); @@ -587,13 +569,13 @@ describe('createRetryOnEmptyMiddleware', () => { jsonrpc: '2.0', method, }; - const sendAsyncSpy = stubProviderRequests(provider, [ + const requestSpy = stubProviderRequests(provider, [ buildStubForBlockNumberRequest(), ]); await engine.handle(request); - expectProviderRequestNotToHaveBeenMade(sendAsyncSpy, request); + expectProviderRequestNotToHaveBeenMade(requestSpy, request); }, ); }); @@ -653,7 +635,7 @@ describe('createRetryOnEmptyMiddleware', () => { buildStubForBlockNumberRequest(), { request, - response: () => { + result: () => { throw rpcErrors.invalidInput('execution reverted'); }, }, @@ -697,7 +679,7 @@ async function withTestSetup( const { middlewareUnderTest, - otherMiddleware = [buildFinalMiddlewareWithDefaultResponse()], + otherMiddleware = [buildFinalMiddlewareWithDefaultResult()], } = configureMiddleware({ engine, provider, blockTracker }); for (const middleware of [middlewareUnderTest, ...otherMiddleware]) { @@ -711,9 +693,9 @@ async function withTestSetup( } /** - * Builds a canned response for a request made to `provider.sendAsync`. Intended + * Builds a canned result for a request made to `provider.request`. Intended * to be used in conjunction with `stubProviderRequests`. Although not strictly - * necessary, it helps to assign a proper type to a request/response pair. + * necessary, it helps to assign a proper type to a request/result pair. * * @param requestStub - The request/response pair. * @returns The request/response pair, properly typed. @@ -725,16 +707,16 @@ function stubGenericRequest( } /** - * Builds a canned response for a request made to `provider.sendAsync` which + * Builds a canned result for a request made to `provider.request` which * will error for the first N instances and then succeed on the last instance. * Intended to be used in conjunction with `stubProviderRequests`. * * @param request - The request matcher for the stub. * @param numberOfTimesToFail - The number of times the request is expected to - * be called until it returns a successful response. - * @param successfulResponse - The response that `provider.sendAsync` will + * be called until it returns a successful result. + * @param successfulResult - The result that `provider.request` will * return when called past `numberOfTimesToFail`. - * @returns The request/response pair, properly typed. + * @returns The request/result pair, properly typed. */ function stubRequestThatFailsThenFinallySucceeds< T extends JsonRpcParams, @@ -742,27 +724,20 @@ function stubRequestThatFailsThenFinallySucceeds< >({ request, numberOfTimesToFail, - successfulResponse, + successfulResult, }: { request: ProviderRequestStub['request']; numberOfTimesToFail: number; - successfulResponse: ProviderRequestStub['response']; + successfulResult: ProviderRequestStub['result']; }): ProviderRequestStub { return stubGenericRequest({ request, - response: (req, callNumber) => { + result: async (callNumber) => { if (callNumber <= numberOfTimesToFail) { - return { - id: req.id, - jsonrpc: '2.0', - error: { - code: -1, - message: 'oops', - }, - }; + throw providerErrors.custom({ code: -1, message: 'oops' }); } - return successfulResponse(req, callNumber); + return await successfulResult(callNumber); }, remainAfterUse: true, }); @@ -770,30 +745,30 @@ function stubRequestThatFailsThenFinallySucceeds< /** * The `retryOnEmpty` middleware, as its name implies, uses the provider to make - * the given request, retrying said request up to 10 times if the response is + * the given request, retrying said request up to 10 times if the result is * empty before failing. Upon retrying, it will wait a brief time using * `setTimeout`. Because we are using Jest's fake timers, we have to manually * trigger the callback passed to `setTimeout` atfter it is called. The problem * is that we don't know when `setTimeout` will be called while the * `retryOnEmpty` middleware is running, so we have to wait. We do this by - * recording how many times `provider.sendAsync` has been called with the + * recording how many times `provider.request` has been called with the * request, and when that number goes up, we assume that `setTimeout` has been * called too and advance through time. We stop the loop when - * `provider.sendAsync` has been called the given number of times. + * `provider.request` has been called the given number of times. * * @param args - The arguments. - * @param sendAsyncSpy - The Jest spy object that represents - * `provider.sendAsync`. + * @param requestSpy - The Jest spy object that represents + * `provider.request`. * @param request - The request object. * @param numberOfTimes - The number of times that we expect - * `provider.sendAsync` to be called with `request`. + * `provider.request` to be called with `request`. */ async function waitForRequestToBeRetried({ - sendAsyncSpy, + requestSpy, request, numberOfTimes, }: { - sendAsyncSpy: jest.SpyInstance; + requestSpy: jest.SpyInstance; request: JsonRpcRequest; numberOfTimes: number; }) { @@ -803,7 +778,7 @@ async function waitForRequestToBeRetried({ await new Promise((resolve) => originalSetTimeout(resolve, 0)); if ( - sendAsyncSpy.mock.calls.filter((args) => requestMatches(args[0], request)) + requestSpy.mock.calls.filter((args) => requestMatches(args[0], request)) .length === iterationNumber ) { jest.runAllTimers(); diff --git a/src/retryOnEmpty.ts b/src/retryOnEmpty.ts index 79d34a71..ace8a61f 100644 --- a/src/retryOnEmpty.ts +++ b/src/retryOnEmpty.ts @@ -2,13 +2,8 @@ import type { PollingBlockTracker } from '@metamask/eth-block-tracker'; import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; import { createAsyncMiddleware } from '@metamask/json-rpc-engine'; -import type { - Json, - JsonRpcParams, - PendingJsonRpcResponse, -} from '@metamask/utils'; +import type { Json, JsonRpcParams } from '@metamask/utils'; import { klona } from 'klona/full'; -import pify from 'pify'; import { projectLogger, createModuleLogger } from './logging-utils'; import type { Block } from './types'; @@ -103,41 +98,34 @@ export function createRetryOnEmptyMiddleware({ // create child request with specific block-ref const childRequest = klona(req); // attempt child request until non-empty response is received - const childResponse: PendingJsonRpcResponse = await retry( - 10, - async () => { - log('Performing request %o', childRequest); - const attemptResponse: PendingJsonRpcResponse = await pify( - provider.sendAsync, - ).call(provider, childRequest); - log('Response is %o', attemptResponse); - // verify result - if (emptyValues.includes(attemptResponse.result as any)) { - throw new Error( - `RetryOnEmptyMiddleware - empty response "${JSON.stringify( - attemptResponse, - )}" for request "${JSON.stringify(childRequest)}"`, - ); - } - return attemptResponse; - }, - ); - log( - 'Copying result %o and error %o', - childResponse.result, - childResponse.error, - ); - // copy child response onto original response - res.result = childResponse.result; - res.error = childResponse.error; + const childResult = await retry(10, async () => { + log('Performing request %o', childRequest); + const attemptResult = await provider.request( + childRequest, + ); + log('Result is %o', attemptResult); + // verify result + const allEmptyValues: unknown[] = emptyValues; + if (allEmptyValues.includes(attemptResult)) { + throw new Error( + `RetryOnEmptyMiddleware - empty result "${JSON.stringify( + attemptResult, + )}" for request "${JSON.stringify(childRequest)}"`, + ); + } + return attemptResult; + }); + log('Copying result %o', childResult); + // copy child result onto original response + res.result = childResult; return undefined; }); } -async function retry( +async function retry( maxRetries: number, - asyncFn: () => Promise>, -): Promise> { + asyncFn: () => Promise, +): Promise { for (let index = 0; index < maxRetries; index++) { try { return await asyncFn(); diff --git a/test/util/helpers.ts b/test/util/helpers.ts index f6380304..46811f6c 100644 --- a/test/util/helpers.ts +++ b/test/util/helpers.ts @@ -1,58 +1,50 @@ import type { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine'; -import type { - Json, - JsonRpcParams, - JsonRpcRequest, - JsonRpcResponse, -} from '@metamask/utils'; +import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils'; import { klona } from 'klona/full'; import { isDeepStrictEqual } from 'util'; /** - * An object that can be used to assign a canned response to a request made via - * `provider.sendAsync`. + * An object that can be used to assign a canned result to a request made via + * `provider.request`. * * @template Params - The type that represents the request params. - * @template Result - The type that represents the response result. + * @template Result - The type that represents the result. * @property request - An object that represents a JsonRpcRequest. Keys such as * `id` or `jsonrpc` may be omitted if you don't care about them. - * @property response - A function that returns a JsonRpcResponse for that - * request. This function takes two arguments: the *real* request and a - * `callNumber`, which is the number of times the request has been made + * @property result - A function that returns a result for that request. + * This function takes `callNumber` argument, + * which is the number of times the request has been made * (counting the first request as 1). This latter argument be used to specify - * different responses for different instances of the same request. + * different results for different instances of the same request. * @property remainAfterUse - Usually, when a request is made via - * `provider.sendAsync`, the ProviderRequestStub which matches that request is + * `provider.request`, the ProviderRequestStub which matches that request is * removed from the list of stubs, so that if the same request comes through * again, there will be no matching stub and an error will be thrown. This - * feature is useful for making sure that all requests have canned responses. + * feature is useful for making sure that all requests have canned results. */ export interface ProviderRequestStub< Params extends JsonRpcParams, Result extends Json, > { request: Partial>; - response: ( - request: JsonRpcRequest, - callNumber: number, - ) => JsonRpcResponse; + result: (callNumber: number) => Promise; remainAfterUse?: boolean; } /** * Creates a middleware function that ends the request, but not before ensuring - * that the response has been filled with something. Additionally this function + * that the result has been filled with something. Additionally this function * is a Jest mock function so that you can make assertions on it. * * @template Params - The type that represents the request params. - * @template Result - The type that represents the response result. + * @template Result - The type that represents the result. * @returns The created middleware, as a mock function. */ -export function buildFinalMiddlewareWithDefaultResponse< +export function buildFinalMiddlewareWithDefaultResult< Params extends JsonRpcParams, Result extends Json, ->(): JsonRpcMiddleware { +>(): JsonRpcMiddleware { return jest.fn((req, res, _next, end) => { if (res.id === undefined) { res.id = req.id; @@ -63,7 +55,7 @@ export function buildFinalMiddlewareWithDefaultResponse< } if (res.result === undefined) { - res.result = 'default response'; + res.result = 'default result'; } end(); @@ -132,38 +124,34 @@ export function buildMockParamsWithoutBlockParamAt( } /** - * Builds a canned response for a `eth_blockNumber` request made to - * `provider.sendAsync` such that the response will return the given block + * Builds a canned result for a `eth_blockNumber` request made to + * `provider.request` such that the result will return the given block * number. Intended to be used in conjunction with `stubProviderRequests`. * * @param blockNumber - The block number (default: '0x0'). - * @returns The request/response pair. + * @returns The request/result pair. */ export function buildStubForBlockNumberRequest( blockNumber = '0x0', -): ProviderRequestStub { +): ProviderRequestStub { return { request: { method: 'eth_blockNumber', params: [], }, - response: (req) => ({ - id: req.id, - jsonrpc: '2.0', - result: blockNumber, - }), + result: async () => blockNumber, }; } /** - * Builds a canned response for a request made to `provider.sendAsync`. Intended + * Builds a canned result for a request made to `provider.request`. Intended * to be used in conjunction with `stubProviderRequests`. Although not strictly - * necessary, it helps to assign a proper type to a request/response pair. + * necessary, it helps to assign a proper type to a request/result pair. * * @template Params - The type that represents the request params. - * @template Result - The type that represents the response result. - * @param requestStub - The request/response pair. - * @returns The request/response pair, properly typed. + * @template Result - The type that represents the result. + * @param requestStub - The request/result pair. + * @returns The request/result pair, properly typed. */ export function buildStubForGenericRequest< Params extends JsonRpcParams, @@ -173,48 +161,48 @@ export function buildStubForGenericRequest< } /** - * Asserts that `provider.sendAsync` has not been called with the given request + * Asserts that `provider.request` has not been called with the given request * object (or an object that can matched to that request). * - * @param sendAsyncSpy - The Jest spy object that represents - * `provider.sendAsync`. + * @param requestSpy - The Jest spy object that represents + * `provider.request`. * @param requestMatcher - An object that can be matched to a request passed to - * `provider.sendAsync`. + * `provider.request`. */ export function expectProviderRequestNotToHaveBeenMade( - sendAsyncSpy: jest.SpyInstance, + requestSpy: jest.SpyInstance, requestMatcher: Partial, ) { expect( - sendAsyncSpy.mock.calls.some((args) => + requestSpy.mock.calls.some((args) => requestMatches(requestMatcher, args[0]), ), ).toBe(false); } /** - * Provides a way to assign specific responses to specific requests that are - * made through a provider. When `provider.sendAsync` is called, a stub matching + * Provides a way to assign specific results to specific requests that are + * made through a provider. When `provider.request` is called, a stub matching * the request will be looked for; if one is found, it is used and then * discarded, unless `remainAfterUse` is set for the stub. * * @param provider - The provider. * @param stubs - A series of pairs, where each pair specifies a request object - * — or part of one, at least — and a response for that request. The response - * is actually a function that takes two arguments: the *real* request and the - * number of times that that request has been made (counting the first as 1). - * This latter argument be used to specify different responses for different - * instances of the same request. The function should return a response object. - * @returns The Jest spy object that represents `provider.sendAsync` (so that + * — or part of one, at least — and a result for that request. The result + * is actually a function that takes one argument, which is the number of times + * that request has been made (counting the first as 1). + * This latter argument be used to specify different results for different + * instances of the same request. The function should return a result. + * @returns The Jest spy object that represents `provider.request` (so that * you can make assertions on the method later, if you like). */ export function stubProviderRequests( provider: SafeEventEmitterProvider, - stubs: ProviderRequestStub[], + stubs: ProviderRequestStub[], ) { const remainingStubs = klona(stubs); const callNumbersByRequest = new Map, number>(); - return jest.spyOn(provider, 'sendAsync').mockImplementation((request, cb) => { + return jest.spyOn(provider, 'request').mockImplementation(async (request) => { const stubIndex = remainingStubs.findIndex((stub) => requestMatches(stub.request, request), ); @@ -225,22 +213,22 @@ export function stubProviderRequests( const stub = remainingStubs[stubIndex]; const callNumber = callNumbersByRequest.get(stub.request) ?? 1; - cb(undefined, stub.response(request, callNumber)); - callNumbersByRequest.set(stub.request, callNumber + 1); if (!stub.remainAfterUse) { remainingStubs.splice(stubIndex, 1); } + + return await stub.result(callNumber); } }); } /** - * When using `stubProviderRequests` to list canned responses for specific - * requests that are made to `provider.sendAsync`, you don't need to provide the - * full request object to go along with the response, but only part of that - * request object. When `provider.sendAsync` is then called, we can look up the + * When using `stubProviderRequests` to list canned results for specific + * requests that are made to `provider.request`, you don't need to provide the + * full request object to go along with the result, but only part of that + * request object. When `provider.request` is then called, we can look up the * compare the real request object to the request object that was specified to * find a match. This function is used to do that comparison (and other * like comparisons). @@ -252,7 +240,7 @@ export function stubProviderRequests( */ export function requestMatches( requestMatcher: Partial, - request: JsonRpcRequest, + request: Partial, ): boolean { return (Object.keys(requestMatcher) as (keyof typeof requestMatcher)[]).every( (key) => isDeepStrictEqual(requestMatcher[key], request[key]), diff --git a/yarn.lock b/yarn.lock index 4563c8c2..a8c5904c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -928,16 +928,16 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-block-tracker@npm:^10.0.0": - version: 10.0.0 - resolution: "@metamask/eth-block-tracker@npm:10.0.0" +"@metamask/eth-block-tracker@npm:^11.0.0": + version: 11.0.0 + resolution: "@metamask/eth-block-tracker@npm:11.0.0" dependencies: - "@metamask/eth-json-rpc-provider": ^4.0.0 + "@metamask/eth-json-rpc-provider": ^4.1.0 "@metamask/safe-event-emitter": ^3.0.0 "@metamask/utils": ^8.1.0 json-rpc-random-id: ^1.0.1 pify: ^5.0.0 - checksum: 3b897a41305debe9828d6d18e079289f05e07f99d829f7425ce8703b16d00f3fcd1f108b34f946dee892400e30f4fe87d8fad66311ba8b46c39258ad875f83a6 + checksum: 27a2622cda97626c80119629108f739aa745b0c704649e00f6841aac10410f23c00ab7feb272539ccf209f092648b1538aa87c1247185ced01c2993b68fb8db5 languageName: node linkType: hard @@ -952,8 +952,8 @@ __metadata: "@metamask/eslint-config-jest": ^12.1.0 "@metamask/eslint-config-nodejs": ^12.1.0 "@metamask/eslint-config-typescript": ^12.1.0 - "@metamask/eth-block-tracker": ^10.0.0 - "@metamask/eth-json-rpc-provider": ^4.0.0 + "@metamask/eth-block-tracker": ^11.0.0 + "@metamask/eth-json-rpc-provider": ^4.1.0 "@metamask/eth-sig-util": ^7.0.0 "@metamask/json-rpc-engine": ^9.0.0 "@metamask/rpc-errors": ^6.0.0 @@ -987,14 +987,16 @@ __metadata: languageName: unknown linkType: soft -"@metamask/eth-json-rpc-provider@npm:^4.0.0": - version: 4.0.0 - resolution: "@metamask/eth-json-rpc-provider@npm:4.0.0" +"@metamask/eth-json-rpc-provider@npm:^4.1.0": + version: 4.1.0 + resolution: "@metamask/eth-json-rpc-provider@npm:4.1.0" dependencies: "@metamask/json-rpc-engine": ^9.0.0 + "@metamask/rpc-errors": ^6.2.1 "@metamask/safe-event-emitter": ^3.0.0 "@metamask/utils": ^8.3.0 - checksum: 4f8ad6a1737d54aeb83c5a1c7073a5cb17223e9cdacb0da4549aac7b57704b8239d9670c438eadf7974fe1167e59a9c54e6c32cce44b111c6514aae71429d6dd + uuid: ^8.3.2 + checksum: c9669c93df073423d36ff941b512247b569e7f7c56cc6110565bc8dc6590ad691a78d6d17eea6243721c1c464f0f008ea1326fc7373f90fb705fba5fb85d804d languageName: node linkType: hard @@ -6904,6 +6906,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df + languageName: node + linkType: hard + "uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1"