contract: simplify lifetimes

This commit is contained in:
Georgios Konstantopoulos 2020-06-02 14:33:21 +03:00
parent 56d22c0360
commit c18e52f918
No known key found for this signature in database
GPG Key ID: FA607837CD26EDBC
3 changed files with 110 additions and 47 deletions

View File

@ -44,18 +44,15 @@ where
} }
} }
/// Returns an `Event` builder for the provided event name. If there are /// Returns an `Event` builder for the provided event name.
/// multiple functions with the same name due to overloading, consider using pub fn event<D: Detokenize>(&self, name: &str) -> Result<Event<P, D>, Error> {
/// the `method_hash` method instead, since this will use the first match.
pub fn event<'b, D: Detokenize>(&'a self, name: &str) -> Result<Event<'a, 'b, P, D>, Error>
where
'a: 'b,
{
// get the event's full name // get the event's full name
let event = self.abi.event(name)?; let event = self.abi.event(name)?;
Ok(Event { Ok(Event {
provider: &self.client.provider(), provider: &self.client.provider(),
filter: Filter::new().event(&event.abi_signature()), filter: Filter::new()
.event(&event.abi_signature())
.address(self.address),
event: &event, event: &event,
datatype: PhantomData, 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`. /// Returns a new contract instance at `address`.
/// ///
/// Clones `self` internally /// Clones `self` internally
@ -138,6 +127,21 @@ where
this.client = client; this.client = client;
this 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<P, S> {
&self.client
}
} }
/// Utility function for creating a mapping between a unique signature and a /// Utility function for creating a mapping between a unique signature and a

View File

@ -4,12 +4,12 @@ use ethers_providers::{JsonRpcClient, Provider};
use ethers_core::{ use ethers_core::{
abi::{Detokenize, Event as AbiEvent, RawLog}, abi::{Detokenize, Event as AbiEvent, RawLog},
types::{BlockNumber, Filter, ValueOrArray, H256}, types::{BlockNumber, Filter, Log, ValueOrArray, H256},
}; };
use std::{collections::HashMap, marker::PhantomData}; 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 filter: Filter,
pub(crate) provider: &'a Provider<P>, pub(crate) provider: &'a Provider<P>,
pub(crate) event: &'b AbiEvent, pub(crate) event: &'b AbiEvent,
@ -17,7 +17,7 @@ pub struct Event<'a, 'b, P, D> {
} }
// TODO: Improve these functions // TODO: Improve these functions
impl<'a, 'b, P, D: Detokenize> Event<'a, 'b, P, D> { impl<P, D: Detokenize> Event<'_, '_, P, D> {
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self { pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
self.filter.from_block = Some(block.into()); self.filter.from_block = Some(block.into());
@ -39,28 +39,50 @@ impl<'a, 'b, P, D: Detokenize> Event<'a, 'b, P, D> {
self.filter.topics[1] = Some(topic.into()); self.filter.topics[1] = Some(topic.into());
self self
} }
pub fn topic2<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
self.filter.topics[2] = Some(topic.into());
self
} }
impl<'a, 'b, P, D> Event<'a, 'b, P, D> pub fn topic3<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
self.filter.topics[3] = Some(topic.into());
self
}
}
impl<P, D> Event<'_, '_, P, D>
where where
P: JsonRpcClient, P: JsonRpcClient,
D: Detokenize + Clone, D: Detokenize + Clone,
{ {
/// Queries the blockchain for the selected filter and returns a vector of matching /// Queries the blockchain for the selected filter and returns a vector of matching
/// event logs /// event logs
pub async fn query(self) -> Result<Vec<D>, ContractError> { pub async fn query(&self) -> Result<Vec<D>, ContractError> {
Ok(self.query_with_hashes().await?.values().cloned().collect()) let logs = self.provider.get_logs(&self.filter).await?;
let events = logs
.into_iter()
.map(|log| self.parse_log(log))
.collect::<Result<Vec<_>, ContractError>>()?;
Ok(events)
} }
/// Queries the blockchain for the selected filter and returns a vector of matching /// Queries the blockchain for the selected filter and returns a hashmap of
/// event logs /// txhash -> logs
pub async fn query_with_hashes(self) -> Result<HashMap<H256, D>, ContractError> { pub async fn query_with_hashes(&self) -> Result<HashMap<H256, D>, ContractError> {
// get the logs
let logs = self.provider.get_logs(&self.filter).await?; let logs = self.provider.get_logs(&self.filter).await?;
let events = logs let events = logs
.into_iter() .into_iter()
.map(|log| { .map(|log| {
let tx_hash = log.transaction_hash.expect("should have tx hash");
let event = self.parse_log(log)?;
Ok((tx_hash, event))
})
.collect::<Result<_, ContractError>>()?;
Ok(events)
}
fn parse_log(&self, log: Log) -> Result<D, ContractError> {
// ethabi parses the unindexed and indexed logs together to a // ethabi parses the unindexed and indexed logs together to a
// vector of tokens // vector of tokens
let tokens = self let tokens = self
@ -73,16 +95,8 @@ where
.into_iter() .into_iter()
.map(|param| param.value) .map(|param| param.value)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// convert the tokens to the requested datatype // convert the tokens to the requested datatype
Ok::<_, ContractError>(( Ok(D::from_tokens(tokens)?)
log.transaction_hash.expect("should have tx hash"),
D::from_tokens(tokens)?,
))
})
.collect::<Result<HashMap<H256, D>, _>>()?;
Ok(events)
} }
// TODO: Add filter watchers // TODO: Add filter watchers

View File

@ -1,5 +1,6 @@
use ethers_contract::ContractFactory; use ethers_contract::ContractFactory;
use ethers_core::{ use ethers_core::{
abi::{Detokenize, InvalidOutputType, Token},
types::{Address, H256}, types::{Address, H256},
utils::{GanacheBuilder, Solc}, utils::{GanacheBuilder, Solc},
}; };
@ -95,4 +96,48 @@ async fn deploy_and_call_contract() {
.unwrap(); .unwrap();
assert_eq!(last_sender.clone().call().await.unwrap(), client.address()); assert_eq!(last_sender.clone().call().await.unwrap(), client.address());
assert_eq!(get_value.clone().call().await.unwrap(), "hi2"); assert_eq!(get_value.clone().call().await.unwrap(), "hi2");
// and we can fetch the events
let logs: Vec<ValueChanged> = 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<ValueChanged> = 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<Token>) -> Result<ValueChanged, InvalidOutputType> {
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,
})
}
} }