derive-eip712: initial implementation of eip712 derive macro (#481)
* derive-eip712: initial implementation of eip712 derive macro
This commit provides an initial implementation for a derive macro
to encode typed data according to EIP-712, https://eips.ethereum.org/EIPS/eip-712
Additionally, this commit introduces a new signer trait method:
async fn sign_typed_data<T: Eip712 + Send + Sync>(
&self,
payload: &T,
) -> Result<Signature, Self::Error>;
And implements the new method for each of the signers (wallet, ledger,
aws).
Additionally, these changes include using `WalletError` for the Wallet
signer error type
At the moment, derive does not recurse the primary type to find nested
Eip712 structs. This is something that is noted in the source and
currently responds with an error regarding custom types.
A subsequent PR should be opened once this issue becomes needed. For the
moment, the current implementation should satisfy non-nested, basic struct types.
* rename to ethers-derive-eip712; move to ethers-core
* refactor of derive-eip712 macro; use ParamType and EthAbiToken
* macro updates; add byte array checker for paramtype; use literal constant for domain type hash
* replace std::convert::Infallible with WalletError as Wallet signer error type
* update workspace members and dev dependencies for examples folder
* add example for eip712 and test against contract
* remove extraneous backward slash in '\x19\x01' prefix; example tests pass
* update unreleased change log
* remove print statements
* use parse_macro_input macro; remove dead code; handle nest struct not implemented error
* move eip712 example to solidity-contract tests folder; update cargo workspace dependencies
* allow optional EIP712Domain parameter when encoding eip712 struct and signing typed data
* add documentation for eip712 feature
* Update ethers-signers/src/ledger/mod.rs
Co-authored-by: Sebastian Martinez <me@sebastinez.dev>
* add error enum for Eip712Error; use sign_payload for ledger signer
* add EIP712WithDomain type for providing a wrapper around custom setting of the domain
* make LedgerWallet sign_payload public
* use optional feature gated dependencies for eip712; add default method for encode_eip712
* add default domain_separator method, pre-compute separator hash
* move derive-eip712 deps to dev deps
* remove invalid sign payload parameter, add await on async method
* remove deprecated comment
* debugging 'bad key handle' error for ledger signer
try using 'sign_message'
* await sign digest for aws signer
* remove extra space, fix fmt warning
* fix test, fmt errors
* use gt 0.6.0 pragma compiler version
* enable ABIEncoderV2 for solidity test contract
* chore: make test constructor public
Co-authored-by: Sebastian Martinez <me@sebastinez.dev>
Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
2021-10-08 15:22:51 +00:00
|
|
|
//! # EIP-712 Derive Macro
|
|
|
|
//! This crate provides a derive macro `Eip712` that is used to encode a rust struct
|
|
|
|
//! into a payload hash, according to [https://eips.ethereum.org/EIPS/eip-712](https://eips.ethereum.org/EIPS/eip-712)
|
|
|
|
//!
|
|
|
|
//! The trait used to derive the macro is found in `ethers_core::transaction::eip712::Eip712`
|
|
|
|
//! Both the derive macro and the trait must be in context when using
|
|
|
|
//!
|
|
|
|
//! This derive macro requires the `#[eip712]` attributes to be included
|
|
|
|
//! for specifying the domain separator used in encoding the hash.
|
|
|
|
//!
|
|
|
|
//! NOTE: In addition to deriving `Eip712` trait, the `EthAbiType` trait must also be derived.
|
|
|
|
//! This allows the struct to be parsed into `ethers_core::abi::Token` for encoding.
|
|
|
|
//!
|
|
|
|
//! # Optional Eip712 Parameters
|
|
|
|
//!
|
|
|
|
//! The only optional parameter is `salt`, which accepts a string
|
|
|
|
//! that is hashed using keccak256 and stored as bytes.
|
|
|
|
//!
|
|
|
|
//! # Example Usage
|
|
|
|
//!
|
|
|
|
//! ```rust
|
|
|
|
//! use ethers_contract::EthAbiType;
|
|
|
|
//! use ethers_derive_eip712::*;
|
|
|
|
//! use ethers_core::types::{transaction::eip712::Eip712, H160};
|
|
|
|
//!
|
|
|
|
//! #[derive(Debug, Eip712, EthAbiType)]
|
|
|
|
//! #[eip712(
|
|
|
|
//! name = "Radicle",
|
|
|
|
//! version = "1",
|
|
|
|
//! chain_id = 1,
|
|
|
|
//! verifying_contract = "0x0000000000000000000000000000000000000000"
|
|
|
|
//! // salt is an optional parameter
|
|
|
|
//! salt = "my-unique-spice"
|
|
|
|
//! )]
|
|
|
|
//! pub struct Puzzle {
|
|
|
|
//! pub organization: H160,
|
|
|
|
//! pub contributor: H160,
|
|
|
|
//! pub commit: String,
|
|
|
|
//! pub project: String,
|
|
|
|
//! }
|
|
|
|
//!
|
|
|
|
//! let puzzle = Puzzle {
|
|
|
|
//! organization: "0000000000000000000000000000000000000000"
|
|
|
|
//! .parse::<H160>()
|
|
|
|
//! .expect("failed to parse address"),
|
|
|
|
//! contributor: "0000000000000000000000000000000000000000"
|
|
|
|
//! .parse::<H160>()
|
|
|
|
//! .expect("failed to parse address"),
|
|
|
|
//! commit: "5693b7019eb3e4487a81273c6f5e1832d77acb53".to_string(),
|
|
|
|
//! project: "radicle-reward".to_string(),
|
|
|
|
//! };
|
|
|
|
//!
|
|
|
|
//! let hash = puzzle.encode_eip712()?;
|
|
|
|
//! ```
|
|
|
|
//!
|
|
|
|
//! # Limitations
|
|
|
|
//!
|
|
|
|
//! At the moment, the derive macro does not recursively encode nested Eip712 structs.
|
|
|
|
//!
|
|
|
|
//! There is an Inner helper attribute `#[eip712]` for fields that will eventually be used to
|
|
|
|
//! determine if there is a nested eip712 struct. However, this work is not yet complete.
|
|
|
|
use std::convert::TryFrom;
|
|
|
|
|
|
|
|
use ethers_core::types::transaction::eip712;
|
|
|
|
|
|
|
|
use proc_macro::TokenStream;
|
|
|
|
use quote::quote;
|
|
|
|
use syn::parse_macro_input;
|
|
|
|
|
|
|
|
#[proc_macro_derive(Eip712, attributes(eip712))]
|
|
|
|
pub fn eip_712_derive(input: TokenStream) -> TokenStream {
|
|
|
|
let ast = parse_macro_input!(input);
|
|
|
|
|
|
|
|
impl_eip_712_macro(&ast)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Main implementation macro, used to compute static values and define
|
|
|
|
// method for encoding the final eip712 payload;
|
|
|
|
fn impl_eip_712_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|
|
|
// Primary type should match the type in the ethereum verifying contract;
|
|
|
|
let primary_type = &ast.ident;
|
|
|
|
|
|
|
|
// Instantiate domain from parsed attributes
|
|
|
|
let domain = match eip712::EIP712Domain::try_from(ast) {
|
|
|
|
Ok(attributes) => attributes,
|
|
|
|
Err(e) => return TokenStream::from(e),
|
|
|
|
};
|
|
|
|
|
|
|
|
let domain_separator = hex::encode(domain.separator());
|
|
|
|
|
|
|
|
//
|
|
|
|
let domain_str = match serde_json::to_string(&domain) {
|
|
|
|
Ok(s) => s,
|
|
|
|
Err(e) => {
|
|
|
|
return TokenStream::from(
|
|
|
|
syn::Error::new(ast.ident.span(), e.to_string()).to_compile_error(),
|
2021-10-29 12:29:35 +00:00
|
|
|
)
|
derive-eip712: initial implementation of eip712 derive macro (#481)
* derive-eip712: initial implementation of eip712 derive macro
This commit provides an initial implementation for a derive macro
to encode typed data according to EIP-712, https://eips.ethereum.org/EIPS/eip-712
Additionally, this commit introduces a new signer trait method:
async fn sign_typed_data<T: Eip712 + Send + Sync>(
&self,
payload: &T,
) -> Result<Signature, Self::Error>;
And implements the new method for each of the signers (wallet, ledger,
aws).
Additionally, these changes include using `WalletError` for the Wallet
signer error type
At the moment, derive does not recurse the primary type to find nested
Eip712 structs. This is something that is noted in the source and
currently responds with an error regarding custom types.
A subsequent PR should be opened once this issue becomes needed. For the
moment, the current implementation should satisfy non-nested, basic struct types.
* rename to ethers-derive-eip712; move to ethers-core
* refactor of derive-eip712 macro; use ParamType and EthAbiToken
* macro updates; add byte array checker for paramtype; use literal constant for domain type hash
* replace std::convert::Infallible with WalletError as Wallet signer error type
* update workspace members and dev dependencies for examples folder
* add example for eip712 and test against contract
* remove extraneous backward slash in '\x19\x01' prefix; example tests pass
* update unreleased change log
* remove print statements
* use parse_macro_input macro; remove dead code; handle nest struct not implemented error
* move eip712 example to solidity-contract tests folder; update cargo workspace dependencies
* allow optional EIP712Domain parameter when encoding eip712 struct and signing typed data
* add documentation for eip712 feature
* Update ethers-signers/src/ledger/mod.rs
Co-authored-by: Sebastian Martinez <me@sebastinez.dev>
* add error enum for Eip712Error; use sign_payload for ledger signer
* add EIP712WithDomain type for providing a wrapper around custom setting of the domain
* make LedgerWallet sign_payload public
* use optional feature gated dependencies for eip712; add default method for encode_eip712
* add default domain_separator method, pre-compute separator hash
* move derive-eip712 deps to dev deps
* remove invalid sign payload parameter, add await on async method
* remove deprecated comment
* debugging 'bad key handle' error for ledger signer
try using 'sign_message'
* await sign digest for aws signer
* remove extra space, fix fmt warning
* fix test, fmt errors
* use gt 0.6.0 pragma compiler version
* enable ABIEncoderV2 for solidity test contract
* chore: make test constructor public
Co-authored-by: Sebastian Martinez <me@sebastinez.dev>
Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
2021-10-08 15:22:51 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Must parse the AST at compile time.
|
|
|
|
let parsed_fields = match eip712::parse_fields(ast) {
|
|
|
|
Ok(fields) => fields,
|
|
|
|
Err(e) => return TokenStream::from(e),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Compute the type hash for the derived struct using the parsed fields from above;
|
2021-10-29 12:29:35 +00:00
|
|
|
let type_hash =
|
|
|
|
hex::encode(eip712::make_type_hash(primary_type.clone().to_string(), &parsed_fields));
|
derive-eip712: initial implementation of eip712 derive macro (#481)
* derive-eip712: initial implementation of eip712 derive macro
This commit provides an initial implementation for a derive macro
to encode typed data according to EIP-712, https://eips.ethereum.org/EIPS/eip-712
Additionally, this commit introduces a new signer trait method:
async fn sign_typed_data<T: Eip712 + Send + Sync>(
&self,
payload: &T,
) -> Result<Signature, Self::Error>;
And implements the new method for each of the signers (wallet, ledger,
aws).
Additionally, these changes include using `WalletError` for the Wallet
signer error type
At the moment, derive does not recurse the primary type to find nested
Eip712 structs. This is something that is noted in the source and
currently responds with an error regarding custom types.
A subsequent PR should be opened once this issue becomes needed. For the
moment, the current implementation should satisfy non-nested, basic struct types.
* rename to ethers-derive-eip712; move to ethers-core
* refactor of derive-eip712 macro; use ParamType and EthAbiToken
* macro updates; add byte array checker for paramtype; use literal constant for domain type hash
* replace std::convert::Infallible with WalletError as Wallet signer error type
* update workspace members and dev dependencies for examples folder
* add example for eip712 and test against contract
* remove extraneous backward slash in '\x19\x01' prefix; example tests pass
* update unreleased change log
* remove print statements
* use parse_macro_input macro; remove dead code; handle nest struct not implemented error
* move eip712 example to solidity-contract tests folder; update cargo workspace dependencies
* allow optional EIP712Domain parameter when encoding eip712 struct and signing typed data
* add documentation for eip712 feature
* Update ethers-signers/src/ledger/mod.rs
Co-authored-by: Sebastian Martinez <me@sebastinez.dev>
* add error enum for Eip712Error; use sign_payload for ledger signer
* add EIP712WithDomain type for providing a wrapper around custom setting of the domain
* make LedgerWallet sign_payload public
* use optional feature gated dependencies for eip712; add default method for encode_eip712
* add default domain_separator method, pre-compute separator hash
* move derive-eip712 deps to dev deps
* remove invalid sign payload parameter, add await on async method
* remove deprecated comment
* debugging 'bad key handle' error for ledger signer
try using 'sign_message'
* await sign digest for aws signer
* remove extra space, fix fmt warning
* fix test, fmt errors
* use gt 0.6.0 pragma compiler version
* enable ABIEncoderV2 for solidity test contract
* chore: make test constructor public
Co-authored-by: Sebastian Martinez <me@sebastinez.dev>
Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
2021-10-08 15:22:51 +00:00
|
|
|
|
|
|
|
let implementation = quote! {
|
|
|
|
impl 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 = hex::decode(#type_hash)?;
|
|
|
|
let byte_array: [u8; 32] = <[u8; 32]>::try_from(&decoded[..])?;
|
|
|
|
Ok(byte_array)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the pre-computed domain separator from compile time;
|
|
|
|
fn domain_separator(&self) -> Result<[u8; 32], Self::Error> {
|
|
|
|
use std::convert::TryFrom;
|
|
|
|
let decoded = hex::decode(#domain_separator)?;
|
|
|
|
let byte_array: [u8; 32] = <[u8; 32]>::try_from(&decoded[..])?;
|
|
|
|
Ok(byte_array)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn domain(&self) -> Result<ethers_core::types::transaction::eip712::EIP712Domain, Self::Error> {
|
|
|
|
let domain: ethers_core::types::transaction::eip712::EIP712Domain = serde_json::from_str(#domain_str)?;
|
|
|
|
|
|
|
|
Ok(domain)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn struct_hash(&self) -> Result<[u8; 32], Self::Error> {
|
|
|
|
use ethers_core::abi::Tokenizable;
|
|
|
|
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() {
|
|
|
|
for token in tokens {
|
|
|
|
match &token {
|
|
|
|
ethers_core::abi::Token::Tuple(t) => {
|
|
|
|
// TODO: check for nested Eip712 Type;
|
|
|
|
// Challenge is determining the type hash
|
|
|
|
return Err(Self::Error::NestedEip712StructNotImplemented);
|
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
items.push(ethers_core::types::transaction::eip712::encode_eip712_type(token));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let struct_hash = ethers_core::utils::keccak256(ethers_core::abi::encode(
|
|
|
|
&items,
|
|
|
|
));
|
|
|
|
|
|
|
|
Ok(struct_hash)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
implementation.into()
|
|
|
|
}
|