use df_hyperliquid_core::client::evm::abi::{self, AbiError, Param, Token};
use crate::B256;
use crate::client::records::Log;
use crate::utils::keccak256;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Event {
pub signature: &'static str,
pub params: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BadLog {
#[error("the log is not {0}")]
NotThisEvent(&'static str),
#[error("{event}: {error}")]
Abi { event: &'static str, error: AbiError },
}
impl Event {
#[must_use]
pub const fn new(signature: &'static str, params: &'static str) -> Event {
Event { signature, params }
}
#[must_use]
pub fn topic(&self) -> B256 {
B256(keccak256(self.signature.as_bytes()))
}
#[must_use]
pub fn name(&self) -> &'static str {
self.signature.split_once('(').map_or(self.signature, |(name, _)| name)
}
#[must_use]
pub fn matches(&self, log: &Log) -> bool {
log.topics.first() == Some(&self.topic())
}
pub fn decode(&self, log: &Log) -> Result<Vec<Token>, BadLog> {
let abi = |error: AbiError| BadLog::Abi { event: self.name(), error };
if !self.matches(log) {
return Err(BadLog::NotThisEvent(self.name()));
}
let params = abi::params(self.params).map_err(abi)?;
let unindexed: Vec<Param> = params.iter().filter(|param| !param.indexed).cloned().collect();
let mut data = abi::decode(&unindexed, &log.data).map_err(abi)?.into_iter();
let mut topics = log.topics.iter().skip(1);
let mut tokens = Vec::with_capacity(params.len());
for param in ¶ms {
let token = match param.indexed {
true => {
let topic = topics.next().ok_or(AbiError::Short(log.topics.len() * 32)).map_err(abi)?;
match param.kind.is_dynamic() {
true => Token::Fixed(topic.0.to_vec()),
false => abi::decode(std::slice::from_ref(param), &topic.0)
.map_err(abi)?
.into_iter()
.next()
.ok_or(AbiError::Short(32))
.map_err(abi)?,
}
}
false => data.next().ok_or(AbiError::Short(log.data.len())).map_err(abi)?,
};
tokens.push(token);
}
Ok(tokens)
}
}
#[cfg(test)]
#[path = "tests/event.rs"]
mod tests;