Skip to content

Commit

Permalink
feature: DynSolCall (#632)
Browse files Browse the repository at this point in the history
* feature: DynSolCall

* lint: clippy

* Update crates/dyn-abi/src/eip712/mod.rs

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

* fix: revert crates/dyn-abi/src/ext/abi.rs

---------

Co-authored-by: DaniPopes <[email protected]>
  • Loading branch information
prestwich and DaniPopes authored Jun 18, 2024
1 parent 4aeccbb commit 5c71dda
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 3 deletions.
156 changes: 156 additions & 0 deletions crates/dyn-abi/src/dynamic/call.rs
Original file line number Diff line number Diff line change
@@ -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<DynSolType>,
/// The method name of the call, if available.
method: Option<String>,
/// 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<DynSolType>,
method: Option<String>,
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<Vec<u8>> {
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<Vec<u8>> {
encode_typeck(&self.parameters, values)
}

/// ABI decode the given data as function returns.
pub fn abi_decode_input(&self, data: &[u8], validate: bool) -> Result<Vec<DynSolValue>> {
abi_decode(data, &self.parameters, validate)
}

/// ABI encode the given values as function return values.
pub fn abi_encode_output(&self, values: &[DynSolValue]) -> Result<Vec<u8>> {
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<Vec<DynSolValue>> {
self.returns.abi_decode_output(data, validate)
}
}

/// A representation of a Solidity call's returns.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DynSolReturns(Vec<DynSolType>);

impl From<Vec<DynSolType>> for DynSolReturns {
fn from(types: Vec<DynSolType>) -> Self {
Self(types)
}
}

impl From<DynSolReturns> for Vec<DynSolType> {
fn from(returns: DynSolReturns) -> Vec<DynSolType> {
returns.0
}
}

impl DynSolReturns {
/// Create a new `DynSolReturns` with the given types.
pub fn new(types: Vec<DynSolType>) -> 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<Vec<u8>> {
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<Vec<DynSolValue>> {
abi_decode(data, self.types(), validate)
}
}

#[inline]
pub(crate) fn prefix_selector(selector: Selector) -> impl FnOnce(Vec<u8>) -> Vec<u8> {
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<Vec<u8>> {
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(|| "<none>".into()).into_owned(),
});
}
}

Ok(abi_encode(values))
}

#[inline]
pub(crate) fn abi_encode(values: &[DynSolValue]) -> Vec<u8> {
DynSolValue::encode_seq(values)
}

pub(crate) fn abi_decode(
data: &[u8],
tys: &[DynSolType],
validate: bool,
) -> Result<Vec<DynSolValue>> {
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)
}
3 changes: 3 additions & 0 deletions crates/dyn-abi/src/dynamic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
37 changes: 37 additions & 0 deletions crates/dyn-abi/src/eip712/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,40 @@ 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: -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());
}
}
3 changes: 2 additions & 1 deletion crates/dyn-abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
22 changes: 20 additions & 2 deletions crates/dyn-abi/src/specifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -155,6 +155,24 @@ impl Specifier<DynSolType> for EventParam {
}
}

impl Specifier<DynSolCall> for Function {
#[inline]
fn resolve(&self) -> Result<DynSolCall> {
let selector = self.selector();
let parameters =
self.inputs.iter().map(Specifier::<DynSolType>::resolve).collect::<Result<Vec<_>>>()?;
let returns = self
.outputs
.iter()
.map(Specifier::<DynSolType>::resolve)
.collect::<Result<Vec<_>>>()?
.into();
let method = self.name.clone();

Ok(DynSolCall::new(selector, parameters, Some(method), returns))
}
}

fn resolve_param(
ty: &str,
components: &[Param],
Expand Down

0 comments on commit 5c71dda

Please sign in to comment.