Skip to content

Commit

Permalink
feat: json-abi event selector
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes committed Jun 12, 2023
1 parent dd969b0 commit 669d283
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 50 deletions.
2 changes: 1 addition & 1 deletion crates/json-abi/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde::{
Deserialize, Serialize,
};

/// The JSON contract ABI, as specified in the [Solidity documentation][ref].
/// The JSON contract ABI, as specified in the [Solidity ABI spec][ref].
///
/// [ref]: https://docs.soliditylang.org/en/latest/abi-spec.html#json
#[derive(Debug, Clone, PartialEq, Eq, Default)]
Expand Down
29 changes: 27 additions & 2 deletions crates/json-abi/src/event_param.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::param::Param;
use alloc::{string::String, vec::Vec};
use alloc::{borrow::Cow, string::String, vec::Vec};
use serde::{Deserialize, Serialize};

/// A Solidity Event parameter.
Expand All @@ -11,7 +11,7 @@ pub struct SimpleEventParam {
#[serde(rename = "type")]
pub ty: String,
/// Whether the parameter is indexed. Indexed parameters have their
///value, or the hash of their value, stored in the log topics.
/// value, or the hash of their value, stored in the log topics.
pub indexed: bool,
/// The internal type of the parameter. This type represents the type that
/// the author of the solidity contract specified. E.g. for a contract, this
Expand All @@ -20,6 +20,14 @@ pub struct SimpleEventParam {
pub internal_type: String,
}

impl SimpleEventParam {
/// Type used to encode the preimage of the function or error selector, or
/// event topic
pub fn selector_type(&self) -> &str {
&self.ty
}
}

/// JSON representation of a complex event parameter.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ComplexEventParam {
Expand All @@ -41,6 +49,13 @@ pub struct ComplexEventParam {
pub internal_type: String,
}

impl ComplexEventParam {
/// Type used to encode the preimage of the event selector.
pub fn selector_type(&self) -> String {
crate::utils::signature("", &self.components)
}
}

/// A Solidity Event parameter. Event parameters are distinct from function
/// parameters in that they have an `indexed` field.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
Expand All @@ -51,3 +66,13 @@ pub enum EventParam {
/// [`SimpleEventParam`] variant
Simple(SimpleEventParam),
}

impl EventParam {
/// Type used to encode the preimage of the event selector.
pub fn selector_type(&self) -> Cow<'_, str> {
match self {
Self::Complex(c) => c.selector_type().into(),
Self::Simple(s) => s.selector_type().into(),
}
}
}
69 changes: 30 additions & 39 deletions crates/json-abi/src/item.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{event_param::EventParam, param::Param, StateMutability};
use crate::{event_param::EventParam, param::Param, utils::*, StateMutability};
use alloc::{borrow::Cow, string::String, vec::Vec};
use alloy_primitives::{keccak256, Selector, B256};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

// Serde order:
Expand Down Expand Up @@ -196,56 +197,46 @@ impl<'a, 'r> From<&'r AbiItem<'a>> for &'r private::AbiItem<'a> {
}

