From c18e52f9187e5c692e97adb6dacadb8f9c276919 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Tue, 2 Jun 2020 14:33:21 +0300 Subject: [PATCH] contract: simplify lifetimes --- ethers-contract/src/contract.rs | 36 ++++++++------- ethers-contract/src/event.rs | 76 ++++++++++++++++++------------- ethers-contract/tests/contract.rs | 45 ++++++++++++++++++ 3 files changed, 110 insertions(+), 47 deletions(-) diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs index 3fbbfd08..05250120 100644 --- a/ethers-contract/src/contract.rs +++ b/ethers-contract/src/contract.rs @@ -44,18 +44,15 @@ where } } - /// Returns an `Event` builder for the provided event name. If there are - /// multiple functions with the same name due to overloading, consider using - /// the `method_hash` method instead, since this will use the first match. - pub fn event<'b, D: Detokenize>(&'a self, name: &str) -> Result, Error> - where - 'a: 'b, - { + /// Returns an `Event` builder for the provided event name. + pub fn event(&self, name: &str) -> Result, Error> { // get the event's full name let event = self.abi.event(name)?; Ok(Event { provider: &self.client.provider(), - filter: Filter::new().event(&event.abi_signature()), + filter: Filter::new() + .event(&event.abi_signature()) + .address(self.address), event: &event, datatype: PhantomData, }) @@ -113,14 +110,6 @@ where }) } - pub fn address(&self) -> Address { - self.address - } - - pub fn abi(&self) -> &Abi { - &self.abi - } - /// Returns a new contract instance at `address`. /// /// Clones `self` internally @@ -138,6 +127,21 @@ where this.client = client; this } + + /// Returns the contract's address + pub fn address(&self) -> Address { + self.address + } + + /// Returns a reference to the contract's ABI + pub fn abi(&self) -> &Abi { + &self.abi + } + + /// Returns a reference to the contract's client + pub fn client(&self) -> &Client { + &self.client + } } /// Utility function for creating a mapping between a unique signature and a diff --git a/ethers-contract/src/event.rs b/ethers-contract/src/event.rs index cbbed4fa..4c28a6b6 100644 --- a/ethers-contract/src/event.rs +++ b/ethers-contract/src/event.rs @@ -4,12 +4,12 @@ use ethers_providers::{JsonRpcClient, Provider}; use ethers_core::{ abi::{Detokenize, Event as AbiEvent, RawLog}, - types::{BlockNumber, Filter, ValueOrArray, H256}, + types::{BlockNumber, Filter, Log, ValueOrArray, H256}, }; use std::{collections::HashMap, marker::PhantomData}; -pub struct Event<'a, 'b, P, D> { +pub struct Event<'a: 'b, 'b, P, D> { pub filter: Filter, pub(crate) provider: &'a Provider

, pub(crate) event: &'b AbiEvent, @@ -17,7 +17,7 @@ pub struct Event<'a, 'b, P, D> { } // TODO: Improve these functions -impl<'a, 'b, P, D: Detokenize> Event<'a, 'b, P, D> { +impl Event<'_, '_, P, D> { #[allow(clippy::wrong_self_convention)] pub fn from_block>(mut self, block: T) -> Self { self.filter.from_block = Some(block.into()); @@ -39,51 +39,65 @@ impl<'a, 'b, P, D: Detokenize> Event<'a, 'b, P, D> { self.filter.topics[1] = Some(topic.into()); self } + + pub fn topic2>>(mut self, topic: T) -> Self { + self.filter.topics[2] = Some(topic.into()); + self + } + + pub fn topic3>>(mut self, topic: T) -> Self { + self.filter.topics[3] = Some(topic.into()); + self + } } -impl<'a, 'b, P, D> Event<'a, 'b, P, D> +impl Event<'_, '_, P, D> where P: JsonRpcClient, D: Detokenize + Clone, { /// Queries the blockchain for the selected filter and returns a vector of matching /// event logs - pub async fn query(self) -> Result, ContractError> { - Ok(self.query_with_hashes().await?.values().cloned().collect()) + pub async fn query(&self) -> Result, ContractError> { + let logs = self.provider.get_logs(&self.filter).await?; + let events = logs + .into_iter() + .map(|log| self.parse_log(log)) + .collect::, ContractError>>()?; + Ok(events) } - /// Queries the blockchain for the selected filter and returns a vector of matching - /// event logs - pub async fn query_with_hashes(self) -> Result, ContractError> { - // get the logs + /// Queries the blockchain for the selected filter and returns a hashmap of + /// txhash -> logs + pub async fn query_with_hashes(&self) -> Result, ContractError> { let logs = self.provider.get_logs(&self.filter).await?; - let events = logs .into_iter() .map(|log| { - // ethabi parses the unindexed and indexed logs together to a - // vector of tokens - let tokens = self - .event - .parse_log(RawLog { - topics: log.topics, - data: log.data.0, - })? - .params - .into_iter() - .map(|param| param.value) - .collect::>(); - - // convert the tokens to the requested datatype - Ok::<_, ContractError>(( - log.transaction_hash.expect("should have tx hash"), - D::from_tokens(tokens)?, - )) + let tx_hash = log.transaction_hash.expect("should have tx hash"); + let event = self.parse_log(log)?; + Ok((tx_hash, event)) }) - .collect::, _>>()?; - + .collect::>()?; Ok(events) } + fn parse_log(&self, log: Log) -> Result { + // ethabi parses the unindexed and indexed logs together to a + // vector of tokens + let tokens = self + .event + .parse_log(RawLog { + topics: log.topics, + data: log.data.0, + })? + .params + .into_iter() + .map(|param| param.value) + .collect::>(); + // convert the tokens to the requested datatype + Ok(D::from_tokens(tokens)?) + } + // TODO: Add filter watchers } diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs index 4792eabf..bae981e9 100644 --- a/ethers-contract/tests/contract.rs +++ b/ethers-contract/tests/contract.rs @@ -1,5 +1,6 @@ use ethers_contract::ContractFactory; use ethers_core::{ + abi::{Detokenize, InvalidOutputType, Token}, types::{Address, H256}, utils::{GanacheBuilder, Solc}, }; @@ -95,4 +96,48 @@ async fn deploy_and_call_contract() { .unwrap(); assert_eq!(last_sender.clone().call().await.unwrap(), client.address()); assert_eq!(get_value.clone().call().await.unwrap(), "hi2"); + + // and we can fetch the events + let logs: Vec = contract + .event("ValueChanged") + .unwrap() + .from_block(0u64) + .query() + .await + .unwrap(); + assert_eq!(logs[0].new_value, "initial value"); + assert_eq!(logs[1].new_value, "hi"); + assert_eq!(logs[2].new_value, "hi2"); + + let logs: Vec = contract2 + .event("ValueChanged") + .unwrap() + .from_block(0u64) + .query() + .await + .unwrap(); + assert_eq!(logs[0].new_value, "initial value"); + assert_eq!(logs.len(), 1); +} + +// Note: We also provide the `abigen` macro for generating these bindings automatically +#[derive(Clone, Debug)] +struct ValueChanged { + author: Address, + old_value: String, + new_value: String, +} + +impl Detokenize for ValueChanged { + fn from_tokens(tokens: Vec) -> Result { + let author: Address = tokens[0].clone().to_address().unwrap(); + let old_value = tokens[1].clone().to_string().unwrap(); + let new_value = tokens[2].clone().to_string().unwrap(); + + Ok(Self { + author, + old_value, + new_value, + }) + } }