chore: move eip712 into ethers-contract-derive
This commit is contained in:
parent
85f6710471
commit
66a9995df7
|
@ -651,15 +651,6 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "convert_case"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-segmentation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
@ -1239,7 +1230,6 @@ dependencies = [
|
||||||
"ethers-contract-abigen",
|
"ethers-contract-abigen",
|
||||||
"ethers-contract-derive",
|
"ethers-contract-derive",
|
||||||
"ethers-core",
|
"ethers-core",
|
||||||
"ethers-derive-eip712",
|
|
||||||
"ethers-providers",
|
"ethers-providers",
|
||||||
"ethers-signers",
|
"ethers-signers",
|
||||||
"ethers-solc",
|
"ethers-solc",
|
||||||
|
@ -1284,11 +1274,13 @@ dependencies = [
|
||||||
name = "ethers-contract-derive"
|
name = "ethers-contract-derive"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"Inflector",
|
||||||
"ethers-contract-abigen",
|
"ethers-contract-abigen",
|
||||||
"ethers-core",
|
"ethers-core",
|
||||||
"hex",
|
"hex",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"serde_json",
|
||||||
"syn 2.0.0",
|
"syn 2.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1301,7 +1293,6 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"cargo_metadata",
|
"cargo_metadata",
|
||||||
"chrono",
|
"chrono",
|
||||||
"convert_case",
|
|
||||||
"elliptic-curve",
|
"elliptic-curve",
|
||||||
"ethabi",
|
"ethabi",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
|
@ -1324,19 +1315,6 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ethers-derive-eip712"
|
|
||||||
version = "2.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"ethers-contract-derive",
|
|
||||||
"ethers-core",
|
|
||||||
"hex",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"serde_json",
|
|
||||||
"syn 2.0.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethers-etherscan"
|
name = "ethers-etherscan"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
@ -1435,7 +1413,6 @@ dependencies = [
|
||||||
"eth-keystore",
|
"eth-keystore",
|
||||||
"ethers-contract-derive",
|
"ethers-contract-derive",
|
||||||
"ethers-core",
|
"ethers-core",
|
||||||
"ethers-derive-eip712",
|
|
||||||
"futures-executor",
|
"futures-executor",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hex",
|
"hex",
|
||||||
|
@ -4484,12 +4461,6 @@ dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-segmentation"
|
|
||||||
version = "1.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
|
|
|
@ -76,7 +76,6 @@ ethers-solc = { version = "2.0.0", path = "ethers-solc", default-features = fals
|
||||||
|
|
||||||
ethers-contract-abigen = { version = "2.0.0", path = "ethers-contract/ethers-contract-abigen", 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-derive = { version = "2.0.0", path = "ethers-contract/ethers-contract-derive", default-features = false }
|
||||||
ethers-derive-eip712 = { version = "2.0.0", path = "ethers-core/ethers-derive-eip712", default-features = false }
|
|
||||||
|
|
||||||
# async / async utils
|
# async / async utils
|
||||||
tokio = "1.26"
|
tokio = "1.26"
|
||||||
|
@ -112,6 +111,7 @@ async-trait = "0.1.66"
|
||||||
auto_impl = "1.0"
|
auto_impl = "1.0"
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
|
Inflector = "0.11"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
once_cell = "1.17"
|
once_cell = "1.17"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
|
|
@ -39,10 +39,8 @@ hex.workspace = true
|
||||||
ethers-contract-abigen = { workspace = true, optional = true }
|
ethers-contract-abigen = { workspace = true, optional = true }
|
||||||
ethers-contract-derive = { workspace = true, optional = true }
|
ethers-contract-derive = { workspace = true, optional = true }
|
||||||
|
|
||||||
# eip712
|
|
||||||
ethers-derive-eip712 = { workspace = true, optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
ethers-providers = { workspace = true, features = ["ws"] }
|
||||||
ethers-signers.workspace = true
|
ethers-signers.workspace = true
|
||||||
ethers-solc.workspace = true
|
ethers-solc.workspace = true
|
||||||
|
|
||||||
|
@ -52,8 +50,6 @@ tokio = { workspace = true, features = ["macros"] }
|
||||||
[features]
|
[features]
|
||||||
default = ["abigen"]
|
default = ["abigen"]
|
||||||
|
|
||||||
eip712 = ["ethers-derive-eip712", "ethers-core/eip712"]
|
|
||||||
|
|
||||||
abigen-offline = ["ethers-contract-abigen", "ethers-contract-derive"]
|
abigen-offline = ["ethers-contract-abigen", "ethers-contract-derive"]
|
||||||
abigen = ["abigen-offline", "ethers-contract-abigen/online"]
|
abigen = ["abigen-offline", "ethers-contract-abigen/online"]
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ quote.workspace = true
|
||||||
syn = { workspace = true, features = ["full"] }
|
syn = { workspace = true, features = ["full"] }
|
||||||
prettyplease = "0.1.25"
|
prettyplease = "0.1.25"
|
||||||
|
|
||||||
Inflector = "0.11"
|
Inflector.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
|
|
|
@ -28,4 +28,6 @@ proc-macro2.workspace = true
|
||||||
quote.workspace = true
|
quote.workspace = true
|
||||||
syn.workspace = true
|
syn.workspace = true
|
||||||
|
|
||||||
|
Inflector.workspace = true
|
||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
use crate::utils;
|
||||||
|
use ethers_core::{
|
||||||
|
abi::{Address, ParamType},
|
||||||
|
macros::ethers_core_crate,
|
||||||
|
types::transaction::eip712::EIP712Domain,
|
||||||
|
utils::keccak256,
|
||||||
|
};
|
||||||
|
use inflector::Inflector;
|
||||||
|
use proc_macro2::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> {
|
||||||
|
// Primary type should match the type in the ethereum verifying contract;
|
||||||
|
let primary_type = &input.ident;
|
||||||
|
|
||||||
|
// Instantiate domain from parsed attributes
|
||||||
|
let domain = parse_attributes(input)?;
|
||||||
|
|
||||||
|
let domain_separator = hex::encode(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));
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Ok(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a Vec of `(name, param_type)`
|
||||||
|
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 field_name = f.ident.as_ref().unwrap().to_string().to_camel_case();
|
||||||
|
let field_type =
|
||||||
|
match f.attrs.iter().find(|a| a.path().segments.iter().any(|s| s.ident == "eip712")) {
|
||||||
|
// Found nested Eip712 Struct
|
||||||
|
// TODO: Implement custom
|
||||||
|
Some(a) => {
|
||||||
|
return Err(Error::new(a.span(), "nested Eip712 struct are not yet supported"))
|
||||||
|
}
|
||||||
|
// Not a nested eip712 struct, return the field param type;
|
||||||
|
None => crate::utils::find_parameter_type(&f.ty)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
fields.push((field_name, field_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 sig = format!("{primary_type}({parameters})");
|
||||||
|
|
||||||
|
keccak256(sig)
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ mod call;
|
||||||
pub(crate) mod calllike;
|
pub(crate) mod calllike;
|
||||||
mod codec;
|
mod codec;
|
||||||
mod display;
|
mod display;
|
||||||
|
mod eip712;
|
||||||
mod error;
|
mod error;
|
||||||
mod event;
|
mod event;
|
||||||
mod spanned;
|
mod spanned;
|
||||||
|
@ -357,3 +358,75 @@ pub fn derive_abi_error(input: TokenStream) -> TokenStream {
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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>
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
#[proc_macro_derive(Eip712, attributes(eip712))]
|
||||||
|
pub fn derive_eip712(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
match eip712::impl_derive_eip712(&input) {
|
||||||
|
Ok(tokens) => tokens,
|
||||||
|
Err(e) => e.to_compile_error(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
|
@ -59,16 +59,13 @@ pub use ethers_contract_abigen::{
|
||||||
#[cfg(any(test, feature = "abigen"))]
|
#[cfg(any(test, feature = "abigen"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
|
||||||
pub use ethers_contract_derive::{
|
pub use ethers_contract_derive::{
|
||||||
abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthError, EthEvent,
|
abigen, Eip712, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthError, EthEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hide the Lazy re-export, it's just for convenience
|
// Hide the Lazy re-export, it's just for convenience
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use once_cell::sync::Lazy;
|
pub use once_cell::sync::Lazy;
|
||||||
|
|
||||||
#[cfg(feature = "eip712")]
|
|
||||||
pub use ethers_derive_eip712::*;
|
|
||||||
|
|
||||||
// For macro expansions only, not public API.
|
// For macro expansions only, not public API.
|
||||||
// See: [#2235](https://github.com/gakonst/ethers-rs/pull/2235)
|
// See: [#2235](https://github.com/gakonst/ethers-rs/pull/2235)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
use ethers_contract::{
|
use ethers_contract::{
|
||||||
abigen, ContractFactory, ContractInstance, EthAbiType, EthEvent, LogMeta, Multicall,
|
abigen, ContractFactory, ContractInstance, Eip712, EthAbiType, EthEvent, LogMeta, Multicall,
|
||||||
MulticallError, MulticallVersion,
|
MulticallError, MulticallVersion,
|
||||||
};
|
};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
|
@ -11,7 +11,7 @@ use ethers_core::{
|
||||||
},
|
},
|
||||||
utils::{keccak256, Anvil},
|
utils::{keccak256, Anvil},
|
||||||
};
|
};
|
||||||
use ethers_providers::{Http, Middleware, MiddlewareError, Provider, StreamExt};
|
use ethers_providers::{Http, Middleware, MiddlewareError, Provider, StreamExt, Ws};
|
||||||
use ethers_signers::{LocalWallet, Signer};
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
@ -342,7 +342,7 @@ async fn watch_events() {
|
||||||
let mut stream = event.stream().await.unwrap();
|
let mut stream = event.stream().await.unwrap();
|
||||||
|
|
||||||
// Also set up a subscription for the same thing
|
// Also set up a subscription for the same thing
|
||||||
let ws = Provider::connect(anvil.ws_endpoint()).await.unwrap();
|
let ws = Provider::<Ws>::connect(anvil.ws_endpoint()).await.unwrap();
|
||||||
let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws.into());
|
let contract2 = ethers_contract::Contract::new(contract.address(), abi, ws.into());
|
||||||
let event2 = contract2.event::<ValueChanged>();
|
let event2 = contract2.event::<ValueChanged>();
|
||||||
let mut subscription = event2.subscribe().await.unwrap();
|
let mut subscription = event2.subscribe().await.unwrap();
|
||||||
|
@ -381,7 +381,7 @@ async fn watch_subscription_events_multiple_addresses() {
|
||||||
let contract_1 = deploy(client.clone(), abi.clone(), bytecode.clone()).await;
|
let contract_1 = deploy(client.clone(), abi.clone(), bytecode.clone()).await;
|
||||||
let contract_2 = deploy(client.clone(), abi.clone(), bytecode).await;
|
let contract_2 = deploy(client.clone(), abi.clone(), bytecode).await;
|
||||||
|
|
||||||
let ws = Provider::connect(anvil.ws_endpoint()).await.unwrap();
|
let ws = Provider::<Ws>::connect(anvil.ws_endpoint()).await.unwrap();
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.address(ValueOrArray::Array(vec![contract_1.address(), contract_2.address()]));
|
.address(ValueOrArray::Array(vec![contract_1.address(), contract_2.address()]));
|
||||||
let mut stream = ws.subscribe_logs(&filter).await.unwrap();
|
let mut stream = ws.subscribe_logs(&filter).await.unwrap();
|
||||||
|
@ -786,16 +786,15 @@ async fn multicall_aggregate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[cfg(feature = "eip712")]
|
|
||||||
async fn test_derive_eip712() {
|
async fn test_derive_eip712() {
|
||||||
use ethers_derive_eip712::*;
|
|
||||||
|
|
||||||
// Generate Contract ABI Bindings
|
// Generate Contract ABI Bindings
|
||||||
abigen!(
|
mod contract {
|
||||||
DeriveEip712Test,
|
ethers_contract::abigen!(
|
||||||
"./ethers-contract/tests/solidity-contracts/derive_eip712_abi.json",
|
DeriveEip712Test,
|
||||||
event_derives(serde::Deserialize, serde::Serialize)
|
"./ethers-contract/tests/solidity-contracts/derive_eip712_abi.json",
|
||||||
);
|
derives(serde::Deserialize, serde::Serialize)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Create derived structs
|
// Create derived structs
|
||||||
|
|
||||||
|
@ -842,7 +841,7 @@ async fn test_derive_eip712() {
|
||||||
|
|
||||||
let addr = contract.address();
|
let addr = contract.address();
|
||||||
|
|
||||||
let contract = DeriveEip712Test::new(addr, client.clone());
|
let contract = contract::DeriveEip712Test::new(addr, client.clone());
|
||||||
|
|
||||||
let foo_bar = FooBar {
|
let foo_bar = FooBar {
|
||||||
foo: I256::from(10u64),
|
foo: I256::from(10u64),
|
||||||
|
@ -853,7 +852,7 @@ async fn test_derive_eip712() {
|
||||||
out: Address::from([0; 20]),
|
out: Address::from([0; 20]),
|
||||||
};
|
};
|
||||||
|
|
||||||
let derived_foo_bar = derive_eip_712_test::FooBar {
|
let derived_foo_bar = contract::FooBar {
|
||||||
foo: foo_bar.foo,
|
foo: foo_bar.foo,
|
||||||
bar: foo_bar.bar,
|
bar: foo_bar.bar,
|
||||||
fizz: foo_bar.fizz.clone(),
|
fizz: foo_bar.fizz.clone(),
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
#![allow(clippy::extra_unused_type_parameters)]
|
use ethers_contract_derive::{Eip712, EthAbiType};
|
||||||
|
|
||||||
use ethers_contract_derive::EthAbiType;
|
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
types::{
|
types::{
|
||||||
transaction::eip712::{
|
transaction::eip712::{
|
||||||
|
@ -11,7 +9,6 @@ use ethers_core::{
|
||||||
},
|
},
|
||||||
utils::{keccak256, parse_ether},
|
utils::{keccak256, parse_ether},
|
||||||
};
|
};
|
||||||
use ethers_derive_eip712::*;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_derive_eip712() {
|
fn test_derive_eip712() {
|
|
@ -7,6 +7,8 @@ mod derive;
|
||||||
|
|
||||||
mod contract_call;
|
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;
|
mod common;
|
||||||
|
|
||||||
|
|
|
@ -51,9 +51,6 @@ num_enum = "0.5"
|
||||||
|
|
||||||
# macros feature enabled dependencies
|
# macros feature enabled dependencies
|
||||||
cargo_metadata = { version = "0.15.3", optional = true }
|
cargo_metadata = { version = "0.15.3", optional = true }
|
||||||
|
|
||||||
# eip712 feature enabled dependencies
|
|
||||||
convert_case = { version = "0.6.0", optional = true }
|
|
||||||
syn = { workspace = true, optional = true }
|
syn = { workspace = true, optional = true }
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
@ -72,5 +69,4 @@ rand.workspace = true
|
||||||
[features]
|
[features]
|
||||||
celo = ["legacy"] # celo support extends the transaction format with extra fields
|
celo = ["legacy"] # celo support extends the transaction format with extra fields
|
||||||
legacy = []
|
legacy = []
|
||||||
eip712 = ["convert_case", "syn"]
|
|
||||||
macros = ["syn", "cargo_metadata", "once_cell"]
|
macros = ["syn", "cargo_metadata", "once_cell"]
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "ethers-derive-eip712"
|
|
||||||
authors = ["Ryan Tate <ryan.michael.tate@gmail.com>"]
|
|
||||||
description = "Derive procedural macro for EIP-712 typed data"
|
|
||||||
|
|
||||||
version.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
rust-version.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
documentation.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
homepage.workspace = true
|
|
||||||
categories.workspace = true
|
|
||||||
keywords.workspace = true
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
proc-macro = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
ethers-core = { workspace = true, features = ["eip712", "macros"] }
|
|
||||||
|
|
||||||
proc-macro2.workspace = true
|
|
||||||
quote.workspace = true
|
|
||||||
syn.workspace = true
|
|
||||||
|
|
||||||
hex.workspace = true
|
|
||||||
serde_json.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
ethers-contract-derive.workspace = true
|
|
|
@ -1,163 +0,0 @@
|
||||||
//! # 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 proc_macro2::TokenStream as TokenStream2;
|
|
||||||
use quote::quote;
|
|
||||||
use syn::{parse_macro_input, Result};
|
|
||||||
|
|
||||||
/// Derive macro for `Eip712`
|
|
||||||
#[proc_macro_derive(Eip712, attributes(eip712))]
|
|
||||||
pub fn eip_712_derive(input: TokenStream) -> TokenStream {
|
|
||||||
let input = parse_macro_input!(input);
|
|
||||||
match impl_eip_712_macro(&input) {
|
|
||||||
Ok(tokens) => tokens,
|
|
||||||
Err(e) => e.to_compile_error(),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main implementation macro, used to compute static values and define
|
|
||||||
// method for encoding the final eip712 payload;
|
|
||||||
fn impl_eip_712_macro(input: &syn::DeriveInput) -> Result<TokenStream2> {
|
|
||||||
// Primary type should match the type in the ethereum verifying contract;
|
|
||||||
let primary_type = &input.ident;
|
|
||||||
|
|
||||||
// Instantiate domain from parsed attributes
|
|
||||||
let domain = eip712::EIP712Domain::try_from(input)?;
|
|
||||||
|
|
||||||
let domain_separator = hex::encode(domain.separator());
|
|
||||||
|
|
||||||
//
|
|
||||||
let domain_str = serde_json::to_string(&domain).unwrap();
|
|
||||||
|
|
||||||
// Must parse the AST at compile time.
|
|
||||||
let parsed_fields = eip712::parse_fields(input)?;
|
|
||||||
|
|
||||||
// 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 tokens = 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(tokens)
|
|
||||||
}
|
|
|
@ -220,7 +220,6 @@ pub enum EthersCrate {
|
||||||
EthersContractAbigen,
|
EthersContractAbigen,
|
||||||
EthersContractDerive,
|
EthersContractDerive,
|
||||||
EthersCore,
|
EthersCore,
|
||||||
EthersDeriveEip712,
|
|
||||||
EthersEtherscan,
|
EthersEtherscan,
|
||||||
EthersMiddleware,
|
EthersMiddleware,
|
||||||
EthersProviders,
|
EthersProviders,
|
||||||
|
@ -250,7 +249,6 @@ impl EthersCrate {
|
||||||
Self::EthersContractAbigen => "ethers-contract-abigen",
|
Self::EthersContractAbigen => "ethers-contract-abigen",
|
||||||
Self::EthersContractDerive => "ethers-contract-derive",
|
Self::EthersContractDerive => "ethers-contract-derive",
|
||||||
Self::EthersCore => "ethers-core",
|
Self::EthersCore => "ethers-core",
|
||||||
Self::EthersDeriveEip712 => "ethers-derive-eip712",
|
|
||||||
Self::EthersEtherscan => "ethers-etherscan",
|
Self::EthersEtherscan => "ethers-etherscan",
|
||||||
Self::EthersMiddleware => "ethers-middleware",
|
Self::EthersMiddleware => "ethers-middleware",
|
||||||
Self::EthersProviders => "ethers-providers",
|
Self::EthersProviders => "ethers-providers",
|
||||||
|
@ -268,7 +266,6 @@ impl EthersCrate {
|
||||||
Self::EthersContractAbigen => "::ethers_contract_abigen",
|
Self::EthersContractAbigen => "::ethers_contract_abigen",
|
||||||
Self::EthersContractDerive => "::ethers_contract_derive",
|
Self::EthersContractDerive => "::ethers_contract_derive",
|
||||||
Self::EthersCore => "::ethers_core",
|
Self::EthersCore => "::ethers_core",
|
||||||
Self::EthersDeriveEip712 => "::ethers_derive_eip712",
|
|
||||||
Self::EthersEtherscan => "::ethers_etherscan",
|
Self::EthersEtherscan => "::ethers_etherscan",
|
||||||
Self::EthersMiddleware => "::ethers_middleware",
|
Self::EthersMiddleware => "::ethers_middleware",
|
||||||
Self::EthersProviders => "::ethers_providers",
|
Self::EthersProviders => "::ethers_providers",
|
||||||
|
@ -284,7 +281,6 @@ impl EthersCrate {
|
||||||
// re-exported in ethers::contract
|
// re-exported in ethers::contract
|
||||||
Self::EthersContractAbigen => "::ethers::contract", // partially
|
Self::EthersContractAbigen => "::ethers::contract", // partially
|
||||||
Self::EthersContractDerive => "::ethers::contract",
|
Self::EthersContractDerive => "::ethers::contract",
|
||||||
Self::EthersDeriveEip712 => "::ethers::contract",
|
|
||||||
|
|
||||||
Self::EthersAddressbook => "::ethers::addressbook",
|
Self::EthersAddressbook => "::ethers::addressbook",
|
||||||
Self::EthersContract => "::ethers::contract",
|
Self::EthersContract => "::ethers::contract",
|
||||||
|
@ -303,7 +299,6 @@ impl EthersCrate {
|
||||||
match self {
|
match self {
|
||||||
Self::EthersContractAbigen => "ethers-contract/ethers-contract-abigen",
|
Self::EthersContractAbigen => "ethers-contract/ethers-contract-abigen",
|
||||||
Self::EthersContractDerive => "ethers-contract/ethers-contract-derive",
|
Self::EthersContractDerive => "ethers-contract/ethers-contract-derive",
|
||||||
Self::EthersDeriveEip712 => "ethers-core/ethers-derive-eip712",
|
|
||||||
_ => self.crate_name(),
|
_ => self.crate_name(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,12 @@ use crate::{
|
||||||
types::{serde_helpers::StringifiedNumeric, Address, Bytes, U256},
|
types::{serde_helpers::StringifiedNumeric, Address, Bytes, U256},
|
||||||
utils::keccak256,
|
utils::keccak256,
|
||||||
};
|
};
|
||||||
use convert_case::{Case, Casing};
|
|
||||||
use core::convert::TryFrom;
|
|
||||||
use ethabi::encode;
|
use ethabi::encode;
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashSet},
|
collections::{BTreeMap, HashSet},
|
||||||
convert::TryInto,
|
|
||||||
iter::FromIterator,
|
iter::FromIterator,
|
||||||
};
|
};
|
||||||
use syn::{
|
|
||||||
parse::Error, spanned::Spanned, Data, DeriveInput, Expr, Fields, GenericArgument, Lit, LitInt,
|
|
||||||
LitStr, PathArguments, Type,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Custom types for `TypedData`
|
/// Custom types for `TypedData`
|
||||||
pub type Types = BTreeMap<String, Vec<Eip712DomainType>>;
|
pub type Types = BTreeMap<String, Vec<Eip712DomainType>>;
|
||||||
|
@ -39,7 +32,7 @@ pub const EIP712_DOMAIN_TYPE_HASH_WITH_SALT: [u8; 32] = [
|
||||||
202, 46, 220, 207, 34, 164, 108, 114, 154, 197, 100, 114,
|
202, 46, 220, 207, 34, 164, 108, 114, 154, 197, 100, 114,
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Error typed used by Eip712 derive macro
|
/// An EIP-712 error.
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Eip712Error {
|
pub enum Eip712Error {
|
||||||
#[error("Failed to serialize serde JSON object")]
|
#[error("Failed to serialize serde JSON object")]
|
||||||
|
@ -56,8 +49,7 @@ pub enum Eip712Error {
|
||||||
Message(String),
|
Message(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Eip712 trait provides helper methods for computing
|
/// Helper methods for computing the typed data hash used in `eth_signTypedData`.
|
||||||
/// the typed data hash used in `eth_signTypedData`.
|
|
||||||
///
|
///
|
||||||
/// The ethers-rs `derive_eip712` crate provides a derive macro to
|
/// The ethers-rs `derive_eip712` crate provides a derive macro to
|
||||||
/// implement the trait for a given struct. See documentation
|
/// implement the trait for a given struct. See documentation
|
||||||
|
@ -244,69 +236,6 @@ impl<T: Eip712 + Clone> Eip712 for EIP712WithDomain<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the AST of the struct to determine the domain attributes
|
|
||||||
impl TryFrom<&syn::DeriveInput> for EIP712Domain {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn try_from(input: &syn::DeriveInput) -> Result<EIP712Domain, Self::Error> {
|
|
||||||
const ERROR: &str = "unrecognized eip712 attribute";
|
|
||||||
const ALREADY_SPECIFIED: &str = "eip712 attribute already specified";
|
|
||||||
|
|
||||||
let mut domain = EIP712Domain::default();
|
|
||||||
|
|
||||||
for attr in input.attrs.iter() {
|
|
||||||
if !attr.path().is_ident("eip712") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
attr.parse_nested_meta(|meta| {
|
|
||||||
let ident = meta.path.get_ident().ok_or_else(|| meta.error(ERROR))?.to_string();
|
|
||||||
match ident.as_str() {
|
|
||||||
"name" if domain.name.is_none() => {
|
|
||||||
let litstr: LitStr = meta.input.parse()?;
|
|
||||||
domain.name = Some(litstr.value());
|
|
||||||
}
|
|
||||||
"name" => return Err(meta.error(ALREADY_SPECIFIED)),
|
|
||||||
|
|
||||||
"version" if domain.version.is_none() => {
|
|
||||||
let litstr: LitStr = meta.input.parse()?;
|
|
||||||
domain.version = Some(litstr.value());
|
|
||||||
}
|
|
||||||
"version" => return Err(meta.error(ALREADY_SPECIFIED)),
|
|
||||||
|
|
||||||
"chain_id" if domain.chain_id.is_none() => {
|
|
||||||
let litint: LitInt = meta.input.parse()?;
|
|
||||||
let n: u64 = litint.base10_parse()?;
|
|
||||||
domain.chain_id = Some(n.into());
|
|
||||||
}
|
|
||||||
"chain_id" => return Err(meta.error(ALREADY_SPECIFIED)),
|
|
||||||
|
|
||||||
"verifying_contract" if domain.verifying_contract.is_none() => {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
"verifying_contract" => return Err(meta.error(ALREADY_SPECIFIED)),
|
|
||||||
|
|
||||||
"salt" if domain.salt.is_none() => {
|
|
||||||
let litstr: LitStr = meta.input.parse()?;
|
|
||||||
let hash = keccak256(litstr.value());
|
|
||||||
domain.salt = Some(hash);
|
|
||||||
}
|
|
||||||
"salt" => return Err(meta.error(ALREADY_SPECIFIED)),
|
|
||||||
|
|
||||||
_ => return Err(meta.error(ERROR)),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed data object.
|
/// Represents the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed data object.
|
||||||
///
|
///
|
||||||
/// Typed data is a JSON object containing type information, domain separator parameters and the
|
/// Typed data is a JSON object containing type information, domain separator parameters and the
|
||||||
|
@ -641,125 +570,6 @@ pub fn encode_field(
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the eth abi parameter type based on the syntax type;
|
|
||||||
/// this method is copied from <https://github.com/gakonst/ethers-rs/blob/master/ethers-contract/ethers-contract-derive/src/lib.rs#L600>
|
|
||||||
/// with additional modifications for finding byte arrays
|
|
||||||
pub fn find_parameter_type(ty: &Type) -> Result<ParamType, Error> {
|
|
||||||
match ty {
|
|
||||||
Type::Array(ty) => {
|
|
||||||
let param = find_parameter_type(ty.elem.as_ref())?;
|
|
||||||
if let Expr::Lit(ref expr) = ty.len {
|
|
||||||
if let Lit::Int(ref len) = expr.lit {
|
|
||||||
if let Ok(size) = len.base10_parse::<usize>() {
|
|
||||||
if let ParamType::Uint(_) = param {
|
|
||||||
return Ok(ParamType::FixedBytes(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(ParamType::FixedArray(Box::new(param), size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::new(ty.span(), "Failed to derive proper ABI from array field"))
|
|
||||||
}
|
|
||||||
Type::Path(ty) => {
|
|
||||||
if let Some(ident) = ty.path.get_ident() {
|
|
||||||
let ident = ident.to_string().to_lowercase();
|
|
||||||
return match ident.as_str() {
|
|
||||||
"address" => Ok(ParamType::Address),
|
|
||||||
"string" => Ok(ParamType::String),
|
|
||||||
"bool" => Ok(ParamType::Bool),
|
|
||||||
"int256" | "int" | "uint" | "uint256" => Ok(ParamType::Uint(256)),
|
|
||||||
"h160" => Ok(ParamType::FixedBytes(20)),
|
|
||||||
"h256" | "secret" | "hash" => Ok(ParamType::FixedBytes(32)),
|
|
||||||
"h512" | "public" => Ok(ParamType::FixedBytes(64)),
|
|
||||||
"bytes" => Ok(ParamType::Bytes),
|
|
||||||
s => parse_int_param_type(s).ok_or_else(|| {
|
|
||||||
Error::new(
|
|
||||||
ty.span(),
|
|
||||||
format!("Failed to derive proper ABI from field: {s})"),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// check for `Vec`
|
|
||||||
if ty.path.segments.len() == 1 && ty.path.segments[0].ident == "Vec" {
|
|
||||||
if let PathArguments::AngleBracketed(ref args) = ty.path.segments[0].arguments {
|
|
||||||
if args.args.len() == 1 {
|
|
||||||
if let GenericArgument::Type(ref ty) = args.args.iter().next().unwrap() {
|
|
||||||
let kind = find_parameter_type(ty)?;
|
|
||||||
|
|
||||||
// Check if byte array is found
|
|
||||||
if let ParamType::Uint(size) = kind {
|
|
||||||
if size == 8 {
|
|
||||||
return Ok(ParamType::Bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(ParamType::Array(Box::new(kind)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::new(ty.span(), "Failed to derive proper ABI from fields"))
|
|
||||||
}
|
|
||||||
Type::Tuple(ty) => {
|
|
||||||
let params = ty.elems.iter().map(find_parameter_type).collect::<Result<Vec<_>, _>>()?;
|
|
||||||
Ok(ParamType::Tuple(params))
|
|
||||||
}
|
|
||||||
_ => Err(Error::new(ty.span(), "Failed to derive proper ABI from fields")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_int_param_type(s: &str) -> Option<ParamType> {
|
|
||||||
let size = s.chars().skip(1).collect::<String>().parse::<usize>().ok()?;
|
|
||||||
if s.starts_with('u') {
|
|
||||||
Some(ParamType::Uint(size))
|
|
||||||
} else if s.starts_with('i') {
|
|
||||||
Some(ParamType::Int(size))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return HashMap of the field name and the field type
|
|
||||||
pub fn parse_fields(input: &DeriveInput) -> Result<Vec<(String, ParamType)>, Error> {
|
|
||||||
let mut fields = Vec::new();
|
|
||||||
|
|
||||||
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(name) => name,
|
|
||||||
_ => return Err(Error::new(input.span(), "unnamed fields are not supported")),
|
|
||||||
};
|
|
||||||
|
|
||||||
for f in named_fields.named.iter() {
|
|
||||||
let field_name = f.ident.as_ref().unwrap().to_string().to_case(Case::Camel);
|
|
||||||
let field_type =
|
|
||||||
match f.attrs.iter().find(|a| a.path().segments.iter().any(|s| s.ident == "eip712")) {
|
|
||||||
// Found nested Eip712 Struct
|
|
||||||
// TODO: Implement custom
|
|
||||||
Some(a) => {
|
|
||||||
return Err(Error::new(a.span(), "nested Eip712 struct are not yet supported"))
|
|
||||||
}
|
|
||||||
// Not a nested eip712 struct, return the field param type;
|
|
||||||
None => find_parameter_type(&f.ty)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
fields.push((field_name, field_type));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert hash map of field names and types into a type hash corresponding to enc types;
|
/// Convert hash map of field names and types into a type hash corresponding to enc types;
|
||||||
pub fn make_type_hash(primary_type: String, fields: &[(String, ParamType)]) -> [u8; 32] {
|
pub fn make_type_hash(primary_type: String, fields: &[(String, ParamType)]) -> [u8; 32] {
|
||||||
let parameters =
|
let parameters =
|
||||||
|
|
|
@ -5,7 +5,6 @@ pub mod eip1559;
|
||||||
pub mod eip2718;
|
pub mod eip2718;
|
||||||
pub mod eip2930;
|
pub mod eip2930;
|
||||||
|
|
||||||
#[cfg(feature = "eip712")]
|
|
||||||
pub mod eip712;
|
pub mod eip712;
|
||||||
|
|
||||||
pub(crate) const BASE_NUM_TX_FIELDS: usize = 9;
|
pub(crate) const BASE_NUM_TX_FIELDS: usize = 9;
|
||||||
|
|
|
@ -52,11 +52,9 @@ const OVERFLOW_I256_UNITS: usize = 77;
|
||||||
/// U256 overflows for numbers wider than 78 units.
|
/// U256 overflows for numbers wider than 78 units.
|
||||||
const OVERFLOW_U256_UNITS: usize = 78;
|
const OVERFLOW_U256_UNITS: usize = 78;
|
||||||
|
|
||||||
/// Re-export of serde-json
|
// Re-export serde-json for macro usage
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod __serde_json {
|
pub use serde_json as __serde_json;
|
||||||
pub use serde_json::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ConversionError {
|
pub enum ConversionError {
|
||||||
|
|
|
@ -24,7 +24,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethers-core = { workspace = true, features = ["eip712"] }
|
ethers-core.workspace = true
|
||||||
|
|
||||||
# crypto
|
# crypto
|
||||||
coins-bip32 = "0.8.3"
|
coins-bip32 = "0.8.3"
|
||||||
|
@ -66,7 +66,6 @@ yubihsm = { version = "0.42.0-pre.0", features = ["secp256k1", "http", "usb"], o
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ethers-contract-derive.workspace = true
|
ethers-contract-derive.workspace = true
|
||||||
ethers-derive-eip712.workspace = true
|
|
||||||
|
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
|
|
|
@ -293,11 +293,10 @@ impl LedgerEthereum {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::Signer;
|
use crate::Signer;
|
||||||
use ethers_contract_derive::EthAbiType;
|
use ethers_contract_derive::{Eip712, EthAbiType};
|
||||||
use ethers_core::types::{
|
use ethers_core::types::{
|
||||||
transaction::eip712::Eip712, Address, TransactionRequest, I256, U256,
|
transaction::eip712::Eip712, Address, TransactionRequest, I256, U256,
|
||||||
};
|
};
|
||||||
use ethers_derive_eip712::*;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eip712, EthAbiType)]
|
#[derive(Debug, Clone, Eip712, EthAbiType)]
|
||||||
|
|
|
@ -233,7 +233,7 @@ impl TrezorEthereum {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::Signer;
|
use crate::Signer;
|
||||||
use ethers_contract_derive::EthAbiType;
|
use ethers_contract_derive::{Eip712, EthAbiType};
|
||||||
use ethers_core::types::{
|
use ethers_core::types::{
|
||||||
transaction::{
|
transaction::{
|
||||||
eip2930::{AccessList, AccessListItem},
|
eip2930::{AccessList, AccessListItem},
|
||||||
|
@ -241,7 +241,6 @@ mod tests {
|
||||||
},
|
},
|
||||||
Address, Eip1559TransactionRequest, TransactionRequest, I256, U256,
|
Address, Eip1559TransactionRequest, TransactionRequest, I256, U256,
|
||||||
};
|
};
|
||||||
use ethers_derive_eip712::*;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eip712, EthAbiType)]
|
#[derive(Debug, Clone, Eip712, EthAbiType)]
|
||||||
|
|
|
@ -38,8 +38,6 @@ celo = [
|
||||||
legacy = ["ethers-core/legacy", "ethers-contract/legacy"]
|
legacy = ["ethers-core/legacy", "ethers-contract/legacy"]
|
||||||
|
|
||||||
# individual features per sub-crate
|
# individual features per sub-crate
|
||||||
## core
|
|
||||||
eip712 = ["ethers-contract/eip712", "ethers-core/eip712"]
|
|
||||||
## providers
|
## providers
|
||||||
ws = ["ethers-providers/ws"]
|
ws = ["ethers-providers/ws"]
|
||||||
ipc = ["ethers-providers/ipc"]
|
ipc = ["ethers-providers/ipc"]
|
||||||
|
|
|
@ -15,7 +15,7 @@ trezor = ["ethers/trezor"]
|
||||||
yubi = ["ethers/yubi"]
|
yubi = ["ethers/yubi"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ethers = { workspace = true, features = ["abigen", "eip712", "ws", "rustls"] }
|
ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
|
||||||
|
|
||||||
tokio = { workspace = true, features = ["macros"] }
|
tokio = { workspace = true, features = ["macros"] }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue