diff --git a/CHANGELOG.md b/CHANGELOG.md index 992be8ad..59f25388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Unreleased +- An `Event` builder can be instantiated specifying the event filter type, without the need to instantiate a contract. - Add 'ethers_core::types::OpCode' and use in 'ethers_core::types::VMOperation' [1857](https://github.com/gakonst/ethers-rs/issues/1857) - Remove rust_decimals dependency for ethers-core - Add support for numbers greater than 2^96 for `ethers_core::utils::parse_units` [#1822](https://github.com/gakonst/ethers-rs/issues/1822) @@ -348,4 +349,4 @@ ### 0.5.3 - Added Time Lagged middleware - [#457](https://github.com/gakonst/ethers-rs/pull/457) + [#457](https://github.com/gakonst/ethers-rs/pull/457) \ No newline at end of file diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs index 2a4e2c8c..7da01b7c 100644 --- a/ethers-contract/src/contract.rs +++ b/ethers-contract/src/contract.rs @@ -191,6 +191,19 @@ impl Contract { } } +impl Contract { + /// 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<'a, D: EthEvent>(client: &'a Arc) -> Event<'a, M, D> { + Event { + provider: client, + filter: Filter::new().event(&D::abi_signature()), + datatype: PhantomData, + } + } +} + impl Contract { /// Creates a new contract from the provided client, abi and address pub fn new( diff --git a/ethers-contract/src/event.rs b/ethers-contract/src/event.rs index b4a922d4..23864128 100644 --- a/ethers-contract/src/event.rs +++ b/ethers-contract/src/event.rs @@ -2,8 +2,8 @@ use crate::{log::LogMeta, stream::EventStream, ContractError, EthLogDecode}; use ethers_core::{ - abi::{Detokenize, RawLog}, - types::{BlockNumber, Filter, Log, Topic, H256}, + abi::{Address, Detokenize, RawLog}, + types::{BlockNumber, Filter, Log, Topic, ValueOrArray, H256}, }; use ethers_providers::{FilterWatcher, Middleware, PubsubClient, SubscriptionStream}; use std::{borrow::Cow, marker::PhantomData}; @@ -108,6 +108,12 @@ impl Event<'_, M, D> { self.filter.topics[3] = Some(topic.into()); self } + + /// Sets the filter's address. + pub fn address(mut self, address: ValueOrArray
) -> Self { + self.filter = self.filter.address(address); + self + } } impl<'a, M, D> Event<'a, M, D> diff --git a/ethers-contract/tests/it/contract.rs b/ethers-contract/tests/it/contract.rs index 5968b19d..0c28bcde 100644 --- a/ethers-contract/tests/it/contract.rs +++ b/ethers-contract/tests/it/contract.rs @@ -6,7 +6,7 @@ use ethers_core::types::{Filter, ValueOrArray, H256}; #[cfg(not(feature = "celo"))] mod eth_tests { use super::*; - use ethers_contract::{LogMeta, Multicall, MulticallVersion}; + use ethers_contract::{EthEvent, LogMeta, Multicall, MulticallVersion}; use ethers_core::{ abi::{encode, Detokenize, Token, Tokenizable}, types::{transaction::eip712::Eip712, Address, BlockId, Bytes, I256, U256}, @@ -324,6 +324,21 @@ mod eth_tests { assert_eq!(log_2.address, contract_2.address()); } + #[tokio::test] + async fn build_event_of_type() { + abigen!( + AggregatorInterface, + r#"[ + event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt) + ]"#, + ); + + let anvil = Anvil::new().spawn(); + let client = connect(&anvil, 0); + let event = ethers_contract::Contract::event_of_type::(&client); + assert_eq!(event.filter, Filter::new().event(&AnswerUpdatedFilter::abi_signature())); + } + #[tokio::test] async fn signer_on_node() { let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol"); diff --git a/examples/subscribe_events_by_type.rs b/examples/subscribe_events_by_type.rs new file mode 100644 index 00000000..9a3aa56f --- /dev/null +++ b/examples/subscribe_events_by_type.rs @@ -0,0 +1,52 @@ +use ethers::{contract::Contract, prelude::*}; +use std::{error::Error, sync::Arc}; +abigen!( + AggregatorInterface, + r#"[ + event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt) + ]"#, +); + +const PRICE_FEED_1: &str = "0x7de93682b9b5d80d45cd371f7a14f74d49b0914c"; +const PRICE_FEED_2: &str = "0x0f00392fcb466c0e4e4310d81b941e07b4d5a079"; +const PRICE_FEED_3: &str = "0xebf67ab8cff336d3f609127e8bbf8bd6dd93cd81"; + +/// Subscribe to a typed event stream without requiring a `Contract` instance. +/// In this example we subscribe Chainlink price feeds and filter out them +/// by address. +/// ------------------------------------------------------------------------------- +/// In order to run this example you need to include Ws and TLS features +/// Run this example with +/// `cargo run -p ethers --example subscribe_events_by_type --features="ws","rustls"` +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = get_client().await; + let client = Arc::new(client); + + // 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) + .from_block(16022082) + .to_block(16022282) + .address(ValueOrArray::Array(vec![ + PRICE_FEED_1.parse()?, + PRICE_FEED_2.parse()?, + PRICE_FEED_3.parse()?, + ])); + + let mut stream = event.subscribe_with_meta().await?; + + // Note that `log` has type AnswerUpdatedFilter + while let Some(Ok((log, meta))) = stream.next().await { + println!("{:?}", log); + println!("{:?}", meta) + } + + Ok(()) +} + +async fn get_client() -> Provider { + Provider::::connect("wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27") + .await + .unwrap() +}