Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

feat: syn 2.0, merge eip712 crate into ethers-contract-derive #2279

Merged
merged 13 commits into from
Apr 9, 2023
165 changes: 88 additions & 77 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ ethers-providers = { version = "2.0.1", path = "ethers-providers", default-featu
ethers-signers = { version = "2.0.1", path = "ethers-signers", default-features = false }
ethers-solc = { version = "2.0.1", path = "ethers-solc", default-features = false }

ethers-contract-abigen = { version = "2.0.0", path = "ethers-contract/ethers-contract-abigen", default-features = false }
ethers-contract-derive = { version = "2.0.0", path = "ethers-contract/ethers-contract-derive", default-features = false }
ethers-contract-abigen = { version = "2.0.1", path = "ethers-contract/ethers-contract-abigen", default-features = false }
ethers-contract-derive = { version = "2.0.1", path = "ethers-contract/ethers-contract-derive", default-features = false }

# async / async utils
tokio = "1.26"
Expand Down
1 change: 0 additions & 1 deletion ethers-contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ ethers-contract-abigen = { workspace = true, optional = true }
ethers-contract-derive = { workspace = true, optional = true }

[dev-dependencies]
ethers-providers = { workspace = true, features = ["ws"] }
ethers-signers.workspace = true
ethers-solc.workspace = true
ethers-providers = { workspace = true, features = ["ws"] }
Expand Down
2 changes: 1 addition & 1 deletion ethers-contract/ethers-contract-abigen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ ethers-core = { workspace = true, features = ["macros"] }
proc-macro2.workspace = true
quote.workspace = true
syn = { workspace = true, features = ["full"] }
prettyplease = "0.1.25"
prettyplease = "0.2.1"

Inflector.workspace = true
serde.workspace = true
Expand Down
8 changes: 3 additions & 5 deletions ethers-contract/ethers-contract-abigen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,9 @@ impl ToTokens for ContractBindings {
impl fmt::Display for ContractBindings {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.format {
// TODO:
todo!("enable formatting when prettyplease uses syn 2.0")
// let syntax_tree = syn::parse2::<syn::File>(self.tokens.clone()).unwrap();
// let s = prettyplease::unparse(&syntax_tree);
// f.write_str(&s)
let syntax_tree = syn::parse2::<syn::File>(self.tokens.clone()).unwrap();
let s = prettyplease::unparse(&syntax_tree);
f.write_str(&s)
} else {
fmt::Display::fmt(&self.tokens, f)
}
Expand Down
59 changes: 34 additions & 25 deletions ethers-contract/ethers-contract-derive/src/eip712.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use ethers_core::{
utils::keccak256,
};
use inflector::Inflector;
use proc_macro2::TokenStream;
use proc_macro2::{Literal, TokenStream};
use quote::quote;
use syn::{spanned::Spanned, Data, DeriveInput, Error, Fields, LitInt, LitStr, Result, Token};

Expand All @@ -17,51 +17,46 @@ pub(crate) fn impl_derive_eip712(input: &DeriveInput) -> Result<TokenStream> {
// Instantiate domain from parsed attributes
let domain = parse_attributes(input)?;

let domain_separator = hex::encode(domain.separator());
let domain_separator = into_tokens(domain.separator());

let domain_str = serde_json::to_string(&domain).unwrap();

// Must parse the AST at compile time.
let parsed_fields = parse_fields(input)?;

// Compute the type hash for the derived struct using the parsed fields from above.
let type_hash = hex::encode(make_type_hash(primary_type.to_string(), &parsed_fields));
let type_hash = into_tokens(make_type_hash(primary_type.to_string(), &parsed_fields));

// Use reference to ethers_core instead of directly using the crate itself.
let ethers_core = ethers_core_crate();

let tokens = quote! {
impl Eip712 for #primary_type {
impl #ethers_core::types::transaction::eip712::Eip712 for #primary_type {
type Error = #ethers_core::types::transaction::eip712::Eip712Error;

fn type_hash() -> Result<[u8; 32], Self::Error> {
use std::convert::TryFrom;
let decoded = #ethers_core::utils::hex::decode(#type_hash)?;
let byte_array: [u8; 32] = <[u8; 32]>::try_from(&decoded[..])?;
Ok(byte_array)
#[inline]
fn type_hash() -> ::core::result::Result<[u8; 32], Self::Error> {
Ok([#(#type_hash),*])
}

// Return the pre-computed domain separator from compile time;
fn domain_separator(&self) -> Result<[u8; 32], Self::Error> {
use std::convert::TryFrom;
let decoded = #ethers_core::utils::hex::decode(#domain_separator)?;
let byte_array: [u8; 32] = <[u8; 32]>::try_from(&decoded[..])?;
Ok(byte_array)
#[inline]
fn domain_separator(&self) -> ::core::result::Result<[u8; 32], Self::Error> {
Ok([#(#domain_separator),*])
}

fn domain(&self) -> Result<#ethers_core::types::transaction::eip712::EIP712Domain, Self::Error> {
let domain: #ethers_core::types::transaction::eip712::EIP712Domain = # ethers_core::utils::__serde_json::from_str(#domain_str)?;

Ok(domain)
fn domain(&self) -> ::core::result::Result<#ethers_core::types::transaction::eip712::EIP712Domain, Self::Error> {
#ethers_core::utils::__serde_json::from_str(#domain_str).map_err(::core::convert::Into::into)
}

fn struct_hash(&self) -> Result<[u8; 32], Self::Error> {
use #ethers_core::abi::Tokenizable;
fn struct_hash(&self) -> ::core::result::Result<[u8; 32], Self::Error> {
let mut items = vec![#ethers_core::abi::Token::Uint(
#ethers_core::types::U256::from(&Self::type_hash()?[..]),
)];

if let #ethers_core::abi::Token::Tuple(tokens) = self.clone().into_token() {
if let #ethers_core::abi::Token::Tuple(tokens) =
#ethers_core::abi::Tokenizable::into_token(::core::clone::Clone::clone(self))
{
items.reserve(tokens.len());
for token in tokens {
match &token {
#ethers_core::abi::Token::Tuple(t) => {
Expand Down Expand Up @@ -163,10 +158,24 @@ fn parse_fields(input: &DeriveInput) -> Result<Vec<(String, ParamType)>> {

/// Convert hash map of field names and types into a type hash corresponding to enc types;
fn make_type_hash(primary_type: String, fields: &[(String, ParamType)]) -> [u8; 32] {
let parameters =
fields.iter().map(|(k, v)| format!("{v} {k}")).collect::<Vec<String>>().join(",");
let mut sig = String::with_capacity(256);

sig.push_str(&primary_type);

let sig = format!("{primary_type}({parameters})");
sig.push('(');
for (i, (name, ty)) in fields.iter().enumerate() {
sig.push_str(&ty.to_string());
sig.push(' ');
sig.push_str(name);
if i < fields.len() - 1 {
sig.push(',');
}
}
sig.push(')');

keccak256(sig)
}

fn into_tokens(bytes: [u8; 32]) -> impl Iterator<Item = Literal> {
bytes.into_iter().map(Literal::u8_suffixed)
}
7 changes: 5 additions & 2 deletions ethers-contract/ethers-contract-derive/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,11 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
let ty = find_parameter_type(&arr.elem)?;
if let Expr::Lit(ref expr) = arr.len {
if let Lit::Int(ref len) = expr.lit {
if let Ok(size) = len.base10_parse::<usize>() {
return Ok(ParamType::FixedArray(Box::new(ty), size))
if let Ok(len) = len.base10_parse::<usize>() {
return match (ty, len) {
(ParamType::Uint(8), 32) => Ok(ParamType::FixedBytes(32)),
(ty, len) => Ok(ParamType::FixedArray(Box::new(ty), len)),
}
}
}
}
Expand Down
43 changes: 15 additions & 28 deletions ethers-contract/tests/it/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use ethers_contract::{
};
use ethers_core::{
abi::{encode, AbiEncode, Token, Tokenizable},
types::{Address, BlockId, Bytes, Filter, ValueOrArray, H160, H256, U256},
types::{
transaction::eip712::*, Address, BlockId, Bytes, Filter, ValueOrArray, H160, H256, I256,
U256,
},
utils::{keccak256, Anvil},
};
use ethers_providers::{Http, Middleware, MiddlewareError, Provider, StreamExt, Ws};
Expand Down Expand Up @@ -787,7 +790,7 @@ async fn test_derive_eip712() {
mod contract {
ethers_contract::abigen!(
DeriveEip712Test,
"./ethers-contract/tests/solidity-contracts/derive_eip712_abi.json",
"./ethers-contract/tests/solidity-contracts/DeriveEip712Test.json",
derives(serde::Deserialize, serde::Serialize)
);
}
Expand All @@ -811,41 +814,25 @@ async fn test_derive_eip712() {
out: Address,
}

// get ABI and bytecode for the DeriveEip712Test contract
let (abi, bytecode) = compile_contract("DeriveEip712Test", "DeriveEip712Test.sol");

// launch the network & connect to it
let anvil = Anvil::new().spawn();
let from = anvil.addresses()[0];
let wallet: LocalWallet = anvil.keys()[0].clone().into();
let provider = Provider::try_from(anvil.endpoint())
.unwrap()
.with_sender(from)
.with_sender(wallet.address())
.interval(std::time::Duration::from_millis(10));
let client = Arc::new(provider);

let wallet: LocalWallet = anvil.keys()[0].clone().into();

let factory = ContractFactory::new(abi.clone(), bytecode.clone(), client.clone());

let contract = factory
.deploy(())
.expect("failed to deploy DeriveEip712Test contract")
.legacy()
.send()
.await
.expect("failed to instantiate factory for DeriveEip712 contract");

let addr = contract.address();

let contract = contract::DeriveEip712Test::new(addr, client.clone());
let contract: contract::DeriveEip712Test<_> =
contract::DeriveEip712Test::deploy(client.clone(), ()).unwrap().send().await.unwrap();

let foo_bar = FooBar {
foo: I256::from(10u64),
bar: U256::from(20u64),
fizz: b"fizz".into(),
buzz: keccak256("buzz"),
far: String::from("space"),
out: Address::from([0; 20]),
out: Address::zero(),
};

let derived_foo_bar = contract::FooBar {
Expand All @@ -859,11 +846,11 @@ async fn test_derive_eip712() {

let sig = wallet.sign_typed_data(&foo_bar).await.expect("failed to sign typed data");

let r = <[u8; 32]>::try_from(sig.r)
.expect("failed to parse 'r' value from signature into [u8; 32]");
let s = <[u8; 32]>::try_from(sig.s)
.expect("failed to parse 's' value from signature into [u8; 32]");
let v = u8::try_from(sig.v).expect("failed to parse 'v' value from signature into u8");
let mut r = [0; 32];
sig.r.to_big_endian(&mut r);
let mut s = [0; 32];
sig.s.to_big_endian(&mut s);
let v = sig.v as u8;

let domain_separator = contract
.domain_separator()
Expand Down
4 changes: 2 additions & 2 deletions ethers-contract/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ mod contract_call;

mod eip712;

#[cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
// #[cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
mod common;

#[cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
// #[cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
DaniPopes marked this conversation as resolved.
Show resolved Hide resolved
mod contract;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"domainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"int256","name":"foo","type":"int256"},{"internalType":"uint256","name":"bar","type":"uint256"},{"internalType":"bytes","name":"fizz","type":"bytes"},{"internalType":"bytes32","name":"buzz","type":"bytes32"},{"internalType":"string","name":"far","type":"string"},{"internalType":"address","name":"out","type":"address"}],"internalType":"struct DeriveEip712Test.FooBar","name":"fooBar","type":"tuple"}],"name":"encodeEip712","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"int256","name":"foo","type":"int256"},{"internalType":"uint256","name":"bar","type":"uint256"},{"internalType":"bytes","name":"fizz","type":"bytes"},{"internalType":"bytes32","name":"buzz","type":"bytes32"},{"internalType":"string","name":"far","type":"string"},{"internalType":"address","name":"out","type":"address"}],"internalType":"struct DeriveEip712Test.FooBar","name":"fooBar","type":"tuple"}],"name":"structHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"typeHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"components":[{"internalType":"int256","name":"foo","type":"int256"},{"internalType":"uint256","name":"bar","type":"uint256"},{"internalType":"bytes","name":"fizz","type":"bytes"},{"internalType":"bytes32","name":"buzz","type":"bytes32"},{"internalType":"string","name":"far","type":"string"},{"internalType":"address","name":"out","type":"address"}],"internalType":"struct DeriveEip712Test.FooBar","name":"fooBar","type":"tuple"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"}],"name":"verifyFooBar","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"}],"bytecode":{"object":"0x608060405234801561001057600080fd5b50610588806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80630e81d1ea1461005c5780631a82ff96146100825780634992602814610095578063d8ef409b146100bb578063f698da25146100de575b600080fd5b61006f61006a366004610499565b6100e6565b6040519081526020015b60405180910390f35b61006f610090366004610499565b610135565b7f444a4f3bd4b9709dad515de69ec13a80dfaf37cdaff8705e0b7ad3b050f8a05d61006f565b6100ce6100c93660046104d6565b6101c2565b6040519015158152602001610079565b61006f610246565b60006100f0610246565b6100f983610135565b60405161190160f01b6020820152602281019290925260428201526062015b604051602081830303815290604052805190602001209050919050565b60007f444a4f3bd4b9709dad515de69ec13a80dfaf37cdaff8705e0b7ad3b050f8a05d82516020808501516040808701518051908401206060808901516080808b015180519088012060a0808d01518751998a019b909b52958801989098529186019490945284015282015260c08101919091526001600160a01b0390911660e082015261010001610118565b600060016101cf866100e6565b6040805160008152602081018083529290925260ff851690820152606081018690526080810185905260a0016020604051602081039080840390855afa15801561021d573d6000803e3d6000fd5b505050602060405103516001600160a01b0316866001600160a01b031614905095945050505050565b604080517fd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac5647260208201527f980de4e67a47e184738cefeb37eafc9ae849b00443ea6d49487ca4488633f4fd918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6606082015260016080820181905260a08201527f6f6f42aa4c3c5871ad1af387553372290fd350e98f6d38559f840f21b79da02e60c082015260009060e00160405160208183030381529060405280519060200120905090565b634e487b7160e01b600052604160045260246000fd5b60405160c0810167ffffffffffffffff8111828210171561034c5761034c610313565b60405290565b600082601f83011261036357600080fd5b813567ffffffffffffffff8082111561037e5761037e610313565b604051601f8301601f19908116603f011681019082821181831017156103a6576103a6610313565b816040528381528660208588010111156103bf57600080fd5b836020870160208301376000602085830101528094505050505092915050565b80356001600160a01b03811681146103f657600080fd5b919050565b600060c0828403121561040d57600080fd5b610415610329565b90508135815260208201356020820152604082013567ffffffffffffffff8082111561044057600080fd5b61044c85838601610352565b604084015260608401356060840152608084013591508082111561046f57600080fd5b5061047c84828501610352565b60808301525061048e60a083016103df565b60a082015292915050565b6000602082840312156104ab57600080fd5b813567ffffffffffffffff8111156104c257600080fd5b6104ce848285016103fb565b949350505050565b600080600080600060a086880312156104ee57600080fd5b6104f7866103df565b9450602086013567ffffffffffffffff81111561051357600080fd5b61051f888289016103fb565b9450506040860135925060608601359150608086013560ff8116811461054457600080fd5b80915050929550929590935056fea2646970667358221220cf29bf472d8a14fac807f7e952af4f1e27c3ec66e38093f54930dfc99f473b3264736f6c63430008130033","sourceMap":"101:2177:0:-:0;;;737:23;;;;;;;;;;101:2177;;;;;;","linkReferences":{}}}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity >=0.6.0;
pragma experimental ABIEncoderV2;

// note that this file is not synced with DeriveEip712Test.json
contract DeriveEip712Test {
uint256 constant chainId = 1;
bytes32 constant salt = keccak256("eip712-test-75F0CCte");
Expand Down
Loading