Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contract events: filter indexed. #204

Closed
kyriediculous opened this issue Jun 18, 2018 · 13 comments
Closed

Contract events: filter indexed. #204

kyriediculous opened this issue Jun 18, 2018 · 13 comments
Labels
enhancement New feature or improvement.

Comments

@kyriediculous
Copy link

Hi,

I find the documentation on events a little confusing. How do I find events filtered by event type and an indexed parameter?

Web3 has a really simple and good syntax to do this with .watch() :

const event = myContract.myEvent({_from: '0x.......})
event.watch().then(...

As I see it there are two methods to retrieve contract events with ethersjs.

contract.on_Eventname_().then()

As far as I can tell there is no way to add a filter object to this method like we can do with getLogs()?
I assume just because we use the contract object this doesn't necessarily mean it binds to wallet.address and looks only for events in which wallet.address is indexed?

And provider.getLogs :

provider.getLogs({
fromBlock: 1,
toBlock: 'latest',
address: 0x.....,
topics: event.topics
}).then()

Is this the correct syntax for retrieving all events with a topic that has user addresses indexed?

For example can we get all ERC20 transfer events originating from an address like follows? :
const event = this.contract.interface.events.Transfer
event.topics[1] = wallet.address

provider.getLogs({
fromBlock: 1,
toBlock: 'latest',
address: 0x.... (token contract address) ,
topics: event.topics
})

What do we put at 'topics'?
Since event.topics[0] is the signature we should keep that one
Should we replace event.topics[1] (which would be the 'from' address in Transfer event) with the address we want to index by?

What's the correct approach?
Perhaps a more unified approach comparable to the web3 one is interesting ?

Thanks in advance.

@kyriediculous
Copy link
Author

kyriediculous commented Jun 18, 2018

#So from what I've found this is the correct answer I think:

let event = this.contract.interface.events.Transfer
event.topics[1] = keccak256(wallet.address)
const logs = provider.getLogs({
fromBlock: 1,
toBlock: 'latest',
address: 0x..... ,
topics: event.topics
})

If we want to filter by both sender and recipient we would add event.topics[2] = keccak256(recipientAddress)

Is it possible to update the documentation for this perhaps?

I turned this into a semi-generic function. Need to add errors on contract, wallet or eventName null.


/** retrieves contract events
* @param {Object} contract  - contract to get events from
* @param {string} eventName - Name of the event to get logs from
* @param {Array} extraTopics - Array of extra filtering options , in the exact order as the ABI specifies for the event (indexed event args)
* @param {number} fromBlock - block to start lookup from
* @param (number) toBlock - last block to end lookup at
* @returns logs - Parsed event logs
*/

async function _getEvents(contract, eventName, wallet, extraTopics = null, fromBlock = 0, toBlock = 'latest') {
  try {
    //get Provider
    const provider = initProvider()
    //Get event
    let event = contract.interface.events[eventName]
     if (extraTopics!== null) {
      event.topics[1] = keccak256(wallet.address)
      for (let i = 0; i < extraTopics.length; i++) {
        event.topics[i+1] = keccak256(extraTopics[i])
      }
    }
    let logs = await provider.getLogs({
      fromBlock,
      toBlock,
      address: contract.address,
      topics: event.topics
    })
    return logs.map(log => event.parse(log.topics, log.data))
  } catch (err) {
    throw new Error ("Something went wrong while retrieving event: " + err)
  }
}

@ricmoo
Copy link
Member

ricmoo commented Jul 8, 2018

A simpler method is coming in v4 (the TypeScript branch) which should address this. I’m just updat Ng the documentation n now and it should be available this week. :)

@ricmoo ricmoo added enhancement New feature or improvement. v4.0 labels Jul 8, 2018
@kyriediculous
Copy link
Author

Cool cant wait to check it out!

@ricmoo
Copy link
Member

ricmoo commented Jul 9, 2018

One more note regarding the to and from addresses; you do not want to hash them, since they fit, so you just have to pad it with 0’s.

@ricmoo
Copy link
Member

ricmoo commented Jul 18, 2018

This now works in the v4 (npm install ethers@next).

let filterFromMe = contract.filters.Transfer(myAddress, null);
let filterToMe = contract.filters.Transfer(null, myAddress);
let handleEvent = function(from, to) {
};
contract.on(filterFromMe, handleEvent);
contract.on(filterToMe, handleEvent);

A future (backwards compatible) feature will be coming to create OR patterns, where you would be able to do contract.filters.Transfer(OR(myAddress, null), OR(myAddress, null)) to accomplish this in one event, but for now the above should satisfy most peoples needs. :)

@kyriediculous
Copy link
Author

kyriediculous commented Sep 14, 2018

@ricmoo is there also the possibility of browsing for events through the provider prototype? The documentation isn't really clear there.

This might seem like an unnecessary feature but to me it's necessary when browsing for events when the contract address is unknown or we have multiple contract addresses to fetch events from with the same signature (eg getting ALL ERC20 transfer events from ALL tokens)

something similar to:

event myEvent(address indexed _address, uint _something, bytes32  _somethingElse);
eth.filter({
fromBlock: 1,
toBlock: 'latest',
topics: [sha3("myEvent(address, uint, bytes32)", sha3(address)]
})

Where we swap the 'eth' for provider prototype perhaps?

I'll try to come up with something as for some reason I'm getting errors with web3 when ethers is already installed. Weird, I didn't know web3 used ethers for ABI Encoding? -> web3/web3.js#1946

@kyriediculous
Copy link
Author

kyriediculous commented Sep 14, 2018

Sorry, my bad it's already there. It just took me a while to realise I have to encode manually and can not use the ABI since no contract address is known (results in ENS error)

Edit: Or use the Interface I just found out about !

export async function submissionsFrom(userAddress) {
  const event = (new Interface(Bounty.abi)).events.logSubmission
  event.topics[1] = keccak256(userAddress)
   const logs = await this.provider.getLogs({
    fromBlock: 1,
    toBlock: 'latest',
    topics: event.topics
  })
  return logs.map(log => event.parse(log.topics, log.data)) 
}

Regarding hashing the addresses, it works but you're probably right (as always!). Do they get fit automatically like function params in solidity or do I have to add the padding manually here?

@mrwillis
Copy link

For clarity, does the filters.[EVENTNAME] method only work with indexed (in Solidity) parameters?

@ricmoo
Copy link
Member

ricmoo commented Sep 28, 2018

Yes, only indexed parameters are placed in topics by the EVM.

It would certainly be possible though, for me to do client side filtering on non-indexed parameters.

I would need to think more about that, since it would need some way of communicating that the filter is not saving bandwidth... interesting idea though, thanks! :)

@mrwillis
Copy link

mrwillis commented Oct 3, 2018

Is it possible to apply the the v4 filters to provider.getLogs to get historical events? So perhaps something like:

let filterFromMe = contract.filters.Transfer(myAddress, null);
const logs = await provider.getLogs(filterFromMe);
... decode

Or perhaps I need to use the existing filter like

const filter = {
  address: contract.address,
  fromBlock: 0,
  topics: [contract.interface.events.Transfer.topic]
};

And somehow manipulate the topics with a nested array? (my hunch)

Thanks :)

@ricmoo
Copy link
Member

ricmoo commented Oct 3, 2018

That will totally work, and is how it is handled internally:

let filterFromMe = contract.filters.Transfer(myAddress, null);
filterFromMe.fromBlock = 0;

let logs = await provider.getLogs(filterFromMe);

More advanced filters are coming soon, which will allow:

// Check from is either myAddress OR otherAddress
let filterFromMe = contract.filters.Transfer(OR(myAddress, otherAddress), null);

I'm just trying to figure out the best way to add OR to the API. :)

@mrwillis
Copy link

mrwillis commented Oct 4, 2018

Thanks @ricmoo. I tried it and it works fine. I think there is a slight issue with the typings on 4.0.1. The EventFilter which is contract.filters.Transfer(myAddress, null) is type EventFilter which doesn't have fromBlock on it:

export declare type EventFilter = {
    address?: string;
    topics?: Array<string>;
};

Perhaps instead it should be

export declare type EventFilter = {
    address?: string;
    topics?: Array<string>;
    fromBlock?: BlockTag;
    toBlock?: BLockTag
};

@naddison36
Copy link

@ricmoo did you end up implementing the more advanced OR event filtering?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or improvement.
Projects
None yet
Development

No branches or pull requests

4 participants