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:
parent
e1bbcb09fe
commit
69095c15cf
|
@ -1,10 +1,9 @@
|
|||
#![allow(clippy::return_self_not_must_use)]
|
||||
|
||||
use crate::{log::LogMeta, stream::EventStream, ContractError, EthLogDecode};
|
||||
|
||||
use ethers_core::{
|
||||
abi::{Detokenize, RawLog},
|
||||
types::{BlockNumber, Filter, Log, ValueOrArray, H256},
|
||||
types::{BlockNumber, Filter, Log, Topic, H256},
|
||||
};
|
||||
use ethers_providers::{FilterWatcher, Middleware, PubsubClient, SubscriptionStream};
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
match kind {
|
||||
ParamType::Uint(8) => true,
|
||||
ParamType::Array(kind) => contains_uint8(&*kind),
|
||||
ParamType::FixedArray(kind, _) => contains_uint8(&*kind),
|
||||
ParamType::Array(kind) => contains_uint8(kind),
|
||||
ParamType::FixedArray(kind, _) => contains_uint8(kind),
|
||||
ParamType::Tuple(tuple) => tuple.iter().any(contains_uint8),
|
||||
_ => false,
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -293,7 +293,7 @@ impl I256 {
|
|||
|
||||
/// Convert from a decimal string.
|
||||
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::Negative, &value[1..]),
|
||||
_ => (Sign::Positive, value),
|
||||
|
@ -308,7 +308,7 @@ impl I256 {
|
|||
|
||||
/// Convert from a hexadecimal string.
|
||||
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::Negative, &value[1..]),
|
||||
_ => (Sign::Positive, value),
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
// Adapted from https://github.com/tomusdrw/rust-web3/blob/master/src/types/log.rs
|
||||
use crate::{
|
||||
types::{Address, BlockNumber, Bytes, H160, H256, U256, U64},
|
||||
utils::keccak256,
|
||||
};
|
||||
use serde::{
|
||||
de::DeserializeOwned, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use std::ops::{Range, RangeFrom, RangeTo};
|
||||
use crate::types::{Address, Bytes, H256, U256, U64};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A log produced by a transaction.
|
||||
#[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
|
||||
|
||||
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]}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,10 @@ pub use block::{Block, BlockId, BlockNumber, TimeError};
|
|||
pub use block::Randomness;
|
||||
|
||||
mod log;
|
||||
pub use log::{Filter, FilterBlockOption, Log, ValueOrArray};
|
||||
pub use log::Log;
|
||||
|
||||
mod filter;
|
||||
pub use filter::*;
|
||||
|
||||
mod ens;
|
||||
pub use ens::NameOrAddress;
|
||||
|
|
Loading…
Reference in New Issue