1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! # 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
//!
//! ```ignore
//! 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().unwrap();
//! ```
//!
//! # 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.

#![deny(missing_docs, unsafe_code, rustdoc::broken_intra_doc_links)]
use ethers_core::{macros::ethers_core_crate, types::transaction::eip712};
use proc_macro::TokenStream;
use quote::quote;
use std::convert::TryFrom;
use syn::parse_macro_input;

/// Derive macro for `Eip712`
#[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(),
            )
        }
    };

    // 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.
    let type_hash =
        hex::encode(eip712::make_type_hash(primary_type.clone().to_string(), &parsed_fields));

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

    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 = #ethers_core::utils::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 = #ethers_core::utils::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 = # ethers_core::utils::__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()
}