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

Test with injected external providers #5652

Merged
merged 18 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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).

### Fixed

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();
});
});
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);
});
});
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