From 979bb2d209a6d66b405fb43d19fb018f3ac5ee11 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 25 Apr 2023 10:59:29 -0700 Subject: [PATCH 1/7] fix: properly generate 1-element struct tuples --- abi/sol-type-parser/src/struct.rs | 6 +-- abi/src/util.rs | 86 +++++++++++++++++-------------- abi/tests/proc.rs | 6 +++ 3 files changed, 55 insertions(+), 43 deletions(-) diff --git a/abi/sol-type-parser/src/struct.rs b/abi/sol-type-parser/src/struct.rs index cd1d10160..635612395 100644 --- a/abi/sol-type-parser/src/struct.rs +++ b/abi/sol-type-parser/src/struct.rs @@ -76,12 +76,12 @@ impl SolStructDef { .unzip(); quote! { - type UnderlyingSolTuple = (#(#field_ty),*); - type UnderlyingRustTuple = (#(<#field_ty2 as ::ethers_abi_enc::SolType>::RustType),*); + type UnderlyingSolTuple = (#(#field_ty,)*); + type UnderlyingRustTuple = (#(<#field_ty2 as ::ethers_abi_enc::SolType>::RustType,)*); impl From<#name> for UnderlyingRustTuple { fn from(value: #name) -> UnderlyingRustTuple { - (#(value.#field_names),*) + (#(value.#field_names,)*) } } diff --git a/abi/src/util.rs b/abi/src/util.rs index a8caf08c1..6db399e47 100644 --- a/abi/src/util.rs +++ b/abi/src/util.rs @@ -9,9 +9,6 @@ //! Utils used by different modules. -use ethers_primitives::U256; -use serde::{Deserialize, Deserializer}; - use crate::{AbiResult, Error, Word}; /// Converts a u32 to a right aligned array of 32 bytes. @@ -80,49 +77,58 @@ pub(crate) fn check_bool(slice: Word) -> bool { check_zeroes(&slice[..31]) } -/// Helper type to parse numeric strings, `u64` and `U256` -#[derive(serde::Deserialize, Debug, Clone)] -#[serde(untagged)] -pub(crate) enum StringifiedNumeric { - String(String), - U256(U256), - Num(u64), -} +#[cfg(feature = "eip712-serde")] +pub(crate) use serde_helper::*; + +#[cfg(feature = "eip712-serde")] +mod serde_helper { + use ethers_primitives::U256; + use serde::{Deserialize, Deserializer}; + + /// Helper type to parse numeric strings, `u64` and `U256` + #[derive(serde::Deserialize, Debug, Clone)] + #[serde(untagged)] + pub(crate) enum StringifiedNumeric { + String(String), + U256(U256), + Num(u64), + } -impl TryFrom for U256 { - type Error = String; - - fn try_from(value: StringifiedNumeric) -> Result { - match value { - StringifiedNumeric::U256(n) => Ok(n), - StringifiedNumeric::Num(n) => Ok(U256::from(n)), - StringifiedNumeric::String(s) => { - if let Ok(val) = s.parse::() { - Ok(U256::from(val)) - } else if s.starts_with("0x") { - U256::from_str_radix(s.strip_prefix("0x").unwrap(), 16) - .map_err(|err| err.to_string()) - } else { - U256::from_str_radix(&s, 10).map_err(|err| err.to_string()) + impl TryFrom for U256 { + type Error = String; + + fn try_from(value: StringifiedNumeric) -> Result { + match value { + StringifiedNumeric::U256(n) => Ok(n), + StringifiedNumeric::Num(n) => Ok(U256::from(n)), + StringifiedNumeric::String(s) => { + if let Ok(val) = s.parse::() { + Ok(U256::from(val)) + } else if s.starts_with("0x") { + U256::from_str_radix(s.strip_prefix("0x").unwrap(), 16) + .map_err(|err| err.to_string()) + } else { + U256::from_str_radix(&s, 10).map_err(|err| err.to_string()) + } } } } } -} -/// Supports parsing numbers as strings -/// -/// See -pub(crate) fn deserialize_stringified_numeric_opt<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - if let Some(num) = Option::::deserialize(deserializer)? { - num.try_into().map(Some).map_err(serde::de::Error::custom) - } else { - Ok(None) + /// Supports parsing numbers as strings + /// + /// See + pub(crate) fn deserialize_stringified_numeric_opt<'de, D>( + deserializer: D, + ) -> Result, D::Error> + where + D: Deserializer<'de>, + { + if let Some(num) = Option::::deserialize(deserializer)? { + num.try_into().map(Some).map_err(serde::de::Error::custom) + } else { + Ok(None) + } } } diff --git a/abi/tests/proc.rs b/abi/tests/proc.rs index 857547a47..037df2b59 100644 --- a/abi/tests/proc.rs +++ b/abi/tests/proc.rs @@ -2,6 +2,12 @@ use ethers_abi_enc::{sol, SolStruct, SolType}; use ethers_primitives::{B160, U256}; +sol!( + struct HalonGas { + uint256 a; + } +); + sol! { struct MyStruct { uint256 a; From 989e892d5047cb4ed26911b9b81b730ce2a83903 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 25 Apr 2023 10:59:53 -0700 Subject: [PATCH 2/7] feature: ingest_sol_struct --- dyn-abi/src/eip712/parser.rs | 25 +++++++++++- dyn-abi/src/eip712/resolver.rs | 70 +++++++++++++++++++++++++++++--- dyn-abi/src/eip712/typed_data.rs | 6 +-- 3 files changed, 89 insertions(+), 12 deletions(-) diff --git a/dyn-abi/src/eip712/parser.rs b/dyn-abi/src/eip712/parser.rs index be75261b4..e1f6397a5 100644 --- a/dyn-abi/src/eip712/parser.rs +++ b/dyn-abi/src/eip712/parser.rs @@ -1,4 +1,9 @@ -use crate::{no_std_prelude::*, parser::TypeSpecifier, DynAbiError}; +use crate::{ + eip712::resolver::{PropertyDef, TypeDef}, + no_std_prelude::*, + parser::TypeSpecifier, + DynAbiError, +}; /// A property is a type and a name. Of the form `type name`. E.g. /// `uint256 foo` or `(MyStruct[23],bool) bar`. @@ -10,6 +15,13 @@ pub struct PropDef<'a> { pub name: &'a str, } +impl PropDef<'_> { + /// Convert to an owned `PropertyDef` + pub fn to_owned(&self) -> PropertyDef { + PropertyDef::new(self.ty.span, self.name).unwrap() + } +} + impl<'a> TryFrom<&'a str> for PropDef<'a> { type Error = DynAbiError; @@ -38,6 +50,17 @@ pub struct ComponentType<'a> { pub props: Vec>, } +impl ComponentType<'_> { + /// Convert to an owned TypeDef + pub fn to_owned(&self) -> TypeDef { + TypeDef::new( + self.type_name, + self.props.iter().map(|p| p.to_owned()).collect(), + ) + .unwrap() + } +} + // This impl handles impl<'a> TryFrom<&'a str> for ComponentType<'a> { type Error = DynAbiError; diff --git a/dyn-abi/src/eip712/resolver.rs b/dyn-abi/src/eip712/resolver.rs index a29a0d362..1238dc8e5 100644 --- a/dyn-abi/src/eip712/resolver.rs +++ b/dyn-abi/src/eip712/resolver.rs @@ -1,11 +1,12 @@ use core::cmp::Ordering; -use ethers_abi_enc::keccak256; +use ethers_abi_enc::{keccak256, SolStruct}; use ethers_primitives::B256; use serde::{Deserialize, Serialize}; use crate::{ eip712::typed_data::Eip712Types, + eip712_parser::EncodeType, no_std_prelude::*, parser::{RootType, TypeSpecifier, TypeStem}, DynAbiError, DynSolType, DynSolValue, @@ -21,8 +22,6 @@ pub struct PropertyDef { name: String, } -impl PropertyDef {} - impl<'de> serde::Deserialize<'de> for PropertyDef { fn deserialize(deserializer: D) -> Result where @@ -104,9 +103,12 @@ 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: String, props: Vec) -> Result { - let _rt: RootType<'_> = type_name.as_str().try_into()?; - Ok(Self { type_name, props }) + 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, + }) } /// Instantiate a new type definition, without checking that the type name @@ -229,6 +231,17 @@ impl From<&Resolver> for Eip712Types { } } +impl From<&T> for Resolver +where + T: SolStruct, +{ + fn from(_value: &T) -> Self { + let mut resolver = Resolver::default(); + resolver.ingest_sol_struct::(); + resolver + } +} + impl Resolver { /// Detect cycles in the subgraph rooted at `ty` fn detect_cycle<'a>(&'a self, type_name: &'_ str, context: &mut DfsContext<'a>) -> bool { @@ -262,6 +275,20 @@ impl Resolver { false } + /// Ingest types from an EIP-712 `encodeType` + pub fn ingest_string(&mut self, s: impl AsRef) -> Result<(), DynAbiError> { + 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); + } + Ok(()) + } + + /// Ingest a sol struct typedef + pub fn ingest_sol_struct(&mut self) { + self.ingest_string(S::encode_type()).unwrap(); + } + /// Ingest a type pub fn ingest(&mut self, type_def: TypeDef) { let type_name = type_def.type_name.to_owned(); @@ -460,6 +487,8 @@ impl Resolver { #[cfg(test)] mod test { + use ethers_abi_enc::sol; + use super::*; #[test] @@ -583,4 +612,33 @@ mod test { assert_eq!(graph.resolve("B").unwrap(), b); assert_eq!(graph.resolve("A").unwrap(), a); } + + #[test] + fn encode_type_round_trip() { + const ENCODE_TYPE: &str = "A(C myC,B myB)B(C myC)C(uint256 myUint,uint256 myUint2)"; + let mut graph = Resolver::default(); + graph.ingest_string(ENCODE_TYPE).unwrap(); + assert_eq!(graph.encode_type("A").unwrap(), ENCODE_TYPE); + + const ENCODE_TYPE_2: &str = "Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)"; + let mut graph = Resolver::default(); + graph.ingest_string(ENCODE_TYPE_2).unwrap(); + assert_eq!(graph.encode_type("Transaction").unwrap(), ENCODE_TYPE_2); + } + + sol!( + struct MyStruct { + uint256 a; + } + ); + + #[test] + fn it_ingests_sol_structs() { + let mut graph = Resolver::default(); + graph.ingest_sol_struct::(); + assert_eq!( + graph.encode_type("MyStruct").unwrap(), + MyStruct::encode_type() + ); + } } diff --git a/dyn-abi/src/eip712/typed_data.rs b/dyn-abi/src/eip712/typed_data.rs index 4d3ae5263..1096c67e9 100644 --- a/dyn-abi/src/eip712/typed_data.rs +++ b/dyn-abi/src/eip712/typed_data.rs @@ -485,11 +485,7 @@ mod tests { assert_eq!( typed_data.eip712_signing_hash(), Err(DynAbiError::CircularDependency("Mail".into())) - ) - // assert_eq!( - // "0808c17abba0aef844b0470b77df9c994bc0fa3e244dc718afd66a3901c4bd7b", - // hex::encode(&hash[..]) - // ); + ); } #[test] From 7e57317e67437b4881061b75559f0e04bd79a831 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 25 Apr 2023 11:44:27 -0700 Subject: [PATCH 3/7] feature: allow attrs in sol macro --- abi/sol-type-parser/src/lib.rs | 21 ++++++++++++++------- abi/sol-type-parser/src/struct.rs | 7 ++++++- abi/sol-type-parser/src/udt.rs | 4 ++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/abi/sol-type-parser/src/lib.rs b/abi/sol-type-parser/src/lib.rs index 1ee89db4a..586cb5dbe 100644 --- a/abi/sol-type-parser/src/lib.rs +++ b/abi/sol-type-parser/src/lib.rs @@ -1,6 +1,9 @@ use proc_macro::TokenStream as TS; use proc_macro2::TokenStream; -use syn::{parse::Parse, parse_macro_input, token::Struct, Token}; +use syn::{ + parse::{discouraged::Speculative, Parse}, + parse_macro_input, +}; use quote::{quote, ToTokens}; @@ -21,13 +24,17 @@ enum SolInput { impl Parse for SolInput { fn parse(input: syn::parse::ParseStream) -> syn::Result { - if input.peek(Struct) { - Ok(SolInput::StructDef(input.parse()?)) - } else if input.peek(Token![type]) { - Ok(SolInput::ValueTypeDef(input.parse()?)) - } else { - Ok(SolInput::Type(input.parse()?)) + let fork = input.fork(); + if let Ok(sol_ty) = fork.parse::() { + input.advance_to(&fork); + return Ok(SolInput::Type(sol_ty)); } + + if let Ok(udt) = fork.parse::() { + input.advance_to(&fork); + return Ok(SolInput::ValueTypeDef(udt)); + } + Ok(SolInput::StructDef(input.parse()?)) } } diff --git a/abi/sol-type-parser/src/struct.rs b/abi/sol-type-parser/src/struct.rs index 635612395..f8ff73e9b 100644 --- a/abi/sol-type-parser/src/struct.rs +++ b/abi/sol-type-parser/src/struct.rs @@ -7,7 +7,7 @@ use syn::{ parse::Parse, punctuated::Punctuated, token::{Brace, Struct}, - Ident, Index, Token, + Attribute, Ident, Index, Token, }; #[derive(Debug, Clone)] @@ -42,6 +42,7 @@ impl ToTokens for SolStructField { } pub struct SolStructDef { + attrs: Vec, _struct: Struct, name: Ident, _brace: Brace, @@ -52,6 +53,7 @@ impl Parse for SolStructDef { fn parse(input: syn::parse::ParseStream) -> syn::Result { let content; Ok(Self { + attrs: input.call(Attribute::parse_outer)?, _struct: input.parse()?, name: input.parse()?, _brace: braced!(content in input), @@ -147,9 +149,12 @@ impl SolStructDef { )*].concat() } }; + let attrs = self.attrs.iter(); quote! { #[doc = #doc] + #(#attrs)* + #[allow(non_snake_case)] #[derive(Debug, Clone, PartialEq)] pub struct #name { #(pub #fields),* diff --git a/abi/sol-type-parser/src/udt.rs b/abi/sol-type-parser/src/udt.rs index 35f2219e7..df6c2372c 100644 --- a/abi/sol-type-parser/src/udt.rs +++ b/abi/sol-type-parser/src/udt.rs @@ -8,6 +8,7 @@ mod kw { } pub struct Udt { + attrs: Vec, _type: syn::Token![type], name: syn::Ident, _is: kw::is, @@ -18,6 +19,7 @@ pub struct Udt { impl Parse for Udt { fn parse(input: syn::parse::ParseStream) -> syn::Result { Ok(Udt { + attrs: input.call(syn::Attribute::parse_outer)?, _type: input.parse()?, name: input.parse()?, _is: input.parse()?, @@ -32,6 +34,7 @@ impl ToTokens for Udt { let name = &self.name; let mod_name = syn::Ident::new(&format!("__{}", name), name.span()); let ty = &self.ty; + let attrs = self.attrs.iter(); tokens.extend(quote! { pub use #mod_name::#name; #[allow(non_snake_case)] @@ -39,6 +42,7 @@ impl ToTokens for Udt { use ::ethers_abi_enc::define_udt; define_udt! { /// A solidity user-defined type + #(#attrs)* #name, underlying: #ty, } From fd282764629618fcf1f97ac782541e3918d7594d Mon Sep 17 00:00:00 2001 From: James Date: Tue, 25 Apr 2023 11:45:07 -0700 Subject: [PATCH 4/7] test:test attrs in macro expansion --- abi/tests/proc.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/abi/tests/proc.rs b/abi/tests/proc.rs index 037df2b59..8470cf72c 100644 --- a/abi/tests/proc.rs +++ b/abi/tests/proc.rs @@ -3,7 +3,9 @@ use ethers_abi_enc::{sol, SolStruct, SolType}; use ethers_primitives::{B160, U256}; sol!( - struct HalonGas { + /// Hello this is extra docs + #[derive(Hash)] + struct MySingleProp { uint256 a; } ); From 8232f1553223210997af0ec7fd10b4143d72bf46 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 25 Apr 2023 11:45:33 -0700 Subject: [PATCH 5/7] test: basic test for from_sol_struct --- dyn-abi/src/eip712/typed_data.rs | 38 +++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/dyn-abi/src/eip712/typed_data.rs b/dyn-abi/src/eip712/typed_data.rs index 1096c67e9..260d6ed66 100644 --- a/dyn-abi/src/eip712/typed_data.rs +++ b/dyn-abi/src/eip712/typed_data.rs @@ -1,4 +1,4 @@ -use ethers_abi_enc::{keccak256, Eip712Domain}; +use ethers_abi_enc::{keccak256, Eip712Domain, SolStruct}; use ethers_primitives::B256; use serde::{Deserialize, Serialize}; @@ -151,6 +151,17 @@ impl<'de> Deserialize<'de> for TypedData { } impl TypedData { + /// Instantiate [`TypedData`] from a [`SolStruct`] that implements + /// [`serde::Serialize`]. + pub fn from_struct(s: &S, domain: Option) -> Self { + Self { + domain: domain.unwrap_or_default(), + resolver: Resolver::from(s), + primary_type: S::NAME.to_string(), + message: serde_json::to_value(s).unwrap(), + } + } + /// Returns the domain for this typed data pub const fn domain(&self) -> &Eip712Domain { &self.domain @@ -231,6 +242,8 @@ impl TypedData { // Adapted tests from #[cfg(test)] mod tests { + use ethers_abi_enc::sol; + use super::*; #[test] @@ -610,4 +623,27 @@ mod tests { hex::encode(&hash[..]) ); } + + sol!( + /// Fancy struct + #[derive(serde::Serialize, serde::Deserialize)] + struct MyStruct { + string name; + string otherThing; + } + ); + + #[test] + fn from_sol_struct() { + let s = MyStruct { + name: "hello".to_string(), + otherThing: "world".to_string(), + }; + + let typed_data = TypedData::from_struct(&s, None); + assert_eq!( + typed_data.encode_type().unwrap(), + "MyStruct(string name,string otherThing)" + ); + } } From 283736b701d2e12afc7830370e8c37a317c797ce Mon Sep 17 00:00:00 2001 From: James Date: Tue, 25 Apr 2023 12:59:00 -0700 Subject: [PATCH 6/7] fix: no-std tests and forking in sol --- abi/sol-type-parser/src/lib.rs | 2 ++ abi/src/coder/encoder.rs | 9 +-------- dyn-abi/src/eip712/resolver.rs | 15 +++++++-------- dyn-abi/src/eip712/typed_data.rs | 8 +------- dyn-abi/src/lib.rs | 2 +- 5 files changed, 12 insertions(+), 24 deletions(-) diff --git a/abi/sol-type-parser/src/lib.rs b/abi/sol-type-parser/src/lib.rs index 586cb5dbe..6692dc929 100644 --- a/abi/sol-type-parser/src/lib.rs +++ b/abi/sol-type-parser/src/lib.rs @@ -30,10 +30,12 @@ impl Parse for SolInput { return Ok(SolInput::Type(sol_ty)); } + let fork = input.fork(); if let Ok(udt) = fork.parse::() { input.advance_to(&fork); return Ok(SolInput::ValueTypeDef(udt)); } + Ok(SolInput::StructDef(input.parse()?)) } } diff --git a/abi/src/coder/encoder.rs b/abi/src/coder/encoder.rs index f2dc5a4be..27134ce90 100644 --- a/abi/src/coder/encoder.rs +++ b/abi/src/coder/encoder.rs @@ -194,7 +194,7 @@ mod tests { #[cfg(not(feature = "std"))] use crate::no_std_prelude::*; - use crate::{sol_type, util::pad_u32, SolType, TokenType}; + use crate::{sol_type, util::pad_u32, SolType}; #[test] fn encode_address() { @@ -261,7 +261,6 @@ mod tests { #[test] fn encode_fixed_array_of_dynamic_array_of_addresses() { type MyTy = sol_type::FixedArray, 2>; - dbg!(MyTy::sol_type_name()); let fixed = [ vec![B160([0x11u8; 20]), B160([0x22u8; 20])], vec![B160([0x33u8; 20]), B160([0x44u8; 20])], @@ -827,12 +826,6 @@ mod tests { type MyTy = (sol_type::String, sol_type::String); let data = ("gavofyork".to_string(), "gavofyork".to_string()); - let tok1 = <(MyTy,)>::tokenize((data.clone(),)); - let tok2 = ::tokenize(data.clone()); - - dbg!(tok1.head_words()); - dbg!(tok2.head_words()); - let expected = hex!( " 0000000000000000000000000000000000000000000000000000000000000020 diff --git a/dyn-abi/src/eip712/resolver.rs b/dyn-abi/src/eip712/resolver.rs index 1238dc8e5..605885b02 100644 --- a/dyn-abi/src/eip712/resolver.rs +++ b/dyn-abi/src/eip712/resolver.rs @@ -231,18 +231,17 @@ impl From<&Resolver> for Eip712Types { } } -impl From<&T> for Resolver -where - T: SolStruct, -{ - fn from(_value: &T) -> Self { +impl Resolver { + /// Instantiate a new resolver from a `SolStruct` type. + pub fn from_struct() -> Self + where + S: SolStruct, + { let mut resolver = Resolver::default(); - resolver.ingest_sol_struct::(); + resolver.ingest_sol_struct::(); resolver } -} -impl Resolver { /// Detect cycles in the subgraph rooted at `ty` fn detect_cycle<'a>(&'a self, type_name: &'_ str, context: &mut DfsContext<'a>) -> bool { let ty = match self.nodes.get(type_name) { diff --git a/dyn-abi/src/eip712/typed_data.rs b/dyn-abi/src/eip712/typed_data.rs index 260d6ed66..cbb418613 100644 --- a/dyn-abi/src/eip712/typed_data.rs +++ b/dyn-abi/src/eip712/typed_data.rs @@ -156,7 +156,7 @@ impl TypedData { pub fn from_struct(s: &S, domain: Option) -> Self { Self { domain: domain.unwrap_or_default(), - resolver: Resolver::from(s), + resolver: Resolver::from_struct::(), primary_type: S::NAME.to_string(), message: serde_json::to_value(s).unwrap(), } @@ -283,12 +283,6 @@ mod tests { "message": {} }); - dbg!(serde_json::from_str::(r#"{}"#).unwrap()); - - let s = serde_json::to_string_pretty(&json).unwrap(); - println!("{}", &s); - println!("{:?}", serde_json::from_str::(&s)); - let typed_data: TypedData = serde_json::from_value(json).unwrap(); let hash = typed_data.eip712_signing_hash().unwrap(); diff --git a/dyn-abi/src/lib.rs b/dyn-abi/src/lib.rs index c96765c25..c443ccdd9 100644 --- a/dyn-abi/src/lib.rs +++ b/dyn-abi/src/lib.rs @@ -147,7 +147,7 @@ pub use eip712::{parser as eip712_parser, Resolver, TypedData}; #[cfg(test)] mod test { - use crate::{Decoder, DynSolType, DynSolValue, Encoder}; + use crate::{no_std_prelude::*, Decoder, DynSolType, DynSolValue, Encoder}; #[test] fn simple_e2e() { From b0fa0916920b0524a486885a44f3733c2111d388 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 25 Apr 2023 13:04:25 -0700 Subject: [PATCH 7/7] test: e2e test for typed data from sol struct --- dyn-abi/src/eip712/typed_data.rs | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/dyn-abi/src/eip712/typed_data.rs b/dyn-abi/src/eip712/typed_data.rs index cbb418613..019471028 100644 --- a/dyn-abi/src/eip712/typed_data.rs +++ b/dyn-abi/src/eip712/typed_data.rs @@ -640,4 +640,50 @@ mod tests { "MyStruct(string name,string otherThing)" ); } + + sol! { + #[derive(serde::Serialize, serde::Deserialize)] + struct Person { + string name; + address wallet; + } + } + + sol! { + #[derive(serde::Serialize, serde::Deserialize)] + struct Mail { + Person from; + Person to; + string contents; + } + } + + #[test] + fn e2e_from_sol_struct() { + let sender = Person { + name: "Cow".to_string(), + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + .parse() + .unwrap(), + }; + let recipient = Person { + name: "Bob".to_string(), + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + .parse() + .unwrap(), + }; + let mail = Mail { + from: sender, + to: recipient, + contents: "Hello, Bob!".to_string(), + }; + + let typed_data = TypedData::from_struct(&mail, None); + + let hash = typed_data.eip712_signing_hash().unwrap(); + assert_eq!( + "25c3d40a39e639a4d0b6e4d2ace5e1281e039c88494d97d8d08f99a6ea75d775", + hex::encode(&hash[..]) + ); + } }