use crate::utils;
use ethers_core::{
abi::{Address, ParamType},
macros::ethers_core_crate,
types::{transaction::eip712::EIP712Domain, H256},
utils::keccak256,
};
use inflector::Inflector;
use proc_macro2::{Literal, TokenStream};
use quote::quote;
use syn::{spanned::Spanned, Data, DeriveInput, Error, Fields, LitInt, LitStr, Result, Token};
pub(crate) fn impl_derive_eip712(input: &DeriveInput) -> Result<TokenStream> {
let primary_type = &input.ident;
let domain = parse_attributes(input)?;
let domain_separator = into_tokens(domain.separator());
let domain_str = serde_json::to_string(&domain).unwrap();
let parsed_fields = parse_fields(input)?;
let type_hash = into_tokens(make_type_hash(primary_type.to_string(), &parsed_fields));
let ethers_core = ethers_core_crate();
let tokens = quote! {
impl #ethers_core::types::transaction::eip712::Eip712 for #primary_type {
type Error = #ethers_core::types::transaction::eip712::Eip712Error;
#[inline]
fn type_hash() -> ::core::result::Result<[u8; 32], Self::Error> {
Ok([#(#type_hash),*])
}
#[inline]
fn domain_separator(&self) -> ::core::result::Result<[u8; 32], Self::Error> {
Ok([#(#domain_separator),*])
}
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) -> ::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) =
#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) => {
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)
}
}
};
Ok(tokens)
}
fn parse_attributes(input: &DeriveInput) -> Result<EIP712Domain> {
let mut domain = EIP712Domain::default();
utils::parse_attributes!(input.attrs.iter(), "eip712", meta,
"name", domain.name => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
domain.name = Some(litstr.value());
}
"version", domain.version => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
domain.version = Some(litstr.value());
}
"chain_id", domain.chain_id => {
meta.input.parse::<Token![=]>()?;
let litint: LitInt = meta.input.parse()?;
let n: u64 = litint.base10_parse()?;
domain.chain_id = Some(n.into());
}
"verifying_contract", domain.verifying_contract => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
let addr: Address =
litstr.value().parse().map_err(|e| Error::new(litstr.span(), e))?;
domain.verifying_contract = Some(addr);
}
"salt", domain.salt => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
let hash = keccak256(litstr.value());
domain.salt = Some(hash);
}
"raw_salt", domain.salt => {
meta.input.parse::<Token![=]>()?;
let litstr: LitStr = meta.input.parse()?;
let bytes = litstr.value().parse::<H256>().map_err(|e| Error::new(litstr.span(), e))?;
domain.salt = Some(bytes.0);
}
);
Ok(domain)
}
fn parse_fields(input: &DeriveInput) -> Result<Vec<(String, ParamType)>> {
let data = match &input.data {
Data::Struct(s) => s,
Data::Enum(e) => {
return Err(Error::new(e.enum_token.span, "Eip712 is not derivable for enums"))
}
Data::Union(u) => {
return Err(Error::new(u.union_token.span, "Eip712 is not derivable for unions"))
}
};
let named_fields = match &data.fields {
Fields::Named(fields) => fields,
_ => return Err(Error::new(input.span(), "unnamed fields are not supported")),
};
let mut fields = Vec::with_capacity(named_fields.named.len());
for f in named_fields.named.iter() {
let name = f.ident.as_ref().unwrap().to_string();
let s = name.strip_prefix("r#").unwrap_or(&name);
let name = s.to_camel_case();
let ty =
match f.attrs.iter().find(|a| a.path().segments.iter().any(|s| s.ident == "eip712")) {
Some(a) => {
return Err(Error::new(a.span(), "nested Eip712 struct are not yet supported"))
}
None => crate::utils::find_parameter_type(&f.ty)?,
};
fields.push((name, ty));
}
Ok(fields)
}
fn make_type_hash(primary_type: String, fields: &[(String, ParamType)]) -> [u8; 32] {
let mut sig = String::with_capacity(256);
sig.push_str(&primary_type);
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)
}