Skip to content
This repository has been archived by the owner on Mar 5, 2025. It is now read-only.

Commit

Permalink
Test with injected external providers (#5652)
Browse files Browse the repository at this point in the history
* fix: sending tx with injected provider (#5651)

Co-authored-by: Marin Petrunic <[email protected]>

* adding a test for using `ganache` provider

* enable the jsonrpc `id` to optionally be incremented starting from a number
 (Inspired by: #5373 (comment) and needed as a work around for blockchainsllc/in3#46)

* test with `in3` as a provider & skip `in3` test if it takes too long

* increase integration test timeout at web3 package

* add a test for using `hardhat` provider

* improve how legacy providers, such as hardhat, is used

* implement `isPromise` that works in the browsers

* refactor external providers tests

* update CHANGELOG.md
  • Loading branch information
Muhammad-Altabba authored Dec 6, 2022
1 parent 30bffb6 commit 21b7649
Show file tree
Hide file tree
Showing 23 changed files with 1,407 additions and 127 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ tsconfig.tsbuildinfo
package-lock.json

tmp/

# Incubed (in3) nodelist
packages/web3/.in3/
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,8 @@ should use 4.0.1-alpha.0 for testing.
#### web3-utils

- Export a new function `uuidV4` that generates a random v4 Uuid (#5373).
- Enable passing a starting number, to increment based on it, for the Json Rpc Request `id` (#5652).
- Export a new function `isPromise` that checks if an object is a promise (#5652).

#### web3-eth-contract

Expand Down
3 changes: 1 addition & 2 deletions packages/web3-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ export const isEIP1193Provider = <API extends Web3APISpec>(
): provider is EIP1193Provider<API> =>
typeof provider !== 'string' &&
'request' in provider &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(provider.request as any)[Symbol.toStringTag] === 'AsyncFunction';
provider.request.constructor.name === 'AsyncFunction';

export const isLegacySendProvider = <API extends Web3APISpec>(
provider: SupportedProviders<API>,
Expand Down
64 changes: 43 additions & 21 deletions packages/web3-core/src/web3_request_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import { Socket } from 'net';

import {
ContractExecutionError,
InvalidResponseError,
Expand All @@ -42,7 +43,7 @@ import {
Web3BaseProvider,
Web3BaseProviderConstructor,
} from 'web3-types';
import { isNullish, jsonRpc } from 'web3-utils';
import { isNullish, isPromise, jsonRpc } from 'web3-utils';
import {
isEIP1193Provider,
isLegacyRequestProvider,
Expand Down Expand Up @@ -200,34 +201,55 @@ export class Web3RequestManager<
);
}

// TODO: This should be deprecated and removed.
// TODO: This could be deprecated and removed.
if (isLegacyRequestProvider(provider)) {
return new Promise<JsonRpcResponse<ResponseType>>((resolve, reject): void => {
provider.request<ResponseType>(payload, (err, response) => {
if (err) {
return reject(
this._processJsonRpcResponse(
payload,
err as unknown as JsonRpcResponse<ResponseType>,
{
legacy: true,
error: true,
},
),
);
}

return resolve(
return new Promise<JsonRpcResponse<ResponseType>>((resolve, reject) => {
const rejectWithError = (err: unknown) =>
reject(
this._processJsonRpcResponse(
payload,
err as JsonRpcResponse<ResponseType>,
{
legacy: true,
error: true,
},
),
);
const resolveWithResponse = (response: JsonRpcResponse<ResponseType>) =>
resolve(
this._processJsonRpcResponse(payload, response, {
legacy: true,
error: false,
}),
);
});
const result = provider.request<ResponseType>(
payload,
// a callback that is expected to be called after getting the response:
(err, response) => {
if (err) {
return rejectWithError(err);
}

return resolveWithResponse(response);
},
);
// Some providers, that follow a previous drafted version of EIP1193, has a `request` function
// that is not defined as `async`, but it returns a promise.
// Such providers would not be picked with if(isEIP1193Provider(provider)) above
// because the `request` function was not defined with `async` and so the function definition is not `AsyncFunction`.
// Like this provider: https://github.dev/NomicFoundation/hardhat/blob/62bea2600785595ba36f2105564076cf5cdf0fd8/packages/hardhat-core/src/internal/core/providers/backwards-compatibility.ts#L19
// So check if the returned result is a Promise, and resolve with it accordingly.
// Note: in this case we expect the callback provided above to never be called.
if (isPromise(result)) {
const responsePromise = result as unknown as Promise<
JsonRpcResponse<ResponseType>
>;
responsePromise.then(resolveWithResponse).catch(rejectWithError);
}
});
}

// TODO: This should be deprecated and removed.
// TODO: This could be deprecated and removed.
if (isLegacySendProvider(provider)) {
return new Promise<JsonRpcResponse<ResponseType>>((resolve, reject): void => {
provider.send<ResponseType>(payload, (err, response) => {
Expand Down Expand Up @@ -261,7 +283,7 @@ export class Web3RequestManager<
});
}

// TODO: This should be deprecated and removed.
// TODO: This could be deprecated and removed.
if (isLegacySendAsyncProvider(provider)) {
return provider
.sendAsync<ResponseType>(payload)
Expand Down
4 changes: 2 additions & 2 deletions packages/web3-eth/src/utils/reject_if_block_timeout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,12 @@ export async function rejectIfBlockTimeout(
web3Context: Web3Context<EthExecutionAPI>,
transactionHash?: Bytes,
): Promise<[Promise<never>, ResourceCleaner]> {
const provider: Web3BaseProvider = web3Context.requestManager.provider as Web3BaseProvider;
const { provider } = web3Context.requestManager;
let callingRes: [Promise<never>, ResourceCleaner];
const starterBlockNumber = await getBlockNumber(web3Context, NUMBER_DATA_FORMAT);
// TODO: once https://github.com/web3/web3.js/issues/5521 is implemented, remove checking for `enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout`
if (
provider.supportsSubscriptions() &&
(provider as Web3BaseProvider).supportsSubscriptions?.() &&
web3Context.enableExperimentalFeatures.useSubscriptionWhenCheckingBlockTimeout
) {
callingRes = await resolveBySubscription(web3Context, starterBlockNumber, transactionHash);
Expand Down
8 changes: 3 additions & 5 deletions packages/web3-eth/test/integration/eth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import HttpProvider from 'web3-providers-http';
// eslint-disable-next-line import/no-extraneous-dependencies
import { Contract } from 'web3-eth-contract';
// eslint-disable-next-line import/no-extraneous-dependencies
import { SupportedProviders, Web3EthExecutionAPI } from 'web3-types';
import { SupportedProviders } from 'web3-types';
// eslint-disable-next-line import/no-extraneous-dependencies
import IpcProvider from 'web3-providers-ipc';
import { Web3Eth } from '../../src';
Expand Down Expand Up @@ -68,12 +68,10 @@ describe('eth', () => {

const deoloyedContract = await contract.deploy(deployOptions).send(sendOptions);
const { provider } = web3Eth;
web3Eth.setProvider(
deoloyedContract.provider as SupportedProviders<Web3EthExecutionAPI>,
);
web3Eth.setProvider(deoloyedContract.provider as SupportedProviders);

expect(web3Eth.provider).toBe(deoloyedContract.provider);
web3Eth.setProvider(provider as SupportedProviders<Web3EthExecutionAPI>);
web3Eth.setProvider(provider as SupportedProviders);
});
it('providers', () => {
const res = web3Eth.providers;
Expand Down
3 changes: 2 additions & 1 deletion packages/web3-types/src/web3_base_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
JsonRpcSubscriptionResult,
} from './json_rpc_types';
import { Web3APISpec, Web3APIMethod, Web3APIReturnType, Web3APIPayload } from './web3_api_types';
import { Web3EthExecutionAPI } from './apis/web3_eth_execution_api';

const symbol = Symbol.for('web3/base-provider');

Expand Down Expand Up @@ -166,7 +167,7 @@ export abstract class Web3BaseProvider<API extends Web3APISpec = EthExecutionAPI
public abstract reset(): void;
}

export type SupportedProviders<API extends Web3APISpec> =
export type SupportedProviders<API extends Web3APISpec = Web3EthExecutionAPI> =
| EIP1193Provider<API>
| Web3BaseProvider<API>
| LegacyRequestProvider
Expand Down
2 changes: 2 additions & 0 deletions packages/web3-utils/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Export a new function `uuidV4` that generates a random v4 Uuid (#5373).
- Enable passing a starting number, to increment based on it, for the Json Rpc Request `id` (#5652).
- Export a new function `isPromise` that checks if an object is a promise (#5652).

### Fixed

Expand Down
32 changes: 26 additions & 6 deletions packages/web3-utils/src/json_rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,34 @@ export const isBatchResponse = <Result = unknown, Error = unknown>(
): response is JsonRpcBatchResponse<Result, Error> =>
Array.isArray(response) && response.length > 1 && isValidResponse(response);

// internal optional variable to increment and use for the jsonrpc `id`
let requestIdSeed: number | undefined;

/**
* Optionally use to make the jsonrpc `id` start from a specific number.
* Without calling this function, the `id` will be filled with a Uuid.
* But after this being called with a number, the `id` will be a number staring from the provided `start` variable.
* However, if `undefined` was passed to this function, the `id` will be a Uuid again.
* @param start - a number to start incrementing from.
* Or `undefined` to use a new Uuid (this is the default behavior)
*/
export const setRequestIdStart = (start: number | undefined) => {
requestIdSeed = start;
};

export const toPayload = <ParamType = unknown[]>(
request: JsonRpcOptionalRequest<ParamType>,
): JsonRpcPayload<ParamType> => ({
jsonrpc: request.jsonrpc ?? '2.0',
id: request.id ?? uuidV4(),
method: request.method,
params: request.params ?? undefined,
});
): JsonRpcPayload<ParamType> => {
if (typeof requestIdSeed !== 'undefined') {
requestIdSeed += 1;
}
return {
jsonrpc: request.jsonrpc ?? '2.0',
id: request.id ?? requestIdSeed ?? uuidV4(),
method: request.method,
params: request.params ?? undefined,
};
};

export const toBatchPayload = (requests: JsonRpcOptionalRequest<unknown>[]): JsonRpcBatchRequest =>
requests.map(request => toPayload<unknown>(request)) as JsonRpcBatchRequest;
Expand Down
13 changes: 13 additions & 0 deletions packages/web3-utils/src/promise_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.

import { isNullish } from 'web3-validator';

/**
* An alternative to the node function `isPromise` that exists in `util/types` because it is not available on the browser.
* @param object to check if it is a `Promise`
* @returns `true` if it is an `object` or a `function` that has a `then` function. And returns `false` otherwise.
*/
export function isPromise(object: unknown): boolean {
return (
(typeof object === 'object' || typeof object === 'function') &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(typeof (object as any).then as unknown) === 'function'
);
}

export type AsyncFunction<T, K = unknown> = (...args: K[]) => Promise<T>;

export function waitWithTimeout<T>(
Expand Down
1 change: 1 addition & 0 deletions packages/web3/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dist
hardhat.config.js
jest.config.js
webpack.config.js
.eslintrc.js
3 changes: 3 additions & 0 deletions packages/web3/hardhat.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// A dummy file to prevent `hardhat` from throwing
// "HardhatError: HH1: You are not inside a Hardhat project."
// Note: Hardhat is just used inside the tests to ensure its compatibility.
3 changes: 3 additions & 0 deletions packages/web3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
"eslint-config-prettier": "^8.5.0",
"eslint-config-web3-base": "0.1.0",
"eslint-plugin-import": "^2.26.0",
"ganache": "^7.5.0",
"hardhat": "^2.12.2",
"in3": "^3.3.3",
"jest": "^28.1.3",
"jest-extended": "^3.0.1",
"prettier": "^2.7.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import Web3 from '../../../src/index';

describe('compatibility with extremely simple external provider', () => {
it('should accept a simple instance that is compatible with EIP1193', () => {
interface RequestArguments {
readonly method: string;
readonly params?: readonly unknown[] | object;
}

class Provider {
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
public async request(_: RequestArguments): Promise<unknown> {
return undefined as unknown;
}
}

const testProvider = new Provider();
const { provider } = new Web3(testProvider);
expect(provider).toBeDefined();
});
});
35 changes: 35 additions & 0 deletions packages/web3/test/integration/external-providers/ganache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

// eslint-disable-next-line import/no-extraneous-dependencies
import ganache from 'ganache';

import { performBasicRpcCalls } from './helper';

import { getSystemTestMnemonic } from '../../shared_fixtures/system_tests_utils';

describe('compatibility with `ganache` provider', () => {
it('should initialize Web3, get accounts & block number and send a transaction', async () => {
const { provider } = ganache.server({
wallet: {
mnemonic: getSystemTestMnemonic(),
},
});

await performBasicRpcCalls(provider);
});
});
28 changes: 28 additions & 0 deletions packages/web3/test/integration/external-providers/hardhat.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

// eslint-disable-next-line import/no-extraneous-dependencies
import hardhat from 'hardhat';

import { performBasicRpcCalls } from './helper';

describe('compatibility with `hardhat` provider', () => {
it('should initialize Web3, get accounts & block number and send a transaction', async () => {
// use the hardhat provider for web3.js
await performBasicRpcCalls(hardhat.network.provider);
});
});
Loading

0 comments on commit 21b7649

Please sign in to comment.