Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Solidity events support #83

Merged
merged 23 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions crates/sol-macro/src/expand/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Functions which generate Rust code from the Solidity AST.

use ast::{
File, Item, ItemContract, ItemError, ItemFunction, ItemStruct, ItemUdt, Parameters, SolIdent,
Type, VariableDeclaration, Visit,
File, Item, ItemContract, ItemError, ItemEvent, ItemFunction, ItemStruct, ItemUdt, Parameters,
SolIdent, Type, VariableDeclaration, Visit,
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, IdentFragment};
Expand Down Expand Up @@ -80,6 +80,7 @@ impl<'ast> ExpCtxt<'ast> {
match item {
Item::Contract(contract) => self.expand_contract(contract),
Item::Error(error) => self.expand_error(error),
Item::Event(event) => self.expand_event(event),
Item::Function(function) => self.expand_function(function),
Item::Struct(s) => self.expand_struct(s),
Item::Udt(udt) => self.expand_udt(udt),
Expand Down Expand Up @@ -167,7 +168,7 @@ impl<'ast> ExpCtxt<'ast> {
let variants: Vec<_> = errors.iter().map(|error| error.name.0.clone()).collect();
let min_data_len = errors
.iter()
.map(|error| self.min_data_size(&error.fields))
.map(|error| self.min_data_size(&error.parameters))
.max()
.unwrap();
let trt = Ident::new("SolError", Span::call_site());
Expand Down Expand Up @@ -254,20 +255,20 @@ impl<'ast> ExpCtxt<'ast> {

fn expand_error(&self, error: &ItemError) -> Result<TokenStream> {
let ItemError {
fields,
parameters,
name,
attrs,
..
} = error;
self.assert_resolved(fields)?;
self.assert_resolved(parameters)?;

let signature = self.signature(name.as_string(), fields);
let signature = self.signature(name.as_string(), parameters);
let selector = crate::utils::selector(&signature);

let size = self.params_data_size(fields, None);
let size = self.params_data_size(parameters, None);

let converts = expand_from_into_tuples(&name.0, fields);
let fields = fields.iter().map(expand_var);
let converts = expand_from_into_tuples(&name.0, parameters);
let fields = parameters.iter().map(expand_var);
let tokens = quote! {
#(#attrs)*
#[allow(non_camel_case_types, non_snake_case)]
Expand Down Expand Up @@ -305,6 +306,11 @@ impl<'ast> ExpCtxt<'ast> {
Ok(tokens)
}

fn expand_event(&self, event: &ItemEvent) -> Result<TokenStream> {
let _ = event;
todo!()
}

fn expand_function(&self, function: &ItemFunction) -> Result<TokenStream> {
let function_name = self.function_name(function);
let call_name = self.call_name(function_name.clone());
Expand Down
8 changes: 6 additions & 2 deletions crates/sol-macro/src/expand/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ impl ExpCtxt<'_> {
.map(|ty| self.type_base_data_size(ty))
.sum(),
Item::Udt(udt) => self.type_base_data_size(&udt.ty),
Item::Contract(_) | Item::Error(_) | Item::Function(_) => unreachable!(),
Item::Contract(_) | Item::Error(_) | Item::Event(_) | Item::Function(_) => {
unreachable!()
}
},
}
}
Expand Down Expand Up @@ -191,7 +193,9 @@ impl ExpCtxt<'_> {
Type::Custom(name) => match self.get_item(name) {
Item::Struct(strukt) => self.params_data_size(&strukt.fields, Some(field)),
Item::Udt(udt) => self.type_data_size(&udt.ty, field),
Item::Contract(_) | Item::Error(_) | Item::Function(_) => unreachable!(),
Item::Contract(_) | Item::Error(_) | Item::Event(_) | Item::Function(_) => {
unreachable!()
}
},
}
}
Expand Down
21 changes: 11 additions & 10 deletions crates/sol-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@
#[macro_use]
extern crate alloc;

#[doc(inline)]
pub use alloy_sol_macro::sol;

#[doc(hidden)]
pub mod no_std_prelude {
pub use alloc::{
Expand All @@ -164,12 +167,6 @@ pub mod no_std_prelude {
};
}

#[doc(inline)]
pub use alloy_sol_macro::sol;

/// The ABI word type.
pub type Word = alloy_primitives::B256;

mod coder;
pub use coder::{
decode, decode_params, decode_single, encode, encode_params, encode_single,
Expand All @@ -183,15 +180,19 @@ 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;

#[doc(hidden)]
pub use alloy_primitives::keccak256;
#[doc(hidden)]
pub use util::just_ok;

mod eip712;
pub use eip712::Eip712Domain;

#[doc(hidden)]
pub use alloy_primitives::keccak256;

/// The ABI word type.
pub type Word = alloy_primitives::B256;
2 changes: 1 addition & 1 deletion crates/sol-types/src/types/call.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{no_std_prelude::*, token::TokenSeq, Result, SolType};

/// Solidity Call (a tuple with a selector)
/// Solidity call (a tuple with a selector).
///
/// ### Implementer's Guide
///
Expand Down
221 changes: 221 additions & 0 deletions crates/sol-types/src/types/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
use crate::{
token::{TokenSeq, WordToken},
Error, Result, SolType,
};
use alloc::{borrow::Cow, vec::Vec};
use alloy_primitives::FixedBytes;
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) -> Result<Self::RustType>
where
I: IntoIterator<Item = D>,
D: Into<WordToken>;
}

mod sealed {
pub trait Sealed {}
}

macro_rules! impl_for_tuples {
($($c:literal => $($t:ident),*;)+) => {$(
impl<$($t: SolType<TokenType = WordToken>,)*> sealed::Sealed for ($($t,)*) {}
impl<$($t: SolType<TokenType = WordToken>,)*> TopicList for ($($t,)*) {
const COUNT: usize = $c;

fn detokenize<I, D>(topics: I) -> Result<Self::RustType>
where
I: IntoIterator<Item = D>,
D: Into<WordToken>
{
let err = || Error::Other(Cow::Borrowed("topic list length mismatch"));
let mut iter = topics.into_iter().map(Into::into);
Ok(($(
iter.next().ok_or_else(err).map(<$t>::detokenize)?,
)*))
}
}
)+};
}

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

#[inline]
fn detokenize<I, D>(_: I) -> Result<Self::RustType>
where
I: IntoIterator<Item = D>,
D: Into<WordToken>,
{
Ok(())
}
}

impl_for_tuples! {
1 => T;
2 => T, U;
3 => T, U, V;
4 => T, U, V, W;
}

/// Solidity event.
///
/// ### Implementer's Guide
///
/// We do not recommend implementing this trait directly. Instead, we recommend
/// using the [`sol`][crate::sol] proc macro to parse a Solidity event
/// definition.
pub trait SolEvent: Sized {
/// 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 event has no non-indexed parameters, this will be the unit type
/// `()`.
type DataTuple: SolType<TokenType = Self::DataToken>;

/// The [`TokenSeq`] type corresponding to the tuple.
type DataToken: TokenSeq;

/// 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 ABI signature hash, or selector: `keccak256(SIGNATURE)`
///
/// For non-anonymous events, this will be the first topic (`topic0`).
/// For anonymous events, this is unused, but is still present.
const SIGNATURE_HASH: FixedBytes<32>;

/// Whether the event is anonymous.
const ANONYMOUS: bool;

/// The number of topics.
const TOPICS_LEN: usize;

/// Convert decoded rust data to the event type.
fn new(
topics: <Self::TopicList as SolType>::RustType,
body: <Self::DataTuple as SolType>::RustType,
) -> Self;

/// The size of the ABI-encoded dynamic data in bytes.
fn data_size(&self) -> usize;

/// ABI-encode the dynamic data of this event into the given buffer.
fn encode_data_raw(&self, out: &mut Vec<u8>);

/// ABI-encode the dynamic data of this event.
#[inline]
fn encode_data(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(self.data_size());
self.encode_data_raw(&mut out);
out
}

/// Decode the topics of this event from the given data.
fn decode_topics<I, D>(topics: I) -> Result<<Self::TopicList as SolType>::RustType>
where
I: IntoIterator<Item = D>,
D: Into<WordToken>,
{
<Self::TopicList as TopicList>::detokenize(topics)
}

/// Decode the dynamic data of this event from the given .
fn decode_data(data: &[u8], validate: bool) -> Result<<Self::DataTuple as SolType>::RustType> {
<Self::DataTuple as SolType>::decode(data, validate)
}

/// 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_data(body_data, validate)?;
Ok(Self::new(topics, body))
}
}

