diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs index bb08335f..c1a51ded 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs @@ -1,25 +1,21 @@ //! Methods for expanding structs -use std::collections::{HashMap, VecDeque}; - -use eyre::{eyre, Result}; -use inflector::Inflector; -use proc_macro2::TokenStream; -use quote::quote; - -use ethers_core::{ - abi::{ - param_type::Reader, - struct_def::{FieldDeclaration, FieldType, StructFieldType, StructType}, - ParamType, SolStruct, - }, - macros::ethers_contract_crate, -}; - use crate::{ contract::{types, Context}, rawabi::{Component, RawAbi}, util, }; +use ethers_core::{ + abi::{ + struct_def::{FieldDeclaration, FieldType, StructFieldType, StructType}, + HumanReadableParser, ParamType, SolStruct, + }, + macros::ethers_contract_crate, +}; +use eyre::{eyre, Result}; +use inflector::Inflector; +use proc_macro2::TokenStream; +use quote::quote; +use std::collections::{HashMap, VecDeque}; impl Context { /// Generate corresponding types for structs parsed from a human readable ABI @@ -442,7 +438,11 @@ fn insert_structs(structs: &mut HashMap, tuple: &Component) { if let Some(fields) = tuple .components .iter() - .map(|f| Reader::read(&f.type_field).ok().and_then(|kind| field(structs, f, kind))) + .map(|f| { + HumanReadableParser::parse_type(&f.type_field) + .ok() + .and_then(|kind| field(structs, f, kind)) + }) .collect::>>() { let s = SolStruct { name: ident.to_string(), fields }; diff --git a/ethers-contract/ethers-contract-derive/src/call.rs b/ethers-contract/ethers-contract-derive/src/call.rs index 15734a03..15f901a0 100644 --- a/ethers-contract/ethers-contract-derive/src/call.rs +++ b/ethers-contract/ethers-contract-derive/src/call.rs @@ -1,16 +1,14 @@ //! Helper functions for deriving `EthCall` +use crate::{abi_ty, utils}; +use ethers_core::{ + abi::{Function, FunctionExt, HumanReadableParser}, + macros::{ethers_contract_crate, ethers_core_crate}, +}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{parse::Error, spanned::Spanned as _, AttrStyle, DeriveInput, Lit, Meta, NestedMeta}; -use ethers_core::{ - abi::{param_type::Reader, AbiParser, Function, FunctionExt, Param, ParamType}, - macros::{ethers_contract_crate, ethers_core_crate}, -}; - -use crate::{abi_ty, utils}; - /// Generates the `ethcall` trait support pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream { let attributes = match parse_call_attributes(&input) { @@ -22,49 +20,21 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream { attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string()); let mut function = if let Some((src, span)) = attributes.abi { + let raw_function_sig = src.trim_start_matches("function ").trim_start(); // try to parse as solidity function - if let Ok(fun) = parse_function(&src) { + if let Ok(fun) = HumanReadableParser::parse_function(&src) { fun } else { - // try as tuple - let raw_function_signature = src.trim_start_matches("function ").trim_start(); - if let Some(inputs) = - Reader::read(raw_function_signature.trim_start_matches(&function_call_name)) - .ok() - .and_then(|param| match param { - ParamType::Tuple(params) => Some( - params - .into_iter() - .map(|kind| Param { - name: "".to_string(), - kind, - internal_type: None, - }) - .collect(), - ), - _ => None, - }) - { - #[allow(deprecated)] - Function { - name: function_call_name.clone(), - inputs, - outputs: vec![], - constant: None, - state_mutability: Default::default(), - } - } else { - // try to determine the abi by using its fields at runtime - return match derive_trait_impls_with_abi_type( - &input, - &function_call_name, - Some(raw_function_signature), - ) { - Ok(derived) => derived, - Err(err) => { - Error::new(span, format!("Unable to determine ABI for `{}` : {}", src, err)) - .to_compile_error() - } + // try to determine the abi by using its fields at runtime + return match derive_trait_impls_with_abi_type( + &input, + &function_call_name, + Some(raw_function_sig), + ) { + Ok(derived) => derived, + Err(err) => { + Error::new(span, format!("Unable to determine ABI for `{}` : {}", src, err)) + .to_compile_error() } } } @@ -287,15 +257,3 @@ fn parse_call_attributes(input: &DeriveInput) -> Result Result { - let abi = if !abi.trim_start().starts_with("function ") { - format!("function {}", abi) - } else { - abi.to_string() - }; - - AbiParser::default() - .parse_function(&abi) - .map_err(|err| format!("Failed to parse the function ABI: {:?}", err)) -} diff --git a/ethers-contract/ethers-contract-derive/src/event.rs b/ethers-contract/ethers-contract-derive/src/event.rs index e391078c..05d02f4f 100644 --- a/ethers-contract/ethers-contract-derive/src/event.rs +++ b/ethers-contract/ethers-contract-derive/src/event.rs @@ -9,7 +9,7 @@ use syn::{ }; use ethers_core::{ - abi::{param_type::Reader, AbiParser, Event, EventExt, EventParam, ParamType}, + abi::{Event, EventExt, EventParam, HumanReadableParser}, macros::{ethers_contract_crate, ethers_core_crate}, }; use hex::FromHex; @@ -32,41 +32,24 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> TokenStream { let mut event = if let Some((src, span)) = attributes.abi { // try to parse as solidity event - if let Ok(event) = parse_event(&src) { + if let Ok(event) = HumanReadableParser::parse_event(&src) { event } else { - // try as tuple - if let Some(inputs) = Reader::read( - src.trim_start_matches("event ").trim_start().trim_start_matches(&event_name), - ) - .ok() - .and_then(|param| match param { - ParamType::Tuple(params) => Some( - params - .into_iter() - .map(|kind| EventParam { name: "".to_string(), indexed: false, kind }) - .collect(), - ), - _ => None, - }) { - Event { name: event_name.clone(), inputs, anonymous: false } - } else { - match src.parse::().and_then(|s| s.get()) { - Ok(abi) => { - // try to derive the signature from the abi from the parsed abi - // TODO(mattsse): this will fail for events that contain other non - // elementary types in their abi because the parser - // doesn't know how to substitute the types - // this could be mitigated by getting the ABI of each non elementary type - // at runtime and computing the the signature as - // `static Lazy::...` - match parse_event(&abi) { - Ok(event) => event, - Err(err) => return Error::new(span, err).to_compile_error(), - } + match src.parse::().and_then(|s| s.get()) { + Ok(abi) => { + // try to derive the signature from the abi from the parsed abi + // TODO(mattsse): this will fail for events that contain other non + // elementary types in their abi because the parser + // doesn't know how to substitute the types + // this could be mitigated by getting the ABI of each non elementary type + // at runtime and computing the the signature as + // `static Lazy::...` + match HumanReadableParser::parse_event(&abi) { + Ok(event) => event, + Err(err) => return Error::new(span, err).to_compile_error(), } - Err(err) => return Error::new(span, err).to_compile_error(), } + Err(err) => return Error::new(span, err).to_compile_error(), } } } else { @@ -362,17 +345,6 @@ fn parse_field_attributes(field: &Field) -> Result<(Option, bool), Error Ok((topic_name, indexed)) } -fn parse_event(abi: &str) -> Result { - let abi = if !abi.trim_start().starts_with("event ") { - format!("event {}", abi) - } else { - abi.to_string() - }; - AbiParser::default() - .parse_event(&abi) - .map_err(|err| format!("Failed to parse the event ABI: {:?}", err)) -} - /// All the attributes the `EthEvent` macro supports #[derive(Default)] struct EthEventAttributes { diff --git a/ethers-core/src/abi/human_readable/lexer.rs b/ethers-core/src/abi/human_readable/lexer.rs index 216f369d..5e807fb9 100644 --- a/ethers-core/src/abi/human_readable/lexer.rs +++ b/ethers-core/src/abi/human_readable/lexer.rs @@ -1,4 +1,4 @@ -use ethabi::{Event, EventParam, ParamType}; +use ethabi::{Event, EventParam, Function, Param, ParamType, StateMutability}; use std::{fmt, iter::Peekable, str::CharIndices}; use unicode_xid::UnicodeXID; @@ -81,6 +81,7 @@ impl<'input> Token<'input> { Token::Bool => ParamType::Bool, Token::Address => ParamType::Address, Token::String => ParamType::String, + Token::Tuple => ParamType::Tuple(vec![]), _ => return None, }; @@ -307,6 +308,18 @@ impl<'input> HumanReadableParser<'input> { Self::new(input).take_param() } + /// Parses a [Function] from a human readable form + /// + /// # Example + /// + /// ``` + /// use ethers_core::abi::HumanReadableParser; + /// let mut fun = HumanReadableParser::parse_function("function get(address author, string oldValue, string newValue)").unwrap(); + /// ``` + pub fn parse_function(input: &'input str) -> Result { + Self::new(input).take_function() + } + /// Parses an [Event] from a human readable form /// /// # Example @@ -319,10 +332,58 @@ impl<'input> HumanReadableParser<'input> { Self::new(input).take_event() } + pub fn take_function(&mut self) -> Result { + let name = self.take_identifier(Token::Function)?; + + self.take_open_parenthesis()?; + let inputs = self.take_function_params()?; + self.take_close_parenthesis()?; + + let mut state_mutability = Default::default(); + let mut outputs = vec![]; + if self.peek().is_some() { + let _visibility = self.take_visibility(); + if let Some(mutability) = self.take_state_mutability() { + state_mutability = mutability; + } + if self.peek_next(Token::Virtual) { + self.next(); + } + if self.peek_next(Token::Override) { + self.next(); + } + if self.peek_next(Token::Returns) { + self.next(); + } + + if self.peek_next(Token::OpenParenthesis) { + self.take_open_parenthesis()?; + outputs = self.take_function_params()?; + self.take_close_parenthesis()?; + } + } + + Ok( + #[allow(deprecated)] + Function { name: name.to_string(), inputs, outputs, constant: None, state_mutability }, + ) + } + pub fn take_event(&mut self) -> Result { + let name = self.take_identifier(Token::Event)?; + self.take_open_parenthesis()?; + let inputs = self.take_event_params()?; + self.take_close_parenthesis()?; + let event = Event { name: name.to_string(), inputs, anonymous: self.take_anonymous() }; + + Ok(event) + } + + /// Returns an identifier, optionally prefixed with a token like `function? ` + fn take_identifier(&mut self, prefixed: Token) -> Result<&'input str, LexerError> { let (l, token, r) = self.next_spanned()?; let name = match token { - Token::Event => { + i if i == prefixed => { let (_, next, _) = self.lexer.peek().cloned().ok_or(LexerError::EndOfFile)??; if let Token::Identifier(name) = next { self.next(); @@ -334,13 +395,74 @@ impl<'input> HumanReadableParser<'input> { Token::Identifier(name) => name, t => unrecognised!(l, r, t.to_string()), }; + Ok(name) + } - self.take_open_parenthesis()?; - let inputs = self.take_event_params()?; - self.take_close_parenthesis()?; - let event = Event { name: name.to_string(), inputs, anonymous: self.take_anonymous() }; + fn take_name_opt(&mut self) -> Result, LexerError> { + if let (_, Token::Identifier(name), _) = self.peek_some()? { + self.next(); + Ok(Some(name)) + } else { + Ok(None) + } + } - Ok(event) + fn take_visibility(&mut self) -> Option { + match self.lexer.peek() { + Some(Ok((_, Token::Internal, _))) => { + self.next(); + Some(Visibility::Internal) + } + Some(Ok((_, Token::External, _))) => { + self.next(); + Some(Visibility::External) + } + Some(Ok((_, Token::Private, _))) => { + self.next(); + Some(Visibility::Private) + } + Some(Ok((_, Token::Public, _))) => { + self.next(); + Some(Visibility::Public) + } + _ => None, + } + } + + fn take_state_mutability(&mut self) -> Option { + match self.lexer.peek() { + Some(Ok((_, Token::View, _))) => { + self.next(); + Some(StateMutability::View) + } + Some(Ok((_, Token::Pure, _))) => { + self.next(); + Some(StateMutability::Pure) + } + Some(Ok((_, Token::Payable, _))) => { + self.next(); + Some(StateMutability::Payable) + } + _ => None, + } + } + + fn take_data_location(&mut self) -> Option { + match self.lexer.peek() { + Some(Ok((_, Token::Memory, _))) => { + self.next(); + Some(DataLocation::Memory) + } + Some(Ok((_, Token::Storage, _))) => { + self.next(); + Some(DataLocation::Storage) + } + Some(Ok((_, Token::Calldata, _))) => { + self.next(); + Some(DataLocation::Calldata) + } + _ => None, + } } fn take_anonymous(&mut self) -> bool { @@ -352,16 +474,19 @@ impl<'input> HumanReadableParser<'input> { } } - /// Parses all event params - fn take_event_params(&mut self) -> Result, LexerError> { + /// Takes comma separated values via `f` until the `token` is parsed + fn take_csv_until(&mut self, token: Token, f: F) -> Result, LexerError> + where + F: Fn(&mut Self) -> Result, + { let mut params = Vec::new(); - if self.peek_next(Token::CloseParenthesis) { + if self.peek_next(token) { return Ok(params) } loop { - params.push(self.take_event_param()?); + params.push(f(self)?); let (l, next, r) = match self.peek() { Some(next) => next?, @@ -369,16 +494,33 @@ impl<'input> HumanReadableParser<'input> { }; match next { + i if i == token => break, Token::Comma => { self.next_spanned()?; } - Token::CloseParenthesis => break, t => unrecognised!(l, r, t.to_string()), } } Ok(params) } + /// Parses all function input params + fn take_function_params(&mut self) -> Result, LexerError> { + self.take_csv_until(Token::CloseParenthesis, |s| s.take_input_param()) + } + + fn take_input_param(&mut self) -> Result { + let kind = self.take_param()?; + let _location = self.take_data_location(); + let name = self.take_name_opt()?.unwrap_or(""); + Ok(Param { name: name.to_string(), kind, internal_type: None }) + } + + /// Parses all event params + fn take_event_params(&mut self) -> Result, LexerError> { + self.take_csv_until(Token::CloseParenthesis, |s| s.take_event_param()) + } + fn take_event_param(&mut self) -> Result { let kind = self.take_param()?; let mut name = ""; @@ -651,10 +793,146 @@ fn keyword(id: &str) -> Option { Some(token) } +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Visibility { + Internal, + External, + Private, + Public, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum DataLocation { + Memory, + Storage, + Calldata, +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn test_parse_function() { + #[allow(deprecated)] + let f = Function { + name: "get".to_string(), + inputs: vec![ + Param { name: "author".to_string(), kind: ParamType::Address, internal_type: None }, + Param { + name: "oldValue".to_string(), + kind: ParamType::String, + internal_type: None, + }, + Param { + name: "newValue".to_string(), + kind: ParamType::String, + internal_type: None, + }, + ], + outputs: vec![], + constant: None, + state_mutability: Default::default(), + }; + let parsed = HumanReadableParser::parse_function( + "function get(address author, string oldValue, string newValue)", + ) + .unwrap(); + assert_eq!(f, parsed); + + let parsed = HumanReadableParser::parse_function( + "get(address author, string oldValue, string newValue)", + ) + .unwrap(); + assert_eq!(f, parsed); + + #[allow(deprecated)] + let f = Function { + name: "get".to_string(), + inputs: vec![ + Param { name: "".to_string(), kind: ParamType::Address, internal_type: None }, + Param { name: "".to_string(), kind: ParamType::String, internal_type: None }, + Param { name: "".to_string(), kind: ParamType::String, internal_type: None }, + ], + outputs: vec![], + constant: None, + state_mutability: Default::default(), + }; + + let parsed = + HumanReadableParser::parse_function("get(address , string , string )").unwrap(); + assert_eq!(f, parsed); + } + + #[test] + fn test_parse_function_output() { + #[allow(deprecated)] + let f = Function { + name: "get".to_string(), + inputs: vec![ + Param { name: "author".to_string(), kind: ParamType::Address, internal_type: None }, + Param { + name: "oldValue".to_string(), + kind: ParamType::String, + internal_type: None, + }, + Param { + name: "newValue".to_string(), + kind: ParamType::String, + internal_type: None, + }, + ], + outputs: vec![ + Param { + name: "result".to_string(), + kind: ParamType::Uint(256), + internal_type: None, + }, + Param { name: "output".to_string(), kind: ParamType::Address, internal_type: None }, + ], + constant: None, + state_mutability: Default::default(), + }; + let parsed = HumanReadableParser::parse_function( + "function get(address author, string oldValue, string newValue) returns (uint256 result, address output)", + ) + .unwrap(); + assert_eq!(f, parsed); + + let parsed = HumanReadableParser::parse_function( + " get(address author, string oldValue, string newValue) returns (uint256 result, address output)", + ) + .unwrap(); + assert_eq!(f, parsed); + #[allow(deprecated)] + let mut f = Function { + name: "get".to_string(), + inputs: vec![ + Param { name: "".to_string(), kind: ParamType::Address, internal_type: None }, + Param { name: "".to_string(), kind: ParamType::String, internal_type: None }, + Param { name: "".to_string(), kind: ParamType::String, internal_type: None }, + ], + outputs: vec![ + Param { name: "".to_string(), kind: ParamType::Uint(256), internal_type: None }, + Param { name: "".to_string(), kind: ParamType::Address, internal_type: None }, + ], + constant: None, + state_mutability: Default::default(), + }; + let parsed = HumanReadableParser::parse_function( + "function get(address, string, string) (uint256, address)", + ) + .unwrap(); + assert_eq!(f, parsed); + + f.state_mutability = StateMutability::View; + let parsed = HumanReadableParser::parse_function( + "function get(address, string memory, string calldata) public view (uint256, address)", + ) + .unwrap(); + assert_eq!(f, parsed); + } + #[test] fn test_parse_param() { assert_eq!(HumanReadableParser::parse_type("address").unwrap(), ParamType::Address); diff --git a/ethers-core/src/abi/human_readable/mod.rs b/ethers-core/src/abi/human_readable/mod.rs index 6d97e730..763798d7 100644 --- a/ethers-core/src/abi/human_readable/mod.rs +++ b/ethers-core/src/abi/human_readable/mod.rs @@ -2,9 +2,9 @@ use std::collections::{BTreeMap, HashMap, VecDeque}; use crate::abi::{ error::{bail, format_err, ParseError, Result}, - param_type::Reader, struct_def::{FieldType, StructFieldType}, - Abi, Constructor, Event, EventParam, Function, Param, ParamType, SolStruct, StateMutability, + Abi, Constructor, Event, EventParam, Function, HumanReadableParser, Param, ParamType, + SolStruct, StateMutability, }; pub mod lexer; @@ -372,14 +372,8 @@ impl AbiParser { /// contains a `uint8`. This however can still lead to false detection of `uint8` and is only /// solvable with a more sophisticated parser: fn parse_type(&self, type_str: &str) -> Result<(ParamType, Option)> { - if let Ok(kind) = Reader::read(type_str) { - if is_likely_tuple_not_uint8(&kind, type_str) { - // if we detected an `ParamType::Uint(8)` but the input string does not include a - // `uint8` then it's highly likely that we try parsing a struct instead - self.parse_struct_type(type_str) - } else { - Ok((kind, None)) - } + if let Ok(kind) = HumanReadableParser::parse_type(type_str) { + Ok((kind, None)) } else { // try struct instead self.parse_struct_type(type_str) @@ -516,30 +510,6 @@ fn detect_state_mutability(s: &str) -> StateMutability { } } -/// Checks if the input `ParamType` contains a `uint8` that the `type_str` also contains `uint8` -/// -/// Returns `true` if `kind` contains `uint8` but the type_str doesnt -/// -/// See `AbiParser::parse_type` -pub(crate) fn is_likely_tuple_not_uint8(kind: &ParamType, type_str: &str) -> bool { - if contains_uint8(kind) { - !type_str.contains("uint8") - } else { - false - } -} - -/// Returns true if the `ParamType` contains an `uint8` -pub fn contains_uint8(kind: &ParamType) -> bool { - match kind { - ParamType::Uint(8) => true, - ParamType::Array(kind) => contains_uint8(kind), - ParamType::FixedArray(kind, _) => contains_uint8(kind), - ParamType::Tuple(tuple) => tuple.iter().any(contains_uint8), - _ => false, - } -} - pub(crate) fn is_first_ident_char(c: char) -> bool { matches!(c, 'a'..='z' | 'A'..='Z' | '_') } diff --git a/ethers-core/src/abi/struct_def.rs b/ethers-core/src/abi/struct_def.rs index b4bf661d..3a77bb7e 100644 --- a/ethers-core/src/abi/struct_def.rs +++ b/ethers-core/src/abi/struct_def.rs @@ -1,9 +1,8 @@ //! Solidity struct definition parsing support use crate::abi::{ error::{bail, format_err, Result}, - human_readable::{is_likely_tuple_not_uint8, is_whitespace, parse_identifier}, - param_type::Reader, - ParamType, + human_readable::{is_whitespace, parse_identifier}, + HumanReadableParser, ParamType, }; /// A field declaration inside a struct @@ -337,14 +336,8 @@ fn parse_field_type(s: &str) -> Result { // special case for `address payable` input = input[..input.len() - 7].trim_end(); } - if let Ok(ty) = Reader::read(input) { - // See `AbiParser::parse_type` - if is_likely_tuple_not_uint8(&ty, s) { - // likely that an unknown type was resolved as `uint8` - StructFieldType::parse(input.trim_end()) - } else { - Ok(FieldType::Elementary(ty)) - } + if let Ok(ty) = HumanReadableParser::parse_type(input) { + Ok(FieldType::Elementary(ty)) } else { // parsing elementary datatype failed, try struct StructFieldType::parse(input.trim_end()) @@ -363,16 +356,12 @@ fn parse_mapping(s: &str) -> Result { .next() .ok_or_else(|| format_err!("Expected mapping key type at `{}`", input)) .map(str::trim) - .map(Reader::read)??; + .map(HumanReadableParser::parse_type)??; - let is_illegal_ty = if let ParamType::Array(_) | - ParamType::FixedArray(_, _) | - ParamType::Tuple(_) = &key_type - { - true - } else { - is_likely_tuple_not_uint8(&key_type, s) - }; + let is_illegal_ty = matches!( + &key_type, + ParamType::Array(_) | ParamType::FixedArray(_, _) | ParamType::Tuple(_) + ); if is_illegal_ty { bail!("Expected elementary mapping key type at `{}` got {:?}", input, key_type) diff --git a/ethers-core/src/types/block.rs b/ethers-core/src/types/block.rs index 8a977a1c..e2bfb668 100644 --- a/ethers-core/src/types/block.rs +++ b/ethers-core/src/types/block.rs @@ -1,5 +1,5 @@ // Taken from -use crate::types::{Address, Bloom, Bytes, Transaction, TxHash, H256, H64, U256, U64}; +use crate::types::{Address, Bloom, Bytes, Transaction, TxHash, H256, U256, U64}; use chrono::{DateTime, TimeZone, Utc}; #[cfg(not(feature = "celo"))] use core::cmp::Ordering; @@ -15,7 +15,7 @@ use thiserror::Error; /// The block type returned from RPC calls. /// This is generic over a `TX` type which will be either the hash or the full transaction, /// i.e. `Block` or Block`. -#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct Block { /// Hash of the block pub hash: Option, @@ -81,7 +81,7 @@ pub struct Block { pub mix_hash: Option, /// Nonce #[cfg(not(feature = "celo"))] - pub nonce: Option, + pub nonce: Option, /// Base fee per unit of gas (if past London) #[serde(rename = "baseFeePerGas")] pub base_fee_per_gas: Option, diff --git a/ethers-core/src/types/trace/mod.rs b/ethers-core/src/types/trace/mod.rs index 26736ce8..7a77e86e 100644 --- a/ethers-core/src/types/trace/mod.rs +++ b/ethers-core/src/types/trace/mod.rs @@ -42,7 +42,7 @@ pub struct BlockTrace { //---------------- State Diff ---------------- /// Aux type for Diff::Changed. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub struct ChangedType { /// Previous value. pub from: T, @@ -51,7 +51,7 @@ pub struct ChangedType { } /// Serde-friendly `Diff` shadow. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub enum Diff { /// No change. #[serde(rename = "=")] @@ -68,7 +68,7 @@ pub enum Diff { } /// Serde-friendly `AccountDiff` shadow. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub struct AccountDiff { /// Account balance. pub balance: Diff, @@ -81,7 +81,7 @@ pub struct AccountDiff { } /// Serde-friendly `StateDiff` shadow. -#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub struct StateDiff(pub BTreeMap); // ------------------ Trace -------------