Skip to content

Commit

Permalink
Prestwich/events (#87)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
prestwich authored Jun 9, 2023
1 parent c2fbead commit 68980f8
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 41 deletions.
3 changes: 2 additions & 1 deletion crates/sol-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
308 changes: 268 additions & 40 deletions crates/sol-types/src/types/event.rs
Original file line number Diff line number Diff line change
@@ -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<I, D>(topics: I) -> Self::RustType
where
I: IntoIterator<Item = D>,
D: Into<WordToken>;
}

impl TopicList for () {
const COUNT: usize = 0;

fn detokenize<I, D>(_topics: I) -> Self::RustType
where
I: IntoIterator<Item = D>,
D: Into<WordToken>,
{
}
}

impl<T> TopicList for (T,)
where
T: SolType<TokenType = WordToken>,
{
const COUNT: usize = 1;

fn detokenize<I, D>(topics: I) -> Self::RustType
where
I: IntoIterator<Item = D>,
D: Into<WordToken>,
{
let mut iter = topics.into_iter().map(Into::into);
let topic0 = T::detokenize(iter.next().unwrap_or_default()).unwrap();

(topic0,)
}
}

impl<T, U> TopicList for (T, U)
where
T: SolType<TokenType = WordToken>,
U: SolType<TokenType = WordToken>,
{
const COUNT: usize = 2;

fn detokenize<I, D>(topics: I) -> Self::RustType
where
I: IntoIterator<Item = D>,
D: Into<WordToken>,
{
let mut iter: core::iter::Map<<I as IntoIterator>::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<T, U, V> TopicList for (T, U, V)
where
T: SolType<TokenType = WordToken>,
U: SolType<TokenType = WordToken>,
V: SolType<TokenType = WordToken>,
{
const COUNT: usize = 3;

fn detokenize<I, D>(topics: I) -> Self::RustType
where
I: IntoIterator<Item = D>,
D: Into<WordToken>,
{
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<T, U, V, W> TopicList for (T, U, V, W)
where
T: SolType<TokenType = WordToken>,
U: SolType<TokenType = WordToken>,
V: SolType<TokenType = WordToken>,
W: SolType<TokenType = WordToken>,
{
const COUNT: usize = 4;

fn detokenize<I, D>(topics: I) -> Self::RustType
where
I: IntoIterator<Item = D>,
D: Into<WordToken>,
{
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.
///
Expand All @@ -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<TokenType = Self::Token>;
/// If this event has no non-indexed parameters, this will be the unit type
/// `()`.
type DataTuple: SolType<TokenType = Self::DataToken>;

/// 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) -> <Self::Tuple as SolType>::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: <Self::Tuple as SolType>::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<<Self::DataTuple as SolType>::RustType> {
<Self::DataTuple as SolType>::decode(data, validate)
}

/// Encode the body of this event.
fn encode_data(&self) -> Vec<u8>;

/// 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<I, D>(topics: I) -> <Self::TopicList as SolType>::RustType
where
I: IntoIterator<Item = D>,
D: Into<WordToken>,
{
<Self::TopicList as TopicList>::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<Self> {
<Self::Tuple as SolType>::decode(data, validate).map(Self::from_rust)
/// Convert decoded rust data to the event type.
fn new(
topics: <Self::TopicList as SolType>::RustType,
body: <Self::DataTuple as SolType>::RustType,
) -> Self;

/// Decode the event from the given log info.
fn decode_log<I, D>(topics: I, body_data: &[u8], validate: bool) -> Result<Self>
where
I: IntoIterator<Item = D>,
D: Into<WordToken>,
{
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<Self> {
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<T> Sealed for (T,) where T: SolType<TokenType = WordToken> {}
impl<T, U> Sealed for (T, U)
where
T: SolType<TokenType = WordToken>,
U: SolType<TokenType = WordToken>,
{
}
impl<T, U, V> Sealed for (T, U, V)
where
T: SolType<TokenType = WordToken>,
U: SolType<TokenType = WordToken>,
V: SolType<TokenType = WordToken>,
{
}
impl<T, U, V, W> Sealed for (T, U, V, W)
where
T: SolType<TokenType = WordToken>,
U: SolType<TokenType = WordToken>,
V: SolType<TokenType = WordToken>,
W: SolType<TokenType = WordToken>,
{
}
}

#[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<u8>) {
out.reserve(self.data_size());
out.extend(<Self::Tuple as SolType>::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<u8>,
}

/// ABI encode the call to the given buffer **with** its selector.
#[inline]
fn encode(&self) -> Vec<u8> {
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 = (
<sol_data::Uint<256> as SolType>::TokenType,
<sol_data::Bytes as SolType>::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<u8> {
todo!()
}

fn new(
topics: <Self::TopicList as SolType>::RustType,
body: <Self::DataTuple as SolType>::RustType,
) -> Self {
Self {
a: topics.1,
b: body.0,
hash_c: topics.2,
d: body.1,
}
}
}
}
3 changes: 3 additions & 0 deletions crates/sol-types/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

0 comments on commit 68980f8

Please sign in to comment.