#[cfg(test)]
#[allow(clippy::all, unused)]
mod compile_test {
use super::*;
use crate::{sol_data, SolEvent, SolType};
use alloy_primitives::{FixedBytes, U256};
use hex_literal::hex;

// event MyEvent(bytes32 indexed a, uint256 b, string indexed c, bytes d);
struct MyEvent {
a: [u8; 32],
b: U256,
c: String,
d: Vec<u8>,
}

impl SolEvent for MyEvent {
type DataTuple = (sol_data::Uint<256>, sol_data::String, sol_data::Bytes);
type DataToken = (
<sol_data::Uint<256> as SolType>::TokenType,
<sol_data::String 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>);

const SIGNATURE: &'static str = "MyEvent(bytes32,uint256,string,bytes)";
const SIGNATURE_HASH: FixedBytes<32> = FixedBytes(hex!(
"a0361149ae231b28afd460baabadd0a64949fcaed9a1d488cbf2747a9a8be6dd"
));
const ANONYMOUS: bool = false;
const TOPICS_LEN: usize = 3;

fn data_size(&self) -> usize {
// abi encoded length of: b + c + d
32 + (64 + (self.c.len() / 31) * 32) + (64 + (self.d.len() / 31) * 32)
}

fn encode_data_raw(&self, out: &mut 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,
c: body.1,
d: body.2,
}
}
}
}
15 changes: 9 additions & 6 deletions crates/sol-types/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
mod error;
pub use error::{Panic, PanicKind, Revert, SolError};

mod call;
pub use call::SolCall;

mod r#type;
pub use r#type::SolType;
pub mod data_type;

mod error;
pub use error::{Panic, PanicKind, Revert, SolError};

mod event;
pub use event::{SolEvent, TopicList};

mod r#struct;
pub use r#struct::SolStruct;

pub mod data_type;
mod r#type;
pub use r#type::SolType;

// Solidity user-defined value types.
// No exports are needed as the only item is a macro.
Expand Down
Loading