Skip to content

Commit

Permalink
feat(json-abi): add full_signature (#480)
Browse files Browse the repository at this point in the history
* add full signature (#1)

* WIP probably working

* add tests

* nits

add more comment

clean up

* clippy vec macro

* Apply suggestions from code review

Co-authored-by: DaniPopes <[email protected]>

* signature_with_outputs, udpate comments

---------

Co-authored-by: DaniPopes <[email protected]>
  • Loading branch information
BrennerSpear and DaniPopes authored Jan 9, 2024
1 parent 477edb3 commit 49e347b
Show file tree
Hide file tree
Showing 3 changed files with 288 additions and 5 deletions.
24 changes: 23 additions & 1 deletion crates/json-abi/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,10 +498,21 @@ impl Function {
/// This is the same as [`signature`](Self::signature), but also includes
/// the output types.
#[inline]
pub fn signature_full(&self) -> String {
pub fn signature_with_outputs(&self) -> String {
signature(&self.name, &self.inputs, Some(&self.outputs))
}

/// Returns this function's full signature including names of params:
/// `function $name($($inputs $names),*) state_mutability returns ($($outputs $names),*)`.
///
/// This is a full human-readable string, including all parameter names, any optional modifiers
/// (e.g. view, payable, pure) and white-space to aid in human readability. This is useful for
/// storing a string which can still fully reconstruct the original Fragment
#[inline]
pub fn full_signature(&self) -> String {
full_signature(&self.name, &self.inputs, Some(&self.outputs), self.state_mutability)
}

/// Computes this error's selector: `keccak256(self.signature())[..4]`
#[inline]
pub fn selector(&self) -> Selector {
Expand Down Expand Up @@ -563,6 +574,17 @@ impl Event {
event_signature(&self.name, &self.inputs)
}

/// Returns this event's full signature
/// `event $name($($inputs indexed $names),*)`.
///
/// This is a full human-readable string, including all parameter names, any optional modifiers
/// (e.g. indexed) and white-space to aid in human readability. This is useful for
/// storing a string which can still fully reconstruct the original Fragment
#[inline]
pub fn full_signature(&self) -> String {
event_full_signature(&self.name, &self.inputs)
}

/// Computes this event's selector: `keccak256(self.signature())`
#[inline]
pub fn selector(&self) -> B256 {
Expand Down
32 changes: 32 additions & 0 deletions crates/json-abi/src/param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,22 @@ impl Param {
}
}

/// Formats the canonical type of this parameter into the given string including then names of
/// the params.
#[inline]
pub fn full_selector_type_raw(&self, s: &mut String) {
if self.components.is_empty() {
s.push_str(&self.ty);
} else {
s.push_str("tuple");
crate::utils::full_signature_raw(&self.components, s);
// checked during deserialization, but might be invalid from a user
if let Some(suffix) = self.ty.strip_prefix("tuple") {
s.push_str(suffix);
}
}
}

/// Returns the canonical type of this parameter.
///
/// This is used to encode the preimage of a function or error selector.
Expand Down Expand Up @@ -456,6 +472,22 @@ impl EventParam {
}
}

/// Formats the canonical type of this parameter into the given string including then names of
/// the params.
#[inline]
pub fn full_selector_type_raw(&self, s: &mut String) {
if self.components.is_empty() {
s.push_str(&self.ty);
} else {
s.push_str("tuple");
crate::utils::full_signature_raw(&self.components, s);
// checked during deserialization, but might be invalid from a user
if let Some(suffix) = self.ty.strip_prefix("tuple") {
s.push_str(suffix);
}
}
}

/// Returns the canonical type of this parameter.
///
/// This is used to encode the preimage of the event selector.
Expand Down
237 changes: 233 additions & 4 deletions crates/json-abi/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{EventParam, Param};
use alloc::{string::String, vec::Vec};
use crate::{EventParam, Param, StateMutability};
use alloc::{
string::{String, ToString},
vec::Vec,
};
use alloy_primitives::Selector;
use core::{fmt::Write, num::NonZeroUsize};
use parser::{ParameterSpecifier, TypeSpecifier, TypeStem};
Expand Down Expand Up @@ -33,6 +36,45 @@ macro_rules! signature {
};
}

macro_rules! event_full_signature {
($inputs:expr, $preimage:expr) => {
$preimage.push('(');
for (i, input) in $inputs.iter().enumerate() {
if i > 0 {
$preimage.push(',');
$preimage.push(' ');
}
input.full_selector_type_raw($preimage);
if input.indexed {
$preimage.push_str(" indexed");
}
if !input.name.is_empty() {
$preimage.push(' ');
$preimage.push_str(&input.name);
}
}
$preimage.push(')');
};
}

macro_rules! full_signature {
($inputs:expr, $preimage:expr) => {
$preimage.push('(');
for (i, input) in $inputs.iter().enumerate() {
if i > 0 {
$preimage.push(',');
$preimage.push(' ');
}
input.full_selector_type_raw($preimage);
if !input.name.is_empty() {
$preimage.push(' ');
$preimage.push_str(&input.name);
}
}
$preimage.push(')');
};
}

/// `$name($($inputs),*)($($outputs),*)`
pub(crate) fn signature(name: &str, inputs: &[Param], outputs: Option<&[Param]>) -> String {
let parens = 2 + outputs.is_some() as usize * 2;
Expand All @@ -47,11 +89,47 @@ pub(crate) fn signature(name: &str, inputs: &[Param], outputs: Option<&[Param]>)
preimage
}

pub(crate) fn full_signature(
name: &str,
inputs: &[Param],
outputs: Option<&[Param]>,
state_mutability: StateMutability,
) -> String {
let parens = 2 + outputs.is_some() as usize * 2;
let n_outputs = outputs.map(<[_]>::len).unwrap_or(0);
let mut state_mutability_str = format!(" {}", state_mutability.as_str().unwrap_or_default());
if state_mutability_str.trim().is_empty() {
state_mutability_str = "".to_string();
}
let cap = "function ".len()
+ name.len()
+ parens
+ (inputs.len() + n_outputs) * PARAM
+ state_mutability_str.len();
let mut preimage = String::with_capacity(cap);

preimage.push_str("function ");
preimage.push_str(name);
full_signature_raw(inputs, &mut preimage);
preimage.push_str(&state_mutability_str);
if let Some(outputs) = outputs {
if !outputs.is_empty() {
preimage.push_str(" returns ");
full_signature_raw(outputs, &mut preimage);
}
}
preimage
}

/// `($($params),*)`
pub(crate) fn signature_raw(params: &[Param], preimage: &mut String) {
signature!(params, preimage);
}

pub(crate) fn full_signature_raw(params: &[Param], preimage: &mut String) {
full_signature!(params, preimage);
}

/// `$name($($inputs),*)`
pub(crate) fn event_signature(name: &str, inputs: &[EventParam]) -> String {
let mut preimage = String::with_capacity(name.len() + 2 + inputs.len() * PARAM);
Expand All @@ -60,6 +138,16 @@ pub(crate) fn event_signature(name: &str, inputs: &[EventParam]) -> String {
preimage
}

/// `$name($($inputs indexed names),*)`
pub(crate) fn event_full_signature(name: &str, inputs: &[EventParam]) -> String {
let mut preimage =
String::with_capacity("event ".len() + name.len() + 2 + inputs.len() * PARAM);
preimage.push_str("event ");
preimage.push_str(name);
event_full_signature!(inputs, &mut preimage);
preimage
}

/// `keccak256(preimage)[..4]`
pub(crate) fn selector(preimage: &str) -> Selector {
// SAFETY: splitting an array
Expand Down Expand Up @@ -152,12 +240,20 @@ mod tests {
}

fn eparam(kind: &str) -> EventParam {
eparam_with_indexed(kind, "param", false)
}

fn eparam2(kind: &str, name: &str, indexed: bool) -> EventParam {
eparam_with_indexed(kind, name, indexed)
}

fn eparam_with_indexed(kind: &str, name: &str, indexed: bool) -> EventParam {
EventParam {
name: "param".into(),
name: name.into(),
ty: kind.into(),
internal_type: None,
components: vec![],
indexed: false,
indexed,
}
}

Expand All @@ -166,6 +262,28 @@ mod tests {
crate::Param { name: "param".into(), ty: "tuple".into(), internal_type: None, components }
}

fn full_signature_raw(
name: &str,
inputs: &[Param],
outputs: Option<&[Param]>,
state_mutability: StateMutability,
) -> String {
full_signature(name, inputs, outputs, state_mutability)
}

fn full_signature_np(name: &str, inputs: &[Param], outputs: Option<&[Param]>) -> String {
full_signature_raw(name, inputs, outputs, StateMutability::NonPayable)
}

fn full_signature_with_sm(
name: &str,
inputs: &[Param],
outputs: Option<&[Param]>,
state_mutability: StateMutability,
) -> String {
full_signature_raw(name, inputs, outputs, state_mutability)
}

#[test]
fn test_signature() {
assert_eq!(signature("foo", &[], None), "foo()");
Expand All @@ -187,13 +305,124 @@ mod tests {
);
}

#[test]
fn test_full_signature() {
assert_eq!(full_signature_np("foo", &[], None), "function foo()");
assert_eq!(full_signature_np("foo", &[], Some(&[])), "function foo()");
assert_eq!(full_signature_np("bar", &[param2("bool", "")], None), "function bar(bool)");
assert_eq!(
full_signature_np("bar", &[param2("bool", "")], Some(&[param2("bool", "")])),
"function bar(bool) returns (bool)"
);
assert_eq!(
full_signature_np(
"foo",
&[param2("address", "asset"), param2("uint256", "amount")],
None
),
"function foo(address asset, uint256 amount)"
);
assert_eq!(
full_signature_np(
"foo",
&[param2("address", "asset")],
Some(&[param2("uint256", "amount")])
),
"function foo(address asset) returns (uint256 amount)"
);

let components = vec![
param2("address", "pool"),
param2("uint256", "tokenInParam"),
param2("uint256", "tokenOutParam"),
param2("uint256", "maxPrice"),
];
let swaps =
Param { name: "swaps".into(), ty: "tuple[]".into(), internal_type: None, components };

assert_eq!(
full_signature_with_sm(
"batchSwapExactIn",
&[
swaps,
param2("address", "tokenIn"),
param2("address", "tokenOut"),
param2("uint256", "totalAmountIn"),
param2("uint256", "minTotalAmountOut"),
],
Some(&[param2("uint256", "totalAmountOut")]),
StateMutability::Payable,
),
"function batchSwapExactIn(tuple(address pool, uint256 tokenInParam, uint256 tokenOutParam, uint256 maxPrice)[] swaps, address tokenIn, address tokenOut, uint256 totalAmountIn, uint256 minTotalAmountOut) payable returns (uint256 totalAmountOut)"
);

assert_eq!(
full_signature_with_sm(
"name",
&[],
Some(&[param2("string", "")]),
StateMutability::View
),
"function name() view returns (string)"
);

assert_eq!(
full_signature_with_sm(
"calculateHash",
&[param2("address[]", "_addresses")],
Some(&[param2("bytes32", "")]),
StateMutability::Pure,
),
"function calculateHash(address[] _addresses) pure returns (bytes32)"
);
}

#[test]
fn test_event_signature() {
assert_eq!(event_signature("foo", &[]), "foo()");
assert_eq!(event_signature("foo", &[eparam("bool")]), "foo(bool)");
assert_eq!(event_signature("foo", &[eparam("bool"), eparam("string")]), "foo(bool,string)");
}

#[test]
fn test_event_full_signature() {
assert_eq!(event_full_signature("foo", &[]), "event foo()");
assert_eq!(
event_full_signature("foo", &[eparam2("bool", "confirmed", true)]),
"event foo(bool indexed confirmed)"
);
assert_eq!(
event_full_signature(
"foo",
&[eparam2("bool", "confirmed", true), eparam2("string", "message", false)]
),
"event foo(bool indexed confirmed, string message)"
);

let components = vec![
param2("uint256", "amount"),
param2("uint256", "startTime"),
param2("uint256", "interval"),
];
let info = EventParam {
name: "info".into(),
ty: "tuple".into(),
internal_type: None,
components,
indexed: false,
};
assert_eq!(
event_full_signature(
"SetupDirectDebit",
&[
eparam2("address", "debtor", true),
eparam2("address", "receiver", true),
info,
] ),
"event SetupDirectDebit(address indexed debtor, address indexed receiver, tuple(uint256 amount, uint256 startTime, uint256 interval) info)"
);
}

#[test]
fn test_item_parse() {
assert_eq!(parse_sig::<true>("foo()"), Ok(("foo".into(), vec![], vec![], false)));
Expand Down

0 comments on commit 49e347b

Please sign in to comment.