feat: Instantiate an event builder without a contract instance (#1882)

* Build an `Event` without requiring a contract instance

* Contract unit test

* Function to set Event address

* Example

* Typo to improve readability

* CHANGELOG

* cargo +nightly fmt

* FIX conflict

* review: applied Address alias

Co-authored-by: Andrea Simeoni <>
Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
Andrea Simeoni 2022-11-22 22:15:36 +01:00 committed by GitHub
parent f4b6178332
commit 845aa4920f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 91 additions and 4 deletions

View File

@ -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)

View File

@ -191,6 +191,19 @@ impl<M> Contract<M> {
}
}
impl<M: Middleware> Contract<M> {
/// 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<M>) -> Event<'a, M, D> {
Event {
provider: client,
filter: Filter::new().event(&D::abi_signature()),
datatype: PhantomData,
}
}
}
impl<M: Middleware> Contract<M> {
/// Creates a new contract from the provided client, abi and address
pub fn new(

View File

@ -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<M, D: EthLogDecode> Event<'_, M, D> {
self.filter.topics[3] = Some(topic.into());
self
}
/// Sets the filter's address.
pub fn address(mut self, address: ValueOrArray<Address>) -> Self {
self.filter = self.filter.address(address);
self
}
}
impl<'a, M, D> Event<'a, M, D>

View File

@ -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::<AnswerUpdatedFilter>(&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");

View File

@ -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<dyn Error>> {
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::<AnswerUpdatedFilter>(&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<Ws> {
Provider::<Ws>::connect("wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27")
.await
.unwrap()
}