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

fix(sol-types): check signature in SolEvent if non-anonymous #741

Merged
merged 2 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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();
}