use crate::{errors::ExecutionError, tokens::Tokenize};
use ethcontract_common::abi::{Event as AbiEvent, RawLog as AbiRawLog, Token};
use web3::types::{Log, H160, H256};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Event<T> {
pub data: T,
pub meta: Option<EventMetadata>,
}
pub type StreamEvent<T> = Event<EventStatus<T>>;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum EventStatus<T> {
Added(T),
Removed(T),
}
impl<T> Event<T> {
pub(crate) fn from_past_log<E, F>(log: Log, f: F) -> Result<Self, ExecutionError>
where
F: FnOnce(RawLog) -> Result<T, E>,
ExecutionError: From<E>,
{
if log.removed == Some(true) {
return Err(ExecutionError::RemovedLog(Box::new(log)));
}
let meta = EventMetadata::from_log(&log);
let raw = RawLog::from(log);
let data = f(raw)?;
Ok(Event { data, meta })
}
}
impl<T> Event<EventStatus<T>> {
pub(crate) fn from_streamed_log<E, F>(log: Log, f: F) -> Result<Self, ExecutionError>
where
F: FnOnce(RawLog) -> Result<T, E>,
ExecutionError: From<E>,
{
let removed = log.removed == Some(true);
let meta = EventMetadata::from_log(&log);
let raw = RawLog::from(log);
let inner_data = f(raw)?;
let data = if removed {
EventStatus::Removed(inner_data)
} else {
EventStatus::Added(inner_data)
};
Ok(Event { data, meta })
}
pub fn inner_data(&self) -> &T {
match &self.data {
EventStatus::Added(value) => value,
EventStatus::Removed(value) => value,
}
}
pub fn is_added(&self) -> bool {
matches!(&self.data, EventStatus::Added(_))
}
pub fn is_removed(&self) -> bool {
matches!(&self.data, EventStatus::Removed(_))
}
pub fn added(self) -> Option<T> {
match self.data {
EventStatus::Added(value) => Some(value),
EventStatus::Removed(_) => None,
}
}
pub fn removed(self) -> Option<T> {
match self.data {
EventStatus::Removed(value) => Some(value),
EventStatus::Added(_) => None,
}
}
pub fn map<U, F>(self, f: F) -> StreamEvent<U>
where
F: FnOnce(T) -> U,
{
Event {
data: match self.data {
EventStatus::Added(inner) => EventStatus::Added(f(inner)),
EventStatus::Removed(inner) => EventStatus::Removed(f(inner)),
},
meta: self.meta,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct EventMetadata {
pub address: H160,
pub block_hash: H256,
pub block_number: u64,
pub transaction_hash: H256,
pub transaction_index: usize,
pub log_index: usize,
pub transaction_log_index: Option<usize>,
pub log_type: Option<String>,
}
impl EventMetadata {
fn from_log(log: &Log) -> Option<Self> {
Some(EventMetadata {
address: log.address,
block_hash: log.block_hash?,
block_number: log.block_number?.as_u64(),
transaction_hash: log.transaction_hash?,
transaction_index: log.transaction_index?.as_usize(),
log_index: log.log_index?.as_usize(),
transaction_log_index: log.transaction_log_index.map(|index| index.as_usize()),
log_type: log.log_type.clone(),
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RawLog {
pub topics: Vec<H256>,
pub data: Vec<u8>,
}
impl RawLog {
pub fn decode<D>(self, event: &AbiEvent) -> Result<D, ExecutionError>
where
D: Tokenize,
{
let event_log = event.parse_log(AbiRawLog {
topics: self.topics,
data: self.data,
})?;
let tokens = event_log
.params
.into_iter()
.map(|param| param.value)
.collect::<Vec<_>>();
let data = D::from_token(Token::Tuple(tokens))?;
Ok(data)
}
}
impl From<Log> for RawLog {
fn from(log: Log) -> Self {
RawLog {
topics: log.topics,
data: log.data.0,
}
}
}
pub trait ParseLog: Sized + Send + Sync {
fn parse_log(log: RawLog) -> Result<Self, ExecutionError>;
}
impl ParseLog for RawLog {
fn parse_log(log: RawLog) -> Result<Self, ExecutionError> {
Ok(log)
}
}