diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index 6d49d071..bcab1c3a 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -24,7 +24,7 @@ jobs: - prepare strategy: matrix: - node-version: [18.x, 20.x, 22.5.1] + node-version: [18.x, 20.x, 22.x] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} @@ -49,7 +49,7 @@ jobs: - prepare strategy: matrix: - node-version: [18.x, 20.x, 22.5.1] + node-version: [18.x, 20.x, 22.x] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} @@ -80,7 +80,7 @@ jobs: - prepare strategy: matrix: - node-version: [18.x, 20.x, 22.5.1] + node-version: [18.x, 20.x, 22.x] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} diff --git a/package.json b/package.json index 319d1509..74dc9c25 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,12 @@ "test:watch": "jest --watch" }, "dependencies": { - "@metamask/eth-block-tracker": "^10.0.0", - "@metamask/eth-json-rpc-provider": "^4.0.0", - "@metamask/eth-sig-util": "^7.0.0", - "@metamask/json-rpc-engine": "^9.0.0", - "@metamask/rpc-errors": "^6.0.0", - "@metamask/utils": "^8.1.0", + "@metamask/eth-block-tracker": "^11.0.1", + "@metamask/eth-json-rpc-provider": "^4.1.1", + "@metamask/eth-sig-util": "^7.0.3", + "@metamask/json-rpc-engine": "^9.0.2", + "@metamask/rpc-errors": "^6.3.1", + "@metamask/utils": "^9.1.0", "@types/bn.js": "^5.1.5", "bn.js": "^5.2.1", "klona": "^2.0.6", 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..d7c3bf4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -853,13 +853,13 @@ __metadata: languageName: node linkType: hard -"@metamask/abi-utils@npm:^2.0.2": - version: 2.0.2 - resolution: "@metamask/abi-utils@npm:2.0.2" +"@metamask/abi-utils@npm:^2.0.4": + version: 2.0.4 + resolution: "@metamask/abi-utils@npm:2.0.4" dependencies: - "@metamask/utils": ^8.0.0 - superstruct: ^1.0.3 - checksum: 5ec153e7691a4e1dc8738a0ba1a99a354ddb13851fa88a40a19f002f6308310e71c2cee28c3a25d9f7f67e839c7dffe4760e93e308dd17fa725b08d0dc73a3d4 + "@metamask/superstruct": ^3.1.0 + "@metamask/utils": ^9.0.0 + checksum: 85b15419248ddec1ab59ec5f3e41276f7509dadd9ced871658fa3cc04805ad35ace96986416aaecd24e3630e92b0ed078328966c92383ffa9b1cc3f0f357ad6c languageName: node linkType: hard @@ -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.1": + version: 11.0.1 + resolution: "@metamask/eth-block-tracker@npm:11.0.1" dependencies: - "@metamask/eth-json-rpc-provider": ^4.0.0 - "@metamask/safe-event-emitter": ^3.0.0 - "@metamask/utils": ^8.1.0 + "@metamask/eth-json-rpc-provider": ^4.1.1 + "@metamask/safe-event-emitter": ^3.1.1 + "@metamask/utils": ^9.1.0 json-rpc-random-id: ^1.0.1 pify: ^5.0.0 - checksum: 3b897a41305debe9828d6d18e079289f05e07f99d829f7425ce8703b16d00f3fcd1f108b34f946dee892400e30f4fe87d8fad66311ba8b46c39258ad875f83a6 + checksum: 74c1259f7c2ee3074d5d5dc337fb81a89f562c755756210cd3eccc7fb9674c9101d58d88d6ac4c1a48a5676f85b99b87773819b26c52f41eae8e6ced454e11f9 languageName: node linkType: hard @@ -952,12 +952,12 @@ __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-sig-util": ^7.0.0 - "@metamask/json-rpc-engine": ^9.0.0 - "@metamask/rpc-errors": ^6.0.0 - "@metamask/utils": ^8.1.0 + "@metamask/eth-block-tracker": ^11.0.1 + "@metamask/eth-json-rpc-provider": ^4.1.1 + "@metamask/eth-sig-util": ^7.0.3 + "@metamask/json-rpc-engine": ^9.0.2 + "@metamask/rpc-errors": ^6.3.1 + "@metamask/utils": ^9.1.0 "@types/bn.js": ^5.1.5 "@types/btoa": ^1.2.3 "@types/jest": ^27.4.1 @@ -987,73 +987,82 @@ __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.1": + version: 4.1.1 + resolution: "@metamask/eth-json-rpc-provider@npm:4.1.1" dependencies: - "@metamask/json-rpc-engine": ^9.0.0 + "@metamask/json-rpc-engine": ^9.0.1 + "@metamask/rpc-errors": ^6.3.1 "@metamask/safe-event-emitter": ^3.0.0 - "@metamask/utils": ^8.3.0 - checksum: 4f8ad6a1737d54aeb83c5a1c7073a5cb17223e9cdacb0da4549aac7b57704b8239d9670c438eadf7974fe1167e59a9c54e6c32cce44b111c6514aae71429d6dd + "@metamask/utils": ^9.0.0 + uuid: ^8.3.2 + checksum: a429d9511f33a62eb5f2f2f82f26d30a2a8f27d48aa3c8819d6ba03c2c0eef0e7eee7894a0899dd9af4b7d7f3e2092c25304bb34ab6e8d0daffee717f4109b5c languageName: node linkType: hard -"@metamask/eth-sig-util@npm:^7.0.0": - version: 7.0.1 - resolution: "@metamask/eth-sig-util@npm:7.0.1" +"@metamask/eth-sig-util@npm:^7.0.3": + version: 7.0.3 + resolution: "@metamask/eth-sig-util@npm:7.0.3" dependencies: "@ethereumjs/util": ^8.1.0 - "@metamask/abi-utils": ^2.0.2 - "@metamask/utils": ^8.1.0 + "@metamask/abi-utils": ^2.0.4 + "@metamask/utils": ^9.0.0 + "@scure/base": ~1.1.3 ethereum-cryptography: ^2.1.2 tweetnacl: ^1.0.3 - tweetnacl-util: ^0.15.1 - checksum: 98d056bd83aeb2d29ec3de09cd18e67d97ea295a59d405a9ce3fe274badd2d4f18da1fe530a266b4c777650855ed75ecd3577decd607a561e938dd7a808c5839 + checksum: fd4d0710857525815b241ddecce64988dd12303a9638577429baf180c62cf9cef9403aed01bc046b4860b332d455604c84e4b2a9b5997db16f444125b4b39398 languageName: node linkType: hard -"@metamask/json-rpc-engine@npm:^9.0.0": - version: 9.0.0 - resolution: "@metamask/json-rpc-engine@npm:9.0.0" +"@metamask/json-rpc-engine@npm:^9.0.1, @metamask/json-rpc-engine@npm:^9.0.2": + version: 9.0.2 + resolution: "@metamask/json-rpc-engine@npm:9.0.2" dependencies: - "@metamask/rpc-errors": ^6.2.1 + "@metamask/rpc-errors": ^6.3.1 "@metamask/safe-event-emitter": ^3.0.0 - "@metamask/utils": ^8.3.0 - checksum: b97170b36843145361015dabc5651df1d2c7f28f0756d3c9c05aef6a483098d562a9983cbe0e15f7fd1a66aa26481132b03ccb9061a2c48f0d3249c1f2348e97 + "@metamask/utils": ^9.1.0 + checksum: 4c852c9f30d05706ee497a2aca3ef6df12aabcff4a71a7426a27d95829f20cf2ff45c774eb9d95224bf16c9555a8cd7e44dccaea1bd44eda4dc43bf298885272 languageName: node linkType: hard -"@metamask/rpc-errors@npm:^6.0.0, @metamask/rpc-errors@npm:^6.2.1": - version: 6.2.1 - resolution: "@metamask/rpc-errors@npm:6.2.1" +"@metamask/rpc-errors@npm:^6.3.1": + version: 6.3.1 + resolution: "@metamask/rpc-errors@npm:6.3.1" dependencies: - "@metamask/utils": ^8.3.0 + "@metamask/utils": ^9.0.0 fast-safe-stringify: ^2.0.6 - checksum: a9223c3cb9ab05734ea0dda990597f90a7cdb143efa0c026b1a970f2094fe5fa3c341ed39b1e7623be13a96b98fb2c697ef51a2e2b87d8f048114841d35ee0a9 + checksum: 8761f5c0161cb3b342abd3ccccbd7b792f36a987e1f22c3f89b1bd29f72a2e35a2c91b58164fdd9dc3e5b67157500dcbdb5d04245117c14310c34cf42f7b8463 languageName: node linkType: hard -"@metamask/safe-event-emitter@npm:^3.0.0": - version: 3.0.0 - resolution: "@metamask/safe-event-emitter@npm:3.0.0" - checksum: 8dc58a76f9f75bf2405931465fc311c68043d851e6b8ebe9f82ae339073a08a83430dba9338f8e3adc4bfc8067607125074bcafa32baee3a5157f42343dc89e5 +"@metamask/safe-event-emitter@npm:^3.0.0, @metamask/safe-event-emitter@npm:^3.1.1": + version: 3.1.1 + resolution: "@metamask/safe-event-emitter@npm:3.1.1" + checksum: e24db4d7c20764bfc5b025065f92518c805f0ffb1da4820078b8cff7dcae964c0f354cf053fcb7ac659de015d5ffdf21aae5e8d44e191ee8faa9066855f22653 + languageName: node + linkType: hard + +"@metamask/superstruct@npm:^3.1.0": + version: 3.1.0 + resolution: "@metamask/superstruct@npm:3.1.0" + checksum: 00e4d0c0aae8b25ccc1885c1db0bb4ed1590010570140c255e4deee3bf8a10c859c8fce5e475b4ae09c8a56316207af87585b91f7f5a5c028d668ccd111f19e3 languageName: node linkType: hard -"@metamask/utils@npm:^8.0.0, @metamask/utils@npm:^8.1.0, @metamask/utils@npm:^8.3.0": - version: 8.4.0 - resolution: "@metamask/utils@npm:8.4.0" +"@metamask/utils@npm:^9.0.0, @metamask/utils@npm:^9.1.0": + version: 9.1.0 + resolution: "@metamask/utils@npm:9.1.0" dependencies: "@ethereumjs/tx": ^4.2.0 + "@metamask/superstruct": ^3.1.0 "@noble/hashes": ^1.3.1 "@scure/base": ^1.1.3 "@types/debug": ^4.1.7 debug: ^4.3.4 pony-cause: ^2.1.10 semver: ^7.5.4 - superstruct: ^1.0.3 uuid: ^9.0.1 - checksum: b0397e97bac7192f6189a8625a2dfcb56d3c2cf4dd2cb3d4e012a7e9786f04f59f6917805544bc131a6dacd2c8344e237ae43ad47429bb5eb35c6cf1248440b4 + checksum: 01f2c71a8f06158d5335bfe96bfd2f3aa39ec6b2323c5d0ff1d3136071a3e8ff7c1804d640ba1d4e07f96f3e68a95ff7729ddfcd34b373e5fefd86d6ef12d034 languageName: node linkType: hard @@ -1216,10 +1225,10 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:^1.1.3, @scure/base@npm:~1.1.0": - version: 1.1.3 - resolution: "@scure/base@npm:1.1.3" - checksum: 1606ab8a4db898cb3a1ada16c15437c3bce4e25854fadc8eb03ae93cbbbac1ed90655af4b0be3da37e12056fef11c0374499f69b9e658c9e5b7b3e06353c630c +"@scure/base@npm:^1.1.3, @scure/base@npm:~1.1.0, @scure/base@npm:~1.1.3": + version: 1.1.7 + resolution: "@scure/base@npm:1.1.7" + checksum: d9084be9a2f27971df1684af9e40bb750e86f549345e1bb3227fb61673c0c83569c92c1cb0a4ddccb32650b39d3cd3c145603b926ba751c9bc60c27317549b20 languageName: node linkType: hard @@ -6421,13 +6430,6 @@ __metadata: languageName: node linkType: hard -"superstruct@npm:^1.0.3": - version: 1.0.3 - resolution: "superstruct@npm:1.0.3" - checksum: 761790bb111e6e21ddd608299c252f3be35df543263a7ebbc004e840d01fcf8046794c274bcb351bdf3eae4600f79d317d085cdbb19ca05803a4361840cc9bb1 - languageName: node - linkType: hard - "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -6673,13 +6675,6 @@ __metadata: languageName: node linkType: hard -"tweetnacl-util@npm:^0.15.1": - version: 0.15.1 - resolution: "tweetnacl-util@npm:0.15.1" - checksum: ae6aa8a52cdd21a95103a4cc10657d6a2040b36c7a6da7b9d3ab811c6750a2d5db77e8c36969e75fdee11f511aa2b91c552496c6e8e989b6e490e54aca2864fc - languageName: node - linkType: hard - "tweetnacl@npm:^1.0.3": version: 1.0.3 resolution: "tweetnacl@npm:1.0.3" @@ -6904,6 +6899,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"