diff --git a/crates/dyn-abi/Cargo.toml b/crates/dyn-abi/Cargo.toml index 87f9566825..0758ceae36 100644 --- a/crates/dyn-abi/Cargo.toml +++ b/crates/dyn-abi/Cargo.toml @@ -25,6 +25,7 @@ hex.workspace = true itoa.workspace = true # eip712 +derive_more = { workspace = true, optional = true } serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } @@ -36,7 +37,7 @@ ethabi = "18" [features] default = ["std"] std = ["alloy-sol-types/std", "alloy-primitives/std", "hex/std", "serde?/std", "serde_json?/std"] -eip712 = ["alloy-sol-types/eip712-serde", "dep:serde", "dep:serde_json"] +eip712 = ["alloy-sol-types/eip712-serde", "dep:derive_more", "dep:serde", "dep:serde_json"] [[bench]] name = "abi" diff --git a/crates/dyn-abi/src/eip712/coerce.rs b/crates/dyn-abi/src/eip712/coerce.rs index 46f6e02154..014d80f005 100644 --- a/crates/dyn-abi/src/eip712/coerce.rs +++ b/crates/dyn-abi/src/eip712/coerce.rs @@ -1,6 +1,5 @@ -use crate::{DynAbiError, DynSolType, DynSolValue, Word}; +use crate::{DynAbiError, DynAbiResult, DynSolType, DynSolValue, Word}; use alloc::{ - borrow::ToOwned, boxed::Box, string::{String, ToString}, vec::Vec, @@ -8,17 +7,17 @@ use alloc::{ use alloy_primitives::{Address, I256, U256}; impl DynSolType { - /// Coerce a json value to a sol value via this type. - pub fn coerce(&self, value: &serde_json::Value) -> Result { + /// Coerce a [`serde_json::Value`] to a [`DynSolValue`] via this type. + pub fn coerce(&self, value: &serde_json::Value) -> DynAbiResult { match self { DynSolType::Address => address(value), - DynSolType::Bytes => bytes(value), + DynSolType::Bool => bool(value), DynSolType::Int(n) => int(*n, value), DynSolType::Uint(n) => uint(*n, value), - DynSolType::Bool => bool(value), - DynSolType::Array(inner) => array(inner, value), - DynSolType::String => string(value), DynSolType::FixedBytes(n) => fixed_bytes(*n, value), + DynSolType::String => string(value), + DynSolType::Bytes => bytes(value), + DynSolType::Array(inner) => array(inner, value), DynSolType::FixedArray(inner, n) => fixed_array(inner, *n, value), DynSolType::Tuple(inner) => tuple(inner, value), DynSolType::CustomStruct { @@ -26,13 +25,11 @@ impl DynSolType { prop_names, tuple, } => coerce_custom_struct(name, prop_names, tuple, value), - DynSolType::CustomValue { name } => coerce_custom_value(name, value), } } } -/// Coerce a `serde_json::Value` to a `DynSolValue::Address` -pub(crate) fn address(value: &serde_json::Value) -> Result { +fn address(value: &serde_json::Value) -> DynAbiResult { let address = value .as_str() .map(|s| { @@ -44,7 +41,7 @@ pub(crate) fn address(value: &serde_json::Value) -> Result Result { +fn bool(value: &serde_json::Value) -> DynAbiResult { if let Some(bool) = value.as_bool() { return Ok(DynSolValue::Bool(bool)) } @@ -59,26 +56,7 @@ pub(crate) fn bool(value: &serde_json::Value) -> Result Result { - let bytes = value - .as_str() - .map(|s| hex::decode(s).map_err(|_| DynAbiError::type_mismatch(DynSolType::Bytes, value))) - .ok_or_else(|| DynAbiError::type_mismatch(DynSolType::Bytes, value))??; - Ok(DynSolValue::Bytes(bytes)) -} - -pub(crate) fn fixed_bytes(n: usize, value: &serde_json::Value) -> Result { - if let Some(Ok(buf)) = value.as_str().map(hex::decode) { - let mut word: Word = Default::default(); - let min = n.min(buf.len()); - word[..min].copy_from_slice(&buf[..min]); - return Ok(DynSolValue::FixedBytes(word, n)) - } - - Err(DynAbiError::type_mismatch(DynSolType::FixedBytes(n), value)) -} - -pub(crate) fn int(n: usize, value: &serde_json::Value) -> Result { +fn int(n: usize, value: &serde_json::Value) -> DynAbiResult { if let Some(num) = value.as_i64() { return Ok(DynSolValue::Int(I256::try_from(num).unwrap(), n)) } @@ -90,7 +68,7 @@ pub(crate) fn int(n: usize, value: &serde_json::Value) -> Result Result { +fn uint(n: usize, value: &serde_json::Value) -> DynAbiResult { if let Some(num) = value.as_u64() { return Ok(DynSolValue::Uint(U256::from(num), n)) } @@ -108,7 +86,18 @@ pub(crate) fn uint(n: usize, value: &serde_json::Value) -> Result Result { +fn fixed_bytes(n: usize, value: &serde_json::Value) -> DynAbiResult { + if let Some(Ok(buf)) = value.as_str().map(hex::decode) { + let mut word: Word = Default::default(); + let min = n.min(buf.len()); + word[..min].copy_from_slice(&buf[..min]); + return Ok(DynSolValue::FixedBytes(word, n)) + } + + Err(DynAbiError::type_mismatch(DynSolType::FixedBytes(n), value)) +} + +fn string(value: &serde_json::Value) -> DynAbiResult { let string = value .as_str() .map(|s| s.to_string()) @@ -116,10 +105,15 @@ pub(crate) fn string(value: &serde_json::Value) -> Result Result { +fn bytes(value: &serde_json::Value) -> DynAbiResult { + let bytes = value + .as_str() + .map(|s| hex::decode(s).map_err(|_| DynAbiError::type_mismatch(DynSolType::Bytes, value))) + .ok_or_else(|| DynAbiError::type_mismatch(DynSolType::Bytes, value))??; + Ok(DynSolValue::Bytes(bytes)) +} + +fn tuple(inner: &[DynSolType], value: &serde_json::Value) -> DynAbiResult { if let Some(arr) = value.as_array() { if inner.len() != arr.len() { return Err(DynAbiError::type_mismatch( @@ -143,10 +137,7 @@ pub(crate) fn tuple( )) } -pub(crate) fn array( - inner: &DynSolType, - value: &serde_json::Value, -) -> Result { +fn array(inner: &DynSolType, value: &serde_json::Value) -> DynAbiResult { if let Some(arr) = value.as_array() { let array = arr .iter() @@ -162,11 +153,11 @@ pub(crate) fn array( )) } -pub(crate) fn fixed_array( +fn fixed_array( inner: &DynSolType, n: usize, value: &serde_json::Value, -) -> Result { +) -> DynAbiResult { if let Some(arr) = value.as_array() { if arr.len() != n { return Err(DynAbiError::type_mismatch( @@ -228,29 +219,6 @@ pub(crate) fn coerce_custom_struct( )) } -pub(crate) fn coerce_custom_value( - name: &str, - value: &serde_json::Value, -) -> Result { - if let Some(Ok(buf)) = value.as_str().map(hex::decode) { - let mut word: Word = Default::default(); - let amnt = if buf.len() > 32 { 32 } else { buf.len() }; - word[..amnt].copy_from_slice(&buf[..amnt]); - - return Ok(DynSolValue::CustomValue { - name: name.to_string(), - inner: word, - }) - } - - Err(DynAbiError::type_mismatch( - DynSolType::CustomValue { - name: name.to_owned(), - }, - value, - )) -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/dyn-abi/src/eip712/mod.rs b/crates/dyn-abi/src/eip712/mod.rs index 49bdda3d3b..0f949cc181 100644 --- a/crates/dyn-abi/src/eip712/mod.rs +++ b/crates/dyn-abi/src/eip712/mod.rs @@ -13,6 +13,6 @@ mod typed_data; pub use typed_data::{Eip712Types, TypedData}; mod resolver; -pub use resolver::{PropertyDef, Resolver}; +pub use resolver::{PropertyDef, Resolver, TypeDef}; pub(crate) mod coerce; diff --git a/crates/dyn-abi/src/eip712/parser.rs b/crates/dyn-abi/src/eip712/parser.rs index 7368eb9bff..497202afd0 100644 --- a/crates/dyn-abi/src/eip712/parser.rs +++ b/crates/dyn-abi/src/eip712/parser.rs @@ -31,7 +31,6 @@ impl<'a> TryFrom<&'a str> for PropDef<'a> { let (ty, name) = input .rsplit_once(' ') .ok_or_else(|| DynAbiError::invalid_property_def(input))?; - Ok(PropDef { ty: ty.trim().try_into()?, name: name.trim(), diff --git a/crates/dyn-abi/src/eip712/resolver.rs b/crates/dyn-abi/src/eip712/resolver.rs index 79e65f6a51..f8bf34d63c 100644 --- a/crates/dyn-abi/src/eip712/resolver.rs +++ b/crates/dyn-abi/src/eip712/resolver.rs @@ -2,11 +2,10 @@ use crate::{ eip712::typed_data::Eip712Types, eip712_parser::EncodeType, parser::{RootType, TypeSpecifier, TypeStem}, - DynAbiError, DynSolType, DynSolValue, + DynAbiError, DynAbiResult, DynSolType, DynSolValue, }; use alloc::{ borrow::ToOwned, - boxed::Box, collections::{BTreeMap, BTreeSet}, string::{String, ToString}, vec::Vec, @@ -17,7 +16,7 @@ use core::{cmp::Ordering, fmt}; use serde::{Deserialize, Deserializer, Serialize}; /// An EIP-712 property definition. -#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct PropertyDef { /// Typename. #[serde(rename = "type")] @@ -41,39 +40,54 @@ impl<'de> Deserialize<'de> for PropertyDef { impl PropertyDef { /// Instantiate a new name-type pair. - pub fn new(type_name: impl AsRef, name: impl AsRef) -> Result { - let type_name: TypeSpecifier<'_> = type_name.as_ref().try_into()?; + #[inline] + pub fn new(type_name: T, name: N) -> DynAbiResult + where + T: Into, + N: Into, + { + let type_name = type_name.into(); + TypeSpecifier::try_from(type_name.as_str())?; Ok(Self::new_unchecked(type_name, name)) } /// Instantiate a new name-type pair, without checking that the type name /// is a valid root type. - pub fn new_unchecked(type_name: impl AsRef, name: impl AsRef) -> Self { + #[inline] + pub fn new_unchecked(type_name: T, name: N) -> Self + where + T: Into, + N: Into, + { Self { - type_name: type_name.as_ref().to_owned(), - name: name.as_ref().to_owned(), + type_name: type_name.into(), + name: name.into(), } } + /// Returns the name of the property. + #[inline] + pub fn name(&self) -> &str { + &self.name + } + /// Returns the type name of the property. + #[inline] pub fn type_name(&self) -> &str { &self.type_name } /// Returns the root type of the name/type pair, stripping any array. + #[inline] pub fn root_type_name(&self) -> &str { self.type_name .split_once('[') .map(|t| t.0) .unwrap_or(&self.type_name) } - - /// Returns the name of the property. - pub fn name(&self) -> &str { - &self.name - } } +/// An EIP-712 type definition. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TypeDef { /// Must always be a ROOT type name with any array stripped. @@ -105,46 +119,52 @@ impl fmt::Display for TypeDef { impl TypeDef { /// Instantiate a new type definition, checking that the type name is a /// valid root type. - pub fn new(type_name: impl AsRef, props: Vec) -> Result { - let _rt: RootType<'_> = type_name.as_ref().try_into()?; - Ok(Self { - type_name: type_name.as_ref().to_owned(), - props, - }) + #[inline] + pub fn new>(type_name: S, props: Vec) -> DynAbiResult { + let type_name = type_name.into(); + RootType::try_from(type_name.as_str())?; + Ok(Self { type_name, props }) } /// Instantiate a new type definition, without checking that the type name /// is a valid root type. This may result in bad behavior in a resolver. + #[inline] pub fn new_unchecked(type_name: String, props: Vec) -> Self { Self { type_name, props } } /// Returns the type name of the type definition. + #[inline] pub fn type_name(&self) -> &str { &self.type_name } /// Returns the property definitions of the type definition. + #[inline] pub fn props(&self) -> &[PropertyDef] { &self.props } /// Returns the property names of the type definition. + #[inline] pub fn prop_names(&self) -> impl Iterator + '_ { self.props.iter().map(|p| p.name()) } /// Returns the root property types of the type definition. + #[inline] pub fn prop_root_types(&self) -> impl Iterator + '_ { self.props.iter().map(|p| p.root_type_name()) } /// Returns the property types of the type definition. + #[inline] pub fn prop_types(&self) -> impl Iterator + '_ { self.props.iter().map(|p| p.type_name()) } /// Produces the EIP-712 `encodeType` typestring for this type definition. + #[inline] pub fn eip712_encode_type(&self) -> String { let mut s = String::with_capacity(self.type_name.len() + 2 + self.props_bytes_len()); self.fmt_eip712_encode_type(&mut s).unwrap(); @@ -170,6 +190,7 @@ impl TypeDef { /// Returns the number of bytes that the properties of this type definition /// will take up when formatted in the EIP-712 `encodeType` typestring. + #[inline] pub fn props_bytes_len(&self) -> usize { self.props .iter() @@ -178,6 +199,7 @@ impl TypeDef { } /// Return the root type. + #[inline] pub fn root_type(&self) -> RootType<'_> { self.type_name .as_str() @@ -285,11 +307,11 @@ impl Resolver { false } - /// Ingest types from an EIP-712 `encodeType` - pub fn ingest_string(&mut self, s: impl AsRef) -> Result<(), DynAbiError> { + /// Ingest types from an EIP-712 `encodeType`. + pub fn ingest_string(&mut self, s: impl AsRef) -> DynAbiResult<()> { let encode_type: EncodeType<'_> = s.as_ref().try_into()?; - for type_def in encode_type.types.into_iter().map(|t| t.to_owned()) { - self.ingest(type_def); + for t in encode_type.types { + self.ingest(t.to_owned()); } Ok(()) } @@ -329,7 +351,7 @@ impl Resolver { &'a self, resolution: &mut Vec<&'a TypeDef>, root_type: RootType<'_>, - ) -> Result<(), DynAbiError> { + ) -> DynAbiResult<()> { if root_type.try_basic_solidity().is_ok() { return Ok(()) } @@ -354,7 +376,7 @@ impl Resolver { /// This function linearizes a type into a list of typedefs of its /// dependencies. - pub fn linearize(&self, type_name: &str) -> Result, DynAbiError> { + pub fn linearize(&self, type_name: &str) -> DynAbiResult> { let mut context = DfsContext::default(); if self.detect_cycle(type_name, &mut context) { return Err(DynAbiError::circular_dependency(type_name)) @@ -365,11 +387,34 @@ impl Resolver { Ok(resolution) } + /// Resolve a typename into a [`crate::DynSolType`] or return an error if + /// the type is missing, or contains a circular dependency. + pub fn resolve(&self, type_name: &str) -> DynAbiResult { + if self.detect_cycle(type_name, &mut Default::default()) { + return Err(DynAbiError::circular_dependency(type_name)) + } + self.unchecked_resolve(&type_name.try_into()?) + } + + /// Resolve a type into a [`crate::DynSolType`] without checking for cycles. + fn unchecked_resolve(&self, type_spec: &TypeSpecifier<'_>) -> DynAbiResult { + let ty = match &type_spec.root { + TypeStem::Root(root) => self.resolve_root_type(*root), + TypeStem::Tuple(tuple) => tuple + .types + .iter() + .map(|ty| self.unchecked_resolve(ty)) + .collect::>() + .map(DynSolType::Tuple), + }?; + Ok(type_spec.wrap_type(ty)) + } + /// Resolves a root Solidity type into either a basic type or a custom /// struct. - fn resolve_root_type(&self, root_type: RootType<'_>) -> Result { - if root_type.try_basic_solidity().is_ok() { - return root_type.resolve_basic_solidity() + fn resolve_root_type(&self, root_type: RootType<'_>) -> DynAbiResult { + if let Ok(ty) = root_type.resolve_basic_solidity() { + return Ok(ty) } let ty = self @@ -377,11 +422,11 @@ impl Resolver { .get(root_type.as_str()) .ok_or_else(|| DynAbiError::missing_type(root_type.as_str()))?; - let prop_names = ty.prop_names().map(str::to_string).collect(); - let tuple = ty + let prop_names: Vec<_> = ty.prop_names().map(str::to_string).collect(); + let tuple: Vec<_> = ty .prop_types() - .map(|ty| self.unchecked_resolve(ty.try_into()?)) - .collect::, _>>()?; + .map(|ty| self.unchecked_resolve(&ty.try_into()?)) + .collect::>()?; Ok(DynSolType::CustomStruct { name: ty.type_name.clone(), @@ -390,41 +435,10 @@ impl Resolver { }) } - /// Resolve a type into a [`crate::DynSolType`] without checking for cycles. - fn unchecked_resolve(&self, type_spec: TypeSpecifier<'_>) -> Result { - let ty = match type_spec.root { - TypeStem::Root(root) => self.resolve_root_type(root)?, - TypeStem::Tuple(tuple) => { - let tuple = tuple - .types - .into_iter() - .map(|ty| self.unchecked_resolve(ty)) - .collect::, _>>()?; - DynSolType::Tuple(tuple) - } - }; - - let ty = type_spec.sizes.iter().fold(ty, |acc, item| match item { - Some(size) => DynSolType::FixedArray(Box::new(acc), size.get()), - None => DynSolType::Array(Box::new(acc)), - }); - - Ok(ty) - } - - /// Resolve a typename into a [`crate::DynSolType`] or return an error if - /// the type is missing, or contains a circular dependency. - pub fn resolve(&self, type_name: &str) -> Result { - if self.detect_cycle(type_name, &mut Default::default()) { - return Err(DynAbiError::circular_dependency(type_name)) - } - self.unchecked_resolve(type_name.try_into()?) - } - /// Encode the type into an EIP-712 `encodeType` string /// /// - pub fn encode_type(&self, name: &str) -> Result { + pub fn encode_type(&self, name: &str) -> DynAbiResult { let linear = self.linearize(name)?; let first = linear.first().unwrap().eip712_encode_type(); @@ -442,64 +456,65 @@ impl Resolver { } /// Compute the keccak256 hash of the EIP-712 `encodeType` string. - pub fn type_hash(&self, name: &str) -> Result { + pub fn type_hash(&self, name: &str) -> DynAbiResult { self.encode_type(name).map(keccak256) } /// Encode the data according to EIP-712 `encodeData` rules. - pub fn encode_data(&self, value: &DynSolValue) -> Result>, DynAbiError> { - match value { + pub fn encode_data(&self, value: &DynSolValue) -> DynAbiResult>> { + Ok(match value { DynSolValue::CustomStruct { tuple: inner, .. } | DynSolValue::Array(inner) | DynSolValue::FixedArray(inner) => { - let inner = inner.iter().try_fold(Vec::new(), |mut acc, v| { - acc.extend(self.eip712_data_word(v)?.as_slice()); - Ok::<_, DynAbiError>(acc) - })?; - Ok(Some(inner)) + let mut bytes = Vec::with_capacity(inner.len() * 32); + for v in inner { + bytes.extend(self.eip712_data_word(v)?.as_slice()); + } + Some(bytes) } - DynSolValue::Bytes(buf) => Ok(Some(buf.to_vec())), - DynSolValue::String(s) => Ok(Some(s.as_bytes().to_vec())), - _ => Ok(None), - } + DynSolValue::Bytes(buf) => Some(buf.to_vec()), + DynSolValue::String(s) => Some(s.as_bytes().to_vec()), + _ => None, + }) } /// Encode the data as a struct property according to EIP-712 `encodeData` /// rules. Atomic types are encoded as-is, while non-atomic types are /// encoded as their `encodeData` hash. - pub fn eip712_data_word(&self, value: &DynSolValue) -> Result { + pub fn eip712_data_word(&self, value: &DynSolValue) -> DynAbiResult { if let Some(word) = value.as_word() { return Ok(word) } - match value { + let mut bytes; + let to_hash = match value { DynSolValue::CustomStruct { name, tuple, .. } => { - let type_hash = self.type_hash(name)?.to_vec(); - let inner = tuple.iter().try_fold(type_hash, |mut acc, v| { - acc.extend(self.eip712_data_word(v)?.as_slice()); - Ok::<_, DynAbiError>(acc) - })?; - Ok(keccak256(inner)) + bytes = self.type_hash(name)?.to_vec(); + for v in tuple { + bytes.extend(self.eip712_data_word(v)?.as_slice()); + } + &bytes[..] } DynSolValue::Array(inner) | DynSolValue::FixedArray(inner) => { - let inner = inner.iter().try_fold(Vec::new(), |mut acc, v| { - acc.extend(self.eip712_data_word(v)?.as_slice()); - Ok::<_, DynAbiError>(acc) - })?; - Ok(keccak256(inner)) + bytes = Vec::with_capacity(inner.len() * 32); + for v in inner { + bytes.extend(self.eip712_data_word(v)?); + } + &bytes[..] } - DynSolValue::Bytes(buf) => Ok(keccak256(buf)), - DynSolValue::String(s) => Ok(keccak256(s.as_bytes())), + DynSolValue::Bytes(buf) => buf, + DynSolValue::String(s) => s.as_bytes(), _ => unreachable!("all types are words or covered in the match"), - } + }; + Ok(keccak256(to_hash)) } } #[cfg(test)] mod tests { - use alloy_sol_types::sol; - use super::*; + use alloc::boxed::Box; + use alloy_sol_types::sol; #[test] fn it_detects_cycles() { @@ -636,14 +651,14 @@ mod tests { assert_eq!(graph.encode_type("Transaction").unwrap(), ENCODE_TYPE_2); } - sol!( - struct MyStruct { - uint256 a; - } - ); - #[test] fn it_ingests_sol_structs() { + sol!( + struct MyStruct { + uint256 a; + } + ); + let mut graph = Resolver::default(); graph.ingest_sol_struct::(); assert_eq!( diff --git a/crates/dyn-abi/src/eip712/typed_data.rs b/crates/dyn-abi/src/eip712/typed_data.rs index 00e7420afe..77f3bd4305 100644 --- a/crates/dyn-abi/src/eip712/typed_data.rs +++ b/crates/dyn-abi/src/eip712/typed_data.rs @@ -10,11 +10,14 @@ use alloc::{ }; use alloy_primitives::{keccak256, B256}; use alloy_sol_types::{Eip712Domain, SolStruct}; +use derive_more::{Deref, DerefMut, From, Into, IntoIterator}; use serde::{Deserialize, Serialize}; -/// Custom types for `TypedData` -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)] -pub struct Eip712Types(BTreeMap>); +/// Custom types for `TypedData`. +#[derive( + Clone, Debug, Default, PartialEq, Eq, Serialize, Deref, DerefMut, From, Into, IntoIterator, +)] +pub struct Eip712Types(#[into_iterator(ref, ref mut, owned)] BTreeMap>); impl<'de> Deserialize<'de> for Eip712Types { fn deserialize>(deserializer: D) -> Result { @@ -30,31 +33,10 @@ impl<'de> Deserialize<'de> for Eip712Types { } } -impl IntoIterator for Eip712Types { - type Item = (String, Vec); - type IntoIter = alloc::collections::btree_map::IntoIter>; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl Eip712Types { - /// Iterate over the underlying map. - pub fn iter(&self) -> impl Iterator)> { - self.0.iter() - } - - /// Insert a new type. - pub fn insert(&mut self, key: String, value: Vec) { - self.0.insert(key, value); - } -} - /// Represents the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed data object. /// /// Typed data is a JSON object containing type information, domain separator -/// parameters and the message object which has the following schema +/// parameters and the message object which has the following schema: /// /// ```json /// { @@ -85,7 +67,7 @@ impl Eip712Types { /// "required": ["types", "primaryType", "domain", "message"] /// } /// ``` -#[derive(Debug, Clone, serde::Serialize)] +#[derive(Debug, Clone, Serialize)] pub struct TypedData { /// Signing domain metadata. The signing domain is the intended context for /// the signature (e.g. the dapp, protocol, etc. that it's intended for). @@ -238,7 +220,6 @@ mod tests { use serde_json::json; #[test] - #[ignore = "Uint Serde"] fn test_full_domain() { let json = json!({ "types": { diff --git a/crates/dyn-abi/src/lib.rs b/crates/dyn-abi/src/lib.rs index 0eed57aaed..6175610a1e 100644 --- a/crates/dyn-abi/src/lib.rs +++ b/crates/dyn-abi/src/lib.rs @@ -48,7 +48,7 @@ pub mod parser; #[cfg(feature = "eip712")] pub mod eip712; #[cfg(feature = "eip712")] -pub use eip712::{parser as eip712_parser, Resolver, TypedData}; +pub use eip712::{parser as eip712_parser, Eip712Types, PropertyDef, Resolver, TypeDef, TypedData}; #[cfg(test)] mod tests { diff --git a/crates/dyn-abi/src/parser.rs b/crates/dyn-abi/src/parser.rs index 63e645225b..473a23dd70 100644 --- a/crates/dyn-abi/src/parser.rs +++ b/crates/dyn-abi/src/parser.rs @@ -6,13 +6,16 @@ use crate::{DynAbiError, DynAbiResult, DynSolType}; use alloc::{boxed::Box, vec::Vec}; use core::{fmt, num::NonZeroUsize}; +/// Returns `true` if the given character is valid at the start of a Solidity +/// identfier. #[inline] -const fn is_id_start(c: char) -> bool { +pub const fn is_id_start(c: char) -> bool { matches!(c, 'a'..='z' | 'A'..='Z' | '_' | '$') } +/// Returns `true` if the given character is valid in a Solidity identfier. #[inline] -const fn is_id_continue(c: char) -> bool { +pub const fn is_id_continue(c: char) -> bool { matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '$') } @@ -22,7 +25,7 @@ const fn is_id_continue(c: char) -> bool { /// /// #[inline] -fn is_valid_identifier(s: &str) -> bool { +pub fn is_valid_identifier(s: &str) -> bool { let mut chars = s.chars(); if let Some(first) = chars.next() { is_id_start(first) && chars.all(is_id_continue) @@ -390,10 +393,18 @@ impl<'a> TypeSpecifier<'a> { /// Resolve the type string into a basic Solidity type if possible. pub fn resolve_basic_solidity(&self) -> Result { let ty = self.root.resolve_basic_solidity()?; - Ok(self.sizes.iter().fold(ty, |acc, item| match item { - Some(size) => DynSolType::FixedArray(Box::new(acc), size.get()), - _ => DynSolType::Array(Box::new(acc)), - })) + Ok(self.wrap_type(ty)) + } + + #[inline] + pub(crate) fn wrap_type(&self, mut ty: DynSolType) -> DynSolType { + for size in &self.sizes { + ty = match size { + Some(size) => DynSolType::FixedArray(Box::new(ty), size.get()), + None => DynSolType::Array(Box::new(ty)), + }; + } + ty } } diff --git a/crates/dyn-abi/src/token.rs b/crates/dyn-abi/src/token.rs index 4990ada12b..1cac3dbb84 100644 --- a/crates/dyn-abi/src/token.rs +++ b/crates/dyn-abi/src/token.rs @@ -138,7 +138,7 @@ impl<'a> DynToken<'a> { let dynamic = self.is_dynamic(); match self { Self::Word(w) => *w = WordToken::decode_from(dec)?.0, - Self::FixedSeq(_, _) => { + Self::FixedSeq(..) => { let mut child = if dynamic { dec.take_indirection()? } else { diff --git a/crates/dyn-abi/src/type.rs b/crates/dyn-abi/src/type.rs index ecbf5304b0..d2450d3e5c 100644 --- a/crates/dyn-abi/src/type.rs +++ b/crates/dyn-abi/src/type.rs @@ -46,25 +46,29 @@ struct StructProp { pub enum DynSolType { /// Address. Address, - /// Dynamic bytes. - Bytes, + /// Boolean. + Bool, /// Signed Integer. Int(usize), /// Unsigned Integer. Uint(usize), - /// Boolean. - Bool, - /// Dynamically sized array. - Array(Box), - /// String. - String, /// Fixed-size bytes, up to 32. FixedBytes(usize), + + /// Dynamic bytes. + Bytes, + /// String. + String, + + /// Dynamically sized array. + Array(Box), /// Fixed-sized array. FixedArray(Box, usize), /// Tuple. Tuple(Vec), + /// User-defined struct. + #[cfg(feature = "eip712")] CustomStruct { /// Name of the struct. name: String, @@ -73,11 +77,6 @@ pub enum DynSolType { /// Inner types. tuple: Vec, }, - /// User-defined value. - CustomValue { - /// Name of the value type. - name: String, - }, } impl core::str::FromStr for DynSolType { @@ -107,11 +106,16 @@ impl DynSolType { value, DynSolValue::FixedArray(v) if v.len() == *size && v.iter().all(|v| t.matches(v)) ), - Self::Tuple(types) => matches!( - value, - DynSolValue::CustomStruct { tuple, .. } | DynSolValue::Tuple(tuple) - if types.iter().zip(tuple).all(|(t, v)| t.matches(v)) - ), + Self::Tuple(types) => match value { + #[cfg(feature = "eip712")] + DynSolValue::Tuple(tuple) | DynSolValue::CustomStruct { tuple, .. } => { + types.iter().zip(tuple).all(|(t, v)| t.matches(v)) + } + #[cfg(not(feature = "eip712"))] + DynSolValue::Tuple(tuple) => types.iter().zip(tuple).all(|(t, v)| t.matches(v)), + _ => false, + }, + #[cfg(feature = "eip712")] Self::CustomStruct { name, prop_names, @@ -130,9 +134,6 @@ impl DynSolType { false } } - Self::CustomValue { name } => { - matches!(value, DynSolValue::CustomValue { name: n, .. } if name == n) - } } } @@ -196,6 +197,7 @@ impl DynSolType { .collect::>() .map(DynSolValue::FixedArray) } + #[cfg(feature = "eip712")] ( DynSolType::CustomStruct { name, @@ -221,7 +223,6 @@ impl DynSolType { tuple, }) } - (DynSolType::CustomValue { .. }, token) => DynSolType::FixedBytes(32).detokenize(token), _ => Err(crate::Error::custom( "mismatched types on dynamic detokenization", )), @@ -229,13 +230,15 @@ impl DynSolType { } #[inline] + #[allow(clippy::missing_const_for_fn)] fn sol_type_name_simple(&self) -> Option<&str> { match self { Self::Address => Some("address"), Self::Bool => Some("bool"), Self::Bytes => Some("bytes"), Self::String => Some("string"), - Self::CustomStruct { name, .. } | Self::CustomValue { name, .. } => Some(name), + #[cfg(feature = "eip712")] + Self::CustomStruct { name, .. } => Some(name), _ => None, } } @@ -243,12 +246,12 @@ impl DynSolType { #[inline] fn sol_type_name_raw(&self, out: &mut String) { match self { - Self::Address - | Self::Bool - | Self::Bytes - | Self::String - | Self::CustomStruct { .. } - | Self::CustomValue { .. } => { + #[cfg(feature = "eip712")] + Self::Address | Self::Bool | Self::Bytes | Self::String | Self::CustomStruct { .. } => { + out.push_str(unsafe { self.sol_type_name_simple().unwrap_unchecked() }); + } + #[cfg(not(feature = "eip712"))] + Self::Address | Self::Bool | Self::Bytes | Self::String => { out.push_str(unsafe { self.sol_type_name_simple().unwrap_unchecked() }); } @@ -323,11 +326,11 @@ impl DynSolType { DynSolType::FixedArray(t, size) => { DynToken::FixedSeq(vec![t.empty_dyn_token(); *size].into(), *size) } + #[cfg(feature = "eip712")] DynSolType::CustomStruct { tuple, .. } => DynToken::FixedSeq( tuple.iter().map(|t| t.empty_dyn_token()).collect(), tuple.len(), ), - DynSolType::CustomValue { .. } => DynToken::Word(Word::ZERO), } } diff --git a/crates/dyn-abi/src/value.rs b/crates/dyn-abi/src/value.rs index dce8e6f2ac..afe22fd9b5 100644 --- a/crates/dyn-abi/src/value.rs +++ b/crates/dyn-abi/src/value.rs @@ -3,6 +3,19 @@ use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec}; use alloy_primitives::{Address, I256, U256}; use alloy_sol_types::{utils::words_for_len, Encoder}; +#[cfg(feature = "eip712")] +macro_rules! as_fixed_seq { + ($tuple:tt) => { + Self::CustomStruct { tuple: $tuple, .. } | Self::FixedArray($tuple) | Self::Tuple($tuple) + }; +} +#[cfg(not(feature = "eip712"))] +macro_rules! as_fixed_seq { + ($tuple:tt) => { + Self::FixedArray($tuple) | Self::Tuple($tuple) + }; +} + /// This type represents a Solidity value that has been decoded into rust. It /// is broadly similar to `serde_json::Value` in that it is an enum of possible /// types, and the user must inspect and disambiguate. @@ -12,23 +25,27 @@ pub enum DynSolValue { Address(Address), /// A boolean. Bool(bool), - /// A dynamic-length byte array. - Bytes(Vec), - /// A fixed-length byte string. - FixedBytes(Word, usize), /// A signed integer. Int(I256, usize), /// An unsigned integer. Uint(U256, usize), + /// A fixed-length byte string. + FixedBytes(Word, usize), + + /// A dynamic-length byte array. + Bytes(Vec), /// A string. String(String), - /// A tuple of values. - Tuple(Vec), + /// A dynamically-sized array of values. Array(Vec), /// A fixed-size array of values. FixedArray(Vec), + /// A tuple of values. + Tuple(Vec), + /// A named struct, treated as a tuple with a name parameter. + #[cfg(feature = "eip712")] CustomStruct { /// The name of the struct. name: String, @@ -37,13 +54,6 @@ pub enum DynSolValue { /// The inner types. tuple: Vec, }, - /// A user-defined value type. - CustomValue { - /// The name of the custom value type. - name: String, - /// The value itself. - inner: Word, - }, } impl From
for DynSolValue { @@ -163,6 +173,7 @@ impl DynSolValue { Self::FixedArray(inner) => { DynSolType::FixedArray(Box::new(Self::sol_type(inner.first()?)?), inner.len()) } + #[cfg(feature = "eip712")] Self::CustomStruct { name, prop_names, @@ -175,20 +186,20 @@ impl DynSolValue { .map(Self::sol_type) .collect::>>()?, }, - Self::CustomValue { name, .. } => DynSolType::CustomValue { name: name.clone() }, }; Some(ty) } #[inline] + #[allow(clippy::missing_const_for_fn)] fn sol_type_name_simple(&self) -> Option<&str> { match self { Self::Address(_) => Some("address"), Self::Bool(_) => Some("bool"), Self::Bytes(_) => Some("bytes"), Self::String(_) => Some("string"), - Self::CustomStruct { name, .. } | Self::CustomValue { name, .. } => Some(name.as_str()), - + #[cfg(feature = "eip712")] + Self::CustomStruct { name, .. } => Some(name.as_str()), _ => None, } } @@ -196,12 +207,16 @@ impl DynSolValue { #[inline] fn sol_type_name_raw(&self, out: &mut String) -> bool { match self { + #[cfg(not(feature = "eip712"))] + Self::Address(_) | Self::Bool(_) | Self::Bytes(_) | Self::String(_) => { + out.push_str(unsafe { self.sol_type_name_simple().unwrap_unchecked() }); + } + #[cfg(feature = "eip712")] Self::Address(_) | Self::Bool(_) | Self::Bytes(_) | Self::String(_) - | Self::CustomStruct { .. } - | Self::CustomValue { .. } => { + | Self::CustomStruct { .. } => { out.push_str(unsafe { self.sol_type_name_simple().unwrap_unchecked() }); } @@ -283,10 +298,9 @@ impl DynSolValue { self, Self::Address(_) | Self::Bool(_) - | Self::FixedBytes(_, _) - | Self::Int(_, _) - | Self::Uint(_, _) - | Self::CustomValue { .. } + | Self::FixedBytes(..) + | Self::Int(..) + | Self::Uint(..) ) } @@ -299,7 +313,6 @@ impl DynSolValue { Self::FixedBytes(w, _) => Some(w), Self::Int(i, _) => Some(i.into()), Self::Uint(u, _) => Some(u.into()), - Self::CustomValue { inner, .. } => Some(inner), _ => None, } } @@ -396,6 +409,7 @@ impl DynSolValue { /// Fallible cast to the contents of a variant. #[inline] + #[cfg(feature = "eip712")] pub fn as_custom_struct(&self) -> Option<(&str, &[String], &[DynSolValue])> { match self { Self::CustomStruct { @@ -407,22 +421,10 @@ impl DynSolValue { } } - /// Fallible cast to the contents of a variant. - #[inline] - pub fn as_custom_value(&self) -> Option<(&str, Word)> { - match self { - Self::CustomValue { name, inner } => Some((name, *inner)), - _ => None, - } - } - /// Returns true if the value is a sequence type. #[inline] pub const fn is_sequence(&self) -> bool { - matches!( - self, - Self::Array(_) | Self::FixedArray(_) | Self::Tuple(_) | Self::CustomStruct { .. } - ) + matches!(self, as_fixed_seq!(_) | Self::Array(_)) } /// Fallible cast to a fixed-size array. Any of a `FixedArray`, a `Tuple`, @@ -430,9 +432,7 @@ impl DynSolValue { #[inline] pub fn as_fixed_seq(&self) -> Option<&[DynSolValue]> { match self { - Self::FixedArray(tuple) | Self::Tuple(tuple) | Self::CustomStruct { tuple, .. } => { - Some(tuple) - } + as_fixed_seq!(tuple) => Some(tuple), _ => None, } } @@ -453,14 +453,11 @@ impl DynSolValue { match self { Self::Address(_) | Self::Bool(_) - | Self::Int(_, _) - | Self::Uint(_, _) - | Self::FixedBytes(_, _) - | Self::CustomValue { .. } => false, + | Self::Int(..) + | Self::Uint(..) + | Self::FixedBytes(..) => false, Self::Bytes(_) | Self::String(_) | Self::Array(_) => true, - Self::Tuple(tuple) | Self::FixedArray(tuple) | Self::CustomStruct { tuple, .. } => { - tuple.iter().any(Self::is_dynamic) - } + as_fixed_seq!(tuple) => tuple.iter().any(Self::is_dynamic), } } @@ -493,10 +490,9 @@ impl DynSolValue { // `self.is_word()` Self::Address(_) | Self::Bool(_) - | Self::FixedBytes(_, _) - | Self::Int(_, _) - | Self::Uint(_, _) - | Self::CustomValue { .. } => 0, + | Self::FixedBytes(..) + | Self::Int(..) + | Self::Uint(..) => 0, // `self.as_packed_seq()` // 1 for the length, then the body padded to the next word. @@ -506,7 +502,7 @@ impl DynSolValue { // `self.as_fixed_seq()` // if static, 0. // If dynamic, all words for all elements. - Self::FixedArray(tuple) | Self::Tuple(tuple) | Self::CustomStruct { tuple, .. } => { + as_fixed_seq!(tuple) => { // `is_dynamic` iterates over all elements, and we need to sum all elements' // total words, so do both things at once let mut any_dynamic = false; @@ -534,45 +530,49 @@ impl DynSolValue { /// Append this data to the head of an in-progress blob via the encoder. #[inline] pub fn head_append(&self, enc: &mut Encoder) { - if let Some(word) = self.as_word() { - return enc.append_word(word) - } + match self { + Self::Address(_) + | Self::Bool(_) + | Self::FixedBytes(..) + | Self::Int(..) + | Self::Uint(..) => enc.append_word(unsafe { self.as_word().unwrap_unchecked() }), - if self.is_dynamic() { - return enc.append_indirection() - } + Self::String(_) | Self::Bytes(_) | Self::Array(_) => enc.append_indirection(), - let seq = self - .as_fixed_seq() - .expect("is definitely a non-dynamic fixed sequence"); - seq.iter().for_each(|inner| inner.head_append(enc)) + as_fixed_seq!(s) => { + if s.iter().any(Self::is_dynamic) { + enc.append_indirection() + } else { + s.iter().for_each(|inner| inner.head_append(enc)) + } + } + } } /// Append this data to the tail of an in-progress blob via the encoder. #[inline] pub fn tail_append(&self, enc: &mut Encoder) { - if self.is_word() { - return - } + match self { + Self::Address(_) + | Self::Bool(_) + | Self::FixedBytes(..) + | Self::Int(..) + | Self::Uint(..) => {} - if let Some(buf) = self.as_packed_seq() { - return enc.append_packed_seq(buf) - } + Self::String(string) => enc.append_packed_seq(string.as_bytes()), + Self::Bytes(bytes) => enc.append_packed_seq(bytes), - if let Some(sli) = self.as_fixed_seq() { - if self.is_dynamic() { - Self::encode_sequence(sli, enc); + as_fixed_seq!(s) => { + if self.is_dynamic() { + Self::encode_sequence(s, enc); + } } - return - } - if let Some(sli) = self.as_array() { - enc.append_seq_len(sli); - Self::encode_sequence(sli, enc); - return + Self::Array(array) => { + enc.append_seq_len(array); + Self::encode_sequence(array, enc); + } } - - unreachable!() } /// Encodes the packed value and appends it to the end of a byte array. @@ -583,7 +583,6 @@ impl DynSolValue { Self::String(s) => buf.extend_from_slice(s.as_bytes()), Self::Bytes(bytes) => buf.extend_from_slice(bytes), Self::FixedBytes(word, size) => buf.extend_from_slice(&word[..*size]), - Self::CustomValue { inner, .. } => buf.extend_from_slice(inner.as_slice()), Self::Int(num, size) => { let mut bytes = num.to_be_bytes::<32>(); let start = 32 - *size; @@ -597,11 +596,7 @@ impl DynSolValue { Self::Uint(num, size) => { buf.extend_from_slice(&num.to_be_bytes::<32>()[(32 - *size)..]) } - - Self::Tuple(inner) - | Self::Array(inner) - | Self::FixedArray(inner) - | Self::CustomStruct { tuple: inner, .. } => { + as_fixed_seq!(inner) | Self::Array(inner) => { inner.iter().for_each(|v| v.encode_packed_to(buf)) } } @@ -626,11 +621,8 @@ impl DynSolValue { Self::Int(int, _) => int.to_be_bytes::<32>().into(), Self::Uint(uint, _) => uint.to_be_bytes::<32>().into(), Self::String(s) => DynToken::PackedSeq(s.as_bytes()), - Self::Tuple(t) => DynToken::from_fixed_seq(t), Self::Array(t) => DynToken::from_dyn_seq(t), - Self::FixedArray(t) => DynToken::from_fixed_seq(t), - Self::CustomStruct { tuple, .. } => DynToken::from_fixed_seq(tuple), - Self::CustomValue { inner, .. } => (*inner).into(), + as_fixed_seq!(t) => DynToken::from_fixed_seq(t), } } diff --git a/crates/sol-types/src/coder/encoder.rs b/crates/sol-types/src/coder/encoder.rs index ab7652de2b..d07c90b7e4 100644 --- a/crates/sol-types/src/coder/encoder.rs +++ b/crates/sol-types/src/coder/encoder.rs @@ -68,7 +68,8 @@ impl Encoder { /// Determine the current suffix offset. #[inline] pub fn suffix_offset(&self) -> u32 { - *self.suffix_offset.last().unwrap() + debug_assert!(!self.suffix_offset.is_empty()); + unsafe { *self.suffix_offset.last().unwrap_unchecked() } } /// Appends a suffix offset.