Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: DynSolCall #632

Merged
merged 4 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
prestwich marked this conversation as resolved.
Show resolved Hide resolved
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