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)]
|
#![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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
@ -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),
|
||||||
|
|
|
@ -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]}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue