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"
|
||||
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]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
|
@ -1239,7 +1230,6 @@ dependencies = [
|
|||
"ethers-contract-abigen",
|
||||
"ethers-contract-derive",
|
||||
"ethers-core",
|
||||
"ethers-derive-eip712",
|
||||
"ethers-providers",
|
||||
"ethers-signers",
|
||||
"ethers-solc",
|
||||
|
@ -1284,11 +1274,13 @@ dependencies = [
|
|||
name = "ethers-contract-derive"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"ethers-contract-abigen",
|
||||
"ethers-core",
|
||||
"hex",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_json",
|
||||
"syn 2.0.0",
|
||||
]
|
||||
|
||||
|
@ -1301,7 +1293,6 @@ dependencies = [
|
|||
"bytes",
|
||||
"cargo_metadata",
|
||||
"chrono",
|
||||
"convert_case",
|
||||
"elliptic-curve",
|
||||
"ethabi",
|
||||
"generic-array",
|
||||
|
@ -1324,19 +1315,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "ethers-etherscan"
|
||||
version = "2.0.0"
|
||||
|
@ -1435,7 +1413,6 @@ dependencies = [
|
|||
"eth-keystore",
|
||||
"ethers-contract-derive",
|
||||
"ethers-core",
|
||||
"ethers-derive-eip712",
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"hex",
|
||||
|
@ -4484,12 +4461,6 @@ dependencies = [
|
|||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
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-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
|
||||
tokio = "1.26"
|
||||
|
@ -112,6 +111,7 @@ async-trait = "0.1.66"
|
|||
auto_impl = "1.0"
|
||||
|
||||
# misc
|
||||
Inflector = "0.11"
|
||||
thiserror = "1.0"
|
||||
once_cell = "1.17"
|
||||
hex = "0.4"
|
||||
|
|
|
@ -39,10 +39,8 @@ hex.workspace = true
|
|||
ethers-contract-abigen = { workspace = true, optional = true }
|
||||
ethers-contract-derive = { workspace = true, optional = true }
|
||||
|
||||
# eip712
|
||||
ethers-derive-eip712 = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ethers-providers = { workspace = true, features = ["ws"] }
|
||||
ethers-signers.workspace = true
|
||||
ethers-solc.workspace = true
|
||||
|
||||
|
@ -52,8 +50,6 @@ tokio = { workspace = true, features = ["macros"] }
|
|||
[features]
|
||||
default = ["abigen"]
|
||||
|
||||
eip712 = ["ethers-derive-eip712", "ethers-core/eip712"]
|
||||
|
||||
abigen-offline = ["ethers-contract-abigen", "ethers-contract-derive"]
|
||||
abigen = ["abigen-offline", "ethers-contract-abigen/online"]
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ quote.workspace = true
|
|||
syn = { workspace = true, features = ["full"] }
|
||||
prettyplease = "0.1.25"
|
||||
|
||||
Inflector = "0.11"
|
||||
Inflector.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
hex.workspace = true
|
||||
|
|
|
@ -28,4 +28,6 @@ proc-macro2.workspace = true
|
|||
quote.workspace = true
|
||||
syn.workspace = true
|
||||
|
||||
Inflector.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;
|
||||
mod codec;
|
||||
mod display;
|
||||
mod eip712;
|
||||
mod error;
|
||||
mod event;
|
||||
mod spanned;
|
||||
|
@ -357,3 +358,75 @@ pub fn derive_abi_error(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
.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_attr(docsrs, doc(cfg(feature = "abigen")))]
|
||||
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
|
||||
#[doc(hidden)]
|
||||
pub use once_cell::sync::Lazy;
|
||||
|
||||
#[cfg(feature = "eip712")]
|
||||
pub use ethers_derive_eip712::*;
|
||||
|
||||
// For macro expansions only, not public API.
|
||||
// See: [#2235](https://github.com/gakonst/ethers-rs/pull/2235)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::common::*;
|
||||
use ethers_contract::{
|
||||
abigen, ContractFactory, ContractInstance, EthAbiType, EthEvent, LogMeta, Multicall,
|
||||
abigen, ContractFactory, ContractInstance, Eip712, EthAbiType, EthEvent, LogMeta, Multicall,
|
||||
MulticallError, MulticallVersion,
|
||||
};
|
||||
use ethers_core::{
|
||||
|
@ -11,7 +11,7 @@ use ethers_core::{
|
|||
},
|
||||
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 std::{sync::Arc, time::Duration};
|
||||
|
||||
|
@ -342,7 +342,7 @@ async fn watch_events() {
|
|||
let mut stream = event.stream().await.unwrap();
|
||||
|
||||
// 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 event2 = contract2.event::<ValueChanged>();
|
||||
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_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()
|
||||
.address(ValueOrArray::Array(vec![contract_1.address(), contract_2.address()]));
|
||||
let mut stream = ws.subscribe_logs(&filter).await.unwrap();
|
||||
|
@ -786,16 +786,15 @@ async fn multicall_aggregate() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg(feature = "eip712")]
|
||||
async fn test_derive_eip712() {
|
||||
use ethers_derive_eip712::*;
|
||||
|
||||
// Generate Contract ABI Bindings
|
||||
abigen!(
|
||||
mod contract {
|
||||
ethers_contract::abigen!(
|
||||
DeriveEip712Test,
|
||||
"./ethers-contract/tests/solidity-contracts/derive_eip712_abi.json",
|
||||
event_derives(serde::Deserialize, serde::Serialize)
|
||||
derives(serde::Deserialize, serde::Serialize)
|
||||
);
|
||||
}
|
||||
|
||||
// Create derived structs
|
||||
|
||||
|
@ -842,7 +841,7 @@ async fn test_derive_eip712() {
|
|||
|
||||
let addr = contract.address();
|
||||
|
||||
let contract = DeriveEip712Test::new(addr, client.clone());
|
||||
let contract = contract::DeriveEip712Test::new(addr, client.clone());
|
||||
|
||||
let foo_bar = FooBar {
|
||||
foo: I256::from(10u64),
|
||||
|
@ -853,7 +852,7 @@ async fn test_derive_eip712() {
|
|||
out: Address::from([0; 20]),
|
||||
};
|
||||
|
||||
let derived_foo_bar = derive_eip_712_test::FooBar {
|
||||
let derived_foo_bar = contract::FooBar {
|
||||
foo: foo_bar.foo,
|
||||
bar: foo_bar.bar,
|
||||
fizz: foo_bar.fizz.clone(),
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#![allow(clippy::extra_unused_type_parameters)]
|
||||
|
||||
use ethers_contract_derive::EthAbiType;
|
||||
use ethers_contract_derive::{Eip712, EthAbiType};
|
||||
use ethers_core::{
|
||||
types::{
|
||||
transaction::eip712::{
|
||||
|
@ -11,7 +9,6 @@ use ethers_core::{
|
|||
},
|
||||
utils::{keccak256, parse_ether},
|
||||
};
|
||||
use ethers_derive_eip712::*;
|
||||
|
||||
#[test]
|
||||
fn test_derive_eip712() {
|
|
@ -7,6 +7,8 @@ mod derive;
|
|||
|
||||
mod contract_call;
|
||||
|
||||
mod eip712;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
||||
mod common;
|
||||
|
||||
|
|
|
@ -51,9 +51,6 @@ num_enum = "0.5"
|
|||
|
||||
# macros feature enabled dependencies
|
||||
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 }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
@ -72,5 +69,4 @@ rand.workspace = true
|
|||
[features]
|
||||
celo = ["legacy"] # celo support extends the transaction format with extra fields
|
||||
legacy = []
|
||||
eip712 = ["convert_case", "syn"]
|
||||
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,
|
||||
EthersContractDerive,
|
||||
EthersCore,
|
||||
EthersDeriveEip712,
|
||||
EthersEtherscan,
|
||||
EthersMiddleware,
|
||||
EthersProviders,
|
||||
|
@ -250,7 +249,6 @@ impl EthersCrate {
|
|||
Self::EthersContractAbigen => "ethers-contract-abigen",
|
||||
Self::EthersContractDerive => "ethers-contract-derive",
|
||||
Self::EthersCore => "ethers-core",
|
||||
Self::EthersDeriveEip712 => "ethers-derive-eip712",
|
||||
Self::EthersEtherscan => "ethers-etherscan",
|
||||
Self::EthersMiddleware => "ethers-middleware",
|
||||
Self::EthersProviders => "ethers-providers",
|
||||
|
@ -268,7 +266,6 @@ impl EthersCrate {
|
|||
Self::EthersContractAbigen => "::ethers_contract_abigen",
|
||||
Self::EthersContractDerive => "::ethers_contract_derive",
|
||||
Self::EthersCore => "::ethers_core",
|
||||
Self::EthersDeriveEip712 => "::ethers_derive_eip712",
|
||||
Self::EthersEtherscan => "::ethers_etherscan",
|
||||
Self::EthersMiddleware => "::ethers_middleware",
|
||||
Self::EthersProviders => "::ethers_providers",
|
||||
|
@ -284,7 +281,6 @@ impl EthersCrate {
|
|||
// re-exported in ethers::contract
|
||||
Self::EthersContractAbigen => "::ethers::contract", // partially
|
||||
Self::EthersContractDerive => "::ethers::contract",
|
||||
Self::EthersDeriveEip712 => "::ethers::contract",
|
||||
|
||||
Self::EthersAddressbook => "::ethers::addressbook",
|
||||
Self::EthersContract => "::ethers::contract",
|
||||
|
@ -303,7 +299,6 @@ impl EthersCrate {
|
|||
match self {
|
||||
Self::EthersContractAbigen => "ethers-contract/ethers-contract-abigen",
|
||||
Self::EthersContractDerive => "ethers-contract/ethers-contract-derive",
|
||||
Self::EthersDeriveEip712 => "ethers-core/ethers-derive-eip712",
|
||||
_ => self.crate_name(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,19 +4,12 @@ use crate::{
|
|||
types::{serde_helpers::StringifiedNumeric, Address, Bytes, U256},
|
||||
utils::keccak256,
|
||||
};
|
||||
use convert_case::{Case, Casing};
|
||||
use core::convert::TryFrom;
|
||||
use ethabi::encode;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
convert::TryInto,
|
||||
iter::FromIterator,
|
||||
};
|
||||
use syn::{
|
||||
parse::Error, spanned::Spanned, Data, DeriveInput, Expr, Fields, GenericArgument, Lit, LitInt,
|
||||
LitStr, PathArguments, Type,
|
||||
};
|
||||
|
||||
/// Custom types for `TypedData`
|
||||
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,
|
||||
];
|
||||
|
||||
/// Error typed used by Eip712 derive macro
|
||||
/// An EIP-712 error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Eip712Error {
|
||||
#[error("Failed to serialize serde JSON object")]
|
||||
|
@ -56,8 +49,7 @@ pub enum Eip712Error {
|
|||
Message(String),
|
||||
}
|
||||
|
||||
/// The Eip712 trait provides helper methods for computing
|
||||
/// the typed data hash used in `eth_signTypedData`.
|
||||
/// Helper methods for computing the typed data hash used in `eth_signTypedData`.
|
||||
///
|
||||
/// The ethers-rs `derive_eip712` crate provides a derive macro to
|
||||
/// 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.
|
||||
///
|
||||
/// Typed data is a JSON object containing type information, domain separator parameters and the
|
||||
|
@ -641,125 +570,6 @@ pub fn encode_field(
|
|||
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;
|
||||
pub fn make_type_hash(primary_type: String, fields: &[(String, ParamType)]) -> [u8; 32] {
|
||||
let parameters =
|
||||
|
|
|
@ -5,7 +5,6 @@ pub mod eip1559;
|
|||
pub mod eip2718;
|
||||
pub mod eip2930;
|
||||
|
||||
#[cfg(feature = "eip712")]
|
||||
pub mod eip712;
|
||||
|
||||
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.
|
||||
const OVERFLOW_U256_UNITS: usize = 78;
|
||||
|
||||
/// Re-export of serde-json
|
||||
// Re-export serde-json for macro usage
|
||||
#[doc(hidden)]
|
||||
pub mod __serde_json {
|
||||
pub use serde_json::*;
|
||||
}
|
||||
pub use serde_json as __serde_json;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ConversionError {
|
||||
|
|
|
@ -24,7 +24,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||
all-features = true
|
||||
|
||||
[dependencies]
|
||||
ethers-core = { workspace = true, features = ["eip712"] }
|
||||
ethers-core.workspace = true
|
||||
|
||||
# crypto
|
||||
coins-bip32 = "0.8.3"
|
||||
|
@ -66,7 +66,6 @@ yubihsm = { version = "0.42.0-pre.0", features = ["secp256k1", "http", "usb"], o
|
|||
|
||||
[dev-dependencies]
|
||||
ethers-contract-derive.workspace = true
|
||||
ethers-derive-eip712.workspace = true
|
||||
|
||||
serde_json.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
|
|
@ -293,11 +293,10 @@ impl LedgerEthereum {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::Signer;
|
||||
use ethers_contract_derive::EthAbiType;
|
||||
use ethers_contract_derive::{Eip712, EthAbiType};
|
||||
use ethers_core::types::{
|
||||
transaction::eip712::Eip712, Address, TransactionRequest, I256, U256,
|
||||
};
|
||||
use ethers_derive_eip712::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, Eip712, EthAbiType)]
|
||||
|
|
|
@ -233,7 +233,7 @@ impl TrezorEthereum {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::Signer;
|
||||
use ethers_contract_derive::EthAbiType;
|
||||
use ethers_contract_derive::{Eip712, EthAbiType};
|
||||
use ethers_core::types::{
|
||||
transaction::{
|
||||
eip2930::{AccessList, AccessListItem},
|
||||
|
@ -241,7 +241,6 @@ mod tests {
|
|||
},
|
||||
Address, Eip1559TransactionRequest, TransactionRequest, I256, U256,
|
||||
};
|
||||
use ethers_derive_eip712::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, Eip712, EthAbiType)]
|
||||
|
|
|
@ -38,8 +38,6 @@ celo = [
|
|||
legacy = ["ethers-core/legacy", "ethers-contract/legacy"]
|
||||
|
||||
# individual features per sub-crate
|
||||
## core
|
||||
eip712 = ["ethers-contract/eip712", "ethers-core/eip712"]
|
||||
## providers
|
||||
ws = ["ethers-providers/ws"]
|
||||
ipc = ["ethers-providers/ipc"]
|
||||
|
|
|
@ -15,7 +15,7 @@ trezor = ["ethers/trezor"]
|
|||
yubi = ["ethers/yubi"]
|
||||
|
||||
[dev-dependencies]
|
||||
ethers = { workspace = true, features = ["abigen", "eip712", "ws", "rustls"] }
|
||||
ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
|
||||
|
||||
tokio = { workspace = true, features = ["macros"] }
|
||||
|
||||
|
|
Loading…
Reference in New Issue