diff --git a/crates/dyn-abi/src/eip712/coerce.rs b/crates/dyn-abi/src/eip712/coerce.rs index 8146fa629..e85201483 100644 --- a/crates/dyn-abi/src/eip712/coerce.rs +++ b/crates/dyn-abi/src/eip712/coerce.rs @@ -1,4 +1,4 @@ -use crate::{DynAbiError, DynAbiResult, DynSolType, DynSolValue, Word}; +use crate::{DynSolType, DynSolValue, Error, Result, Word}; use alloc::{ boxed::Box, string::{String, ToString}, @@ -8,7 +8,7 @@ use alloy_primitives::{Address, Function, I256, U256}; impl DynSolType { /// Coerce a [`serde_json::Value`] to a [`DynSolValue`] via this type. - pub fn coerce(&self, value: &serde_json::Value) -> DynAbiResult { + pub fn coerce(&self, value: &serde_json::Value) -> Result { match self { Self::Address => address(value), Self::Function => function(value), @@ -30,31 +30,31 @@ impl DynSolType { } } -fn address(value: &serde_json::Value) -> DynAbiResult { +fn address(value: &serde_json::Value) -> Result { let address = value .as_str() .map(|s| { s.parse::
() - .map_err(|_| DynAbiError::type_mismatch(DynSolType::Address, value)) + .map_err(|_| Error::type_mismatch(&DynSolType::Address, value)) }) - .ok_or_else(|| DynAbiError::type_mismatch(DynSolType::Address, value))??; + .ok_or_else(|| Error::type_mismatch(&DynSolType::Address, value))??; Ok(DynSolValue::Address(address)) } -fn function(value: &serde_json::Value) -> DynAbiResult { +fn function(value: &serde_json::Value) -> Result { let function = value .as_str() .map(|s| { s.parse::() - .map_err(|_| DynAbiError::type_mismatch(DynSolType::Function, value)) + .map_err(|_| Error::type_mismatch(&DynSolType::Function, value)) }) - .ok_or_else(|| DynAbiError::type_mismatch(DynSolType::Function, value))??; + .ok_or_else(|| Error::type_mismatch(&DynSolType::Function, value))??; Ok(DynSolValue::Function(function)) } -fn bool(value: &serde_json::Value) -> DynAbiResult { +fn bool(value: &serde_json::Value) -> Result { if let Some(bool) = value.as_bool() { return Ok(DynSolValue::Bool(bool)) } @@ -63,13 +63,13 @@ fn bool(value: &serde_json::Value) -> DynAbiResult { .as_str() .map(|s| { s.parse::() - .map_err(|_| DynAbiError::type_mismatch(DynSolType::Address, value)) + .map_err(|_| Error::type_mismatch(&DynSolType::Address, value)) }) - .ok_or_else(|| DynAbiError::type_mismatch(DynSolType::Address, value))??; + .ok_or_else(|| Error::type_mismatch(&DynSolType::Address, value))??; Ok(DynSolValue::Bool(bool)) } -fn int(n: usize, value: &serde_json::Value) -> DynAbiResult { +fn int(n: usize, value: &serde_json::Value) -> Result { if let Some(num) = value.as_i64() { return Ok(DynSolValue::Int(I256::try_from(num).unwrap(), n)) } @@ -78,10 +78,10 @@ fn int(n: usize, value: &serde_json::Value) -> DynAbiResult { return Ok(DynSolValue::Int(i, n)) } - Err(DynAbiError::type_mismatch(DynSolType::Int(n), value)) + Err(Error::type_mismatch(&DynSolType::Int(n), value)) } -fn uint(n: usize, value: &serde_json::Value) -> DynAbiResult { +fn uint(n: usize, value: &serde_json::Value) -> Result { if let Some(num) = value.as_u64() { return Ok(DynSolValue::Uint(U256::from(num), n)) } @@ -96,10 +96,10 @@ fn uint(n: usize, value: &serde_json::Value) -> DynAbiResult { } } - Err(DynAbiError::type_mismatch(DynSolType::Uint(n), value)) + Err(Error::type_mismatch(&DynSolType::Uint(n), value)) } -fn fixed_bytes(n: usize, value: &serde_json::Value) -> DynAbiResult { +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()); @@ -107,30 +107,30 @@ fn fixed_bytes(n: usize, value: &serde_json::Value) -> DynAbiResult return Ok(DynSolValue::FixedBytes(word, n)) } - Err(DynAbiError::type_mismatch(DynSolType::FixedBytes(n), value)) + Err(Error::type_mismatch(&DynSolType::FixedBytes(n), value)) } -fn string(value: &serde_json::Value) -> DynAbiResult { +fn string(value: &serde_json::Value) -> Result { let string = value .as_str() .map(|s| s.to_string()) - .ok_or_else(|| DynAbiError::type_mismatch(DynSolType::String, value))?; + .ok_or_else(|| Error::type_mismatch(&DynSolType::String, value))?; Ok(DynSolValue::String(string)) } -fn bytes(value: &serde_json::Value) -> DynAbiResult { +fn bytes(value: &serde_json::Value) -> 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))??; + .map(|s| hex::decode(s).map_err(|_| Error::type_mismatch(&DynSolType::Bytes, value))) + .ok_or_else(|| Error::type_mismatch(&DynSolType::Bytes, value))??; Ok(DynSolValue::Bytes(bytes)) } -fn tuple(inner: &[DynSolType], value: &serde_json::Value) -> DynAbiResult { +fn tuple(inner: &[DynSolType], value: &serde_json::Value) -> Result { if let Some(arr) = value.as_array() { if inner.len() != arr.len() { - return Err(DynAbiError::type_mismatch( - DynSolType::Tuple(inner.to_vec()), + return Err(Error::type_mismatch( + &DynSolType::Tuple(inner.to_vec()), value, )) } @@ -144,13 +144,13 @@ fn tuple(inner: &[DynSolType], value: &serde_json::Value) -> DynAbiResult DynAbiResult { +fn array(inner: &DynSolType, value: &serde_json::Value) -> Result { if let Some(arr) = value.as_array() { let array = arr .iter() @@ -160,21 +160,17 @@ fn array(inner: &DynSolType, value: &serde_json::Value) -> DynAbiResult DynAbiResult { +fn fixed_array(inner: &DynSolType, n: usize, value: &serde_json::Value) -> Result { if let Some(arr) = value.as_array() { if arr.len() != n { - return Err(DynAbiError::type_mismatch( - DynSolType::FixedArray(Box::new(inner.clone()), n), + return Err(Error::type_mismatch( + &DynSolType::FixedArray(Box::new(inner.clone()), n), value, )) } @@ -187,8 +183,8 @@ fn fixed_array( return Ok(DynSolValue::FixedArray(array)) } - Err(DynAbiError::type_mismatch( - DynSolType::FixedArray(Box::new(inner.clone()), n), + Err(Error::type_mismatch( + &DynSolType::FixedArray(Box::new(inner.clone()), n), value, )) } @@ -198,15 +194,15 @@ pub(crate) fn coerce_custom_struct( prop_names: &[String], inner: &[DynSolType], value: &serde_json::Value, -) -> Result { +) -> Result { if let Some(map) = value.as_object() { let mut tuple = vec![]; for (name, ty) in prop_names.iter().zip(inner.iter()) { if let Some(v) = map.get(name) { tuple.push(ty.coerce(v)?); } else { - return Err(DynAbiError::type_mismatch( - DynSolType::CustomStruct { + return Err(Error::type_mismatch( + &DynSolType::CustomStruct { name: name.to_string(), prop_names: prop_names.to_vec(), tuple: inner.to_vec(), @@ -222,8 +218,8 @@ pub(crate) fn coerce_custom_struct( }) } - Err(DynAbiError::type_mismatch( - DynSolType::CustomStruct { + Err(Error::type_mismatch( + &DynSolType::CustomStruct { name: name.to_string(), prop_names: prop_names.to_vec(), tuple: inner.to_vec(), diff --git a/crates/dyn-abi/src/eip712/parser.rs b/crates/dyn-abi/src/eip712/parser.rs index c176a7a46..0fb57945e 100644 --- a/crates/dyn-abi/src/eip712/parser.rs +++ b/crates/dyn-abi/src/eip712/parser.rs @@ -2,7 +2,7 @@ use crate::{ eip712::resolver::{PropertyDef, TypeDef}, - DynAbiError, + Error, }; use alloc::vec::Vec; use alloy_sol_type_parser::{Error as TypeParserError, TypeSpecifier}; @@ -25,12 +25,12 @@ impl PropDef<'_> { } impl<'a> TryFrom<&'a str> for PropDef<'a> { - type Error = DynAbiError; + type Error = Error; fn try_from(input: &'a str) -> Result { let (ty, name) = input .rsplit_once(' ') - .ok_or_else(|| DynAbiError::invalid_property_def(input))?; + .ok_or_else(|| Error::invalid_property_def(input))?; Ok(PropDef { ty: ty.trim().try_into()?, name: name.trim(), @@ -64,12 +64,12 @@ impl ComponentType<'_> { // This impl handles impl<'a> TryFrom<&'a str> for ComponentType<'a> { - type Error = DynAbiError; + type Error = Error; fn try_from(input: &'a str) -> Result { - let (name, props_str) = input.split_once('(').ok_or_else(|| { - DynAbiError::TypeParserError(TypeParserError::invalid_type_string(input)) - })?; + let (name, props_str) = input + .split_once('(') + .ok_or_else(|| Error::TypeParser(TypeParserError::invalid_type_string(input)))?; let mut props = vec![]; let mut depth = 1; // 1 to account for the ( in the split above @@ -112,7 +112,7 @@ pub struct EncodeType<'a> { } impl<'a> TryFrom<&'a str> for EncodeType<'a> { - type Error = DynAbiError; + type Error = Error; fn try_from(input: &'a str) -> Result { let mut types = vec![]; diff --git a/crates/dyn-abi/src/eip712/resolver.rs b/crates/dyn-abi/src/eip712/resolver.rs index 25bdf8cbe..cd3989bca 100644 --- a/crates/dyn-abi/src/eip712/resolver.rs +++ b/crates/dyn-abi/src/eip712/resolver.rs @@ -1,6 +1,6 @@ use crate::{ eip712::typed_data::Eip712Types, eip712_parser::EncodeType, resolve::ResolveSolType, - DynAbiError, DynAbiResult, DynSolType, DynSolValue, + DynSolType, DynSolValue, Error, Result, }; use alloc::{ borrow::ToOwned, @@ -40,7 +40,7 @@ impl<'de> Deserialize<'de> for PropertyDef { impl PropertyDef { /// Instantiate a new name-type pair. #[inline] - pub fn new(type_name: T, name: N) -> DynAbiResult + pub fn new(type_name: T, name: N) -> Result where T: Into, N: Into, @@ -119,7 +119,7 @@ impl TypeDef { /// Instantiate a new type definition, checking that the type name is a /// valid root type. #[inline] - pub fn new>(type_name: S, props: Vec) -> DynAbiResult { + pub fn new>(type_name: S, props: Vec) -> Result { let type_name = type_name.into(); RootType::try_from(type_name.as_str())?; Ok(Self { type_name, props }) @@ -306,7 +306,7 @@ impl Resolver { } /// Ingest types from an EIP-712 `encodeType`. - pub fn ingest_string(&mut self, s: impl AsRef) -> DynAbiResult<()> { + pub fn ingest_string(&mut self, s: impl AsRef) -> Result<()> { let encode_type: EncodeType<'_> = s.as_ref().try_into()?; for t in encode_type.types { self.ingest(t.to_owned()); @@ -348,7 +348,7 @@ impl Resolver { &'a self, resolution: &mut Vec<&'a TypeDef>, root_type: RootType<'_>, - ) -> DynAbiResult<()> { + ) -> Result<()> { if root_type.try_basic_solidity().is_ok() { return Ok(()) } @@ -356,7 +356,7 @@ impl Resolver { let this_type = self .nodes .get(root_type.span()) - .ok_or_else(|| DynAbiError::missing_type(root_type.span()))?; + .ok_or_else(|| Error::missing_type(root_type.span()))?; let edges: &Vec = self.edges.get(root_type.span()).unwrap(); @@ -373,10 +373,10 @@ impl Resolver { /// This function linearizes a type into a list of typedefs of its /// dependencies. - pub fn linearize(&self, type_name: &str) -> DynAbiResult> { + pub fn linearize(&self, type_name: &str) -> Result> { let mut context = DfsContext::default(); if self.detect_cycle(type_name, &mut context) { - return Err(DynAbiError::circular_dependency(type_name)) + return Err(Error::circular_dependency(type_name)) } let root_type = type_name.try_into()?; let mut resolution = vec![]; @@ -386,15 +386,15 @@ impl Resolver { /// 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 { + pub fn resolve(&self, type_name: &str) -> Result { if self.detect_cycle(type_name, &mut Default::default()) { - return Err(DynAbiError::circular_dependency(type_name)) + return Err(Error::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 { + fn unchecked_resolve(&self, type_spec: &TypeSpecifier<'_>) -> Result { let ty = match &type_spec.stem { TypeStem::Root(root) => self.resolve_root_type(*root), TypeStem::Tuple(tuple) => tuple @@ -409,7 +409,7 @@ impl Resolver { /// Resolves a root Solidity type into either a basic type or a custom /// struct. - fn resolve_root_type(&self, root_type: RootType<'_>) -> DynAbiResult { + fn resolve_root_type(&self, root_type: RootType<'_>) -> Result { if let Ok(ty) = root_type.resolve() { return Ok(ty) } @@ -417,7 +417,7 @@ impl Resolver { let ty = self .nodes .get(root_type.span()) - .ok_or_else(|| DynAbiError::missing_type(root_type.span()))?; + .ok_or_else(|| Error::missing_type(root_type.span()))?; let prop_names: Vec<_> = ty.prop_names().map(str::to_string).collect(); let tuple: Vec<_> = ty @@ -435,7 +435,7 @@ impl Resolver { /// Encode the type into an EIP-712 `encodeType` string /// /// - pub fn encode_type(&self, name: &str) -> DynAbiResult { + pub fn encode_type(&self, name: &str) -> Result { let linear = self.linearize(name)?; let first = linear.first().unwrap().eip712_encode_type(); @@ -453,12 +453,12 @@ impl Resolver { } /// Compute the keccak256 hash of the EIP-712 `encodeType` string. - pub fn type_hash(&self, name: &str) -> DynAbiResult { + pub fn type_hash(&self, name: &str) -> Result { self.encode_type(name).map(keccak256) } /// Encode the data according to EIP-712 `encodeData` rules. - pub fn encode_data(&self, value: &DynSolValue) -> DynAbiResult>> { + pub fn encode_data(&self, value: &DynSolValue) -> Result>> { Ok(match value { DynSolValue::CustomStruct { tuple: inner, .. } | DynSolValue::Array(inner) @@ -478,7 +478,7 @@ impl Resolver { /// 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) -> DynAbiResult { + pub fn eip712_data_word(&self, value: &DynSolValue) -> Result { if let Some(word) = value.as_word() { return Ok(word) } diff --git a/crates/dyn-abi/src/eip712/typed_data.rs b/crates/dyn-abi/src/eip712/typed_data.rs index 82c7de6fc..ae94a9351 100644 --- a/crates/dyn-abi/src/eip712/typed_data.rs +++ b/crates/dyn-abi/src/eip712/typed_data.rs @@ -1,6 +1,6 @@ use crate::{ eip712::{PropertyDef, Resolver}, - DynAbiResult, DynSolType, DynSolValue, + DynSolType, DynSolValue, Result, }; use alloc::{collections::BTreeMap, string::String, vec::Vec}; use alloy_primitives::{keccak256, B256}; @@ -143,13 +143,13 @@ impl TypedData { &self.domain } - fn resolve(&self) -> DynAbiResult { + fn resolve(&self) -> Result { self.resolver.resolve(&self.primary_type) } /// Coerce the message to the type specified by `primary_type`, using the /// types map as a resolver. - pub fn coerce(&self) -> DynAbiResult { + pub fn coerce(&self) -> Result { let ty = self.resolve()?; ty.coerce(&self.message) } @@ -159,7 +159,7 @@ impl TypedData { /// Fails if this type is not a struct. /// /// [`encodeType`]: https://eips.ethereum.org/EIPS/eip-712#definition-of-encodetype - pub fn type_hash(&self) -> DynAbiResult { + pub fn type_hash(&self) -> Result { self.encode_type().map(keccak256) } @@ -168,7 +168,7 @@ impl TypedData { /// Fails if this type is not a struct. /// /// [`hashStruct`]: https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct - pub fn hash_struct(&self) -> DynAbiResult { + pub fn hash_struct(&self) -> Result { let mut type_hash = self.type_hash()?.to_vec(); type_hash.extend(self.encode_data()?); Ok(keccak256(type_hash)) @@ -179,7 +179,7 @@ impl TypedData { /// Fails if this type is not a struct. /// /// [`encodeData`]: https://eips.ethereum.org/EIPS/eip-712#definition-of-encodedata - pub fn encode_data(&self) -> DynAbiResult> { + pub fn encode_data(&self) -> Result> { let s = self.coerce()?; Ok(self.resolver.encode_data(&s)?.unwrap()) } @@ -189,7 +189,7 @@ impl TypedData { /// Fails if this type is not a struct. /// /// [`encodeType`]: https://eips.ethereum.org/EIPS/eip-712#definition-of-encodetype - pub fn encode_type(&self) -> DynAbiResult { + pub fn encode_type(&self) -> Result { self.resolver.encode_type(&self.primary_type) } @@ -197,7 +197,7 @@ impl TypedData { /// /// This is the hash of the magic bytes 0x1901 concatenated with the domain /// separator and the `hashStruct` result. - pub fn eip712_signing_hash(&self) -> DynAbiResult { + pub fn eip712_signing_hash(&self) -> Result { let mut buf = [0u8; 66]; buf[0] = 0x19; buf[1] = 0x01; @@ -219,7 +219,7 @@ impl TypedData { #[cfg(test)] mod tests { use super::*; - use crate::DynAbiError; + use crate::Error; use alloc::string::ToString; use alloy_sol_types::sol; use serde_json::json; @@ -519,7 +519,7 @@ mod tests { assert_eq!( typed_data.eip712_signing_hash(), - Err(DynAbiError::CircularDependency("Mail".into())), + Err(Error::CircularDependency("Mail".into())), ); } diff --git a/crates/dyn-abi/src/error.rs b/crates/dyn-abi/src/error.rs index 4a00ab988..6e46c1924 100644 --- a/crates/dyn-abi/src/error.rs +++ b/crates/dyn-abi/src/error.rs @@ -1,94 +1,122 @@ +use alloc::{borrow::Cow, string::String}; use alloy_sol_type_parser::Error as TypeParserError; +use alloy_sol_types::Error as SolTypesError; use core::fmt; +use hex::FromHexError; /// Dynamic ABI result type. -pub type DynAbiResult = core::result::Result; +pub type Result = core::result::Result; /// Error when parsing EIP-712 `encodeType` strings /// /// #[derive(Debug, Clone, PartialEq)] -pub enum DynAbiError { - /// Type mismatch during coercion. - #[cfg(feature = "eip712")] - TypeMismatch { - /// The expected type. - expected: crate::DynSolType, - /// The actual type. - actual: serde_json::Value, - }, +pub enum Error { /// Unknown type referenced from another type. #[cfg(feature = "eip712")] - MissingType(alloc::string::String), + MissingType(String), /// Detected circular dep during typegraph resolution. #[cfg(feature = "eip712")] - CircularDependency(alloc::string::String), - /// Invalid Property definition. + CircularDependency(String), + /// Invalid property definition. #[cfg(feature = "eip712")] - InvalidPropertyDefinition(alloc::string::String), + InvalidPropertyDefinition(String), - /// Hex. - HexError(hex::FromHexError), - /// Type Str Error - TypeParserError(TypeParserError), + /// Type mismatch during encoding or coercion. + TypeMismatch { + /// The expected type. + expected: String, + /// The actual type. + actual: String, + }, + /// Length mismatch during encoding. + EncodeLengthMismatch { + /// The expected length. + expected: usize, + /// The actual length. + actual: usize, + }, + + /// [`hex`] error. + Hex(hex::FromHexError), + /// [`alloy_sol_type_parser`] error. + TypeParser(TypeParserError), + /// [`alloy_sol_types`] error. + SolTypes(SolTypesError), } -impl From for DynAbiError { - #[inline] +impl From for Error { + fn from(e: FromHexError) -> Self { + Self::Hex(e) + } +} + +impl From for Error { + fn from(e: SolTypesError) -> Self { + Self::SolTypes(e) + } +} + +impl From for Error { fn from(e: TypeParserError) -> Self { - Self::TypeParserError(e) + Self::TypeParser(e) } } #[cfg(feature = "std")] -impl std::error::Error for DynAbiError { +impl std::error::Error for Error { #[inline] fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - #[allow(unreachable_patterns)] match self { - Self::HexError(e) => Some(e), - Self::TypeParserError(e) => Some(e), + Self::Hex(e) => Some(e), + Self::TypeParser(e) => Some(e), + Self::SolTypes(e) => Some(e), _ => None, } } } -impl fmt::Display for DynAbiError { +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { #[cfg(feature = "eip712")] - Self::TypeMismatch { expected, actual } => { - write!(f, "Type mismatch, expected: {expected:?}, actual: {actual}") - } - #[cfg(feature = "eip712")] - Self::MissingType(name) => write!(f, "Missing type in type resolution: {name}"), + Self::MissingType(name) => write!(f, "missing type in type resolution: {name}"), #[cfg(feature = "eip712")] - Self::CircularDependency(dep) => write!(f, "Circular dependency: {dep}"), + Self::CircularDependency(dep) => write!(f, "circular dependency: {dep}"), #[cfg(feature = "eip712")] - Self::InvalidPropertyDefinition(def) => { - write!(f, "Invalid property definition: {def}") - } + Self::InvalidPropertyDefinition(def) => write!(f, "invalid property definition: {def}"), - Self::HexError(h) => h.fmt(f), - Self::TypeParserError(e) => e.fmt(f), + Self::TypeMismatch { expected, actual } => write!( + f, + "type mismatch, expected type {expected:?}, got value with type {actual:?}", + ), + &Self::EncodeLengthMismatch { expected, actual } => write!( + f, + "encode length mismatch, expected {expected} types, got {actual}", + ), + + Self::Hex(e) => e.fmt(f), + Self::TypeParser(e) => e.fmt(f), + Self::SolTypes(e) => e.fmt(f), } } } -impl From for DynAbiError { - fn from(e: hex::FromHexError) -> Self { - Self::HexError(e) +#[allow(dead_code)] +impl Error { + /// Instantiates a new error with a static str. + #[inline] + pub fn custom(s: impl Into>) -> Self { + Self::SolTypes(SolTypesError::custom(s)) } -} -#[allow(dead_code)] -impl DynAbiError { #[cfg(feature = "eip712")] - #[inline] - pub(crate) fn type_mismatch(expected: crate::DynSolType, actual: &serde_json::Value) -> Self { + pub(crate) fn type_mismatch(expected: &crate::DynSolType, actual: &serde_json::Value) -> Self { + #[allow(unused_imports)] + use alloc::string::ToString; Self::TypeMismatch { - expected, - actual: actual.clone(), + expected: expected.sol_type_name().into_owned(), + actual: actual.to_string(), } } diff --git a/crates/dyn-abi/src/ext/abi.rs b/crates/dyn-abi/src/ext/abi.rs new file mode 100644 index 000000000..9fb7fab5a --- /dev/null +++ b/crates/dyn-abi/src/ext/abi.rs @@ -0,0 +1,261 @@ +use crate::{DynSolValue, Error as CrateError, ResolveSolType, Result}; +use alloc::vec::Vec; +use alloy_json_abi::{Constructor, Error, Function, Param}; +use alloy_sol_types::Decoder; + +mod sealed { + pub trait Sealed {} + impl Sealed for super::Constructor {} + impl Sealed for super::Error {} + impl Sealed for super::Function {} +} +use sealed::Sealed; + +/// Provides ABI encoding and decoding functionality. +/// +/// This trait is sealed and cannot be implemented for types outside of this +/// crate. It is implemented only for the following types: +/// +/// - [`Constructor`] +/// - [`Error`] +/// - [`Function`] +pub trait JsonAbiExt: Sealed { + /// ABI-encodes the given values, prefixed by this item's selector, if any. + /// + /// The selector is: + /// - `None` for [`Constructor`], + /// - `Some(self.selector())` for [`Error`] and [`Function`]. + /// + /// This behaviour is to ensure consistency with `ethabi`. + /// + /// To encode the data without the selector, use + /// [`encode_input_raw`](JsonAbiExt::encode_input_raw). + /// + /// # Errors + /// + /// This function will return an error if the given values do not match the + /// expected input types. + fn encode_input(&self, values: &[DynSolValue]) -> Result>; + + /// ABI-encodes the given values, without prefixing the data with the item's + /// selector. + /// + /// For [`Constructor`], this is the same as + /// [`encode_input`](JsonAbiExt::encode_input). + /// + /// # Errors + /// + /// This function will return an error if the given values do not match the + /// expected input types. + fn encode_input_raw(&self, values: &[DynSolValue]) -> Result>; + + /// ABI-decodes the given data according to this item's input types. + /// + /// # Errors + /// + /// This function will return an error if the decoded data does not match + /// the expected input types. + fn decode_input(&self, data: &[u8]) -> Result>; +} + +/// Provide ABI encoding and decoding for the [`Function`] type. +/// +/// This trait is sealed and cannot be implemented for types outside of this +/// crate. It is implemented only for [`Function`]. +pub trait FunctionExt: JsonAbiExt + Sealed { + /// ABI-encodes the given values. + /// + /// Note that, contrary to [`encode_input`](JsonAbiExt::encode_input), this + /// method does not prefix the return data with the function selector. + /// + /// # Errors + /// + /// This function will return an error if the given values do not match the + /// expected input types. + fn encode_output(&self, values: &[DynSolValue]) -> Result>; + + /// ABI-decodes the given data according to this functions's output types. + /// + /// This method does not check for any prefixes or selectors. + fn decode_output(&self, data: &[u8]) -> Result>; +} + +impl JsonAbiExt for Constructor { + #[inline] + fn encode_input(&self, values: &[DynSolValue]) -> Result> { + encode_typeck(&self.inputs, values) + } + + #[inline] + fn encode_input_raw(&self, values: &[DynSolValue]) -> Result> { + encode_typeck(&self.inputs, values) + } + + #[inline] + fn decode_input(&self, data: &[u8]) -> Result> { + decode(data, &self.inputs) + } +} + +impl JsonAbiExt for Error { + #[inline] + fn encode_input(&self, values: &[DynSolValue]) -> Result> { + encode_typeck(&self.inputs, values).map(prefix_selector(self.selector())) + } + + #[inline] + fn encode_input_raw(&self, values: &[DynSolValue]) -> Result> { + encode_typeck(&self.inputs, values) + } + + #[inline] + fn decode_input(&self, data: &[u8]) -> Result> { + decode(data, &self.inputs) + } +} + +impl JsonAbiExt for Function { + #[inline] + fn encode_input(&self, values: &[DynSolValue]) -> Result> { + encode_typeck(&self.inputs, values).map(prefix_selector(self.selector())) + } + + #[inline] + fn encode_input_raw(&self, values: &[DynSolValue]) -> Result> { + encode_typeck(&self.inputs, values) + } + + #[inline] + fn decode_input(&self, data: &[u8]) -> Result> { + decode(data, &self.inputs) + } +} + +impl FunctionExt for Function { + #[inline] + fn encode_output(&self, values: &[DynSolValue]) -> Result> { + encode_typeck(&self.outputs, values) + } + + #[inline] + fn decode_output(&self, data: &[u8]) -> Result> { + decode(data, &self.outputs) + } +} + +#[inline] +fn prefix_selector(selector: [u8; 4]) -> impl FnOnce(Vec) -> Vec { + move |data| { + let mut new = Vec::with_capacity(data.len() + 4); + new.extend_from_slice(&selector); + new.extend_from_slice(&data); + new + } +} + +fn encode_typeck(params: &[Param], values: &[DynSolValue]) -> Result> { + if values.len() != params.len() { + return Err(CrateError::EncodeLengthMismatch { + expected: params.len(), + actual: values.len(), + }) + } + for (value, param) in core::iter::zip(values, params) { + let ty = param.resolve()?; + if !ty.matches(value) { + return Err(CrateError::TypeMismatch { + expected: ty.sol_type_name().into_owned(), + actual: value + .sol_type_name() + .unwrap_or_else(|| "".into()) + .into_owned(), + }) + } + } + + Ok(encode(values)) +} + +#[inline] +fn encode(values: &[DynSolValue]) -> Vec { + DynSolValue::encode_sequence(values) +} + +fn decode(data: &[u8], params: &[Param]) -> Result> { + let mut values = Vec::with_capacity(params.len()); + let mut decoder = Decoder::new(data, false); + for param in params { + let ty = param.resolve()?; + let value = ty.decode(&mut decoder, crate::DynToken::decode_single_populate)?; + values.push(value); + } + Ok(values) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{Address, U256}; + + #[test] + fn can_encode_decode_functions() { + let json = r#"{ + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }"#; + + let func: Function = serde_json::from_str(json).unwrap(); + assert_eq!(2, func.inputs.len()); + assert_eq!(1, func.outputs.len()); + assert_eq!(func.signature(), "allowance(address,address)"); + + // encode + let expected = hex_literal::hex!( + "dd62ed3e" + "0000000000000000000000001111111111111111111111111111111111111111" + "0000000000000000000000002222222222222222222222222222222222222222" + ); + let input = [ + DynSolValue::Address(Address::repeat_byte(0x11)), + DynSolValue::Address(Address::repeat_byte(0x22)), + ]; + let result = func.encode_input(&input).unwrap(); + assert_eq!(expected[..], result); + + // Fail on unexpected input + let wrong_input = [ + DynSolValue::Uint(U256::from(10u8), 256), + DynSolValue::Address(Address::repeat_byte(2u8)), + ]; + assert!(func.encode_input(&wrong_input).is_err()); + + // decode + let response = U256::from(1u8).to_be_bytes_vec(); + let decoded = func.decode_output(&response).unwrap(); + assert_eq!(decoded, [DynSolValue::Uint(U256::from(1u8), 256)]); + + // Fail on wrong response type + let bad_response = Address::repeat_byte(3u8).to_vec(); + assert!(func.decode_output(&bad_response).is_err()); + } +} diff --git a/crates/dyn-abi/src/ext/mod.rs b/crates/dyn-abi/src/ext/mod.rs new file mode 100644 index 000000000..0588772b7 --- /dev/null +++ b/crates/dyn-abi/src/ext/mod.rs @@ -0,0 +1,5 @@ +mod abi; +pub use abi::{FunctionExt, JsonAbiExt}; + +// TODO +// mod event; diff --git a/crates/dyn-abi/src/lib.rs b/crates/dyn-abi/src/lib.rs index c85dd2e3c..153da417e 100644 --- a/crates/dyn-abi/src/lib.rs +++ b/crates/dyn-abi/src/lib.rs @@ -32,10 +32,10 @@ extern crate alloc; mod arbitrary; mod error; -pub use error::{DynAbiError, DynAbiResult}; +pub use error::{Error, Result}; -#[doc(no_inline)] -pub use alloy_sol_types::{Decoder, Eip712Domain, Encoder, Error, Result, SolType, Word}; +mod ext; +pub use ext::{FunctionExt, JsonAbiExt}; mod ty; pub use ty::DynSolType; @@ -49,9 +49,12 @@ pub use token::DynToken; pub mod resolve; pub use resolve::ResolveSolType; -pub use alloy_sol_type_parser as parser; - #[cfg(feature = "eip712")] pub mod eip712; #[cfg(feature = "eip712")] pub use eip712::{parser as eip712_parser, Eip712Types, PropertyDef, Resolver, TypeDef, TypedData}; + +#[doc(no_inline)] +pub use alloy_sol_type_parser as parser; +#[doc(no_inline)] +pub use alloy_sol_types::{Decoder, Eip712Domain, Encoder, SolType, Word}; diff --git a/crates/dyn-abi/src/resolve.rs b/crates/dyn-abi/src/resolve.rs index d723390f8..84874bd15 100644 --- a/crates/dyn-abi/src/resolve.rs +++ b/crates/dyn-abi/src/resolve.rs @@ -2,13 +2,12 @@ //! //! This is a simple representation of Solidity type grammar. -use crate::{DynAbiError, DynAbiResult, DynSolType, DynSolValue}; +use crate::{DynSolType, Result}; use alloc::vec::Vec; -use alloy_json_abi::{EventParam, Function, Param}; +use alloy_json_abi::{EventParam, InternalType, Param}; use alloy_sol_type_parser::{ Error as TypeStrError, RootType, TupleSpecifier, TypeSpecifier, TypeStem, }; -use alloy_sol_types::{Error, Result}; /// The ResolveSolType trait is implemented by types that can be resolved into /// a [`DynSolType`]. ABI and related systems have many different ways of @@ -23,9 +22,8 @@ use alloy_sol_types::{Error, Result}; /// ## Example /// /// ``` -/// # use alloy_dyn_abi::{DynSolType, ResolveSolType, DynAbiResult}; +/// # use alloy_dyn_abi::{DynSolType, ResolveSolType}; /// # use alloy_sol_type_parser::{RootType, TypeSpecifier}; -/// # fn main() -> DynAbiResult<()> { /// let my_ty = TypeSpecifier::try_from("bool")?.resolve()?; /// assert_eq!(my_ty, DynSolType::Bool); /// @@ -33,23 +31,22 @@ use alloy_sol_types::{Error, Result}; /// assert_eq!(my_ty, DynSolType::Uint(256)); /// /// assert_eq!("bytes32".resolve()?, DynSolType::FixedBytes(32)); -/// # Ok(()) -/// # } +/// # Ok::<_, alloy_dyn_abi::Error>(()) /// ``` pub trait ResolveSolType { /// Resolve this object into a [`DynSolType`]. - fn resolve(&self) -> DynAbiResult; + fn resolve(&self) -> Result; } impl ResolveSolType for str { #[inline] - fn resolve(&self) -> DynAbiResult { + fn resolve(&self) -> Result { TypeSpecifier::parse(self)?.resolve() } } impl ResolveSolType for RootType<'_> { - fn resolve(&self) -> DynAbiResult { + fn resolve(&self) -> Result { match self.span() { "address" => Ok(DynSolType::Address), "function" => Ok(DynSolType::Function), @@ -96,7 +93,7 @@ impl ResolveSolType for RootType<'_> { impl ResolveSolType for TupleSpecifier<'_> { #[inline] - fn resolve(&self) -> DynAbiResult { + fn resolve(&self) -> Result { self.types .iter() .map(TypeSpecifier::resolve) @@ -107,7 +104,7 @@ impl ResolveSolType for TupleSpecifier<'_> { impl ResolveSolType for TypeStem<'_> { #[inline] - fn resolve(&self) -> Result { + fn resolve(&self) -> Result { match self { Self::Root(root) => root.resolve(), Self::Tuple(tuple) => tuple.resolve(), @@ -117,7 +114,7 @@ impl ResolveSolType for TypeStem<'_> { impl ResolveSolType for TypeSpecifier<'_> { #[inline] - fn resolve(&self) -> Result { + fn resolve(&self) -> Result { self.stem .resolve() .map(|ty| ty.array_wrap_from_iter(self.sizes.iter().copied())) @@ -125,146 +122,47 @@ impl ResolveSolType for TypeSpecifier<'_> { } impl ResolveSolType for Param { - fn resolve(&self) -> DynAbiResult { - let ty = TypeSpecifier::try_from(self.ty.as_str()).expect("always valid"); - - // type is simple, and we can resolve it via the specifier - if self.is_simple_type() { - return ty.resolve() - } - - // type is complex - let tuple = self - .components - .iter() - .map(|c| c.resolve()) - .collect::, _>>()?; - - #[cfg(feature = "eip712")] - { - let prop_names = self.components.iter().map(|c| c.name.clone()).collect(); - if let Some(spec) = self.struct_specifier() { - return Ok(DynSolType::CustomStruct { - name: spec.stem.span().into(), - prop_names, - tuple, - } - .array_wrap_from_iter(spec.sizes.iter().copied())) - } - } - - Ok(DynSolType::Tuple(tuple).array_wrap_from_iter(ty.sizes.iter().copied())) + fn resolve(&self) -> Result { + resolve_param(&self.ty, &self.components, self.internal_type()) } } impl ResolveSolType for EventParam { - fn resolve(&self) -> DynAbiResult { - let ty = TypeSpecifier::try_from(self.ty.as_str()).expect("always valid"); - - // type is simple, and we can resolve it via the specifier - if self.is_simple_type() { - return ty.resolve() - } - - // type is complex. First extract the tuple of inner types - let tuple = self - .components - .iter() - .map(|c| c.resolve()) - .collect::, _>>()?; - - // if we have a struct specifier, we can use it to get the name of the - // struct - #[cfg(feature = "eip712")] - { - let prop_names = self.components.iter().map(|c| c.name.clone()).collect(); - if let Some(spec) = self.struct_specifier() { - return Ok(DynSolType::CustomStruct { - name: spec.stem.span().into(), - prop_names, - tuple, - } - .array_wrap_from_iter(spec.sizes.iter().copied())) - } - } - - Ok(DynSolType::Tuple(tuple).array_wrap_from_iter(ty.sizes.iter().copied())) + fn resolve(&self) -> Result { + resolve_param(&self.ty, &self.components, self.internal_type()) } } -/// Implement to provide encoding and decoding for an ABI Function -pub trait FunctionExt { - /// Create the ABI call with the given input arguments. - fn encode_input(&self, args: DynSolValue) -> Result>; - - /// Parse the ABI output into DynSolValues - fn decode_output(&self, data: &[u8]) -> Result; -} - -impl FunctionExt for Function { - fn encode_input(&self, args: DynSolValue) -> Result> { - // if the function has no input params, it should take and args - if self.inputs.is_empty() { - return Err(Error::Other("no inputs expected for this function".into())) - } - - // resolve params into their respective DynSolTypes - let resolved_params = self - .inputs - .iter() - .map(|i| i.resolve().expect("resolve to DynSolType")) - .collect::>(); +fn resolve_param(ty: &str, components: &[Param], _it: Option<&InternalType>) -> Result { + let ty = TypeSpecifier::parse(ty)?; - // since the above may result in a vec of 1 type, we check here - // to prepare for the check below - let param_type = match resolved_params.len() { - 1 => resolved_params[0].clone(), - _ => DynSolType::Tuple(resolved_params), - }; - - // check the expected type(s) match input args - if !param_type.matches(&args) { - return Err(Error::Other( - "input arguments do not match the expected input types".into(), - )) - } - - // ABI encode the call - let encoded = self - .selector() - .iter() - .copied() - .chain(args.encode_params()) - .collect::>(); - - Ok(encoded) + // type is simple, and we can resolve it via the specifier + if components.is_empty() { + return ty.resolve() } - fn decode_output(&self, data: &[u8]) -> Result { - let resolved_params = self - .outputs - .iter() - .map(|p| p.resolve().expect("resolve to DynSolType")) - .collect::>(); - - // since the above may result in a vec of 1 type, we check here - // to prepare for the check below - let param_type = match resolved_params.len() { - 1 => resolved_params[0].clone(), - _ => DynSolType::Tuple(resolved_params), - }; - - let result = param_type.decode_params(data)?; - - // check the expected type(s) match output params - if !param_type.matches(&result) { - return Err(Error::Other( - "decoded data does not match the expected output types".into(), - )) + // type is complex + let tuple = components + .iter() + .map(Param::resolve) + .collect::, _>>()?; + + #[cfg(feature = "eip712")] + let resolved = if let Some((_, name)) = _it.and_then(|i| i.as_struct()) { + DynSolType::CustomStruct { + // skip array sizes, since we have them already from parsing `ty` + name: name.split('[').next().unwrap().into(), + prop_names: components.iter().map(|c| c.name.clone()).collect(), + tuple, } + } else { + DynSolType::Tuple(tuple) + }; - Ok(result) - } + #[cfg(not(feature = "eip712"))] + let resolved = DynSolType::Tuple(tuple); + + Ok(resolved.array_wrap_from_iter(ty.sizes)) } macro_rules! deref_impl { @@ -272,7 +170,7 @@ macro_rules! deref_impl { $(#[$attr])* impl<$($gen)*> ResolveSolType for $t { #[inline] - fn resolve(&self) -> DynAbiResult { + fn resolve(&self) -> Result { (**self).resolve() } } @@ -293,9 +191,8 @@ deref_impl! { mod tests { use super::*; use alloc::boxed::Box; - use alloy_primitives::{Address, U256}; - fn parse(s: &str) -> Result { + fn parse(s: &str) -> Result { s.parse() } @@ -478,65 +375,4 @@ mod tests { Err(TypeStrError::invalid_type_string("MyStruct")) ); } - - #[test] - fn can_encode_decode_functions() { - let json = r#"{ - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }"#; - - let func: Function = serde_json::from_str(json).unwrap(); - assert_eq!(2, func.inputs.len()); - assert_eq!(1, func.outputs.len()); - - // encode - let expected = vec![ - 221, 98, 237, 62, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - ]; - let input = DynSolValue::Tuple(vec![ - DynSolValue::Address(Address::repeat_byte(1u8)), - DynSolValue::Address(Address::repeat_byte(2u8)), - ]); - let result = func.encode_input(input).unwrap(); - assert_eq!(expected, result); - - // Fail on unexpected input - let wrong_input = DynSolValue::Tuple(vec![ - DynSolValue::Uint(U256::from(10u8), 256), - DynSolValue::Address(Address::repeat_byte(2u8)), - ]); - assert!(func.encode_input(wrong_input).is_err()); - - // decode - let response = U256::from(1u8).to_be_bytes_vec(); - let decoded = func.decode_output(&response).unwrap(); - assert_eq!(DynSolValue::Uint(U256::from(1u8), 256), decoded); - - // Fail on wrong response type - let bad_response = Address::repeat_byte(3u8).to_vec(); - assert!(func.decode_output(&bad_response).is_err()); - } } diff --git a/crates/dyn-abi/src/ty.rs b/crates/dyn-abi/src/ty.rs index 7aa087b05..b78265023 100644 --- a/crates/dyn-abi/src/ty.rs +++ b/crates/dyn-abi/src/ty.rs @@ -1,10 +1,7 @@ -use crate::{ - resolve::ResolveSolType, DynAbiError, DynAbiResult, DynSolValue, DynToken, Result, SolType, - Word, -}; +use crate::{resolve::ResolveSolType, DynSolValue, DynToken, Error, Result, SolType, Word}; use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec}; use alloy_sol_type_parser::TypeSpecifier; -use alloy_sol_types::sol_data; +use alloy_sol_types::{sol_data, Decoder}; use core::{fmt, num::NonZeroUsize, str::FromStr}; #[cfg(feature = "eip712")] @@ -55,7 +52,7 @@ struct StructProp { /// // alternatively, you can use the FromStr impl /// let ty2 = type_name.parse::()?; /// assert_eq!(ty, ty2); -/// # Ok::<_, alloy_dyn_abi::DynAbiError>(()) +/// # Ok::<_, alloy_dyn_abi::Error>(()) /// ``` /// /// Decoding dynamic types: @@ -128,10 +125,10 @@ impl fmt::Display for DynSolType { } impl FromStr for DynSolType { - type Err = DynAbiError; + type Err = Error; #[inline] - fn from_str(s: &str) -> DynAbiResult { + fn from_str(s: &str) -> Result { Self::parse(s) } } @@ -148,9 +145,9 @@ impl DynSolType { /// Iteratively wrap in arrays. pub(crate) fn array_wrap_from_iter( self, - iter: impl Iterator>, + iter: impl IntoIterator>, ) -> Self { - iter.into_iter().fold(self, |ty, size| ty.array_wrap(size)) + iter.into_iter().fold(self, Self::array_wrap) } /// Parses a Solidity type name string into a [`DynSolType`]. @@ -163,12 +160,12 @@ impl DynSolType { /// let ty = DynSolType::parse(type_name)?; /// assert_eq!(ty, DynSolType::Uint(256)); /// assert_eq!(ty.sol_type_name(), type_name); - /// # Ok::<_, alloy_dyn_abi::DynAbiError>(()) + /// # Ok::<_, alloy_dyn_abi::Error>(()) /// ``` #[inline] - pub fn parse(s: &str) -> DynAbiResult { + pub fn parse(s: &str) -> Result { TypeSpecifier::try_from(s) - .map_err(DynAbiError::TypeParserError) + .map_err(Error::TypeParser) .and_then(|t| t.resolve()) } @@ -276,6 +273,7 @@ impl DynSolType { } /// Dynamic detokenization. + // This should not fail when using a token created by `Self::empty_dyn_token`. #[allow(clippy::unnecessary_to_owned)] // https://github.com/rust-lang/rust-clippy/issues/8148 pub fn detokenize(&self, token: DynToken<'_>) -> Result { match (self, token) { @@ -467,7 +465,7 @@ impl DynSolType { } /// Instantiate an empty dyn token, to be decoded into. - pub(crate) fn empty_dyn_token(&self) -> DynToken<'_> { + pub(crate) fn empty_dyn_token<'a>(&self) -> DynToken<'a> { match self { Self::Address | Self::Function @@ -514,6 +512,7 @@ impl DynSolType { /// function myFunc(uint256 b, bool c) public; /// ``` #[inline] + #[cfg_attr(debug_assertions, track_caller)] pub fn decode_params(&self, data: &[u8]) -> Result { match self { Self::Tuple(_) => self.decode_sequence(data), @@ -526,20 +525,40 @@ impl DynSolType { /// /// This method is used for decoding single values. It assumes the `data` /// argument is an encoded single-element sequence wrapping the `self` type. + #[inline] + #[cfg_attr(debug_assertions, track_caller)] pub fn decode_single(&self, data: &[u8]) -> Result { - let mut decoder = crate::Decoder::new(data, false); - let mut token = self.empty_dyn_token(); - token.decode_single_populate(&mut decoder)?; - self.detokenize(token) + self.decode( + &mut Decoder::new(data, false), + DynToken::decode_single_populate, + ) } /// Decode a [`DynSolValue`] from a byte slice. Fails if the value does not /// match this type. + #[inline] + #[cfg_attr(debug_assertions, track_caller)] pub fn decode_sequence(&self, data: &[u8]) -> Result { - let mut decoder = crate::Decoder::new(data, false); + self.decode( + &mut Decoder::new(data, false), + DynToken::decode_sequence_populate, + ) + } + + #[inline] + #[cfg_attr(debug_assertions, track_caller)] + pub(crate) fn decode<'d, F>(&self, decoder: &mut Decoder<'d>, f: F) -> Result + where + F: FnOnce(&mut DynToken<'d>, &mut Decoder<'d>) -> Result<()>, + { let mut token = self.empty_dyn_token(); - token.decode_sequence_populate(&mut decoder)?; - self.detokenize(token) + f(&mut token, decoder)?; + let value = self.detokenize(token).expect("invalid empty_dyn_token"); + debug_assert!( + self.matches(&value), + "decoded value does not match type:\n - type: {self:?}\n - value: {value:?}" + ); + Ok(value) } } @@ -572,7 +591,7 @@ mod tests { DynToken::FixedSeq(vec![DynToken::Word(word1), DynToken::Word(word2)].into(), 2) ); let mut enc = crate::Encoder::default(); - DynSolValue::encode_sequence(val.as_fixed_seq().unwrap(), &mut enc); + DynSolValue::encode_sequence_to(val.as_fixed_seq().unwrap(), &mut enc); assert_eq!(enc.finish(), vec![word1, word2]); } diff --git a/crates/dyn-abi/src/value.rs b/crates/dyn-abi/src/value.rs index 080ef6459..89609812a 100644 --- a/crates/dyn-abi/src/value.rs +++ b/crates/dyn-abi/src/value.rs @@ -636,13 +636,13 @@ impl DynSolValue { as_fixed_seq!(s) => { if self.is_dynamic() { - Self::encode_sequence(s, enc); + Self::encode_sequence_to(s, enc); } } Self::Array(array) => { enc.append_seq_len(array.len()); - Self::encode_sequence(array, enc); + Self::encode_sequence_to(array, enc); } } } @@ -703,7 +703,15 @@ impl DynSolValue { } /// Encode this data as a sequence. - pub(crate) fn encode_sequence(contents: &[Self], enc: &mut Encoder) { + pub(crate) fn encode_sequence(seq: &[Self]) -> Vec { + let sz = seq.iter().map(Self::total_words).sum(); + let mut encoder = Encoder::with_capacity(sz); + Self::encode_sequence_to(seq, &mut encoder); + encoder.into_bytes() + } + + /// Encode this data as a sequence into the given encoder. + pub(crate) fn encode_sequence_to(contents: &[Self], enc: &mut Encoder) { let head_words = contents.iter().map(Self::head_words).sum::(); enc.push_offset(head_words as u32); @@ -745,20 +753,15 @@ impl DynSolValue { /// Encode this value into a byte array by wrapping it into a 1-element /// sequence. + #[inline] pub fn encode_single(&self) -> Vec { - let mut encoder = Encoder::with_capacity(self.total_words()); - Self::encode_sequence(core::slice::from_ref(self), &mut encoder); - encoder.into_bytes() + Self::encode_sequence(core::slice::from_ref(self)) } /// If this value is a fixed sequence, encode it into a byte array. If this /// value is not a fixed sequence, return `None`. + #[inline] pub fn encode(&self) -> Option> { - self.as_fixed_seq().map(|seq| { - let sz = seq.iter().map(Self::total_words).sum(); - let mut encoder = Encoder::with_capacity(sz); - Self::encode_sequence(seq, &mut encoder); - encoder.into_bytes() - }) + self.as_fixed_seq().map(Self::encode_sequence) } } diff --git a/crates/sol-types/src/errors.rs b/crates/sol-types/src/errors.rs index 2f8328b51..73415789f 100644 --- a/crates/sol-types/src/errors.rs +++ b/crates/sol-types/src/errors.rs @@ -14,7 +14,7 @@ use core::fmt; pub type Result = core::result::Result; /// ABI Encoding and Decoding errors. -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum Error { /// A typecheck detected a word that does not match the data type. TypeCheckFail { diff --git a/crates/sol-types/src/lib.rs b/crates/sol-types/src/lib.rs index 0c2a8451d..6954e971a 100644 --- a/crates/sol-types/src/lib.rs +++ b/crates/sol-types/src/lib.rs @@ -171,9 +171,8 @@ mod coder; pub use coder::{ decode, decode_params, decode_single, encode, encode_params, encode_single, token::{self, TokenType}, + Decoder, Encoder, }; -#[doc(hidden)] -pub use coder::{Decoder, Encoder}; mod errors; pub use errors::{Error, Result};