diff --git a/packages/web3-eth-contract/CHANGELOG.md b/packages/web3-eth-contract/CHANGELOG.md index 324fdc99879..159712d744a 100644 --- a/packages/web3-eth-contract/CHANGELOG.md +++ b/packages/web3-eth-contract/CHANGELOG.md @@ -353,4 +353,8 @@ Documentation: - By default, contracts will fill `data` instead of `input` within method calls (#6622) -## [Unreleased] \ No newline at end of file +## [Unreleased] + +### Fixed + +- Fix and error that happen when trying to get past events by calling `contract.getPastEvents` or `contract.events.allEvents()`, if there is no matching events. (#6647) diff --git a/packages/web3-eth-contract/package.json b/packages/web3-eth-contract/package.json index 873bbc78ec8..2f08fae9f45 100644 --- a/packages/web3-eth-contract/package.json +++ b/packages/web3-eth-contract/package.json @@ -67,6 +67,7 @@ "prettier": "^2.7.1", "ts-jest": "^28.0.7", "typescript": "^4.7.4", - "web3-eth-accounts": "^4.1.0" + "web3-eth-accounts": "^4.1.0", + "web3-providers-ws": "^4.0.7" } } diff --git a/packages/web3-eth-contract/src/contract.ts b/packages/web3-eth-contract/src/contract.ts index 86ae21e7d85..eb60285a52d 100644 --- a/packages/web3-eth-contract/src/contract.ts +++ b/packages/web3-eth-contract/src/contract.ts @@ -191,140 +191,140 @@ const contractSubscriptions = { }; /** -* The `web3.eth.Contract` makes it easy to interact with smart contracts on the ethereum blockchain. -* For using contract package, first install Web3 package using: `npm i web3` or `yarn add web3` based on your package manager, after that contracts features can be used as mentioned in following snippet. -* ```ts -* -* import { Web3 } from 'web3'; -* -* const web3 = new Web3('https://127.0.0.1:4545'); -* const abi = [...] as const; // your contract ABI -* -* let contract = new web3.eth.Contract(abi,'0xdAC17F958D2ee523a2206206994597C13D831ec7'); -* await contract.methods.balanceOf('0xdAC17F958D2ee523a2206206994597C13D831ec7').call(); -* ``` -* For using individual package install `web3-eth-contract` and `web3-core` packages using: `npm i web3-eth-contract web3-core` or `yarn add web3-eth-contract web3-core`. This is more efficient approach for building lightweight applications. -* ```ts -* -* import { Web3Context } from 'web3-core'; -* import { Contract } from 'web3-eth-contract'; -* -* const abi = [...] as const; // your contract ABI -* -* let contract = new web3.eth.Contract( -* abi, -* '0xdAC17F958D2ee523a2206206994597C13D831ec7' -* new Web3Context('http://127.0.0.1:8545')); -* -* await contract.methods.balanceOf('0xdAC17F958D2ee523a2206206994597C13D831ec7').call(); -* ``` -* ## Generated Methods -* Following methods are generated by web3.js contract object for each of contract functions by using its ABI. -* -* ### send -* This is used to send a transaction to the smart contract and execute its method. Note this can alter the smart contract state. -* -* #### Parameters -* options?: PayableTxOptions | NonPayableTxOptions -* -* #### Returns -* [Web3PromiEvent](/api/web3/namespace/core#Web3PromiEvent) : Web3 Promi Event -* -* ```ts -* // using the promise -* myContract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'}) -* .then(function(receipt){ -* // other parts of code to use receipt -* }); -* -* -* // using the event emitter -* myContract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'}) -* .on('transactionHash', function(hash){ -* // ... -* }) -* .on('confirmation', function(confirmationNumber, receipt){ -* // ... -* }) -* .on('receipt', function(receipt){ -* // ... -* }) -* .on('error', function(error, receipt) { -* // ... -* }); -* -* ``` -* -* ### call -* This will execute smart contract method in the EVM without sending any transaction. Note calling cannot alter the smart contract state. -* -* #### Parameters -* options?: PayableCallOptions | NonPayableCallOptions, -* block?: BlockNumberOrTag, -* -* #### Returns -* Promise : having results of call -* -* ```ts -* -* let myContract = new web3.eth.Contract(abi, address); -* -* myContract.methods.myFunction().call() -* .then(console.log); -* -* ``` -* ### estimateGas -* Returns the amount of gas consumed by executing the method in EVM without creating a new transaction on the blockchain. The returned amount can be used as a gas estimate for executing the transaction publicly. The actual gas used can be different when sending the transaction later, as the state of the smart contract can be different at that time. -* -* #### Parameters -* options?: PayableCallOptions, -* returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat, -* -* #### Returns -* Promise: The gas amount estimated. -* -* ```ts -* const estimatedGas = await contract.methods.approve('0xdAC17F958D2ee523a2206206994597C13D831ec7', 300) -* .estimateGas(); -* -* ``` -* -* ### encodeABI -* Encodes the ABI for this method. The resulting hex string is 32-bit function signature hash plus the passed parameters in Solidity tightly packed format. This can be used to send a transaction, call a method, or pass it into another smart contract’s method as arguments. Set the data field on web3.eth.sendTransaction options as the encodeABI() result and it is the same as calling the contract method with contract.myMethod.send(). -* -* Some use cases for encodeABI() include: preparing a smart contract transaction for a multisignature wallet, working with offline wallets and cold storage and creating transaction payload for complex smart contract proxy calls. -* -* #### Parameters -* None -* -* #### Returns -* String: The encoded ABI. -* -* ```ts -* const encodedABI = await contract.methods.approve('0xdAC17F958D2ee523a2206206994597C13D831ec7', 300) -* .encodeABI(); -* -* ``` -* -* ### createAccessList -* This will create an access list a method execution will access when executed in the EVM. -* Note: You must specify a from address and gas if it’s not specified in options when instantiating parent contract object. -* -* #### Parameters -* options?: PayableCallOptions | NonPayableCallOptions, -* block?: BlockNumberOrTag, -* -* #### Returns -* Promise: The generated access list for transaction. -* -* ```ts -* const accessList = await contract.methods.approve('0xbEe634C21c16F05B03B704BaE071536121e6cFeA', 300) -* .createAccessList({ -* from: "0x9992695e1053bb737d3cfae4743dcfc4b94f203d" -* }); -* ``` -* -*/ + * The `web3.eth.Contract` makes it easy to interact with smart contracts on the ethereum blockchain. + * For using contract package, first install Web3 package using: `npm i web3` or `yarn add web3` based on your package manager, after that contracts features can be used as mentioned in following snippet. + * ```ts + * + * import { Web3 } from 'web3'; + * + * const web3 = new Web3('https://127.0.0.1:4545'); + * const abi = [...] as const; // your contract ABI + * + * let contract = new web3.eth.Contract(abi,'0xdAC17F958D2ee523a2206206994597C13D831ec7'); + * await contract.methods.balanceOf('0xdAC17F958D2ee523a2206206994597C13D831ec7').call(); + * ``` + * For using individual package install `web3-eth-contract` and `web3-core` packages using: `npm i web3-eth-contract web3-core` or `yarn add web3-eth-contract web3-core`. This is more efficient approach for building lightweight applications. + * ```ts + * + * import { Web3Context } from 'web3-core'; + * import { Contract } from 'web3-eth-contract'; + * + * const abi = [...] as const; // your contract ABI + * + * let contract = new web3.eth.Contract( + * abi, + * '0xdAC17F958D2ee523a2206206994597C13D831ec7' + * new Web3Context('http://127.0.0.1:8545')); + * + * await contract.methods.balanceOf('0xdAC17F958D2ee523a2206206994597C13D831ec7').call(); + * ``` + * ## Generated Methods + * Following methods are generated by web3.js contract object for each of contract functions by using its ABI. + * + * ### send + * This is used to send a transaction to the smart contract and execute its method. Note this can alter the smart contract state. + * + * #### Parameters + * options?: PayableTxOptions | NonPayableTxOptions + * + * #### Returns + * [Web3PromiEvent](/api/web3/namespace/core#Web3PromiEvent) : Web3 Promi Event + * + * ```ts + * // using the promise + * myContract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'}) + * .then(function(receipt){ + * // other parts of code to use receipt + * }); + * + * + * // using the event emitter + * myContract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'}) + * .on('transactionHash', function(hash){ + * // ... + * }) + * .on('confirmation', function(confirmationNumber, receipt){ + * // ... + * }) + * .on('receipt', function(receipt){ + * // ... + * }) + * .on('error', function(error, receipt) { + * // ... + * }); + * + * ``` + * + * ### call + * This will execute smart contract method in the EVM without sending any transaction. Note calling cannot alter the smart contract state. + * + * #### Parameters + * options?: PayableCallOptions | NonPayableCallOptions, + * block?: BlockNumberOrTag, + * + * #### Returns + * Promise : having results of call + * + * ```ts + * + * let myContract = new web3.eth.Contract(abi, address); + * + * myContract.methods.myFunction().call() + * .then(console.log); + * + * ``` + * ### estimateGas + * Returns the amount of gas consumed by executing the method in EVM without creating a new transaction on the blockchain. The returned amount can be used as a gas estimate for executing the transaction publicly. The actual gas used can be different when sending the transaction later, as the state of the smart contract can be different at that time. + * + * #### Parameters + * options?: PayableCallOptions, + * returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat, + * + * #### Returns + * Promise: The gas amount estimated. + * + * ```ts + * const estimatedGas = await contract.methods.approve('0xdAC17F958D2ee523a2206206994597C13D831ec7', 300) + * .estimateGas(); + * + * ``` + * + * ### encodeABI + * Encodes the ABI for this method. The resulting hex string is 32-bit function signature hash plus the passed parameters in Solidity tightly packed format. This can be used to send a transaction, call a method, or pass it into another smart contract’s method as arguments. Set the data field on web3.eth.sendTransaction options as the encodeABI() result and it is the same as calling the contract method with contract.myMethod.send(). + * + * Some use cases for encodeABI() include: preparing a smart contract transaction for a multisignature wallet, working with offline wallets and cold storage and creating transaction payload for complex smart contract proxy calls. + * + * #### Parameters + * None + * + * #### Returns + * String: The encoded ABI. + * + * ```ts + * const encodedABI = await contract.methods.approve('0xdAC17F958D2ee523a2206206994597C13D831ec7', 300) + * .encodeABI(); + * + * ``` + * + * ### createAccessList + * This will create an access list a method execution will access when executed in the EVM. + * Note: You must specify a from address and gas if it’s not specified in options when instantiating parent contract object. + * + * #### Parameters + * options?: PayableCallOptions | NonPayableCallOptions, + * block?: BlockNumberOrTag, + * + * #### Returns + * Promise: The generated access list for transaction. + * + * ```ts + * const accessList = await contract.methods.approve('0xbEe634C21c16F05B03B704BaE071536121e6cFeA', 300) + * .createAccessList({ + * from: "0x9992695e1053bb737d3cfae4743dcfc4b94f203d" + * }); + * ``` + * + */ export class Contract extends Web3Context implements Web3EventEmitter> @@ -663,7 +663,7 @@ export class Contract * * ```ts * myContract.deploy({ - * input: '0x12345...', // data keyword can be used, too. + * input: '0x12345...', // data keyword can be used, too. * arguments: [123, 'My String'] * }) * .send({ @@ -898,11 +898,13 @@ export class Contract ); const logs = await getLogs(this, { fromBlock, toBlock, topics, address }, returnFormat); - const decodedLogs = logs.map(log => - typeof log === 'string' - ? log - : decodeEventABI(abi, log as LogsInput, this._jsonInterface, returnFormat), - ); + const decodedLogs = logs + ? logs.map(log => + typeof log === 'string' + ? log + : decodeEventABI(abi, log as LogsInput, this._jsonInterface, returnFormat), + ) + : []; const filter = options?.filter ?? {}; const filterKeys = Object.keys(filter); @@ -1322,7 +1324,9 @@ export class Contract // emit past events when fromBlock is defined this.getPastEvents(abi.name, { fromBlock, topics }, returnFormat) .then(logs => { - logs.forEach(log => sub.emit('data', log as EventLog)); + if (logs) { + logs.forEach(log => sub.emit('data', log as EventLog)); + } }) .catch((error: Error) => { sub.emit( diff --git a/packages/web3-eth-contract/test/unit/log_subscription.test.ts b/packages/web3-eth-contract/test/unit/log_subscription.test.ts new file mode 100644 index 00000000000..ccc72acaac6 --- /dev/null +++ b/packages/web3-eth-contract/test/unit/log_subscription.test.ts @@ -0,0 +1,79 @@ +/* +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 * as eth from 'web3-eth'; +import { WebSocketProvider } from 'web3-providers-ws'; +import { Contract } from '../../src'; +import { GreeterAbi, GreeterBytecode } from '../shared_fixtures/build/Greeter'; + +jest.mock('web3-eth'); + +describe('contract log subscription', () => { + const contract = new Contract(GreeterAbi); + const sendOptions = { + from: '0x12364916b10Ae90076dDa6dE756EE1395BB69ec2', + gas: '1000000', + }; + const deployedAddr = '0x20bc23D0598b12c34cBDEf1fae439Ba8744DB426'; + const providerString = 'ws://mydomain.com'; + + beforeAll(() => { + jest.spyOn(WebSocketProvider.prototype, 'connect').mockImplementation(() => { + // nothing + }); + jest.spyOn(WebSocketProvider.prototype, 'getStatus').mockImplementation(() => 'connected'); + contract.setProvider(providerString); + + jest.spyOn(eth, 'sendTransaction').mockImplementation(() => { + const newContract = contract.clone(); + newContract.options.address = deployedAddr; + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Promise.resolve(newContract) as any; + }); + }); + + it('Request Manager should call eth_subscribe with correct params', async () => { + const spyRequestManagerSend = jest + .spyOn(contract.requestManager, 'send') + .mockImplementation(async () => { + return 'sub-id'; + }); + + const deployedContract = await contract + .deploy({ + data: GreeterBytecode, + arguments: ['My Greeting'], + }) + .send(sendOptions); + + const topics = ['0x59ebeb90bc63057b6515673c3ecf9438e5058bca0f92585014eced636878c9a5']; + + deployedContract.events.allEvents({ fromBlock: 'earliest', topics }); + + deployedContract.events.GREETING_CHANGED({ fromBlock: 'earliest', topics }); + + expect(spyRequestManagerSend).toHaveBeenCalledTimes(2); + expect(spyRequestManagerSend).toHaveBeenCalledWith({ + method: 'eth_subscribe', + params: [ + 'logs', + // those params has been generated inside: _buildSubscriptionParams + { address: deployedAddr, topics }, + ], + }); + }); +}); diff --git a/packages/web3-eth-ens/package.json b/packages/web3-eth-ens/package.json index 6d38e795878..b1d24ec3b31 100644 --- a/packages/web3-eth-ens/package.json +++ b/packages/web3-eth-ens/package.json @@ -51,7 +51,7 @@ "eslint-config-base-web3": "0.1.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.26.0", - "jest": "^28.1.3", + "jest": "^29.7.0", "jest-extended": "^3.0.1", "prettier": "^2.7.1", "ts-jest": "^28.0.7",