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: add support for parsing visibility and state mutability #682

Merged
merged 6 commits into from
Jun 27, 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
9 changes: 7 additions & 2 deletions crates/json-abi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ exclude.workspace = true

[dependencies]
alloy-primitives = { workspace = true, features = ["serde"] }
alloy-sol-type-parser.workspace = true
alloy-sol-type-parser = { workspace = true, features = ["serde"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, optional = true }

Expand All @@ -27,7 +27,12 @@ serde_json.workspace = true

[features]
default = ["std"]
std = ["serde/std", "alloy-primitives/std", "alloy-sol-type-parser/std", "serde_json?/std"]
std = [
"serde/std",
"alloy-primitives/std",
"alloy-sol-type-parser/std",
"serde_json?/std",
]
serde_json = ["dep:serde_json"]

[[bench]]
Expand Down
76 changes: 58 additions & 18 deletions crates/json-abi/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{param::Param, serde_state_mutability_compat, utils::*, EventParam, S
use alloc::{borrow::Cow, string::String, vec::Vec};
use alloy_primitives::{keccak256, Selector, B256};
use core::str::FromStr;
use parser::utils::ParsedSignature;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

/// Declares all JSON ABI items.
Expand Down Expand Up @@ -328,12 +329,12 @@ impl FromStr for Constructor {
}

impl Constructor {
/// Parses a Solidity constructor string: `constructor($($inputs),*)`
/// Parses a Solidity constructor string:
/// `constructor($($inputs),*) [visibility] [s_mutability]`
///
/// Note:
/// - the name must always be `constructor`
/// - [`state_mutability`](Self::state_mutability) is currently not parsed from the input and is
/// always set to [`StateMutability::NonPayable`]
/// - visibility is ignored
///
/// # Examples
///
Expand All @@ -352,7 +353,8 @@ impl Constructor {
parse_sig::<false>(s).and_then(Self::parsed)
}

fn parsed((name, inputs, outputs, anonymous): ParseSigTuple<Param>) -> parser::Result<Self> {
fn parsed(sig: ParsedSignature<Param>) -> parser::Result<Self> {
let ParsedSignature { name, inputs, outputs, anonymous, state_mutability } = sig;
if name != "constructor" {
return Err(parser::Error::new("constructors' name must be exactly \"constructor\""));
}
Expand All @@ -362,7 +364,7 @@ impl Constructor {
if anonymous {
return Err(parser::Error::new("constructors cannot be anonymous"));
}
Ok(Self { inputs, state_mutability: StateMutability::NonPayable })
Ok(Self { inputs, state_mutability: state_mutability.unwrap_or_default() })
}
}

Expand Down Expand Up @@ -398,13 +400,17 @@ impl Error {
parse_maybe_prefixed(s, "error", parse_sig::<false>).and_then(Self::parsed)
}

fn parsed((name, inputs, outputs, anonymous): ParseSigTuple<Param>) -> parser::Result<Self> {
fn parsed(sig: ParsedSignature<Param>) -> parser::Result<Self> {
let ParsedSignature { name, inputs, outputs, anonymous, state_mutability } = sig;
if !outputs.is_empty() {
return Err(parser::Error::new("errors cannot have outputs"));
}
if anonymous {
return Err(parser::Error::new("errors cannot be anonymous"));
}
if state_mutability.is_some() {
return Err(parser::Error::new("errors cannot have mutability"));
}
Ok(Self { name, inputs })
}

Expand Down Expand Up @@ -434,12 +440,10 @@ impl FromStr for Function {

impl Function {
/// Parses a Solidity function signature string:
/// `$(function)? $name($($inputs),*) $(returns ($($outputs),+))?`
/// `$(function)? $name($($inputs),*) [visibility] [s_mutability] $(returns ($($outputs),+))?`
///
/// Note:
/// - [`state_mutability`](Self::state_mutability) is currently not parsed from the input and is
/// always set to [`StateMutability::NonPayable`]
/// - visibility is rejected
/// - visibility is ignored
///
/// If you want to parse a generic [Human-Readable ABI] string, use [`AbiItem::parse`].
///
Expand Down Expand Up @@ -467,12 +471,12 @@ impl Function {
/// ```
/// # use alloy_json_abi::{Function, Param, StateMutability};
/// assert_eq!(
/// Function::parse("function toString(uint number) returns (string s)"),
/// Function::parse("function toString(uint number) external view returns (string s)"),
/// Ok(Function {
/// name: "toString".to_string(),
/// inputs: vec![Param::parse("uint number").unwrap()],
/// outputs: vec![Param::parse("string s").unwrap()],
/// state_mutability: StateMutability::NonPayable,
/// state_mutability: StateMutability::View,
/// }),
/// );
/// ```
Expand All @@ -481,11 +485,12 @@ impl Function {
parse_maybe_prefixed(s, "function", parse_sig::<true>).and_then(Self::parsed)
}

fn parsed((name, inputs, outputs, anonymous): ParseSigTuple<Param>) -> parser::Result<Self> {
fn parsed(sig: ParsedSignature<Param>) -> parser::Result<Self> {
let ParsedSignature { name, inputs, outputs, anonymous, state_mutability } = sig;
if anonymous {
return Err(parser::Error::new("function cannot be anonymous"));
return Err(parser::Error::new("functions cannot be anonymous"));
}
Ok(Self { name, inputs, outputs, state_mutability: StateMutability::NonPayable })
Ok(Self { name, inputs, outputs, state_mutability: state_mutability.unwrap_or_default() })
}

/// Returns this function's signature: `$name($($inputs),*)`.
Expand Down Expand Up @@ -561,12 +566,14 @@ impl Event {
parse_maybe_prefixed(s, "event", parse_event_sig).and_then(Self::parsed)
}

fn parsed(
(name, inputs, outputs, anonymous): ParseSigTuple<EventParam>,
) -> parser::Result<Self> {
fn parsed(sig: ParsedSignature<EventParam>) -> parser::Result<Self> {
let ParsedSignature { name, inputs, outputs, anonymous, state_mutability } = sig;
if !outputs.is_empty() {
return Err(parser::Error::new("events cannot have outputs"));
}
if state_mutability.is_some() {
return Err(parser::Error::new("events cannot have state mutability"));
}
Ok(Self { name, inputs, anonymous })
}

Expand Down Expand Up @@ -606,6 +613,14 @@ impl Event {
mod tests {
use super::*;

// fn param(kind: &str) -> Param {
// param2(kind, "param")
// }

fn param2(kind: &str, name: &str) -> Param {
Param { ty: kind.into(), name: name.into(), internal_type: None, components: vec![] }
}

#[test]
fn parse_prefixes() {
for prefix in ["function", "error", "event"] {
Expand Down Expand Up @@ -648,4 +663,29 @@ mod tests {
assert_eq!(Error::parse("errorfoo()"), Ok(new("errorfoo")));
assert_eq!(Error::parse("error errorfoo()"), Ok(new("errorfoo")));
}

#[test]
fn parse_full() {
// https://github.com/alloy-rs/core/issues/389
assert_eq!(
Function::parse("function foo(uint256 a, uint256 b) external returns (uint256)"),
Ok(Function {
name: "foo".into(),
inputs: vec![param2("uint256", "a"), param2("uint256", "b")],
outputs: vec![param2("uint256", "")],
state_mutability: StateMutability::NonPayable,
})
);

// https://github.com/alloy-rs/core/issues/681
assert_eq!(
Function::parse("function balanceOf(address owner) view returns (uint256 balance)"),
Ok(Function {
name: "balanceOf".into(),
inputs: vec![param2("address", "owner")],
outputs: vec![param2("uint256", "balance")],
state_mutability: StateMutability::View,
})
);
}
}
3 changes: 1 addition & 2 deletions crates/json-abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ pub use item::{AbiItem, Constructor, Error, Event, Fallback, Function, Receive};
mod param;
pub use param::{EventParam, Param};

mod state_mutability;
pub use state_mutability::{serde_state_mutability_compat, StateMutability};
pub use parser::{serde_state_mutability_compat, StateMutability};

mod internal_type;
pub use internal_type::InternalType;
Expand Down
93 changes: 47 additions & 46 deletions crates/json-abi/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use crate::{EventParam, Param, StateMutability};
use alloc::{
string::{String, ToString},
vec::Vec,
};
use alloc::string::{String, ToString};
use alloy_primitives::Selector;
use core::{fmt::Write, num::NonZeroUsize};
use parser::{ParameterSpecifier, TypeSpecifier, TypeStem};
use parser::{utils::ParsedSignature, ParameterSpecifier, TypeSpecifier, TypeStem};

/// Capacity to allocate per [Param].
const PARAM: usize = 32;
Expand Down Expand Up @@ -160,9 +157,6 @@ pub(crate) fn selector(preimage: &str) -> Selector {
}
}

pub(crate) type ParseSigTuple<T> = (String, Vec<T>, Vec<T>, bool);
pub(crate) type ParseSigResult<T> = parser::Result<ParseSigTuple<T>>;

/// Strips `prefix` from `s` before parsing with `parser`. `prefix` must be followed by whitespace.
pub(crate) fn parse_maybe_prefixed<F: FnOnce(&str) -> R, R>(
mut s: &str,
Expand All @@ -178,12 +172,12 @@ pub(crate) fn parse_maybe_prefixed<F: FnOnce(&str) -> R, R>(
}

#[inline]
pub(crate) fn parse_sig<const O: bool>(s: &str) -> ParseSigResult<Param> {
pub(crate) fn parse_sig<const O: bool>(s: &str) -> parser::Result<ParsedSignature<Param>> {
parser::utils::parse_signature::<O, _, _>(s, |p| mk_param(p.name, p.ty))
}

#[inline]
pub(crate) fn parse_event_sig(s: &str) -> ParseSigResult<EventParam> {
pub(crate) fn parse_event_sig(s: &str) -> parser::Result<ParsedSignature<EventParam>> {
parser::utils::parse_signature::<false, _, _>(s, mk_eparam)
}

Expand Down Expand Up @@ -424,65 +418,72 @@ mod tests {
}

#[test]
fn test_item_parse() {
assert_eq!(parse_sig::<true>("foo()"), Ok(("foo".into(), vec![], vec![], false)));
assert_eq!(parse_sig::<true>("foo()()"), Ok(("foo".into(), vec![], vec![], false)));
assert_eq!(parse_sig::<true>("foo() \t ()"), Ok(("foo".into(), vec![], vec![], false)));
assert_eq!(parse_sig::<true>("foo() ()"), Ok(("foo".into(), vec![], vec![], false)));
fn test_parse_sig() {
let empty_sig = |name: &str, anonymous| ParsedSignature::<Param> {
name: name.into(),
inputs: vec![],
outputs: vec![],
anonymous,
state_mutability: None,
};
let sig = |name: &str, inputs, outputs| ParsedSignature::<Param> {
name: name.into(),
inputs,
outputs,
anonymous: false,
state_mutability: None,
};

assert_eq!(parse_sig::<false>("foo()"), Ok(("foo".into(), vec![], vec![], false)));
assert_eq!(parse_sig::<true>("foo()"), Ok(empty_sig("foo", false)));
assert_eq!(parse_sig::<true>("foo()()"), Ok(empty_sig("foo", false)));
assert_eq!(parse_sig::<true>("foo()external()"), Ok(empty_sig("foo", false)));
assert_eq!(parse_sig::<true>("foo() \t ()"), Ok(empty_sig("foo", false)));
assert_eq!(parse_sig::<true>("foo() ()"), Ok(empty_sig("foo", false)));

assert_eq!(parse_sig::<false>("foo()"), Ok(empty_sig("foo", false)));
parse_sig::<false>("foo()()").unwrap_err();
parse_sig::<false>("foo()view external()").unwrap_err();
parse_sig::<false>("foo(,)()").unwrap_err();
parse_sig::<false>("foo(,)(,)").unwrap_err();

assert_eq!(parse_sig::<false>("foo()anonymous"), Ok(("foo".into(), vec![], vec![], true)));
assert_eq!(
parse_sig::<false>("foo()\t anonymous"),
Ok(("foo".into(), vec![], vec![], true))
);
assert_eq!(parse_sig::<false>("foo()anonymous"), Ok(empty_sig("foo", true)));
assert_eq!(parse_sig::<false>("foo()\t anonymous"), Ok(empty_sig("foo", true)));

assert_eq!(parse_sig::<true>("foo()anonymous"), Ok(("foo".into(), vec![], vec![], true)));
assert_eq!(
parse_sig::<true>("foo()\t anonymous"),
Ok(("foo".into(), vec![], vec![], true))
);
assert_eq!(parse_sig::<true>("foo()anonymous"), Ok(empty_sig("foo", true)));
assert_eq!(parse_sig::<true>("foo()\t anonymous"), Ok(empty_sig("foo", true)));

assert_eq!(
parse_sig::<true>("foo() \t ()anonymous"),
Ok(("foo".into(), vec![], vec![], true))
);
assert_eq!(parse_sig::<true>("foo()()anonymous"), Ok(("foo".into(), vec![], vec![], true)));
assert_eq!(
parse_sig::<true>("foo()()\t anonymous"),
Ok(("foo".into(), vec![], vec![], true))
);
assert_eq!(parse_sig::<true>("foo() \t ()anonymous"), Ok(empty_sig("foo", true)));
assert_eq!(parse_sig::<true>("foo()()anonymous"), Ok(empty_sig("foo", true)));
assert_eq!(parse_sig::<true>("foo()()\t anonymous"), Ok(empty_sig("foo", true)));

assert_eq!(
parse_sig::<false>("foo(uint256 param)"),
Ok(("foo".into(), vec![param("uint256")], vec![], false))
Ok(sig("foo", vec![param("uint256")], vec![]))
);
assert_eq!(
parse_sig::<false>("bar(uint256 param)"),
Ok(("bar".into(), vec![param("uint256")], vec![], false))
Ok(sig("bar", vec![param("uint256")], vec![]))
);
assert_eq!(
parse_sig::<false>("baz(uint256 param, bool param)"),
Ok(("baz".into(), vec![param("uint256"), param("bool")], vec![], false))
Ok(sig("baz", vec![param("uint256"), param("bool")], vec![]))
);

assert_eq!(
parse_sig::<true>("f(a b)(c d)"),
Ok(("f".into(), vec![param2("a", "b")], vec![param2("c", "d")], false))
Ok(sig("f", vec![param2("a", "b")], vec![param2("c", "d")]))
);

assert_eq!(
parse_sig::<true>("toString(uint number)(string s)"),
Ok((
"toString".into(),
vec![param2("uint256", "number")],
vec![param2("string", "s")],
false
))
)
Ok(sig("toString", vec![param2("uint256", "number")], vec![param2("string", "s")]))
);

let mut sig_full = sig("toString", vec![param("uint256")], vec![param("string")]);
sig_full.state_mutability = Some(StateMutability::View);
assert_eq!(
parse_sig::<true>("toString(uint param) external view returns(string param)"),
Ok(sig_full)
);
}
}
5 changes: 5 additions & 0 deletions crates/sol-type-parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@ workspace = true

[dependencies]
winnow.workspace = true
serde = { workspace = true, optional = true, features = ["derive"] }

[dev-dependencies]
serde_json.workspace = true

[features]
default = ["std"]
std = ["winnow/std"]
serde = ["dep:serde"]
debug = ["std"] # intentionally doesn't enable `winnow/debug`
5 changes: 5 additions & 0 deletions crates/sol-type-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@ pub use parameter::{ParameterSpecifier, Parameters, Storage};

/// Generic [`winnow`] parsing utilities.
pub mod utils;

mod state_mutability;
#[cfg(feature = "serde")]
pub use state_mutability::serde_state_mutability_compat;
pub use state_mutability::StateMutability;
Loading