refactor(abigen): replace ethabi::Reader (#1417)

* feat: add human readable function parser

* clippy fix

* revert bad clippy

* refactor(abigen): replace ethabi::Reader

* fix: add token to tuple token parsing
This commit is contained in:
Matthias Seitz 2022-06-27 02:26:43 +02:00 committed by GitHub
parent 94c7559ab5
commit 659ac061b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 359 additions and 192 deletions

View File

@ -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<String, SolStruct>, 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::<Option<Vec<_>>>()
{
let s = SolStruct { name: ident.to_string(), fields };

View File

@ -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<EthCallAttributes, Token
}
Ok(result)
}
fn parse_function(abi: &str) -> Result<Function, String> {
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))
}

View File

@ -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::<Source>().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::<Source>().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<String>, bool), Error
Ok((topic_name, indexed))
}
fn parse_event(abi: &str) -> Result<Event, String> {
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 {

View File

@ -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<Function, LexerError> {
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<Function, LexerError> {
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<Event, LexerError> {
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? <name>`
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<Option<&'input str>, 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<Visibility> {
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<StateMutability> {
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<DataLocation> {
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<Vec<EventParam>, LexerError> {
/// Takes comma separated values via `f` until the `token` is parsed
fn take_csv_until<T, F>(&mut self, token: Token, f: F) -> Result<Vec<T>, LexerError>
where
F: Fn(&mut Self) -> Result<T, LexerError>,
{
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<Vec<Param>, LexerError> {
self.take_csv_until(Token::CloseParenthesis, |s| s.take_input_param())
}
fn take_input_param(&mut self) -> Result<Param, LexerError> {
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<Vec<EventParam>, LexerError> {
self.take_csv_until(Token::CloseParenthesis, |s| s.take_event_param())
}
fn take_event_param(&mut self) -> Result<EventParam, LexerError> {
let kind = self.take_param()?;
let mut name = "";
@ -651,10 +793,146 @@ fn keyword(id: &str) -> Option<Token> {
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);

View File

@ -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: <https://github.com/gakonst/ethers-rs/issues/474>
fn parse_type(&self, type_str: &str) -> Result<(ParamType, Option<String>)> {
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' | '_')
}

View File

@ -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<FieldType> {
// 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<MappingType> {
.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)

View File

@ -1,5 +1,5 @@
// Taken from <https://github.com/tomusdrw/rust-web3/blob/master/src/types/block.rs>
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<TxHash>` or Block<Transaction>`.
#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)]
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Block<TX> {
/// Hash of the block
pub hash: Option<H256>,
@ -81,7 +81,7 @@ pub struct Block<TX> {
pub mix_hash: Option<H256>,
/// Nonce
#[cfg(not(feature = "celo"))]
pub nonce: Option<H64>,
pub nonce: Option<crate::types::H64>,
/// Base fee per unit of gas (if past London)
#[serde(rename = "baseFeePerGas")]
pub base_fee_per_gas: Option<U256>,

View File

@ -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<T> {
/// Previous value.
pub from: T,
@ -51,7 +51,7 @@ pub struct ChangedType<T> {
}
/// Serde-friendly `Diff` shadow.
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub enum Diff<T> {
/// No change.
#[serde(rename = "=")]
@ -68,7 +68,7 @@ pub enum Diff<T> {
}
/// 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<U256>,
@ -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<H160, AccountDiff>);
// ------------------ Trace -------------