From 0c16eb971dcd15270fea72d8dc27517ffe738ded Mon Sep 17 00:00:00 2001 From: James Prestwich <10149425+prestwich@users.noreply.github.com> Date: Mon, 13 Feb 2023 20:14:38 -0500 Subject: [PATCH] Prestwich/event no lifetime (#2105) * refactor(breaking): remove lifetime from Event * fix: example updated to use new event --- CHANGELOG.md | 3 + .../src/contract/events.rs | 10 +- ethers-contract/src/contract.rs | 46 ++++---- ethers-contract/src/event.rs | 108 ++++++++++++------ ethers-contract/tests/it/contract.rs | 2 +- .../src/transformer/ds_proxy/factory.rs | 4 +- .../examples/subscribe_events_by_type.rs | 2 +- 7 files changed, 109 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ca82ecb..975a3c51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,6 +120,7 @@ ### Unreleased +- Abigen now generates events with new `` generic pattern [#2103](https://github.com/gakonst/ethers-rs/pull/2103) - Fix Cargo.toml generation issue that could cause dependency conflicts [#1852](https://github.com/gakonst/ethers-rs/pull/1852) - Use corresponding rust structs for event fields if they're solidity structs [#1674](https://github.com/gakonst/ethers-rs/pull/1674) - Add `ContractFilter` to filter contracts in `MultiAbigen` [#1564](https://github.com/gakonst/ethers-rs/pull/1564) @@ -300,6 +301,8 @@ ### Unreleased +- (Breaking) Make `Event` objects generic over borrow & remove lifetime + [#2105](https://github.com/gakonst/ethers-rs/pull/2105) - Make `Factory` objects generic over the borrow trait, to allow non-arc mware [#2103](https://github.com/gakonst/ethers-rs/pull/2103) - Make `Contract` objects generic over the borrow trait, to allow non-arc mware diff --git a/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/ethers-contract/ethers-contract-abigen/src/contract/events.rs index f0e1d778..1dce3ef7 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/events.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/events.rs @@ -127,7 +127,7 @@ impl Context { quote! { /// Returns an [`Event`](#ethers_contract::builders::Event) builder for all events of this contract - pub fn events(&self) -> #ethers_contract::builders::Event { + pub fn events(&self) -> #ethers_contract::builders::Event, M, #ty> { self.0.event_with_filter(Default::default()) } } @@ -235,7 +235,7 @@ impl Context { quote! { #[doc = #doc_str] - pub fn #function_name(&self) -> #ethers_contract::builders::Event { + pub fn #function_name(&self) -> #ethers_contract::builders::Event, M, #struct_name> { self.0.event() } } @@ -406,7 +406,7 @@ mod tests { #[doc = "Gets the contract's `Transfer` event"] pub fn transfer_event_filter( &self - ) -> ::ethers_contract::builders::Event { + ) -> ::ethers_contract::builders::Event, M, TransferEventFilter> { self.0.event() } }); @@ -425,7 +425,9 @@ mod tests { let cx = test_context(); assert_quote!(cx.expand_filter(&event), { #[doc = "Gets the contract's `Transfer` event"] - pub fn transfer_filter(&self) -> ::ethers_contract::builders::Event { + pub fn transfer_filter( + &self, + ) -> ::ethers_contract::builders::Event, M, TransferFilter> { self.0.event() } }); diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs index b842b120..2fa4f888 100644 --- a/ethers-contract/src/contract.rs +++ b/ethers-contract/src/contract.rs @@ -243,11 +243,12 @@ where /// Returns an [`Event`](crate::builders::Event) builder for the provided event. /// This function operates in a static context, then it does not require a `self` /// to reference to instantiate an [`Event`](crate::builders::Event) builder. - pub fn event_of_type(client: &M) -> Event { + pub fn event_of_type(client: B) -> Event { Event { provider: client, filter: Filter::new().event(&D::abi_signature()), datatype: PhantomData, + _m: PhantomData, } } } @@ -262,27 +263,6 @@ where Self { base_contract: abi.into(), client, address: address.into(), _m: PhantomData } } - /// Returns an [`Event`](crate::builders::Event) builder for the provided event. - pub fn event(&self) -> Event { - self.event_with_filter(Filter::new().event(&D::abi_signature())) - } - - /// Returns an [`Event`](crate::builders::Event) builder with the provided filter. - pub fn event_with_filter(&self, filter: Filter) -> Event { - Event { - provider: self.client.borrow(), - filter: filter.address(ValueOrArray::Value(self.address)), - datatype: PhantomData, - } - } - - /// Returns an [`Event`](crate::builders::Event) builder with the provided name. - pub fn event_for_name(&self, name: &str) -> Result, Error> { - // get the event's full name - let event = self.base_contract.abi.event(name)?; - Ok(self.event_with_filter(Filter::new().event(&event.abi_signature()))) - } - /// Returns a new contract instance using the provided client /// /// Clones `self` internally @@ -321,6 +301,28 @@ where B: Clone + Borrow, M: Middleware, { + /// Returns an [`Event`](crate::builders::Event) builder with the provided filter. + pub fn event_with_filter(&self, filter: Filter) -> Event { + Event { + provider: self.client.clone(), + filter: filter.address(ValueOrArray::Value(self.address)), + datatype: PhantomData, + _m: PhantomData, + } + } + + /// Returns an [`Event`](crate::builders::Event) builder for the provided event. + pub fn event(&self) -> Event { + self.event_with_filter(Filter::new().event(&D::abi_signature())) + } + + /// Returns an [`Event`](crate::builders::Event) builder with the provided name. + pub fn event_for_name(&self, name: &str) -> Result, Error> { + // get the event's full name + let event = self.base_contract.abi.event(name)?; + Ok(self.event_with_filter(Filter::new().event(&event.abi_signature()))) + } + fn method_func( &self, function: &Function, diff --git a/ethers-contract/src/event.rs b/ethers-contract/src/event.rs index c050749e..df529e6d 100644 --- a/ethers-contract/src/event.rs +++ b/ethers-contract/src/event.rs @@ -2,11 +2,22 @@ use crate::{log::LogMeta, stream::EventStream, ContractError, EthLogDecode}; use ethers_core::{ - abi::{Address, Detokenize, RawLog}, + abi::{Address, Detokenize, Error as AbiError, RawLog}, types::{BlockNumber, Filter, Log, Topic, ValueOrArray, H256}, }; use ethers_providers::{FilterWatcher, Middleware, PubsubClient, SubscriptionStream}; -use std::{borrow::Cow, marker::PhantomData}; +use std::{ + borrow::{Borrow, Cow}, + marker::PhantomData, +}; + +/// Attempt to parse a log into a specific output type. +pub fn parse_log(log: Log) -> std::result::Result +where + D: EthLogDecode, +{ + D::decode_log(&RawLog { topics: log.topics, data: log.data.to_vec() }) +} /// A trait for implementing event bindings pub trait EthEvent: Detokenize + Send + Sync { @@ -31,12 +42,14 @@ pub trait EthEvent: Detokenize + Send + Sync { fn is_anonymous() -> bool; /// Returns an Event builder for the ethereum event represented by this types ABI signature. - fn new(filter: Filter, provider: &M) -> Event + fn new(filter: Filter, provider: B) -> Event where Self: Sized, + B: Borrow, + M: Middleware, { let filter = filter.event(&Self::abi_signature()); - Event { filter, provider, datatype: PhantomData } + Event { filter, provider, datatype: PhantomData, _m: PhantomData } } } @@ -53,16 +66,22 @@ impl EthLogDecode for T { /// Helper for managing the event filter before querying or streaming its logs #[derive(Debug)] #[must_use = "event filters do nothing unless you `query` or `stream` them"] -pub struct Event<'a, M, D> { +pub struct Event { /// The event filter's state pub filter: Filter, - pub(crate) provider: &'a M, + pub(crate) provider: B, /// Stores the event datatype pub(crate) datatype: PhantomData, + pub(crate) _m: PhantomData, } // TODO: Improve these functions -impl Event<'_, M, D> { +impl Event +where + B: Borrow, + M: Middleware, + D: EthLogDecode, +{ /// Sets the filter's `from` block #[allow(clippy::wrong_self_convention)] pub fn from_block>(mut self, block: T) -> Self { @@ -116,8 +135,9 @@ impl Event<'_, M, D> { } } -impl<'a, M, D> Event<'a, M, D> +impl Event where + B: Borrow, M: Middleware, D: EthLogDecode, { @@ -161,40 +181,49 @@ where /// # } /// ``` pub async fn stream( - &'a self, + &self, ) -> Result< // Wraps the FilterWatcher with a mapping to the event - EventStream<'a, FilterWatcher<'a, M::Provider, Log>, D, ContractError>, + EventStream<'_, FilterWatcher<'_, M::Provider, Log>, D, ContractError>, ContractError, > { - let filter = - self.provider.watch(&self.filter).await.map_err(ContractError::MiddlewareError)?; - Ok(EventStream::new(filter.id, filter, Box::new(move |log| self.parse_log(log)))) + let filter = self + .provider + .borrow() + .watch(&self.filter) + .await + .map_err(ContractError::MiddlewareError)?; + Ok(EventStream::new(filter.id, filter, Box::new(move |log| Ok(parse_log(log)?)))) } /// As [`Self::stream`], but does not discard [`Log`] metadata. pub async fn stream_with_meta( - &'a self, + &self, ) -> Result< // Wraps the FilterWatcher with a mapping to the event - EventStream<'a, FilterWatcher<'a, M::Provider, Log>, (D, LogMeta), ContractError>, + EventStream<'_, FilterWatcher<'_, M::Provider, Log>, (D, LogMeta), ContractError>, ContractError, > { - let filter = - self.provider.watch(&self.filter).await.map_err(ContractError::MiddlewareError)?; + let filter = self + .provider + .borrow() + .watch(&self.filter) + .await + .map_err(ContractError::MiddlewareError)?; Ok(EventStream::new( filter.id, filter, Box::new(move |log| { let meta = LogMeta::from(&log); - Ok((self.parse_log(log)?, meta)) + Ok((parse_log(log)?, meta)) }), )) } } -impl<'a, M, D> Event<'a, M, D> +impl Event where + B: Borrow, M: Middleware, ::Provider: PubsubClient, D: EthLogDecode, @@ -203,29 +232,31 @@ where /// /// See also [Self::stream()]. pub async fn subscribe( - &'a self, + &self, ) -> Result< // Wraps the SubscriptionStream with a mapping to the event - EventStream<'a, SubscriptionStream<'a, M::Provider, Log>, D, ContractError>, + EventStream<'_, SubscriptionStream<'_, M::Provider, Log>, D, ContractError>, ContractError, > { let filter = self .provider + .borrow() .subscribe_logs(&self.filter) .await .map_err(ContractError::MiddlewareError)?; - Ok(EventStream::new(filter.id, filter, Box::new(move |log| self.parse_log(log)))) + Ok(EventStream::new(filter.id, filter, Box::new(move |log| Ok(parse_log(log)?)))) } pub async fn subscribe_with_meta( - &'a self, + &self, ) -> Result< // Wraps the SubscriptionStream with a mapping to the event - EventStream<'a, SubscriptionStream<'a, M::Provider, Log>, (D, LogMeta), ContractError>, + EventStream<'_, SubscriptionStream<'_, M::Provider, Log>, (D, LogMeta), ContractError>, ContractError, > { let filter = self .provider + .borrow() .subscribe_logs(&self.filter) .await .map_err(ContractError::MiddlewareError)?; @@ -234,25 +265,30 @@ where filter, Box::new(move |log| { let meta = LogMeta::from(&log); - Ok((self.parse_log(log)?, meta)) + Ok((parse_log(log)?, meta)) }), )) } } -impl Event<'_, M, D> +impl Event where + B: Borrow, M: Middleware, D: EthLogDecode, { /// Queries the blockchain for the selected filter and returns a vector of matching /// event logs pub async fn query(&self) -> Result, ContractError> { - let logs = - self.provider.get_logs(&self.filter).await.map_err(ContractError::MiddlewareError)?; + let logs = self + .provider + .borrow() + .get_logs(&self.filter) + .await + .map_err(ContractError::MiddlewareError)?; let events = logs .into_iter() - .map(|log| self.parse_log(log)) + .map(|log| Ok(parse_log(log)?)) .collect::, ContractError>>()?; Ok(events) } @@ -260,20 +296,20 @@ where /// Queries the blockchain for the selected filter and returns a vector of logs /// along with their metadata pub async fn query_with_meta(&self) -> Result, ContractError> { - let logs = - self.provider.get_logs(&self.filter).await.map_err(ContractError::MiddlewareError)?; + let logs = self + .provider + .borrow() + .get_logs(&self.filter) + .await + .map_err(ContractError::MiddlewareError)?; let events = logs .into_iter() .map(|log| { let meta = LogMeta::from(&log); - let event = self.parse_log(log)?; + let event = parse_log(log)?; Ok((event, meta)) }) .collect::>>()?; Ok(events) } - - pub fn parse_log(&self, log: Log) -> Result> { - D::decode_log(&RawLog { topics: log.topics, data: log.data.to_vec() }).map_err(From::from) - } } diff --git a/ethers-contract/tests/it/contract.rs b/ethers-contract/tests/it/contract.rs index 0d3b7a64..b5e85b03 100644 --- a/ethers-contract/tests/it/contract.rs +++ b/ethers-contract/tests/it/contract.rs @@ -410,7 +410,7 @@ mod eth_tests { let anvil = Anvil::new().spawn(); let client = connect(&anvil, 0); - let event = ethers_contract::Contract::event_of_type::(&client); + let event = ethers_contract::Contract::event_of_type::(client); assert_eq!(event.filter, Filter::new().event(&AnswerUpdatedFilter::abi_signature())); } diff --git a/ethers-middleware/src/transformer/ds_proxy/factory.rs b/ethers-middleware/src/transformer/ds_proxy/factory.rs index 71d35043..665d5059 100644 --- a/ethers-middleware/src/transformer/ds_proxy/factory.rs +++ b/ethers-middleware/src/transformer/ds_proxy/factory.rs @@ -98,13 +98,13 @@ mod dsproxyfactory_mod { .expect("method not found (this should never happen)") } ///Gets the contract's `Created` event - pub fn created_filter(&self) -> Event { + pub fn created_filter(&self) -> Event, M, CreatedFilter> { self.0.event() } /// Returns an [`Event`](ethers_contract::builders::Event) builder for all events of this /// contract - pub fn events(&self) -> Event { + pub fn events(&self) -> Event, M, CreatedFilter> { self.0.event_with_filter(Default::default()) } } diff --git a/examples/subscriptions/examples/subscribe_events_by_type.rs b/examples/subscriptions/examples/subscribe_events_by_type.rs index 355404a3..8f935c0b 100644 --- a/examples/subscriptions/examples/subscribe_events_by_type.rs +++ b/examples/subscriptions/examples/subscribe_events_by_type.rs @@ -26,7 +26,7 @@ async fn main() -> Result<(), Box> { // Build an Event by type. We are not tied to a contract instance. We use builder functions to // refine the event filter - let event = Contract::event_of_type::(&client) + let event = Contract::event_of_type::(client) .from_block(16022082) .address(ValueOrArray::Array(vec![ PRICE_FEED_1.parse()?,