diff --git a/packages/web3-core/src/web3_context.ts b/packages/web3-core/src/web3_context.ts index 03dd95e319e..7b0b60b8ae4 100644 --- a/packages/web3-core/src/web3_context.ts +++ b/packages/web3-core/src/web3_context.ts @@ -22,6 +22,7 @@ import { Web3AccountProvider, SupportedProviders, HexString, + EthExecutionAPI, } from 'web3-types'; import { isNullish } from 'web3-utils'; import { ExistingPluginNamespaceError } from 'web3-errors'; @@ -36,8 +37,7 @@ import { Web3BatchRequest } from './web3_batch_request'; // To avoid circular dependencies, we need to export type from here. export type Web3ContextObject< - // eslint-disable-next-line @typescript-eslint/no-explicit-any - API extends Web3APISpec = any, + API extends Web3APISpec = unknown, RegisteredSubs extends { [key: string]: Web3SubscriptionConstructor; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -54,8 +54,7 @@ export type Web3ContextObject< }; export type Web3ContextInitOptions< - // eslint-disable-next-line @typescript-eslint/no-explicit-any - API extends Web3APISpec = any, + API extends Web3APISpec = unknown, RegisteredSubs extends { [key: string]: Web3SubscriptionConstructor; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -70,23 +69,22 @@ export type Web3ContextInitOptions< wallet?: Web3BaseWallet; }; -export type Web3ContextConstructor< - // eslint-disable-next-line no-use-before-define, @typescript-eslint/no-explicit-any - T extends Web3Context, - T2 extends unknown[], -> = new (...args: [...extras: T2, context: Web3ContextObject]) => T; +// eslint-disable-next-line no-use-before-define +export type Web3ContextConstructor = new ( + ...args: [...extras: T2, context: Web3ContextObject] +) => T; // To avoid circular dependencies, we need to export type from here. export type Web3ContextFactory< - // eslint-disable-next-line no-use-before-define, @typescript-eslint/no-explicit-any - T extends Web3Context, + // eslint-disable-next-line no-use-before-define + T extends Web3Context, T2 extends unknown[], > = Web3ContextConstructor & { fromContextObject(this: Web3ContextConstructor, contextObject: Web3ContextObject): T; }; export class Web3Context< - API extends Web3APISpec, + API extends Web3APISpec = unknown, RegisteredSubs extends { [key: string]: Web3SubscriptionConstructor; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -174,7 +172,7 @@ export class Web3Context< } // eslint-disable-next-line @typescript-eslint/no-explicit-any - public static fromContextObject, T3 extends unknown[]>( + public static fromContextObject( this: Web3ContextConstructor, ...args: [Web3ContextObject, ...T3] ) { @@ -200,8 +198,7 @@ export class Web3Context< * and link it to current context. This can be used to initiate a global context object * and then use it to create new objects of any type extended by `Web3Context`. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public use, T2 extends unknown[]>( + public use( ContextRef: Web3ContextConstructor, ...args: [...T2] ) { @@ -220,7 +217,7 @@ export class Web3Context< /** * Link current context to another context. */ - public link>(parentContext: T) { + public link(parentContext: T) { this.setConfig(parentContext.getConfig()); this._requestManager = parentContext.requestManager; this.provider = parentContext.provider; @@ -283,17 +280,52 @@ export class Web3Context< // To avoid cycle dependency declare this type in this file // TODO: When we have `web3-types` package we can share TransactionType -export type TransactionBuilder< - // eslint-disable-next-line @typescript-eslint/no-explicit-any - API extends Web3APISpec = any, -> = >(options: { +export type TransactionBuilder = < + ReturnType = Record, +>(options: { transaction: Record; web3Context: Web3Context; privateKey?: HexString | Buffer; }) => Promise; +/** + * Extend this class when creating a plugin that either doesn't require {@link EthExecutionAPI}, + * or interacts with a RPC node that doesn't fully implement {@link EthExecutionAPI}. + * + * To add type support for RPC methods to the {@link Web3RequestManager}, + * define a {@link Web3APISpec} and pass it as a generic to Web3PluginBase like so: + * + * ```ts + * type CustomRpcApi = { + * custom_rpc_method: () => string; + * custom_rpc_method_with_parameters: (parameter1: string, parameter2: number) => string; + * }; + * + * class CustomPlugin extends Web3PluginBase {...} + * ``` + */ export abstract class Web3PluginBase< API extends Web3APISpec = Web3APISpec, > extends Web3Context { public abstract pluginNamespace: string; } + +/** + * Extend this class when creating a plugin that makes use of {@link EthExecutionAPI}, + * or depends on other Web3 packages (such as `web3-eth-contract`) that depend on {@link EthExecutionAPI}. + * + * To add type support for RPC methods to the {@link Web3RequestManager} (in addition to {@link EthExecutionAPI}), + * define a {@link Web3APISpec} and pass it as a generic to Web3PluginBase like so: + * + * ```ts + * type CustomRpcApi = { + * custom_rpc_method: () => string; + * custom_rpc_method_with_parameters: (parameter1: string, parameter2: number) => string; + * }; + * + * class CustomPlugin extends Web3PluginBase {...} + * ``` + */ +export abstract class Web3EthPluginBase extends Web3PluginBase< + API & EthExecutionAPI +> {} diff --git a/packages/web3-eth-contract/test/fixtures/contracts/SampleStorageContract.sol b/packages/web3-eth-contract/test/fixtures/contracts/SampleStorageContract.sol new file mode 100644 index 00000000000..2c0c640135d --- /dev/null +++ b/packages/web3-eth-contract/test/fixtures/contracts/SampleStorageContract.sol @@ -0,0 +1,34 @@ +/* +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 . +*/ + +pragma solidity ^0.8.7; + +contract SampleStorageContract { + + uint256 uintNum; + + event NEWNUM(uint256 param); + + function storeNum(uint256 param) public { + uintNum = param; + emit NEWNUM(param); + } + + function retrieveNum() public view returns (uint256){ + return uintNum; + } +} \ No newline at end of file diff --git a/packages/web3-eth-contract/test/fixtures/storage.ts b/packages/web3-eth-contract/test/fixtures/storage.ts new file mode 100644 index 00000000000..5ec10ed2c37 --- /dev/null +++ b/packages/web3-eth-contract/test/fixtures/storage.ts @@ -0,0 +1,59 @@ +/* +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 . +*/ + +// sample storage contract ABI +export const sampleStorageContractABI = [ + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'param', + type: 'uint256', + }, + ], + name: 'NEWNUM', + type: 'event', + }, + { + inputs: [], + name: 'retrieveNum', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'param', + type: 'uint256', + }, + ], + name: 'storeNum', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +]; diff --git a/packages/web3-eth-contract/test/fixtures/unitTestFixtures.ts b/packages/web3-eth-contract/test/fixtures/unitTestFixtures.ts new file mode 100644 index 00000000000..1316e0f6e58 --- /dev/null +++ b/packages/web3-eth-contract/test/fixtures/unitTestFixtures.ts @@ -0,0 +1,142 @@ +/* +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 . +*/ + +export const getLogsData = { + request: { + fromBlock: 'earliest', + toBlock: 'latest', + topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'], + }, + + response: [ + { + logIndex: 1, + transactionIndex: 0, + transactionHash: '0xbe70733bcf87282c0ba9bf3c0e2d545084fad48bd571c314140c8dc1db882673', + blockHash: '0x78755c18c9a0a1283fa04b2f78c7794c249395b08f7f7dff304034d64d6a1607', + blockNumber: 25, + address: '0x2D029a4bd792d795f35e0583F64eD9DedeBBa849', + data: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000', + topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'], + type: 'mined', + id: 'log_886b29f0', + }, + ], +}; + +export const getPastEventsData = { + event: 'GREETING_CHANGED', + response: [ + { + logIndex: BigInt(1), + transactionIndex: BigInt(0), + transactionHash: '0xbe70733bcf87282c0ba9bf3c0e2d545084fad48bd571c314140c8dc1db882673', + blockHash: '0x78755c18c9a0a1283fa04b2f78c7794c249395b08f7f7dff304034d64d6a1607', + blockNumber: BigInt(25), + address: '0x2D029a4bd792d795f35e0583F64eD9DedeBBa849', + data: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000', + topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'], + returnValues: { + '0': 'Hello', + __length__: 1, + greeting: 'Hello', + }, + event: 'GREETING_CHANGED', + signature: '0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e', + raw: { + data: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000', + topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'], + }, + }, + ], +}; + +export const AllGetPastEventsData = { + getLogsData: [ + { + logIndex: 0, + transactionIndex: 0, + transactionHash: '0x1ba478ce1810bfa8a0725c0ca94f3cfe163a70c396037a1f3c94cad34e497959', + blockHash: '0x79eece1fb22b7109f302b65bd826b1cebf9f704642e86ae9086ed93baf44a45e', + blockNumber: 20, + address: '0x20bc23D0598b12c34cBDEf1fae439Ba8744DB426', + data: '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000', + topics: ['0x0d363f2fba46ab11b6db8da0125b0d5484787c44e265b48810735998bab12b75'], + type: 'mined', + id: 'log_0a03b06c', + }, + { + logIndex: 1, + transactionIndex: 0, + transactionHash: '0x1ba478ce1810bfa8a0725c0ca94f3cfe163a70c396037a1f3c94cad34e497959', + blockHash: '0x79eece1fb22b7109f302b65bd826b1cebf9f704642e86ae9086ed93baf44a45e', + blockNumber: 20, + address: '0x20bc23D0598b12c34cBDEf1fae439Ba8744DB426', + data: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000', + topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'], + type: 'mined', + id: 'log_389ae161', + }, + ], + + response: [ + { + logIndex: BigInt(0), + transactionIndex: BigInt(0), + transactionHash: '0x1ba478ce1810bfa8a0725c0ca94f3cfe163a70c396037a1f3c94cad34e497959', + blockHash: '0x79eece1fb22b7109f302b65bd826b1cebf9f704642e86ae9086ed93baf44a45e', + blockNumber: BigInt(20), + address: '0x20bc23D0598b12c34cBDEf1fae439Ba8744DB426', + data: '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000', + topics: ['0x0d363f2fba46ab11b6db8da0125b0d5484787c44e265b48810735998bab12b75'], + returnValues: { + '0': 'Hello', + '1': 'Another Greeting', + __length__: 2, + from: 'Hello', + to: 'Another Greeting', + }, + event: 'GREETING_CHANGING', + signature: '0x0d363f2fba46ab11b6db8da0125b0d5484787c44e265b48810735998bab12b75', + raw: { + data: '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000', + topics: ['0x0d363f2fba46ab11b6db8da0125b0d5484787c44e265b48810735998bab12b75'], + }, + }, + { + logIndex: BigInt(1), + transactionIndex: BigInt(0), + transactionHash: '0x1ba478ce1810bfa8a0725c0ca94f3cfe163a70c396037a1f3c94cad34e497959', + blockHash: '0x79eece1fb22b7109f302b65bd826b1cebf9f704642e86ae9086ed93baf44a45e', + blockNumber: BigInt(20), + address: '0x20bc23D0598b12c34cBDEf1fae439Ba8744DB426', + data: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000', + topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'], + returnValues: { + '0': 'Another Greeting', + __length__: 1, + greeting: 'Another Greeting', + }, + event: 'GREETING_CHANGED', + signature: '0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e', + raw: { + data: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000', + topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'], + }, + }, + ], +}; diff --git a/packages/web3-eth-contract/test/unit/contract.test.ts b/packages/web3-eth-contract/test/unit/contract.test.ts index 27feea9d8e9..54b7ca0ede4 100644 --- a/packages/web3-eth-contract/test/unit/contract.test.ts +++ b/packages/web3-eth-contract/test/unit/contract.test.ts @@ -15,7 +15,14 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ +import * as eth from 'web3-eth'; +import { ValidChains } from 'web3-types'; import { Contract } from '../../src'; +import { sampleStorageContractABI } from '../fixtures/storage'; +import { GreeterAbi, GreeterBytecode } from '../shared_fixtures/build/Greeter'; +import { AllGetPastEventsData, getLogsData, getPastEventsData } from '../fixtures/unitTestFixtures'; + +jest.mock('web3-eth'); describe('Contract', () => { describe('constructor', () => { @@ -66,4 +73,406 @@ describe('Contract', () => { expect(contract).toBeInstanceOf(Contract); }); }); + + describe('Contract functions and defaults', () => { + let sendOptions: Record; + const deployedAddr = '0x20bc23D0598b12c34cBDEf1fae439Ba8744DB426'; + + beforeAll(() => { + sendOptions = { + from: '0x12364916b10Ae90076dDa6dE756EE1395BB69ec2', + gas: '1000000', + }; + }); + + it('should deploy contract', async () => { + const data = + '0x60806040523480156200001157600080fd5b5060405162000a6a38038062000a6a8339818101604052810190620000379190620002a4565b80600090805190602001906200004f92919062000057565b505062000359565b828054620000659062000324565b90600052602060002090601f016020900481019282620000895760008555620000d5565b82601f10620000a457805160ff1916838001178555620000d5565b82800160010185558215620000d5579182015b82811115620000d4578251825591602001919060010190620000b7565b5b509050620000e49190620000e8565b5090565b5b8082111562000103576000816000905550600101620000e9565b5090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620001708262000125565b810181811067ffffffffffffffff8211171562000192576200019162000136565b5b80604052505050565b6000620001a762000107565b9050620001b5828262000165565b919050565b600067ffffffffffffffff821115620001d857620001d762000136565b5b620001e38262000125565b9050602081019050919050565b60005b8381101562000210578082015181840152602081019050620001f3565b8381111562000220576000848401525b50505050565b60006200023d6200023784620001ba565b6200019b565b9050828152602081018484840111156200025c576200025b62000120565b5b62000269848285620001f0565b509392505050565b600082601f8301126200028957620002886200011b565b5b81516200029b84826020860162000226565b91505092915050565b600060208284031215620002bd57620002bc62000111565b5b600082015167ffffffffffffffff811115620002de57620002dd62000116565b5b620002ec8482850162000271565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200033d57607f821691505b602082108103620003535762000352620002f5565b5b50919050565b61070180620003696000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae32171461006c575b600080fd5b6100556004803603810190610050919061043f565b61008a565b60405161006392919061052b565b60405180910390f35b6100746101b0565b604051610081919061055b565b60405180910390f35b600060607f0d363f2fba46ab11b6db8da0125b0d5484787c44e265b48810735998bab12b756000846040516100c0929190610672565b60405180910390a182600090805190602001906100de929190610242565b507f7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e600060405161010f91906106a9565b60405180910390a160016000808054610127906105ac565b80601f0160208091040260200160405190810160405280929190818152602001828054610153906105ac565b80156101a05780601f10610175576101008083540402835291602001916101a0565b820191906000526020600020905b81548152906001019060200180831161018357829003601f168201915b5050505050905091509150915091565b6060600080546101bf906105ac565b80601f01602080910402602001604051908101604052809291908181526020018280546101eb906105ac565b80156102385780601f1061020d57610100808354040283529160200191610238565b820191906000526020600020905b81548152906001019060200180831161021b57829003601f168201915b5050505050905090565b82805461024e906105ac565b90600052602060002090601f01602090048101928261027057600085556102b7565b82601f1061028957805160ff19168380011785556102b7565b828001600101855582156102b7579182015b828111156102b657825182559160200191906001019061029b565b5b5090506102c491906102c8565b5090565b5b808211156102e15760008160009055506001016102c9565b5090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61034c82610303565b810181811067ffffffffffffffff8211171561036b5761036a610314565b5b80604052505050565b600061037e6102e5565b905061038a8282610343565b919050565b600067ffffffffffffffff8211156103aa576103a9610314565b5b6103b382610303565b9050602081019050919050565b82818337600083830152505050565b60006103e26103dd8461038f565b610374565b9050828152602081018484840111156103fe576103fd6102fe565b5b6104098482856103c0565b509392505050565b600082601f830112610426576104256102f9565b5b81356104368482602086016103cf565b91505092915050565b600060208284031215610455576104546102ef565b5b600082013567ffffffffffffffff811115610473576104726102f4565b5b61047f84828501610411565b91505092915050565b60008115159050919050565b61049d81610488565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b838110156104dd5780820151818401526020810190506104c2565b838111156104ec576000848401525b50505050565b60006104fd826104a3565b61050781856104ae565b93506105178185602086016104bf565b61052081610303565b840191505092915050565b60006040820190506105406000830185610494565b818103602083015261055281846104f2565b90509392505050565b6000602082019050818103600083015261057581846104f2565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806105c457607f821691505b6020821081036105d7576105d661057d565b5b50919050565b60008190508160005260206000209050919050565b600081546105ff816105ac565b61060981866104ae565b94506001821660008114610624576001811461063657610669565b60ff1983168652602086019350610669565b61063f856105dd565b60005b8381101561066157815481890152600182019150602081019050610642565b808801955050505b50505092915050565b6000604082019050818103600083015261068c81856105f2565b905081810360208301526106a081846104f2565b90509392505050565b600060208201905081810360008301526106c381846105f2565b90509291505056fea26469706673582212203746baed62e6fd543d947eb69460c5abf2eef8b85afff65b442695d4abaaebbc64736f6c634300080d00330000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b4d79204772656574696e67000000000000000000000000000000000000000000'; + const contract = new Contract(GreeterAbi); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const deploySpy = jest + .spyOn(eth, 'sendTransaction') + .mockImplementation((_objInstance, tx) => { + expect(tx.to).toBeUndefined(); + expect(tx.gas).toStrictEqual(sendOptions.gas); + expect(tx.gasPrice).toBeUndefined(); + expect(tx.from).toStrictEqual(sendOptions.from); + expect(tx.data).toStrictEqual(data); // padded data + + const newContract = contract.clone(); + newContract.options.address = deployedAddr; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve(newContract) as any; + }); + + const deployedContract = await contract + .deploy({ + data: GreeterBytecode, + arguments: ['My Greeting'], + }) + .send(sendOptions); + + expect(deployedContract).toBeDefined(); + expect(deployedContract.options.address).toStrictEqual(deployedAddr); + deploySpy.mockClear(); + }); + + // eslint-disable-next-line @typescript-eslint/require-await + it('should not deploy contract with empty data', async () => { + const contract = new Contract(GreeterAbi); + + expect(() => contract.deploy({ data: '' }).send(sendOptions)).toThrow( + 'contract creation without any data provided', + ); + }); + + // eslint-disable-next-line @typescript-eslint/require-await + it('send method on deployed contract should work', async () => { + const arg = 'Hello'; + const contract = new Contract(GreeterAbi); + + const spyTx = jest + .spyOn(eth, 'sendTransaction') + .mockImplementation((_objInstance, _tx) => { + const newContract = contract.clone(); + newContract.options.address = deployedAddr; + + if ( + _tx.data === + '0xa41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000' + ) { + // eslint-disable-next-line + expect(_tx.to).toStrictEqual(deployedAddr); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve({ status: '0x1' }) as any; + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve(newContract) as any; + }); + + const deployedContract = await contract + .deploy({ + data: GreeterBytecode, + arguments: ['My Greeting'], + }) + .send(sendOptions); + + const receipt = await deployedContract.methods.setGreeting(arg).send(sendOptions); + expect(receipt.status).toBe('0x1'); + + spyTx.mockClear(); + }); + + it('call on deployed contract should decode result', async () => { + const arg = 'Hello'; + const encodedArg = + '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000'; + + const contract = new Contract(GreeterAbi); + + const spyTx = jest + .spyOn(eth, 'sendTransaction') + .mockImplementation((_objInstance, _tx) => { + const newContract = contract.clone(); + newContract.options.address = deployedAddr; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve(newContract) as any; + }); + + const spyEthCall = jest.spyOn(eth, 'call').mockImplementation((_objInstance, _tx) => { + expect(_tx.to).toStrictEqual(deployedAddr); + expect(_tx.data).toBe('0xcfae3217'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve(encodedArg) as any; // contract class should decode encodedArg + }); + + const deployedContract = await contract + .deploy({ + data: GreeterBytecode, + arguments: ['My Greeting'], + }) + .send(sendOptions); + + const res = await deployedContract.methods.greet().call(); + expect(res).toStrictEqual(arg); + + spyTx.mockClear(); + spyEthCall.mockClear(); + }); + + it('should clone pre deployed contract with address', () => { + const contract = new Contract( + sampleStorageContractABI, + '0x00000000219ab540356cBB839Cbe05303d7705Fa', + { gas: '0x97254' }, + ); + + const clonnedContract = contract.clone(); + + expect(JSON.stringify(contract)).toStrictEqual(JSON.stringify(clonnedContract)); + }); + + it('should clone new contract', () => { + const contract = new Contract(sampleStorageContractABI); + + const clonnedContract = contract.clone(); + expect(JSON.stringify(contract)).toStrictEqual(JSON.stringify(clonnedContract)); + }); + + it('defaults set and get should work', () => { + const contract = new Contract([], '0x00000000219ab540356cBB839Cbe05303d7705Fa'); + + const defaultAddr = '0xd7E30ae310C1D1800F5B641Baa7af95b2e1FD98C'; + expect(contract.defaultAccount).toBeUndefined(); + contract.defaultAccount = defaultAddr; + expect(contract.defaultAccount).toStrictEqual(defaultAddr); + + const defaultBlock = '0xC43A'; + expect(contract.defaultBlock).toBe('latest'); + contract.defaultBlock = defaultBlock; + expect(contract.defaultBlock).toStrictEqual(defaultBlock); + + const defaultHardfork = 'constantinople'; + expect(contract.defaultHardfork).toBe('london'); + contract.defaultHardfork = defaultHardfork; + expect(contract.defaultHardfork).toStrictEqual(defaultHardfork); + + const defaultCommon = { + customChain: { name: 'testnet', networkId: '5678', chainId: '5634' }, + baseChain: 'mainnet' as ValidChains, + hardFork: 'petersburg', + }; + expect(contract.defaultCommon).toBeUndefined(); + contract.defaultCommon = defaultCommon; + expect(contract.defaultCommon).toStrictEqual(defaultCommon); // TODO check if is being fixed in 5419, it should throw error as contract.defaultHardfork != contract.defaultCommon.hardfork + + const transactionBlockTimeout = 130; + expect(contract.transactionBlockTimeout).toBe(50); + contract.transactionBlockTimeout = transactionBlockTimeout; + expect(contract.transactionBlockTimeout).toStrictEqual(transactionBlockTimeout); + + const transactionConfirmationBlocks = 30; + expect(contract.transactionConfirmationBlocks).toBe(24); + contract.transactionConfirmationBlocks = transactionConfirmationBlocks; + expect(contract.transactionConfirmationBlocks).toStrictEqual( + transactionConfirmationBlocks, + ); + + const transactionPollingInterval = 1000; + expect(contract.transactionPollingInterval).toBe(1000); + contract.transactionPollingInterval = transactionPollingInterval; + expect(contract.transactionPollingInterval).toStrictEqual(transactionPollingInterval); + + const transactionPollingTimeout = 800000; + expect(contract.transactionPollingTimeout).toBe(750000); + contract.transactionPollingTimeout = transactionPollingTimeout; + expect(contract.transactionPollingTimeout).toStrictEqual(transactionPollingTimeout); + + const transactionReceiptPollingInterval = 2000; // its new in 4.x + expect(contract.transactionReceiptPollingInterval).toBe(1000); + contract.transactionReceiptPollingInterval = transactionReceiptPollingInterval; + expect(contract.transactionReceiptPollingInterval).toStrictEqual( + transactionReceiptPollingInterval, + ); + + const transactionConfirmationPollingInterval = 2501; // its new in 4.x + expect(contract.transactionConfirmationPollingInterval).toBe(1000); + contract.transactionConfirmationPollingInterval = + transactionConfirmationPollingInterval; + expect(contract.transactionConfirmationPollingInterval).toStrictEqual( + transactionConfirmationPollingInterval, + ); + + const transactionSendTimeout = 730000; // its new in 4.x + expect(contract.transactionSendTimeout).toBe(750000); + contract.transactionSendTimeout = transactionSendTimeout; + expect(contract.transactionSendTimeout).toStrictEqual(transactionSendTimeout); + + const blockHeaderTimeout = 12; + expect(contract.blockHeaderTimeout).toBe(10); + contract.blockHeaderTimeout = blockHeaderTimeout; + expect(contract.blockHeaderTimeout).toStrictEqual(blockHeaderTimeout); + + expect(contract.handleRevert).toBe(false); + contract.handleRevert = true; + expect(contract.handleRevert).toBe(true); + }); + + it('should set and get correct address', () => { + const addr = '0x1230B93ffd14F2F022039675fA3fc3A46eE4C701'; + const contract = new Contract( + [], + '', + { gas: '123' }, + { config: { defaultAccount: '0x00000000219ab540356cBB839Cbe05303d7705Fa' } }, + ); + + contract.options.address = addr; + expect(contract.options.address).toStrictEqual(addr); + }); + + it.skip('should set and get jsonInterface', () => { + const contract = new Contract( + sampleStorageContractABI, + '0x1230B93ffd14F2F022039675fA3fc3A46eE4C701', + { gas: '123' }, + { config: { defaultAccount: '0x00000000219ab540356cBB839Cbe05303d7705Fa' } }, + ); + + // contract.options.jsonInterface = ERC20TokenAbi; //TODO also check changing abi on the fly bug: https://github.com/web3/web3.js/issues/5474 + expect(contract.options.jsonInterface).toStrictEqual(sampleStorageContractABI); + }); + + it('getPastEvents with filter should work', async () => { + const contract = new Contract(GreeterAbi); + + const spyTx = jest + .spyOn(eth, 'sendTransaction') + .mockImplementation((_objInstance, _tx) => { + const newContract = contract.clone(); + newContract.options.address = deployedAddr; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve(newContract) as any; + }); + + const spyGetLogs = jest + .spyOn(eth, 'getLogs') + .mockImplementation((_objInstance, _params) => { + expect(_params.address).toStrictEqual(deployedAddr.toLocaleLowerCase()); + expect(_params.fromBlock).toStrictEqual(getLogsData.request.fromBlock); + expect(_params.toBlock).toStrictEqual(getLogsData.request.toBlock); + expect(_params.topics).toStrictEqual(getLogsData.request.topics); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve(getLogsData.response) as any; + }); + + const deployedContract = await contract + .deploy({ + data: GreeterBytecode, + arguments: ['My Greeting'], + }) + .send(sendOptions); + + const fromBlock = 'earliest'; + const toBlock = 'latest'; + const pastEvent = await deployedContract.getPastEvents(getPastEventsData.event as any, { + fromBlock, + toBlock, + }); + + expect(pastEvent).toStrictEqual(getPastEventsData.response); + spyTx.mockClear(); + spyGetLogs.mockClear(); + }); + + it('getPastEvents for all events should work', async () => { + const contract = new Contract(GreeterAbi); + + const spyTx = jest + .spyOn(eth, 'sendTransaction') + .mockImplementation((_objInstance, _tx) => { + const newContract = contract.clone(); + newContract.options.address = deployedAddr; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve(newContract) as any; + }); + + const spyGetLogs = jest + .spyOn(eth, 'getLogs') + .mockImplementation((_objInstance, _params) => { + expect(_params.address).toStrictEqual(deployedAddr.toLocaleLowerCase()); + expect(_params.fromBlock).toBeUndefined(); + expect(_params.toBlock).toBeUndefined(); + expect(_params.topics).toBeUndefined(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve(AllGetPastEventsData.getLogsData) as any; // AllGetPastEventsData.getLogsData data test is for: assume two transactions sent to contract with contractInstance.methods.setGreeting("Hello") and contractInstance.methods.setGreeting("Another Greeting") + }); + + const deployedContract = await contract + .deploy({ + data: GreeterBytecode, + arguments: ['My Greeting'], + }) + .send(sendOptions); + + const pastEvent = await deployedContract.getPastEvents('allEvents'); + + expect(pastEvent).toStrictEqual(AllGetPastEventsData.response); + spyTx.mockClear(); + spyGetLogs.mockClear(); + }); + + it('estimateGas should work', async () => { + const arg = 'Hello'; + + const contract = new Contract( + GreeterAbi, + // {data: GreeterBytecode,} // TODO bug fix https://github.com/web3/web3.js/issues/5473 setting data via options causing this issue + ); + + const spyTx = jest + .spyOn(eth, 'sendTransaction') + .mockImplementation((_objInstance, _tx) => { + const newContract = contract.clone(); + newContract.options.address = deployedAddr; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve(newContract) as any; + }); + + const spyEstimateGas = jest + .spyOn(eth, 'estimateGas') + .mockImplementation((_objInstance, _tx, _block) => { + expect(_block).toBe('latest'); + expect(_tx.to).toStrictEqual(deployedAddr); + expect(_tx.from).toStrictEqual(sendOptions.from); + expect(_tx.data).toBe( + '0xa41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000', + ); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve(BigInt(36916)) as any; + }); + + const deployedContract = await contract + .deploy({ + data: GreeterBytecode, + arguments: ['My Greeting'], + }) + .send(sendOptions); + + const result = await deployedContract.methods.setGreeting(arg).estimateGas(sendOptions); + expect(result).toStrictEqual(BigInt(36916)); + + spyTx.mockClear(); + spyEstimateGas.mockClear(); + }); + + it('contract method send without contract address should throw error', async () => { + const arg = 'Hello'; + + const contract = new Contract(GreeterAbi); + + expect(() => contract.methods.setGreeting(arg).send(sendOptions)).toThrow( + 'Contract address not specified', + ); + }); + + it('contract method send without from address should throw error', async () => { + const gas = '1000000'; + const sendOptionsSpecial = { gas }; + const arg = 'Hello'; + + const contract = new Contract(GreeterAbi); + contract.options.address = '0x12364916b10Ae90076dDa6dE756EE1395BB69ec2'; + + /* eslint-disable no-useless-escape */ + expect(() => contract.methods.setGreeting(arg).send(sendOptionsSpecial)).toThrow( + 'Contract "from" address not specified', + ); + }); + }); }); diff --git a/packages/web3-eth/test/integration/defaults.test.ts b/packages/web3-eth/test/integration/defaults.test.ts index 7d9bb4c31f5..a978008735b 100644 --- a/packages/web3-eth/test/integration/defaults.test.ts +++ b/packages/web3-eth/test/integration/defaults.test.ts @@ -693,7 +693,7 @@ describe('defaults', () => { value: '0x174876e800', gas: '0x5208', }, - web3Context: eth2 as Web3Context, + web3Context: eth2 as Web3Context, }); expect(res.networkId).toBe(4); @@ -706,7 +706,7 @@ describe('defaults', () => { gas: '0x5208', networkId: 5, }, - web3Context: eth2 as Web3Context, + web3Context: eth2 as Web3Context, }); expect(resWithPassNetworkId.networkId).toBe(BigInt(5)); @@ -736,7 +736,7 @@ describe('defaults', () => { value: '0x174876e800', gas: '0x5208', }, - web3Context: eth2 as Web3Context, + web3Context: eth2 as Web3Context, }); expect(res.chain).toBe('rinkeby'); }); diff --git a/packages/web3-net/test/fixtures/system_tests_utils.ts b/packages/web3-net/test/fixtures/system_tests_utils.ts new file mode 120000 index 00000000000..2ab08a83752 --- /dev/null +++ b/packages/web3-net/test/fixtures/system_tests_utils.ts @@ -0,0 +1 @@ +../../../../scripts/system_tests_utils.ts \ No newline at end of file diff --git a/packages/web3-net/test/integration/web3_net.test.ts b/packages/web3-net/test/integration/web3_net.test.ts new file mode 100644 index 00000000000..394f7ac0e3f --- /dev/null +++ b/packages/web3-net/test/integration/web3_net.test.ts @@ -0,0 +1,54 @@ +/* +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 . +*/ + +import Net from '../../src'; + +import { getSystemTestProvider, closeOpenConnection } from '../fixtures/system_tests_utils'; + +describe('Web3 net', () => { + let clientUrl: string; + let web3Net: Net; + + beforeAll(async () => { + clientUrl = getSystemTestProvider(); + }); + + afterAll(async () => { + await closeOpenConnection(web3Net); + }); + + it('should be able to create instance', () => { + web3Net = new Net(clientUrl); + + expect(web3Net).toBeInstanceOf(Net); + }); + + it('should be able to get id', async () => { + const networkId = await web3Net.getId(); + expect(networkId).toBe(BigInt(1337)); + }); + + it('should be able to listen', async () => { + const isListening = await web3Net.isListening(); + expect(isListening).toBeTruthy(); + }); + + it('should fetch peer count', async () => { + const peerCount = await web3Net.getPeerCount(); + expect(peerCount).toBe(BigInt(0)); + }); +}); diff --git a/packages/web3-plugin-example/src/contract_method_wrappers.ts b/packages/web3-plugin-example/src/contract_method_wrappers.ts index e0a496c3f01..674cb672ad7 100644 --- a/packages/web3-plugin-example/src/contract_method_wrappers.ts +++ b/packages/web3-plugin-example/src/contract_method_wrappers.ts @@ -18,7 +18,7 @@ import 'web3'; import { Web3Context, Web3PluginBase } from 'web3-core'; import { ContractAbi } from 'web3-eth-abi'; import Contract from 'web3-eth-contract'; -import { Address, Numbers, Web3APISpec } from 'web3-types'; +import { Address, Numbers } from 'web3-types'; import { DataFormat, DEFAULT_RETURN_FORMAT, format, numberToHex } from 'web3-utils'; import { ERC20TokenAbi } from './ERC20Token'; @@ -43,7 +43,7 @@ export class ContractMethodWrappersPlugin extends Web3PluginBase { * @param parentContext - The context to be added to the instance of `ChainlinkPlugin`, * and by extension, the instance of `Contract`. */ - public link(parentContext: Web3Context) { + public link(parentContext: Web3Context) { super.link(parentContext); this._contract.link(parentContext); } diff --git a/packages/web3-types/src/web3_api_types.ts b/packages/web3-types/src/web3_api_types.ts index 481ad4e6b16..e669464ce7d 100644 --- a/packages/web3-types/src/web3_api_types.ts +++ b/packages/web3-types/src/web3_api_types.ts @@ -17,12 +17,12 @@ along with web3.js. If not, see . import { JsonRpcId, JsonRpcIdentifier } from './json_rpc_types'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Web3APISpec = Record any>; -export type Web3APIMethod = string & keyof T; -export type Web3APIParams> = Parameters< - API[Method] ->; +export type Web3APISpec = Record any> | unknown; +export type Web3APIMethod = string & keyof Exclude; +export type Web3APIParams< + API extends Web3APISpec, + Method extends Web3APIMethod, +> = API extends Exclude ? Parameters : unknown; export interface Web3APIRequest> { method: Method; @@ -38,4 +38,4 @@ export interface Web3APIPayload, -> = ReturnType; +> = API extends Record any> ? ReturnType : any; diff --git a/scripts/ganache.sh b/scripts/ganache.sh index d98926ab183..f7f9089b686 100755 --- a/scripts/ganache.sh +++ b/scripts/ganache.sh @@ -12,10 +12,10 @@ start() { if [ -z "${ORIGARGS[1]}" ] then - docker run --publish 8545:8545 trufflesuite/ganache:latest -m "$WEB3_SYSTEM_TEST_MNEMONIC" -a 5 -p $WEB3_SYSTEM_TEST_PORT --wallet.passphrase "123" + docker run --publish 8545:8545 trufflesuite/ganache:latest -m "$WEB3_SYSTEM_TEST_MNEMONIC" -a 5 -p $WEB3_SYSTEM_TEST_PORT --wallet.passphrase "123" --networkId 1337 else echo "Starting ganache ..." - docker run --detach --publish 8545:8545 trufflesuite/ganache:latest -m "$WEB3_SYSTEM_TEST_MNEMONIC" -a 5 -p $WEB3_SYSTEM_TEST_PORT --wallet.passphrase "123" + docker run --detach --publish 8545:8545 trufflesuite/ganache:latest -m "$WEB3_SYSTEM_TEST_MNEMONIC" -a 5 -p $WEB3_SYSTEM_TEST_PORT --wallet.passphrase "123" --networkId 1337 echo "Waiting for ganache..." npx wait-port "$WEB3_SYSTEM_TEST_PORT" diff --git a/scripts/system_tests_utils.ts b/scripts/system_tests_utils.ts index 7cab815b08a..2f4ef528562 100644 --- a/scripts/system_tests_utils.ts +++ b/scripts/system_tests_utils.ts @@ -81,7 +81,7 @@ const maxNumberOfAttempts = 10; const intervalTime = 5000; // ms export const waitForOpenConnection = async ( - web3Context: Web3Context, + web3Context: Web3Context, currentAttempt = 1, status = 'connected', ) => @@ -106,7 +106,7 @@ export const waitForOpenConnection = async ( }, intervalTime); }); -export const closeOpenConnection = async (web3Context: Web3Context) => { +export const closeOpenConnection = async (web3Context: Web3Context) => { if (!isWs && !isIpc) { return; }