feat: add eth filter deserialization and matching (#1389)

* feat: add eth filter deserialization and matching

* chore: rustfmt

* extend test

* add missing conversion
This commit is contained in:
Matthias Seitz 2022-06-17 18:34:16 +02:00 committed by GitHub
parent e1bbcb09fe
commit 69095c15cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1190 additions and 505 deletions

View File

@ -1,10 +1,9 @@
#![allow(clippy::return_self_not_must_use)] #![allow(clippy::return_self_not_must_use)]
use crate::{log::LogMeta, stream::EventStream, ContractError, EthLogDecode}; use crate::{log::LogMeta, stream::EventStream, ContractError, EthLogDecode};
use ethers_core::{ use ethers_core::{
abi::{Detokenize, RawLog}, abi::{Detokenize, RawLog},
types::{BlockNumber, Filter, Log, ValueOrArray, H256}, types::{BlockNumber, Filter, Log, Topic, H256},
}; };
use ethers_providers::{FilterWatcher, Middleware, PubsubClient, SubscriptionStream}; use ethers_providers::{FilterWatcher, Middleware, PubsubClient, SubscriptionStream};
use std::{borrow::Cow, marker::PhantomData}; use std::{borrow::Cow, marker::PhantomData};
@ -87,25 +86,25 @@ impl<M, D: EthLogDecode> Event<'_, M, D> {
} }
/// Sets the filter's 0th topic (typically the event name for non-anonymous events) /// Sets the filter's 0th topic (typically the event name for non-anonymous events)
pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self { pub fn topic0<T: Into<Topic>>(mut self, topic: T) -> Self {
self.filter.topics[0] = Some(topic.into()); self.filter.topics[0] = Some(topic.into());
self self
} }
/// Sets the filter's 1st topic /// Sets the filter's 1st topic
pub fn topic1<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self { pub fn topic1<T: Into<Topic>>(mut self, topic: T) -> Self {
self.filter.topics[1] = Some(topic.into()); self.filter.topics[1] = Some(topic.into());
self self
} }
/// Sets the filter's 2nd topic /// Sets the filter's 2nd topic
pub fn topic2<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self { pub fn topic2<T: Into<Topic>>(mut self, topic: T) -> Self {
self.filter.topics[2] = Some(topic.into()); self.filter.topics[2] = Some(topic.into());
self self
} }
/// Sets the filter's 3rd topic /// Sets the filter's 3rd topic
pub fn topic3<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self { pub fn topic3<T: Into<Topic>>(mut self, topic: T) -> Self {
self.filter.topics[3] = Some(topic.into()); self.filter.topics[3] = Some(topic.into());
self self
} }

View File