impl Error {
/// Generate the selector preimage for this error.
pub fn selector_preimage(&self) -> String {
preimage(&self.name, &self.inputs)
/// Computes this error's signature.
///
/// This is the preimage input used to [compute the
/// selector](Self::selector).
pub fn signature(&self) -> String {
signature(&self.name, &self.inputs)
}

/// Generate the selector for this error.
pub fn selector(&self) -> alloy_primitives::Selector {
selector(&self.selector_preimage())
/// Computes this error's selector: `keccak256(self.signature())[..4]`
pub fn selector(&self) -> Selector {
selector(&self.signature())
}
}

impl Function {
/// Generate the selector preimage for this function.
pub fn selector_preimage(&self) -> String {
preimage(&self.name, &self.inputs)
/// Returns this function's signature.
///
/// This is the preimage input used to [compute the
/// selector](Self::selector).
pub fn signature(&self) -> String {
signature(&self.name, &self.inputs)
}

/// Generate the selector for this function.
pub fn selector(&self) -> alloy_primitives::Selector {
selector(&self.selector_preimage())
/// Computes this error's selector: `keccak256(self.signature())[..4]`
pub fn selector(&self) -> Selector {
selector(&self.signature())
}
}

/// `format!("{name}({inputs.join(",")})")`
fn preimage(name: &str, inputs: &[Param]) -> String {
let mut preimage = String::with_capacity(name.len() + 2 + inputs.len() * 32);
preimage.push_str(name);

preimage.push('(');
let mut first = true;
for input in inputs {
if !first {
preimage.push(',');
}
preimage.push_str(&input.selector_type());
first = false;
impl Event {
/// Returns this event's signature.
///
/// This is the preimage input used to [compute the
/// selector](Self::selector).
pub fn signature(&self) -> String {
event_signature(&self.name, &self.inputs)
}
preimage.push(')');

preimage
}

/// `keccak256({preimage})[..4]`
fn selector(preimage: &str) -> [u8; 4] {
// SAFETY: splitting an array
unsafe {
alloy_primitives::keccak256(preimage.as_bytes())
.0
.get_unchecked(..4)
.try_into()
.unwrap_unchecked()
/// Computes this event's selector: `keccak256(self.signature())`
pub fn selector(&self) -> B256 {
keccak256(self.signature().as_bytes())
}
}
4 changes: 3 additions & 1 deletion crates/json-abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
//! implement a JSON serialization scheme in rust. So while the internals are
//! nearly-identical, the API is our own.
//!
//! [specification]: https://docs.soliditylang.org/en/v0.8.20/abi-spec.html#json
//! [specification]: https://docs.soliditylang.org/en/latest/abi-spec.html#json
//! [ethabi]: https://github.com/rust-ethereum/ethabi
#![warn(
Expand Down Expand Up @@ -48,6 +48,8 @@ pub use item::{AbiItem, Constructor, Error, Event, Fallback, Function, Receive};
mod param;
pub use param::{ComplexParam, Param, SimpleParam};

pub(crate) mod utils;

/// A JSON ABI function's state mutability.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
Expand Down
8 changes: 1 addition & 7 deletions crates/json-abi/src/param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,7 @@ impl ComplexParam {
/// Type used to encode the preimage of the function or error selector, or
/// event topic
pub fn selector_type(&self) -> String {
let mut s = String::with_capacity(2 + self.components.len() * 32);
s.push('(');
for component in &self.components {
s.push_str(&component.selector_type());
}
s.push(')');
s
crate::utils::signature("", &self.components)
}

/// Returns a string representation of the parameter that can be used as a
Expand Down
79 changes: 79 additions & 0 deletions crates/json-abi/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use crate::{EventParam, Param};

macro_rules! signature {
($name:expr, $inputs:expr) => {{
let mut preimage = String::with_capacity($name.len() + 2 + $inputs.len() * 32);
preimage.push_str($name);

preimage.push('(');
let mut first = true;
for input in $inputs {
if !first {
preimage.push(',');
}
preimage.push_str(&input.selector_type());
first = false;
}
preimage.push(')');

preimage
}};
}

pub(crate) fn signature(name: &str, inputs: &[Param]) -> String {
signature!(name, inputs)
}

pub(crate) fn event_signature(name: &str, inputs: &[EventParam]) -> String {
signature!(name, inputs)
}

/// `keccak256(preimage)[..4]`
pub(crate) fn selector(preimage: &str) -> [u8; 4] {
// SAFETY: splitting an array
unsafe {
alloy_primitives::keccak256(preimage.as_bytes())
.0
.get_unchecked(..4)
.try_into()
.unwrap_unchecked()
}
}

#[cfg(test)]
mod tests {
use super::*;

fn param(kind: &str) -> Param {
Param::Simple(crate::SimpleParam {
name: "param".to_string(),
ty: kind.to_string(),
internal_type: "internalType".to_string(),
})
}

fn params(components: impl IntoIterator<Item = &'static str>) -> Param {
let components = components.into_iter().map(param).collect();
Param::Complex(crate::ComplexParam {
name: "param".to_string(),
ty: "ty".to_string(),
internal_type: "internalType".to_string(),
components,
})
}

#[test]
fn test_signature() {
assert_eq!(signature("foo", &[]), "foo()");
assert_eq!(signature("foo", &[param("bool")]), "foo(bool)");
assert_eq!(signature("foo", &[param("bool")]), "foo(bool)");
assert_eq!(
signature("foo", &[param("bool"), param("bool")]),
"foo(bool,bool)"
);
assert_eq!(
signature("foo", &[param("bool"), params(["bool[]"]), param("bool")]),
"foo(bool,(bool[]),bool)"
);
}
}

0 comments on commit 669d283

Please sign in to comment.