From eb7a8aac35e23cf821994c746c1c0e6852e1b281 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 22 May 2024 11:49:26 +0200 Subject: [PATCH] feature: DynSolCall --- crates/dyn-abi/src/dynamic/call.rs | 156 +++++++++++++++++++++++++++++ crates/dyn-abi/src/dynamic/mod.rs | 3 + crates/dyn-abi/src/eip712/mod.rs | 36 +++++++ crates/dyn-abi/src/ext/abi.rs | 10 +- crates/dyn-abi/src/lib.rs | 3 +- crates/dyn-abi/src/specifier.rs | 22 +++- 6 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 crates/dyn-abi/src/dynamic/call.rs diff --git a/crates/dyn-abi/src/dynamic/call.rs b/crates/dyn-abi/src/dynamic/call.rs new file mode 100644 index 0000000000..cfd7275729 --- /dev/null +++ b/crates/dyn-abi/src/dynamic/call.rs @@ -0,0 +1,156 @@ +use crate::{DynSolType, DynSolValue, Error, Result}; +use alloy_primitives::Selector; +use alloy_sol_types::abi::Decoder; + +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; + +/// A representation of a Solidity call +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DynSolCall { + /// The selector of the call. + selector: Selector, + /// The types of the call. + parameters: Vec, + /// The method name of the call, if available. + method: Option, + /// The types of the call's returns. + returns: DynSolReturns, +} + +impl DynSolCall { + /// Create a new `DynSolCall` with the given selector and types. + pub fn new( + selector: Selector, + parameters: Vec, + method: Option, + returns: DynSolReturns, + ) -> Self { + Self { selector, parameters, method, returns } + } + + /// Get the selector of the call. + pub const fn selector(&self) -> Selector { + self.selector + } + + /// Get the types of the call. + pub fn types(&self) -> &[DynSolType] { + &self.parameters + } + + /// Get the method name of the call (if available) + pub fn method(&self) -> Option<&str> { + self.method.as_deref() + } + + /// ABI encode the given values as function params. + pub fn abi_encode_input(&self, values: &[DynSolValue]) -> Result> { + encode_typeck(&self.parameters, values).map(prefix_selector(self.selector)) + } + + /// ABI encode the given values as function params without prefixing the + /// selector. + pub fn abi_encode_input_raw(&self, values: &[DynSolValue]) -> Result> { + encode_typeck(&self.parameters, values) + } + + /// ABI decode the given data as function returns. + pub fn abi_decode_input(&self, data: &[u8], validate: bool) -> Result> { + abi_decode(data, &self.parameters, validate) + } + + /// ABI encode the given values as function return values. + pub fn abi_encode_output(&self, values: &[DynSolValue]) -> Result> { + self.returns.abi_encode_output(values) + } + + /// ABI decode the given data as function return values. + pub fn abi_decode_output(&self, data: &[u8], validate: bool) -> Result> { + self.returns.abi_decode_output(data, validate) + } +} + +/// A representation of a Solidity call's returns. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DynSolReturns(Vec); + +impl From> for DynSolReturns { + fn from(types: Vec) -> Self { + Self(types) + } +} + +impl From for Vec { + fn from(returns: DynSolReturns) -> Vec { + returns.0 + } +} + +impl DynSolReturns { + /// Create a new `DynSolReturns` with the given types. + pub fn new(types: Vec) -> Self { + Self(types) + } + + /// Get the types of the returns. + pub fn types(&self) -> &[DynSolType] { + &self.0 + } + + /// ABI encode the given values as function return values. + pub fn abi_encode_output(&self, values: &[DynSolValue]) -> Result> { + encode_typeck(self.types(), values) + } + + /// ABI decode the given data as function return values. + pub fn abi_decode_output(&self, data: &[u8], validate: bool) -> Result> { + abi_decode(data, self.types(), validate) + } +} + +#[inline] +pub(crate) fn prefix_selector(selector: Selector) -> 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 + } +} + +pub(crate) fn encode_typeck(tys: &[DynSolType], values: &[DynSolValue]) -> Result> { + if values.len() != tys.len() { + return Err(Error::EncodeLengthMismatch { expected: tys.len(), actual: values.len() }); + } + + for (value, ty) in core::iter::zip(values, tys) { + if !ty.matches(value) { + return Err(Error::TypeMismatch { + expected: ty.sol_type_name().into_owned(), + actual: value.sol_type_name().unwrap_or_else(|| "".into()).into_owned(), + }); + } + } + + Ok(abi_encode(values)) +} + +#[inline] +pub(crate) fn abi_encode(values: &[DynSolValue]) -> Vec { + DynSolValue::encode_seq(values) +} + +pub(crate) fn abi_decode( + data: &[u8], + tys: &[DynSolType], + validate: bool, +) -> Result> { + let mut values = Vec::with_capacity(tys.len()); + let mut decoder = Decoder::new(data, validate); + for ty in tys { + let value = ty.abi_decode_inner(&mut decoder, crate::DynToken::decode_single_populate)?; + values.push(value); + } + Ok(values) +} diff --git a/crates/dyn-abi/src/dynamic/mod.rs b/crates/dyn-abi/src/dynamic/mod.rs index 0393d524df..97ffbc17cb 100644 --- a/crates/dyn-abi/src/dynamic/mod.rs +++ b/crates/dyn-abi/src/dynamic/mod.rs @@ -4,6 +4,9 @@ pub use error::{DecodedError, DynSolError}; mod event; pub use event::{DecodedEvent, DynSolEvent}; +mod call; +pub use call::{DynSolCall, DynSolReturns}; + pub(crate) mod ty; pub use ty::DynSolType; diff --git a/crates/dyn-abi/src/eip712/mod.rs b/crates/dyn-abi/src/eip712/mod.rs index 0f949cc181..867ea1cd2d 100644 --- a/crates/dyn-abi/src/eip712/mod.rs +++ b/crates/dyn-abi/src/eip712/mod.rs @@ -16,3 +16,39 @@ mod resolver; pub use resolver::{PropertyDef, Resolver, TypeDef}; pub(crate) mod coerce; + +#[cfg(test)] +mod test { + use super::*; + use alloy_primitives::B256; + use alloy_sol_types::SolStruct; + #[test] + fn repro_i128() { + alloy_sol_types::sol! { + #[derive(serde::Serialize)] + struct Order { + bytes32 sender; + int128 priceX18; + int128 amount; + uint64 expiration; + uint64 nonce; + } + } + + let msg = Order { + sender: B256::repeat_byte(3), + priceX18: -1 * 1000000000000000, + amount: 2, + expiration: 3, + nonce: 4, + }; + + let domain = Default::default(); + + let static_sh = msg.eip712_signing_hash(&domain); + + let fromst = TypedData::from_struct(&msg, Some(domain)); + let dyn_sh = fromst.eip712_signing_hash(); + assert_eq!(static_sh, dyn_sh.unwrap()); + } +} diff --git a/crates/dyn-abi/src/ext/abi.rs b/crates/dyn-abi/src/ext/abi.rs index c37595eb49..9bce82f0e5 100644 --- a/crates/dyn-abi/src/ext/abi.rs +++ b/crates/dyn-abi/src/ext/abi.rs @@ -118,29 +118,29 @@ impl JsonAbiExt for Error { impl JsonAbiExt for Function { #[inline] fn abi_encode_input(&self, values: &[DynSolValue]) -> Result> { - encode_typeck(&self.inputs, values).map(prefix_selector(self.selector())) + self.resolve()?.abi_encode_input(values) } #[inline] fn abi_encode_input_raw(&self, values: &[DynSolValue]) -> Result> { - encode_typeck(&self.inputs, values) + self.resolve()?.abi_encode_input_raw(values) } #[inline] fn abi_decode_input(&self, data: &[u8], validate: bool) -> Result> { - abi_decode(data, &self.inputs, validate) + self.resolve()?.abi_decode_input(data, validate) } } impl FunctionExt for Function { #[inline] fn abi_encode_output(&self, values: &[DynSolValue]) -> Result> { - encode_typeck(&self.outputs, values) + self.resolve()?.abi_encode_output(values) } #[inline] fn abi_decode_output(&self, data: &[u8], validate: bool) -> Result> { - abi_decode(data, &self.outputs, validate) + self.resolve()?.abi_decode_output(data, validate) } } diff --git a/crates/dyn-abi/src/lib.rs b/crates/dyn-abi/src/lib.rs index 23ac4c1783..203123daf5 100644 --- a/crates/dyn-abi/src/lib.rs +++ b/crates/dyn-abi/src/lib.rs @@ -38,7 +38,8 @@ mod coerce; mod dynamic; pub use dynamic::{ - DecodedError, DecodedEvent, DynSolError, DynSolEvent, DynSolType, DynSolValue, DynToken, + DecodedError, DecodedEvent, DynSolCall, DynSolError, DynSolEvent, DynSolReturns, DynSolType, + DynSolValue, DynToken, }; mod error; diff --git a/crates/dyn-abi/src/specifier.rs b/crates/dyn-abi/src/specifier.rs index c49b5d8393..2353a242b3 100644 --- a/crates/dyn-abi/src/specifier.rs +++ b/crates/dyn-abi/src/specifier.rs @@ -2,9 +2,9 @@ //! //! This is a simple representation of Solidity type grammar. -use crate::{DynSolType, Result}; +use crate::{DynSolCall, DynSolType, Result}; use alloc::vec::Vec; -use alloy_json_abi::{EventParam, Param}; +use alloy_json_abi::{EventParam, Function, Param}; use parser::{ParameterSpecifier, Parameters, RootType, TupleSpecifier, TypeSpecifier, TypeStem}; #[cfg(feature = "eip712")] @@ -155,6 +155,24 @@ impl Specifier for EventParam { } } +impl Specifier for Function { + #[inline] + fn resolve(&self) -> Result { + let selector = self.selector(); + let parameters = + self.inputs.iter().map(Specifier::::resolve).collect::>>()?; + let returns = self + .outputs + .iter() + .map(Specifier::::resolve) + .collect::>>()? + .into(); + let method = self.name.clone(); + + Ok(DynSolCall::new(selector, parameters, Some(method), returns)) + } +} + fn resolve_param( ty: &str, components: &[Param],