@ -533,8 +533,8 @@ pub(crate) fn is_likely_tuple_not_uint8(kind: &ParamType, type_str: &str) -> boo
pub fn contains_uint8(kind: &ParamType) -> bool { pub fn contains_uint8(kind: &ParamType) -> bool {
match kind { match kind {
ParamType::Uint(8) => true, ParamType::Uint(8) => true,
ParamType::Array(kind) => contains_uint8(&*kind), ParamType::Array(kind) => contains_uint8(kind),
ParamType::FixedArray(kind, _) => contains_uint8(&*kind), ParamType::FixedArray(kind, _) => contains_uint8(kind),
ParamType::Tuple(tuple) => tuple.iter().any(contains_uint8), ParamType::Tuple(tuple) => tuple.iter().any(contains_uint8),
_ => false, _ => false,
} }

File diff suppressed because it is too large Load Diff

View File

@ -293,7 +293,7 @@ impl I256 {
/// Convert from a decimal string. /// Convert from a decimal string.
pub fn from_dec_str(value: &str) -> Result<Self, ParseI256Error> { pub fn from_dec_str(value: &str) -> Result<Self, ParseI256Error> {
let (sign, value) = match value.as_bytes().get(0) { let (sign, value) = match value.as_bytes().first() {
Some(b'+') => (Sign::Positive, &value[1..]), Some(b'+') => (Sign::Positive, &value[1..]),
Some(b'-') => (Sign::Negative, &value[1..]), Some(b'-') => (Sign::Negative, &value[1..]),
_ => (Sign::Positive, value), _ => (Sign::Positive, value),
@ -308,7 +308,7 @@ impl I256 {
/// Convert from a hexadecimal string. /// Convert from a hexadecimal string.
pub fn from_hex_str(value: &str) -> Result<Self, ParseI256Error> { pub fn from_hex_str(value: &str) -> Result<Self, ParseI256Error> {
let (sign, value) = match value.as_bytes().get(0) { let (sign, value) = match value.as_bytes().first() {
Some(b'+') => (Sign::Positive, &value[1..]), Some(b'+') => (Sign::Positive, &value[1..]),
Some(b'-') => (Sign::Negative, &value[1..]), Some(b'-') => (Sign::Negative, &value[1..]),
_ => (Sign::Positive, value), _ => (Sign::Positive, value),

View File

@ -1,12 +1,6 @@
// Adapted from https://github.com/tomusdrw/rust-web3/blob/master/src/types/log.rs // Adapted from https://github.com/tomusdrw/rust-web3/blob/master/src/types/log.rs
use crate::{ use crate::types::{Address, Bytes, H256, U256, U64};
types::{Address, BlockNumber, Bytes, H160, H256, U256, U64}, use serde::{Deserialize, Serialize};
utils::keccak256,
};
use serde::{
de::DeserializeOwned, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer,
};
use std::ops::{Range, RangeFrom, RangeTo};
/// A log produced by a transaction. /// A log produced by a transaction.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
@ -74,490 +68,4 @@ impl rlp::Encodable for Log {
} }
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FilterBlockOption {
Range { from_block: Option<BlockNumber>, to_block: Option<BlockNumber> },
AtBlockHash(H256),
}
impl From<BlockNumber> for FilterBlockOption {
fn from(block: BlockNumber) -> Self {
let block = Some(block);
FilterBlockOption::Range { from_block: block, to_block: block }
}
}
impl From<U64> for FilterBlockOption {
fn from(block: U64) -> Self {
BlockNumber::from(block).into()
}
}
impl From<u64> for FilterBlockOption {
fn from(block: u64) -> Self {
BlockNumber::from(block).into()
}
}
impl<T: Into<BlockNumber>> From<Range<T>> for FilterBlockOption {
fn from(r: Range<T>) -> Self {
let from_block = Some(r.start.into());
let to_block = Some(r.end.into());
FilterBlockOption::Range { from_block, to_block }
}
}
impl<T: Into<BlockNumber>> From<RangeTo<T>> for FilterBlockOption {
fn from(r: RangeTo<T>) -> Self {
let to_block = Some(r.end.into());
FilterBlockOption::Range { from_block: Some(BlockNumber::Earliest), to_block }
}
}
impl<T: Into<BlockNumber>> From<RangeFrom<T>> for FilterBlockOption {
fn from(r: RangeFrom<T>) -> Self {
let from_block = Some(r.start.into());
FilterBlockOption::Range { from_block, to_block: Some(BlockNumber::Latest) }
}
}
impl From<H256> for FilterBlockOption {
fn from(hash: H256) -> Self {
FilterBlockOption::AtBlockHash(hash)
}
}
impl Default for FilterBlockOption {
fn default() -> Self {
FilterBlockOption::Range { from_block: None, to_block: None }
}
}
impl FilterBlockOption {
#[must_use]
pub fn set_from_block(&self, block: BlockNumber) -> Self {
let to_block =
if let FilterBlockOption::Range { to_block, .. } = self { *to_block } else { None };
FilterBlockOption::Range { from_block: Some(block), to_block }
}
#[must_use]
pub fn set_to_block(&self, block: BlockNumber) -> Self {
let from_block =
if let FilterBlockOption::Range { from_block, .. } = self { *from_block } else { None };
FilterBlockOption::Range { from_block, to_block: Some(block) }
}
#[must_use]
pub fn set_hash(&self, hash: H256) -> Self {
FilterBlockOption::AtBlockHash(hash)
}
}
/// Filter for
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct Filter {
/// Filter block options, specifying on which blocks the filter should
/// match.
// https://eips.ethereum.org/EIPS/eip-234
pub block_option: FilterBlockOption,
/// Address
address: Option<ValueOrArray<Address>>,
/// Topics
// TODO: We could improve the low level API here by using ethabi's RawTopicFilter
// and/or TopicFilter
pub topics: [Option<ValueOrArray<H256>>; 4],
}
impl Serialize for Filter {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_struct("Filter", 5)?;
match self.block_option {
FilterBlockOption::Range { from_block, to_block } => {
if let Some(ref from_block) = from_block {
s.serialize_field("fromBlock", from_block)?;
}
if let Some(ref to_block) = to_block {
s.serialize_field("toBlock", to_block)?;
}
}
FilterBlockOption::AtBlockHash(ref h) => s.serialize_field("blockHash", h)?,
}
if let Some(ref address) = self.address {
s.serialize_field("address", address)?;
}
let mut filtered_topics = Vec::new();
for i in 0..4 {
if self.topics[i].is_some() {
filtered_topics.push(&self.topics[i]);
} else {
// TODO: This can be optimized
if self.topics[i + 1..].iter().any(|x| x.is_some()) {
filtered_topics.push(&None);
}
}
}
s.serialize_field("topics", &filtered_topics)?;
s.end()
}
}
impl Filter {
pub fn new() -> Self {
Self::default()
}
/// Sets the inner filter object
///
/// *NOTE:* ranges are always inclusive
///
/// # Examples
///
/// Match only a specific block
///
/// ```rust
/// # use ethers_core::types::Filter;
/// # fn main() {
/// let filter = Filter::new().select(69u64);
/// # }
/// ```
/// This is the same as `Filter::new().from_block(1337u64).to_block(1337u64)`
///
/// Match the latest block only
///
/// ```rust
/// # use ethers_core::types::{Filter, BlockNumber};
/// # fn main() {
/// let filter = Filter::new().select(BlockNumber::Latest);
/// # }
/// ```
///
/// Match a block by its hash
///
/// ```rust
/// # use ethers_core::types::{Filter, H256};
/// # fn main() {
/// let filter = Filter::new().select(H256::zero());
/// # }
/// ```
/// This is the same as `at_block_hash`
///
/// Match a range of blocks
///
/// ```rust
/// # use ethers_core::types::{Filter, H256};
/// # fn main() {
/// let filter = Filter::new().select(0u64..100u64);
/// # }
/// ```
///
/// Match all blocks in range `(1337..BlockNumber::Latest)`
///
/// ```rust
/// # use ethers_core::types::{Filter, H256};
/// # fn main() {
/// let filter = Filter::new().select(1337u64..);
/// # }
/// ```
///
/// Match all blocks in range `(BlockNumber::Earliest..1337)`
///
/// ```rust
/// # use ethers_core::types::{Filter, H256};
/// # fn main() {
/// let filter = Filter::new().select(..1337u64);
/// # }
/// ```
#[must_use]
pub fn select(mut self, filter: impl Into<FilterBlockOption>) -> Self {
self.block_option = filter.into();
self
}
#[allow(clippy::wrong_self_convention)]
#[must_use]
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
self.block_option = self.block_option.set_from_block(block.into());
self
}
#[allow(clippy::wrong_self_convention)]
#[must_use]
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
self.block_option = self.block_option.set_to_block(block.into());
self
}
#[allow(clippy::wrong_self_convention)]
#[must_use]
pub fn at_block_hash<T: Into<H256>>(mut self, hash: T) -> Self {
self.block_option = self.block_option.set_hash(hash.into());
self
}
/// Sets the inner filter object
///
/// *NOTE:* ranges are always inclusive
///
/// # Examples
///
/// Match only a specific address `("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF")`
///
/// ```rust
/// # use ethers_core::types::{Filter, Address};
/// # fn main() {
/// let filter = Filter::new().address("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::<Address>().unwrap());
/// # }
/// ```
///
/// Match all addresses in array `(vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF",
/// "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8"])`
///
/// ```rust
/// # use ethers_core::types::{Filter, Address, ValueOrArray};
/// # fn main() {
/// let addresses = vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::<Address>().unwrap(),"0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".parse::<Address>().unwrap()];
/// let filter = Filter::new().address(addresses);
/// # }
/// ```
#[must_use]
pub fn address<T: Into<ValueOrArray<Address>>>(mut self, address: T) -> Self {
self.address = Some(address.into());
self
}
/// given the event in string form, it hashes it and adds it to the topics to monitor
#[must_use]
pub fn event(self, event_name: &str) -> Self {
let hash = H256::from(keccak256(event_name.as_bytes()));
self.topic0(hash)
}
/// Sets topic0 (the event name for non-anonymous events)
#[must_use]
pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
self.topics[0] = Some(topic.into());
self
}
/// Sets the 1st indexed topic
#[must_use]
pub fn topic1<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
self.topics[1] = Some(topic.into());
self
}
/// Sets the 2nd indexed topic
#[must_use]
pub fn topic2<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
self.topics[2] = Some(topic.into());
self
}
/// Sets the 3rd indexed topic
#[must_use]
pub fn topic3<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
self.topics[3] = Some(topic.into());
self
}
pub fn is_paginatable(&self) -> bool {
self.get_from_block().is_some()
}
pub fn get_from_block(&self) -> Option<U64> {
match self.block_option {
FilterBlockOption::AtBlockHash(_hash) => None,
FilterBlockOption::Range { from_block, to_block: _ } => {
from_block.map(|block| block.as_number()).unwrap_or(None)
}
}
}
}
/// Union type for representing a single value or a vector of values inside a filter
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum ValueOrArray<T> {
/// A single value
Value(T),
/// A vector of values
Array(Vec<T>),
}
// TODO: Implement more common types - or adjust this to work with all Tokenizable items // TODO: Implement more common types - or adjust this to work with all Tokenizable items
impl From<H160> for ValueOrArray<H160> {
fn from(src: H160) -> Self {
ValueOrArray::Value(src)
}
}
impl From<Vec<H160>> for ValueOrArray<H160> {
fn from(src: Vec<H160>) -> Self {
ValueOrArray::Array(src)
}
}
impl From<H256> for ValueOrArray<H256> {
fn from(src: H256) -> Self {
ValueOrArray::Value(src)
}
}
impl From<Address> for ValueOrArray<H256> {
fn from(src: Address) -> Self {
let mut bytes = [0; 32];
bytes[12..32].copy_from_slice(src.as_bytes());
ValueOrArray::Value(H256::from(bytes))
}
}
impl From<U256> for ValueOrArray<H256> {
fn from(src: U256) -> Self {
let mut bytes = [0; 32];
src.to_big_endian(&mut bytes);
ValueOrArray::Value(H256::from(bytes))
}
}
impl<T> Serialize for ValueOrArray<T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
ValueOrArray::Value(inner) => inner.serialize(serializer),
ValueOrArray::Array(inner) => inner.serialize(serializer),
}
}
}
impl<'a, T> Deserialize<'a> for ValueOrArray<T>
where
T: DeserializeOwned,
{
fn deserialize<D>(deserializer: D) -> Result<ValueOrArray<T>, D::Error>
where
D: Deserializer<'a>,
{
let value = serde_json::Value::deserialize(deserializer)?;
if value.is_null() {
return Ok(ValueOrArray::Array(Vec::new()))
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Variadic<T> {
Value(T),
Array(Vec<T>),
}
match serde_json::from_value::<Variadic<T>>(value).map_err(|err| {
serde::de::Error::custom(format!("Invalid variadic value or array type: {}", err))
})? {
Variadic::Value(val) => Ok(ValueOrArray::Value(val)),
Variadic::Array(arr) => Ok(ValueOrArray::Array(arr)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::serialize;
use serde_json::json;
#[test]
fn can_serde_value_or_array() {
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
struct Item {
value: ValueOrArray<U256>,
}
let item = Item { value: ValueOrArray::Value(U256::one()) };
let json = serde_json::to_value(item.clone()).unwrap();
let deserialized: Item = serde_json::from_value(json).unwrap();
assert_eq!(item, deserialized);
let item = Item { value: ValueOrArray::Array(vec![U256::one(), U256::zero()]) };
let json = serde_json::to_value(item.clone()).unwrap();
let deserialized: Item = serde_json::from_value(json).unwrap();
assert_eq!(item, deserialized);
}
#[test]
fn filter_serialization_test() {
let t1 = "9729a6fbefefc8f6005933898b13dc45c3a2c8b7".parse::<Address>().unwrap();
let t2 = H256::from([0; 32]);
let t3 = U256::from(123);
let t1_padded = H256::from(t1);
let t3_padded = H256::from({
let mut x = [0; 32];
x[31] = 123;
x
});
let event = "ValueChanged(address,string,string)";
let t0 = H256::from(keccak256(event.as_bytes()));
let addr: Address = "f817796F60D268A36a57b8D2dF1B97B14C0D0E1d".parse().unwrap();
let filter = Filter::new();
let ser = serialize(&filter);
assert_eq!(ser, json!({ "topics": [] }));
let filter = filter.address(ValueOrArray::Value(addr));
let ser = serialize(&filter);
assert_eq!(ser, json!({"address" : addr, "topics": []}));
let filter = filter.event(event);
// 0
let ser = serialize(&filter);
assert_eq!(ser, json!({ "address" : addr, "topics": [t0]}));
// 1
let ser = serialize(&filter.clone().topic1(t1));
assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded]}));
// 2
let ser = serialize(&filter.clone().topic2(t2));
assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, t2]}));
// 3
let ser = serialize(&filter.clone().topic3(t3));
assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, null, t3_padded]}));
// 1 & 2
let ser = serialize(&filter.clone().topic1(t1).topic2(t2));
assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, t2]}));
// 1 & 3
let ser = serialize(&filter.clone().topic1(t1).topic3(t3));
assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, null, t3_padded]}));
// 2 & 3
let ser = serialize(&filter.clone().topic2(t2).topic3(t3));
assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, t2, t3_padded]}));
// 1 & 2 & 3
let ser = serialize(&filter.topic1(t1).topic2(t2).topic3(t3));
assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, t2, t3_padded]}));
}
}

View File

@ -37,7 +37,10 @@ pub use block::{Block, BlockId, BlockNumber, TimeError};
pub use block::Randomness; pub use block::Randomness;
mod log; mod log;
pub use log::{Filter, FilterBlockOption, Log, ValueOrArray}; pub use log::Log;
mod filter;
pub use filter::*;
mod ens; mod ens;
pub use ens::NameOrAddress; pub use ens::NameOrAddress;