From 68980f85361a1ab247e808346ce43a8a9b119e3f Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Fri, 9 Jun 2023 13:51:01 -0700 Subject: [PATCH] Prestwich/events (#87) * fix: event trait fixes * fix: re-exporting event * refactor: event trait take 2 * refactor: improve topic detokenization * refactor: change events to account for indexed structs not in body * fix: move test to bottom of event --- crates/sol-types/src/lib.rs | 3 +- crates/sol-types/src/types/event.rs | 308 ++++++++++++++++++++++++---- crates/sol-types/src/types/mod.rs | 3 + 3 files changed, 273 insertions(+), 41 deletions(-) diff --git a/crates/sol-types/src/lib.rs b/crates/sol-types/src/lib.rs index 25c2091fa1..0348637447 100644 --- a/crates/sol-types/src/lib.rs +++ b/crates/sol-types/src/lib.rs @@ -180,7 +180,8 @@ pub use errors::{Error, Result}; mod types; pub use types::{ - data_type as sol_data, Panic, PanicKind, Revert, SolCall, SolError, SolStruct, SolType, + data_type as sol_data, Panic, PanicKind, Revert, SolCall, SolError, SolEvent, SolStruct, + SolType, TopicList, }; mod util; diff --git a/crates/sol-types/src/types/event.rs b/crates/sol-types/src/types/event.rs index c85981b9de..0dad38ac75 100644 --- a/crates/sol-types/src/types/event.rs +++ b/crates/sol-types/src/types/event.rs @@ -1,5 +1,122 @@ -use crate::{no_std_prelude::*, token::TokenSeq, Result, SolType}; -use alloy_primitives::B256; +use alloy_primitives::FixedBytes; + +use crate::{ + token::{TokenSeq, WordToken}, + Result, SolType, +}; + +use sealed::Sealed; + +/// A `TopicList` represents the topics of a Solidity event. A topic list may +/// be 0-4 elements. Topics are included in log +/// +/// This trait is sealed to prevent incorrect downstream implementations of +/// `TopicList` from being created. +pub trait TopicList: SolType + Sealed { + /// The number of topics + const COUNT: usize; + + /// Detokenize the topics into a tuple of rust types. + /// + /// This function accepts an iterator of `WordToken` + fn detokenize(topics: I) -> Self::RustType + where + I: IntoIterator, + D: Into; +} + +impl TopicList for () { + const COUNT: usize = 0; + + fn detokenize(_topics: I) -> Self::RustType + where + I: IntoIterator, + D: Into, + { + } +} + +impl TopicList for (T,) +where + T: SolType, +{ + const COUNT: usize = 1; + + fn detokenize(topics: I) -> Self::RustType + where + I: IntoIterator, + D: Into, + { + let mut iter = topics.into_iter().map(Into::into); + let topic0 = T::detokenize(iter.next().unwrap_or_default()).unwrap(); + + (topic0,) + } +} + +impl TopicList for (T, U) +where + T: SolType, + U: SolType, +{ + const COUNT: usize = 2; + + fn detokenize(topics: I) -> Self::RustType + where + I: IntoIterator, + D: Into, + { + let mut iter: core::iter::Map<::IntoIter, _> = + topics.into_iter().map(Into::into); + let topic0 = T::detokenize(iter.next().unwrap_or_default()).unwrap(); + let topic1 = U::detokenize(iter.next().unwrap_or_default()).unwrap(); + (topic0, topic1) + } +} + +impl TopicList for (T, U, V) +where + T: SolType, + U: SolType, + V: SolType, +{ + const COUNT: usize = 3; + + fn detokenize(topics: I) -> Self::RustType + where + I: IntoIterator, + D: Into, + { + let mut iter = topics.into_iter().map(Into::into); + let topic0 = T::detokenize(iter.next().unwrap_or_default()).unwrap(); + let topic1 = U::detokenize(iter.next().unwrap_or_default()).unwrap(); + let topic2 = V::detokenize(iter.next().unwrap_or_default()).unwrap(); + (topic0, topic1, topic2) + } +} + +impl TopicList for (T, U, V, W) +where + T: SolType, + U: SolType, + V: SolType, + W: SolType, +{ + const COUNT: usize = 4; + + fn detokenize(topics: I) -> Self::RustType + where + I: IntoIterator, + D: Into, + { + let mut iter = topics.into_iter().map(Into::into); + let topic0 = T::detokenize(iter.next().unwrap_or_default()).unwrap(); + let topic1 = U::detokenize(iter.next().unwrap_or_default()).unwrap(); + let topic2 = V::detokenize(iter.next().unwrap_or_default()).unwrap(); + let topic3 = W::detokenize(iter.next().unwrap_or_default()).unwrap(); + (topic0, topic1, topic2, topic3) + } +} /// Solidity event. /// @@ -9,59 +126,170 @@ use alloy_primitives::B256; /// using the [`sol`][crate::sol] proc macro to parse a Solidity event /// definition. pub trait SolEvent: Sized { - /// The underlying tuple type which represents this type's members. + /// The underlying tuple type which represents this event's non-indexed + /// parameters. These parameters are ABI encoded and included in the log + /// body. /// - /// If this type has no arguments, this will be the unit type `()`. - type Tuple: SolType; + /// If this event has no non-indexed parameters, this will be the unit type + /// `()`. + type DataTuple: SolType; - /// The corresponding [TokenSeq] type. - type Token: TokenSeq; + /// The [`TokenSeq`] type corresponding to the tuple. + type DataToken: TokenSeq; - /// The event's ABI signature. + /// The underlying tuple type which represents this event's topics. + /// These are ABI encoded and included in the log struct returned by the + /// RPC node. Complex and dynamic indexed parameters are encoded according + /// to [special rules] and then hashed + /// + /// [special rules]: https://docs.soliditylang.org/en/v0.8.18/abi-spec.html#indexed-event-encoding + type TopicList: TopicList; + + /// The event's ABI signature. For anonymous events, this is unused, but is + /// still present. const SIGNATURE: &'static str; - /// The event's first topic: `keccak256(SIGNATURE)` - const TOPIC_ZERO: B256; + /// The keccak256 hash of the event's ABI signature. For non-anonymous + /// events, this will be the topic0 of the event. For anonymous events, this + /// is unused, but is still present. + /// + /// Also called the event `selector` + const SIGNATURE_HASH: FixedBytes<32>; - /// Converts to the tuple type used for ABI encoding and decoding. - fn to_rust(&self) -> ::RustType; + /// True if the event is anonymous. + const ANONYMOUS: bool; - /// Convert from the tuple type used for ABI encoding and decoding. - fn from_rust(tuple: ::RustType) -> Self; + /// The number of topics. + const TOPICS_LEN: usize; - /// The size of the encoded data in bytes, **without** its selector. + /// Decode the body of this event from the given data. The event body + /// contains the non-indexed parameters. + fn decode_body(data: &[u8], validate: bool) -> Result<::RustType> { + ::decode(data, validate) + } + + /// Encode the body of this event. + fn encode_data(&self) -> Vec; + + /// Decode the topics of this event from the given data. The topics contain + /// the selector (for non-anonymous events) and indexed parameters. + fn decode_topics(topics: I) -> ::RustType + where + I: IntoIterator, + D: Into, + { + ::detokenize(topics) + } + + /// The size of the encoded body data in bytes. fn data_size(&self) -> usize; - /// ABI decode this call's arguments from the given slice, **without** its - /// selector. - #[inline] - fn decode_raw(data: &[u8], validate: bool) -> Result { - ::decode(data, validate).map(Self::from_rust) + /// Convert decoded rust data to the event type. + fn new( + topics: ::RustType, + body: ::RustType, + ) -> Self; + + /// Decode the event from the given log info. + fn decode_log(topics: I, body_data: &[u8], validate: bool) -> Result + where + I: IntoIterator, + D: Into, + { + let topics = Self::decode_topics(topics); + let body = Self::decode_body(body_data, validate)?; + + Ok(Self::new(topics, body)) } +} + +mod sealed { + use super::*; - /// ABI decode this call's arguments from the given slice, **with** the - /// selector. - #[inline] - fn decode(data: &[u8], validate: bool) -> Result { - let data = data - .strip_prefix(&Self::SELECTOR) - .ok_or_else(|| crate::Error::type_check_fail_sig(data, Self::SIGNATURE))?; - Self::decode_raw(data, validate) + pub trait Sealed {} + impl Sealed for () {} + impl Sealed for (T,) where T: SolType {} + impl Sealed for (T, U) + where + T: SolType, + U: SolType, + { + } + impl Sealed for (T, U, V) + where + T: SolType, + U: SolType, + V: SolType, + { } + impl Sealed for (T, U, V, W) + where + T: SolType, + U: SolType, + V: SolType, + W: SolType, + { + } +} + +#[cfg(test)] +mod compile_test { + use alloy_primitives::{FixedBytes, U256}; - /// ABI encode the call to the given buffer **without** its selector. - #[inline] - fn encode_raw(&self, out: &mut Vec) { - out.reserve(self.data_size()); - out.extend(::encode(self.to_rust())); + use crate::{sol_data, SolEvent, SolType}; + + #[allow(unreachable_pub, dead_code)] + /// event MyEvent(bytes32 indexed a, uint256 b, string indexed c, bytes d); + struct MyEvent { + /// bytes indexed a + pub a: [u8; 32], + /// uint256 b + pub b: U256, + /// string indexed c + pub hash_c: [u8; 32], + /// bytes d + pub d: Vec, } - /// ABI encode the call to the given buffer **with** its selector. - #[inline] - fn encode(&self) -> Vec { - let mut out = Vec::with_capacity(4 + self.data_size()); - out.extend(&Self::SELECTOR); - self.encode_raw(&mut out); - out + impl SolEvent for MyEvent { + type DataTuple = (sol_data::Uint<256>, sol_data::Bytes); + /// + type DataToken = ( + as SolType>::TokenType, + ::TokenType, + ); + + // this is a, and keccak256(c) + type TopicList = ( + sol_data::FixedBytes<32>, + sol_data::FixedBytes<32>, + sol_data::FixedBytes<32>, + ); + + const SIGNATURE: &'static str = "MyEvent(bytes32,uint256,string,bytes)"; + const SIGNATURE_HASH: FixedBytes<32> = FixedBytes([0; 32]); // FIXME: caluclate it + const ANONYMOUS: bool = false; + const TOPICS_LEN: usize = 3; + + fn data_size(&self) -> usize { + 0 + // FIXME: as data_size for error. + } + + fn encode_data(&self) -> Vec { + todo!() + } + + fn new( + topics: ::RustType, + body: ::RustType, + ) -> Self { + Self { + a: topics.1, + b: body.0, + hash_c: topics.2, + d: body.1, + } + } } } diff --git a/crates/sol-types/src/types/mod.rs b/crates/sol-types/src/types/mod.rs index 0158a224a4..02bea81e83 100644 --- a/crates/sol-types/src/types/mod.rs +++ b/crates/sol-types/src/types/mod.rs @@ -18,3 +18,6 @@ pub use r#type::SolType; // Solidity user-defined value types. // No exports are needed as the only item is a macro. mod udt; + +mod event; +pub use event::{SolEvent, TopicList};