alloy_dyn_abi/dynamic/
event.rsuse crate::{DynSolType, DynSolValue, Error, Result};
use alloc::vec::Vec;
use alloy_primitives::{IntoLogData, Log, LogData, B256};
#[derive(Clone, Debug, PartialEq)]
pub struct DynSolEvent {
pub(crate) topic_0: Option<B256>,
pub(crate) indexed: Vec<DynSolType>,
pub(crate) body: DynSolType,
}
impl DynSolEvent {
pub const fn new_unchecked(
topic_0: Option<B256>,
indexed: Vec<DynSolType>,
body: DynSolType,
) -> Self {
Self { topic_0, indexed, body }
}
pub fn new(topic_0: Option<B256>, indexed: Vec<DynSolType>, body: DynSolType) -> Option<Self> {
let topics = indexed.len() + topic_0.is_some() as usize;
if topics > 4 || body.as_tuple().is_none() {
return None;
}
Some(Self::new_unchecked(topic_0, indexed, body))
}
pub const fn is_anonymous(&self) -> bool {
self.topic_0.is_none()
}
pub fn decode_log_parts<I>(
&self,
topics: I,
data: &[u8],
validate: bool,
) -> Result<DecodedEvent>
where
I: IntoIterator<Item = B256>,
{
let mut topics = topics.into_iter();
let num_topics = self.indexed.len() + !self.is_anonymous() as usize;
if validate {
match topics.size_hint() {
(n, Some(m)) if n == m && n != num_topics => {
return Err(Error::TopicLengthMismatch { expected: num_topics, actual: n })
}
_ => {}
}
}
if !self.is_anonymous() {
let t = topics.next();
if validate {
match t {
Some(sig) => {
let expected = self.topic_0.expect("not anonymous");
if sig != expected {
return Err(Error::EventSignatureMismatch { expected, actual: sig });
}
}
None => {
return Err(Error::TopicLengthMismatch { expected: num_topics, actual: 0 })
}
}
}
}
let indexed = self
.indexed
.iter()
.zip(topics.by_ref().take(self.indexed.len()))
.map(|(ty, topic)| {
let value = ty.decode_event_topic(topic);
Ok(value)
})
.collect::<Result<_>>()?;
let body = self.body.abi_decode_sequence(data)?.into_fixed_seq().expect("body is a tuple");
if validate {
let remaining = topics.count();
if remaining > 0 {
return Err(Error::TopicLengthMismatch {
expected: num_topics,
actual: num_topics + remaining,
});
}
}
Ok(DecodedEvent { selector: self.topic_0, indexed, body })
}
pub fn decode_log_data(&self, log: &LogData, validate: bool) -> Result<DecodedEvent> {
self.decode_log_parts(log.topics().iter().copied(), &log.data, validate)
}
pub const fn topic_0(&self) -> Option<B256> {
self.topic_0
}
pub fn indexed(&self) -> &[DynSolType] {
&self.indexed
}
pub fn body(&self) -> &[DynSolType] {
self.body.as_tuple().expect("body is a tuple")
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DecodedEvent {
#[doc(alias = "topic_0")]
pub selector: Option<B256>,
pub indexed: Vec<DynSolValue>,
pub body: Vec<DynSolValue>,
}
impl DecodedEvent {
pub const fn is_anonymous(&self) -> bool {
self.selector.is_none()
}
pub fn encode_log_data(&self) -> LogData {
debug_assert!(
self.indexed.len() + !self.is_anonymous() as usize <= 4,
"too many indexed values"
);
LogData::new_unchecked(
self.selector
.iter()
.copied()
.chain(self.indexed.iter().flat_map(DynSolValue::as_word).map(B256::from))
.collect(),
DynSolValue::encode_seq(&self.body).into(),
)
}
pub fn encode_log(log: Log<Self>) -> Log<LogData> {
Log { address: log.address, data: log.data.encode_log_data() }
}
}
impl IntoLogData for DecodedEvent {
fn to_log_data(&self) -> LogData {
self.encode_log_data()
}
fn into_log_data(self) -> LogData {
self.encode_log_data()
}
}
#[cfg(test)]
mod test {
use super::*;
use alloy_primitives::{address, b256, bytes, U256};
#[test]
fn it_decodes_a_simple_log() {
let log = LogData::new_unchecked(vec![], U256::ZERO.to_be_bytes_vec().into());
let event = DynSolEvent {
topic_0: None,
indexed: vec![],
body: DynSolType::Tuple(vec![DynSolType::Uint(256)]),
};
event.decode_log_data(&log, true).unwrap();
}
#[test]
fn it_decodes_logs_with_indexed_params() {
let t0 = b256!("cf74b4e62f836eeedcd6f92120ffb5afea90e6fa490d36f8b81075e2a7de0cf7");
let log: LogData = LogData::new_unchecked(
vec![t0, b256!("0000000000000000000000000000000000000000000000000000000000012321")],
bytes!(
"
0000000000000000000000000000000000000000000000000000000000012345
0000000000000000000000000000000000000000000000000000000000054321
"
),
);
let event = DynSolEvent {
topic_0: Some(t0),
indexed: vec![DynSolType::Address],
body: DynSolType::Tuple(vec![DynSolType::Tuple(vec![
DynSolType::Address,
DynSolType::Address,
])]),
};
let decoded = event.decode_log_data(&log, true).unwrap();
assert_eq!(
decoded.indexed,
vec![DynSolValue::Address(address!("0000000000000000000000000000000000012321"))]
);
let encoded = decoded.encode_log_data();
assert_eq!(encoded, log);
}
}