use crate::types::{BlockNumber, Bytes, Index, H160, H256, U256, U64};
use serde::{Deserialize, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Log {
pub address: H160,
pub topics: Vec<H256>,
pub data: Bytes,
#[serde(rename = "blockHash")]
pub block_hash: Option<H256>,
#[serde(rename = "blockNumber")]
pub block_number: Option<U64>,
#[serde(rename = "transactionHash")]
pub transaction_hash: Option<H256>,
#[serde(rename = "transactionIndex")]
pub transaction_index: Option<Index>,
#[serde(rename = "logIndex")]
pub log_index: Option<U256>,
#[serde(rename = "transactionLogIndex")]
pub transaction_log_index: Option<U256>,
#[serde(rename = "logType")]
pub log_type: Option<String>,
pub removed: Option<bool>,
}
impl Log {
pub fn is_removed(&self) -> bool {
match self.removed {
Some(val_removed) => return val_removed,
None => (),
}
match self.log_type {
Some(ref val_log_type) => {
if val_log_type == "removed" {
return true;
}
}
None => (),
}
false
}
}
#[derive(Default, Debug, PartialEq, Clone)]
struct ValueOrArray<T>(Vec<T>);
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.0.len() {
0 => serializer.serialize_none(),
1 => Serialize::serialize(&self.0[0], serializer),
_ => Serialize::serialize(&self.0, serializer),
}
}
}
#[derive(Default, Debug, PartialEq, Clone, Serialize)]
pub struct Filter {
#[serde(rename = "fromBlock", skip_serializing_if = "Option::is_none")]
from_block: Option<BlockNumber>,
#[serde(rename = "toBlock", skip_serializing_if = "Option::is_none")]
to_block: Option<BlockNumber>,
#[serde(rename = "blockHash", skip_serializing_if = "Option::is_none")]
block_hash: Option<H256>,
#[serde(skip_serializing_if = "Option::is_none")]
address: Option<ValueOrArray<H160>>,
#[serde(skip_serializing_if = "Option::is_none")]
topics: Option<Vec<Option<ValueOrArray<H256>>>>,
#[serde(skip_serializing_if = "Option::is_none")]
limit: Option<usize>,
}
#[derive(Default, Clone)]
pub struct FilterBuilder {
filter: Filter,
}
impl FilterBuilder {
pub fn from_block(mut self, block: BlockNumber) -> Self {
self.filter.block_hash = None;
self.filter.from_block = Some(block);
self
}
pub fn to_block(mut self, block: BlockNumber) -> Self {
self.filter.block_hash = None;
self.filter.to_block = Some(block);
self
}
pub fn block_hash(mut self, hash: H256) -> Self {
self.filter.from_block = None;
self.filter.to_block = None;
self.filter.block_hash = Some(hash);
self
}
pub fn address(mut self, address: Vec<H160>) -> Self {
self.filter.address = Some(ValueOrArray(address));
self
}
pub fn topics(
mut self,
topic1: Option<Vec<H256>>,
topic2: Option<Vec<H256>>,
topic3: Option<Vec<H256>>,
topic4: Option<Vec<H256>>,
) -> Self {
let mut topics = vec![topic1, topic2, topic3, topic4]
.into_iter()
.rev()
.skip_while(Option::is_none)
.map(|option| option.map(ValueOrArray))
.collect::<Vec<_>>();
topics.reverse();
self.filter.topics = Some(topics);
self
}
pub fn topic_filter(self, topic_filter: ethabi::TopicFilter) -> Self {
self.topics(
topic_to_option(topic_filter.topic0),
topic_to_option(topic_filter.topic1),
topic_to_option(topic_filter.topic2),
topic_to_option(topic_filter.topic3),
)
}
pub fn limit(mut self, limit: usize) -> Self {
self.filter.limit = Some(limit);
self
}
pub fn build(&self) -> Filter {
self.filter.clone()
}
}
fn topic_to_option<T>(topic: ethabi::Topic<T>) -> Option<Vec<T>> {
match topic {
ethabi::Topic::Any => None,
ethabi::Topic::OneOf(v) => Some(v),
ethabi::Topic::This(t) => Some(vec![t]),
}
}
#[cfg(test)]
mod tests {
use crate::types::{
log::{FilterBuilder, Log},
Address, H160, H256,
};
use hex_literal::hex;
#[test]
fn is_removed_removed_true() {
let log = Log {
address: Address::from_low_u64_be(1),
topics: vec![],
data: hex!("").into(),
block_hash: Some(H256::from_low_u64_be(2)),
block_number: Some(1.into()),
transaction_hash: Some(H256::from_low_u64_be(3)),
transaction_index: Some(0.into()),
log_index: Some(0.into()),
transaction_log_index: Some(0.into()),
log_type: None,
removed: Some(true),
};
assert_eq!(true, log.is_removed());
}
#[test]
fn is_removed_removed_false() {
let log = Log {
address: H160::from_low_u64_be(1),
topics: vec![],
data: hex!("").into(),
block_hash: Some(H256::from_low_u64_be(2)),
block_number: Some(1.into()),
transaction_hash: Some(H256::from_low_u64_be(3)),
transaction_index: Some(0.into()),
log_index: Some(0.into()),
transaction_log_index: Some(0.into()),
log_type: None,
removed: Some(false),
};
assert_eq!(false, log.is_removed());
}
#[test]
fn is_removed_log_type_removed() {
let log = Log {
address: Address::from_low_u64_be(1),
topics: vec![],
data: hex!("").into(),
block_hash: Some(H256::from_low_u64_be(2)),
block_number: Some(1.into()),
transaction_hash: Some(H256::from_low_u64_be(3)),
transaction_index: Some(0.into()),
log_index: Some(0.into()),
transaction_log_index: Some(0.into()),
log_type: Some("removed".into()),
removed: None,
};
assert_eq!(true, log.is_removed());
}
#[test]
fn is_removed_log_type_mined() {
let log = Log {
address: Address::from_low_u64_be(1),
topics: vec![],
data: hex!("").into(),
block_hash: Some(H256::from_low_u64_be(2)),
block_number: Some(1.into()),
transaction_hash: Some(H256::from_low_u64_be(3)),
transaction_index: Some(0.into()),
log_index: Some(0.into()),
transaction_log_index: Some(0.into()),
log_type: Some("mined".into()),
removed: None,
};
assert_eq!(false, log.is_removed());
}
#[test]
fn is_removed_log_type_and_removed_none() {
let log = Log {
address: Address::from_low_u64_be(1),
topics: vec![],
data: hex!("").into(),
block_hash: Some(H256::from_low_u64_be(2)),
block_number: Some(1.into()),
transaction_hash: Some(H256::from_low_u64_be(3)),
transaction_index: Some(0.into()),
log_index: Some(0.into()),
transaction_log_index: Some(0.into()),
log_type: None,
removed: None,
};
assert_eq!(false, log.is_removed());
}
#[test]
fn does_topic_filter_set_topics_correctly() {
let topic_filter = ethabi::TopicFilter {
topic0: ethabi::Topic::This(H256::from_low_u64_be(3)),
topic1: ethabi::Topic::OneOf(vec![5, 8].into_iter().map(H256::from_low_u64_be).collect()),
topic2: ethabi::Topic::This(H256::from_low_u64_be(13)),
topic3: ethabi::Topic::Any,
};
let filter0 = FilterBuilder::default().topic_filter(topic_filter).build();
let filter1 = FilterBuilder::default()
.topics(
Some(vec![3].into_iter().map(H256::from_low_u64_be).collect()),
Some(vec![5, 8].into_iter().map(H256::from_low_u64_be).collect()),
Some(vec![13].into_iter().map(H256::from_low_u64_be).collect()),
None,
)
.build();
assert_eq!(filter0, filter1);
}
}