From 195cd3c4274568bad7b436981e89aca7264e3d52 Mon Sep 17 00:00:00 2001 From: Oleksii Kosynskyi Date: Fri, 21 Apr 2023 00:40:05 +0300 Subject: [PATCH] Filter option doesn't work in getPastEvents (#6015) * add filter and tests * add topic filter and tests * add filter for all events and tests * add changelog * fix tests * fix events * fix tests * fix tests * revert spaces --- CHANGELOG.md | 3 + packages/web3-eth-contract/src/index.js | 77 ++++++++------ test/e2e.contract.events.js | 127 +++++++++++++++++++++--- 3 files changed, 164 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70bc76b8e03..ac357ed99e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -672,3 +672,6 @@ Released with 1.0.0-beta.37 code base. - `transaction.type` is now formatted to a hex string before being send to provider (#5979) - When sending a transaction, if `transaction.type === '0x1' && transaction.accessList === undefined`, then `transaction.accessList` is set to `[]` (#5979) + +### Added +- Added support for `getPastEvents` method to filter `allEvents` and specific event (#6015) diff --git a/packages/web3-eth-contract/src/index.js b/packages/web3-eth-contract/src/index.js index 6ee2b25e30c..0fa4d894ea9 100644 --- a/packages/web3-eth-contract/src/index.js +++ b/packages/web3-eth-contract/src/index.js @@ -285,7 +285,7 @@ var Contract = function Contract(jsonInterface, address, options) { _this.options.blockHeaderTimeout = val; }, enumerable: true - }); + }); Object.defineProperty(this, 'defaultAccount', { get: function () { return defaultAccount; @@ -415,46 +415,44 @@ Contract.prototype._encodeEventABI = function (event, options) { result[f] = formatters.inputBlockNumberFormatter(options[f]); }); - // use given topics - if(Array.isArray(options.topics)) { - result.topics = options.topics; - // create topics based on filter + let topics = [] + if (options.topics && Array.isArray(options.topics)) { + topics = [...options.topics]; } else { - - result.topics = []; - + topics = []; // add event signature if (event && !event.anonymous && event.name !== 'ALLEVENTS') { - result.topics.push(event.signature); + topics.push( + event.signature || abi.encodeEventSignature(utils.jsonInterfaceMethodToString(event)), + ); } // add event topics (indexed arguments) - if (event.name !== 'ALLEVENTS') { - var indexedTopics = event.inputs.filter(function (i) { - return i.indexed === true; - }).map(function (i) { - var value = filter[i.name]; - if (!value) { - return null; + if (event.name !== 'ALLEVENTS' && event.inputs) { + for (const input of event.inputs) { + if (!input.indexed) { + continue; } - // TODO: https://github.com/ethereum/web3.js/issues/344 - // TODO: deal properly with components + const value = filter[input.name]; + if (!value) { + // eslint-disable-next-line no-null/no-null + topics.push(null); + continue; + } if (Array.isArray(value)) { - return value.map(function (v) { - return abi.encodeParameter(i.type, v); - }); + topics.push(value.map(v => abi.encodeParameter(input.type, v))); + } else { + topics.push(abi.encodeParameter(input.type, value)); } - return abi.encodeParameter(i.type, value); - }); - - result.topics = result.topics.concat(indexedTopics); + } } + } - if(!result.topics.length) - delete result.topics; + if(topics.length) { + result.topics = topics } if(this.options.address) { @@ -682,6 +680,7 @@ Contract.prototype._generateEventOptions = function() { return { params: this._encodeEventABI(event, options), event: event, + filter: options.filter || {}, callback: callback }; }; @@ -784,6 +783,24 @@ Contract.prototype._on = function(){ return subscription; }; +const filterAllEventsResults = (subOptions, data) => { + if (subOptions.event && subOptions.event.name === 'ALLEVENTS' && Array.isArray(data)) { + const filter = subOptions.filter || {}; + const filterKeys = Object.keys(filter); + return filterKeys.length > 0 + ? data.filter(log => typeof log === 'string' ? true : filterKeys.every((k) => Array.isArray(filter[k]) ? (filter[k]).some( + (v) => + String(log.returnValues[k]).toUpperCase() === + String(v).toUpperCase(), + ) : ( + String(log.returnValues[k]).toUpperCase() === + String(filter[k]).toUpperCase() + )), + ) + : data; + } + return data; +}; /** * Get past events from contracts * @@ -808,7 +825,11 @@ Contract.prototype.getPastEvents = function(){ getPastLogs = null; - return call(subOptions.params, subOptions.callback); + return call(subOptions.params, (err, data)=>{ + if(typeof subOptions.callback === 'function'){ + subOptions.callback(err, filterAllEventsResults(subOptions, data)) + } + }).then(filterAllEventsResults.bind(this, subOptions)); }; diff --git a/test/e2e.contract.events.js b/test/e2e.contract.events.js index a3e687bde96..5f878f33850 100644 --- a/test/e2e.contract.events.js +++ b/test/e2e.contract.events.js @@ -5,6 +5,20 @@ var Parent = require('./sources/Parent'); var utils = require('./helpers/test.utils'); var Web3 = utils.getWeb3(); +const prepareEvents = async (instance,address) => { + await instance + .methods + .firesEvent(address, 1) + .send({from: address}); + await instance + .methods + .firesEvent(address, 2) + .send({from: address}); + await instance + .methods + .firesEvent(address, 3) + .send({from: address}); +} describe('contract.events [ @E2E ]', function() { // `getPastEvents` not working with Geth instamine over websockets. if (process.env.GETH_INSTAMINE) return; @@ -33,27 +47,112 @@ describe('contract.events [ @E2E ]', function() { }); it('contract.getPastEvents', async function(){ - await instance - .methods - .firesEvent(accounts[0], 1) - .send({from: accounts[0]}); - - await instance - .methods - .firesEvent(accounts[0], 2) - .send({from: accounts[0]}); - + await prepareEvents(instance, accounts[0]); const events = await instance.getPastEvents({ fromBlock: 0, toBlock: 'latest' }); - assert.equal(events.length, 2); + assert.equal(events.length, 3); assert.equal(events[0].event, 'BasicEvent'); assert.equal(events[1].event, 'BasicEvent'); + assert.equal(events[2].event, 'BasicEvent'); assert.notEqual(events[0].id, events[1].id); }); + it('contract.getPastEvents filter by val', async function() { + await prepareEvents(instance, accounts[0]); + const events = await instance.getPastEvents('BasicEvent', { + filter: { val: 2 }, + fromBlock: 'earliest', + toBlock: 'latest', + }); + assert.equal(events.length, 1); + assert.equal(events[0].returnValues.val, 2); + }); + + it('contract.getPastEvents without specify event name: filter by val', async function() { + await prepareEvents(instance, accounts[0]); + const events = await instance.getPastEvents({ + filter: { val: 2 }, + fromBlock: 'earliest', + toBlock: 'latest', + }); + assert.equal(events.length, 1); + assert.equal(events[0].returnValues.val, 2); + }); + + it('contract.getPastEvents all events: filter by val', async function() { + await prepareEvents(instance, accounts[0]); + const events = await instance.getPastEvents('allEvents', { + filter: { val: 2 }, + fromBlock: 'earliest', + toBlock: 'latest', + }); + assert.equal(events.length, 1); + assert.equal(events[0].returnValues.val, 2); + }); + + it('contract.getPastEvents filter by val different value', async function() { + await prepareEvents(instance, accounts[0]); + const events = await instance.getPastEvents('BasicEvent', { + filter: { val: 3 }, + fromBlock: 'earliest', + toBlock: 'latest', + }); + assert.equal(events.length, 1); + assert.equal(events[0].returnValues.val, 3); + }); + + it('contract.getPastEvents filter by array', async function() { + await prepareEvents(instance, accounts[0]); + const events = await instance.getPastEvents('BasicEvent', { + filter: { val: [2, 3] }, + fromBlock: 'earliest', + toBlock: 'latest', + }); + assert.equal(events.length, 2); + assert.equal(events[0].returnValues.val, 2); + assert.equal(events[1].returnValues.val, 3); + }); + + it('contract.getPastEvents allEvents: filter by array', async function() { + await prepareEvents(instance, accounts[0]); + const events = await instance.getPastEvents('allEvents', { + filter: { val: [2, 3] }, + fromBlock: 'earliest', + toBlock: 'latest', + }); + assert.equal(events.length, 2); + assert.equal(events[0].returnValues.val, 2); + assert.equal(events[1].returnValues.val, 3); + }); + + it('contract.getPastEvents allEvents: filter by array using callback', async function() { + await prepareEvents(instance, accounts[0]); + instance.getPastEvents('allEvents', { + filter: { val: [2, 3] }, + fromBlock: 'earliest', + toBlock: 'latest', + }, (err, events) => { + assert.equal(events.length, 2); + assert.equal(events[0].returnValues.val, 2); + assert.equal(events[1].returnValues.val, 3); + }); + }); + + it('contract.getPastEvents filter by val using callback', async function() { + await prepareEvents(instance, accounts[0]); + instance.getPastEvents('BasicEvent', { + filter: { val: 3 }, + fromBlock: 'earliest', + toBlock: 'latest', + }, (err, events) => { + assert.equal(events.length, 1); + assert.equal(events[0].returnValues.val, 3); + }); + }); + it('contract.events.', function(){ return new Promise(async resolve => { instance @@ -91,7 +190,6 @@ describe('contract.events [ @E2E ]', function() { this.removeAllListeners(); resolve(); }); - assert.equal(message, 'Invalid option: toBlock. Use getPastEvents for specific range.'); console.warn = originalWarn @@ -106,7 +204,6 @@ describe('contract.events [ @E2E ]', function() { const originalWarn = console.warn let message console.warn = function(str) { message = str } - return new Promise(async (resolve, reject) => { instance .events @@ -408,9 +505,9 @@ describe('contract.events [ @E2E ]', function() { assert.equal(events[0].returnValues.str, msg) }); - // Malformed utf-8 sequence in the following two tests comes from + // Malformed utf-8 sequence in the following two tests comes from // https://www.w3.org/2001/06/utf-8-wrong/UTF-8-test.html - // Section: 3.1.8 + // Section: 3.1.8 it('when an invalid utf-8 string is passed in JS as param to emit', async function(){ const msg = '�������';