Skip to content

Commit

Permalink
fix(sol-types): check signature in SolEvent if non-anonymous (#741)
Browse files Browse the repository at this point in the history
* fix(sol-types): check signature in SolEvent if non-anonymous

* chore: clippy
  • Loading branch information
DaniPopes committed Sep 19, 2024
1 parent 9e8f0f3 commit 6d2deb2
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 9 deletions.
14 changes: 14 additions & 0 deletions crates/sol-macro-expander/src/expand/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, event: &ItemEvent) -> Result<TokenStream>
.enumerate()
.map(|(i, p)| expand_event_topic_field(i, p, p.name.as_ref(), cx));

let check_signature = (!anonymous).then(|| {
quote! {
#[inline]
fn check_signature(topics: &<Self::TopicList as alloy_sol_types::SolType>::RustType) -> alloy_sol_types::Result<()> {
if topics.0 != Self::SIGNATURE_HASH {
return Err(alloy_sol_types::Error::invalid_event_signature_hash(Self::SIGNATURE, topics.0, Self::SIGNATURE_HASH));
}
Ok(())
}
}
});

let tokenize_body_impl = expand_event_tokenize(&event.parameters, cx);

let encode_topics_impl = encode_first_topic
Expand Down Expand Up @@ -173,6 +185,8 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, event: &ItemEvent) -> Result<TokenStream>
}
}

#check_signature

#[inline]
fn tokenize_body(&self) -> Self::DataToken<'_> {
#tokenize_body_impl
Expand Down
10 changes: 9 additions & 1 deletion crates/sol-types/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

use crate::abi;
use alloc::{borrow::Cow, boxed::Box, collections::TryReserveError, string::String};
use alloy_primitives::LogData;
use alloy_primitives::{LogData, B256};
use core::fmt;

/// ABI result type.
Expand Down Expand Up @@ -147,6 +147,14 @@ impl Error {
pub fn unknown_selector(name: &'static str, selector: [u8; 4]) -> Self {
Self::UnknownSelector { name, selector: selector.into() }
}

#[doc(hidden)] // Not public API.
#[cold]
pub fn invalid_event_signature_hash(name: &'static str, got: B256, expected: B256) -> Self {
Self::custom(format!(
"invalid signature hash for event {name:?}: got {got}, expected {expected}"
))
}
}

impl From<hex::FromHexError> for Error {
Expand Down
32 changes: 26 additions & 6 deletions crates/sol-types/src/types/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,33 @@ pub trait SolEvent: Sized {
const ANONYMOUS: bool;

/// Convert decoded rust data to the event type.
///
/// Does not check that `topics[0]` is the correct hash.
/// Use [`new_checked`](Self::new_checked) instead.
fn new(
topics: <Self::TopicList as SolType>::RustType,
data: <Self::DataTuple<'_> as SolType>::RustType,
) -> Self;

/// Convert decoded rust data to the event type.
///
/// Checks that `topics[0]` is the correct hash.
#[inline]
fn new_checked(
topics: <Self::TopicList as SolType>::RustType,
data: <Self::DataTuple<'_> as SolType>::RustType,
) -> Result<Self> {
Self::check_signature(&topics).map(|()| Self::new(topics, data))
}

/// Check that the event's signature matches the given topics.
#[inline]
fn check_signature(topics: &<Self::TopicList as SolType>::RustType) -> Result<()> {
// Overridden for non-anonymous events in `sol!`.
let _ = topics;
Ok(())
}

/// Tokenize the event's non-indexed parameters.
fn tokenize_body(&self) -> Self::DataToken<'_>;

Expand Down Expand Up @@ -110,14 +132,10 @@ pub trait SolEvent: Sized {

/// Encode the topics of this event into a fixed-size array.
///
/// # Panics
///
/// This method will panic if `LEN` is not equal to
/// `Self::TopicList::COUNT`.
/// This method will not compile if `LEN` is not equal to `Self::TopicList::COUNT`.
#[inline]
fn encode_topics_array<const LEN: usize>(&self) -> [WordToken; LEN] {
// TODO: make this a compile-time error when `const` blocks are stable
assert_eq!(LEN, Self::TopicList::COUNT, "topic list length mismatch");
const { assert!(LEN == Self::TopicList::COUNT, "topic list length mismatch") };
let mut out = [WordToken(B256::ZERO); LEN];
self.encode_topics_raw(&mut out).unwrap();
out
Expand Down Expand Up @@ -163,6 +181,8 @@ pub trait SolEvent: Sized {
D: Into<WordToken>,
{
let topics = Self::decode_topics(topics)?;
// Check signature before decoding the data.
Self::check_signature(&topics)?;
let body = Self::abi_decode_data(data, validate)?;
Ok(Self::new(topics, body))
}
Expand Down
8 changes: 6 additions & 2 deletions crates/sol-types/src/types/event/topic_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,9 @@ macro_rules! impl_topic_list_tuples {
I: IntoIterator<Item = D>,
D: Into<WordToken>
{
let err = || Error::Other(Cow::Borrowed("topic list length mismatch"));
let mut iter = topics.into_iter();
Ok(($(
<$t>::detokenize(iter.next().ok_or_else(err)?.into()),
<$t>::detokenize(iter.next().ok_or_else(length_mismatch)?.into()),
)*))
}
}
Expand All @@ -77,3 +76,8 @@ impl_topic_list_tuples! {
3 => 'a T, 'b U, 'c V;
4 => 'a T, 'b U, 'c V, 'd W;
}

#[cold]
const fn length_mismatch() -> Error {
Error::Other(Cow::Borrowed("topic list length mismatch"))
}
23 changes: 23 additions & 0 deletions crates/sol-types/tests/macros/sol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1148,3 +1148,26 @@ fn event_indexed_elementary_arrays() {
assert_eq!(AddrUdvtDynArray::SIGNATURE, "AddrUdvtDynArray(address[])");
let _ = AddrUdvtDynArray { y: B256::ZERO };
}

// https://github.com/alloy-rs/core/issues/589
#[test]
#[allow(clippy::assertions_on_constants)]
fn event_check_signature() {
sol! {
#[derive(Debug)]
event MyEvent();
event MyEventAnonymous() anonymous;
}

let no_topics: [B256; 0] = [];

assert!(!MyEvent::ANONYMOUS);
let e = MyEvent::decode_raw_log(no_topics, &[], false).unwrap_err();
assert_eq!(e.to_string(), "topic list length mismatch");
let e = MyEvent::decode_raw_log([B256::ZERO], &[], false).unwrap_err();
assert!(e.to_string().contains("invalid signature hash"), "{e:?}");
let MyEvent {} = MyEvent::decode_raw_log([MyEvent::SIGNATURE_HASH], &[], false).unwrap();

assert!(MyEventAnonymous::ANONYMOUS);
let MyEventAnonymous {} = MyEventAnonymous::decode_raw_log(no_topics, &[], false).unwrap();
}

0 comments on commit 6d2deb2

Please sign in to comment.