use ethabi::{self, Address, Bytes}; use std::str::FromStr; use std::{fmt, u8}; use web3; use web3::futures::Future; // TODO: Evaluate whether any of these would make sense to include in `web3`. /// Parses the string as a 40-digit hexadecimal number, and returns the corresponding `Address`. pub fn parse_address(mut s: &str) -> Option
{ if &s[..2] == "0x" { s = &s[2..]; } Address::from_str(s).ok() } /// Returns a wrapper of a contract address, to make function calls using the latest block. pub fn raw_call( to: Address, eth: web3::api::Eth, ) -> Box Result> { Box::new(move |bytes: Bytes| -> Result { let req = web3::types::CallRequest { from: None, to, gas: None, gas_price: None, value: None, data: Some(bytes.into()), }; eth.call(req, Some(web3::types::BlockNumber::Latest)) .wait() .map(|bytes| bytes.0) .map_err(|err| err.to_string()) }) } trait TopicExt { /// Returns the union of the two topics. fn or(self, other: Self) -> Self; /// Converts this topic into an `Option>`, where `Any` corresponds to `None`, /// `This` to a vector with one element, and `OneOf` to any vector. fn to_opt_vec(self) -> Option>; } impl TopicExt for ethabi::Topic { fn or(self, other: Self) -> Self { match (self.to_opt_vec(), other.to_opt_vec()) { (Some(mut v0), Some(v1)) => { for e in v1 { if !v0.contains(&e) { v0.push(e); } } if v0.len() == 1 { ethabi::Topic::This(v0.into_iter().next().expect("has a single element; qed")) } else { ethabi::Topic::OneOf(v0) } } (_, _) => ethabi::Topic::Any, } } fn to_opt_vec(self) -> Option> { match self { ethabi::Topic::Any => None, ethabi::Topic::OneOf(v) => Some(v), ethabi::Topic::This(t) => Some(vec![t]), } } } pub trait TopicFilterExt { /// Returns a `web3::types::FilterBuilder` with these topics, starting from the first block. fn to_filter_builder(self) -> web3::types::FilterBuilder; /// Returns the "disjunction" of the two filters, i.e. it filters for everything that matches /// at least one of the two in every topic. fn or(self, other: ethabi::TopicFilter) -> ethabi::TopicFilter; /// Returns the vector of logs that match this filter. fn logs( self, web3: &web3::Web3, ) -> Result, web3::error::Error>; } impl TopicFilterExt for ethabi::TopicFilter { fn to_filter_builder(self) -> web3::types::FilterBuilder { web3::types::FilterBuilder::default() .topics( self.topic0.to_opt_vec(), self.topic1.to_opt_vec(), self.topic2.to_opt_vec(), self.topic3.to_opt_vec(), ) .from_block(web3::types::BlockNumber::Earliest) .to_block(web3::types::BlockNumber::Latest) } fn or(self, other: ethabi::TopicFilter) -> ethabi::TopicFilter { ethabi::TopicFilter { topic0: self.topic0.or(other.topic0), topic1: self.topic1.or(other.topic1), topic2: self.topic2.or(other.topic2), topic3: self.topic3.or(other.topic3), } } fn logs( self, web3: &web3::Web3, ) -> Result, web3::error::Error> { web3.eth_filter() .create_logs_filter(self.to_filter_builder().build()) .wait()? .logs() .wait() } } pub trait Web3LogExt { fn into_raw(self) -> ethabi::RawLog; } impl Web3LogExt for web3::types::Log { fn into_raw(self) -> ethabi::RawLog { (self.topics, self.data.0).into() } } /// Wrapper for a byte array, whose `Display` implementation outputs shortened hexadecimal strings. pub struct HexBytes<'a>(pub &'a [u8]); impl<'a> fmt::Display for HexBytes<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "0x")?; for i in &self.0[..2] { write!(f, "{:02x}", i)?; } write!(f, "…")?; for i in &self.0[(self.0.len() - 2)..] { write!(f, "{:02x}", i)?; } Ok(()) } } /// Wrapper for a list of byte arrays, whose `Display` implementation outputs shortened hexadecimal /// strings. pub struct HexList<'a, T: 'a>(pub &'a [T]); impl<'a, T: 'a> fmt::Display for HexList<'a, T> where T: AsRef<[u8]>, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[")?; for (i, item) in self.0.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", HexBytes(item.as_ref()))?; } write!(f, "]") } } #[cfg(test)] mod tests { #[test] fn test_parse_address() { let addr_str = "0x2b1dbc7390a65dc40f7d64d67ea11b4d627dd1bf"; let addr = super::parse_address(addr_str).expect("parse address with 0x"); let addr2 = super::parse_address(&addr_str[2..]).expect("parse address without 0x"); assert_eq!(addr, addr2); assert_eq!(addr_str, &format!("{:?}", addr)); } }