Skip to content

Commit

Permalink
Support abi v2
Browse files Browse the repository at this point in the history
We use a fork of ethabi that has patches applied so that it in turn
supports abi v2. Once the mainline ethabi gets updated we can switch
back to it.

We move tokenization related traits from rust-web3 into ethcontract
because rust the web3 version does not support tokenizing tuples and
because this gives us more control over the traits.

This commit is technically a breaking change because we touch some parts
of the public interface like the error enum but for most users nothing
should change.

Abi v2 functions are tested in the `abi` example. Some events there had
to be changed to give names to the parameters because of yet unfixed bug
in ethabi where it addresses the parameters based on a map of their name
which leads it to use a copy of one of tokens for all of the parameters.
This bug is already present in the current version of ethabi but now
throws errors in the example because of stricter verification of the
size of integer parameters.
  • Loading branch information
vkgnosis committed Mar 4, 2021
1 parent 8214590 commit 404b73b
Show file tree
Hide file tree
Showing 22 changed files with 692 additions and 152 deletions.
2 changes: 1 addition & 1 deletion ethcontract-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Common types for ethcontract-rs runtime and proc macro.
"""

[dependencies]
ethabi = "13.0"
ethabi = { git = "https://github.com/vkgnosis/ethabi.git", rev ="f23954bb5b86687f0d780935b2eacc503393dc44" }
hex = "0.4"
serde = "1.0"
serde_derive = "1.0"
Expand Down
1 change: 1 addition & 0 deletions ethcontract-common/src/truffle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ impl Artifact {
functions: HashMap::new(),
events: HashMap::new(),
fallback: false,
receive: false,
},
bytecode: Default::default(),
networks: HashMap::new(),
Expand Down
2 changes: 2 additions & 0 deletions ethcontract-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ impl Parse for Method {
})
.collect::<ParseResult<Vec<_>>>()?;

#[allow(deprecated)]
Function {
name,
inputs,
Expand All @@ -350,6 +351,7 @@ impl Parse for Method {
// affect its signature.
outputs: vec![],
constant: false,
state_mutability: Default::default(),
}
};
let signature = function.abi_signature();
Expand Down
30 changes: 4 additions & 26 deletions ethcontract-generate/src/contract/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ fn expand_data_type(event: &Event, event_derives: &[Path]) -> Result<TokenStream
let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));

let params = expand_params(event)?;
let param_names = params.iter().map(|param| &param.0);

let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
let (data_type_definition, data_type_construction) = if all_anonymous_fields {
Expand All @@ -68,17 +69,6 @@ fn expand_data_type(event: &Event, event_derives: &[Path]) -> Result<TokenStream
expand_data_struct(&event_name, &params)
};

let params_len = Literal::usize_unsuffixed(params.len());
let read_param_token = params
.iter()
.map(|(name, ty)| {
quote! {
let #name = <#ty as self::ethcontract::web3::contract::tokens::Tokenizable>
::from_token(tokens.next().unwrap())?;
}
})
.collect::<Vec<_>>();

let derives = expand_derives(event_derives);

Ok(quote! {
Expand All @@ -102,23 +92,11 @@ fn expand_data_type(event: &Event, event_derives: &[Path]) -> Result<TokenStream
}
}

impl self::ethcontract::web3::contract::tokens::Detokenize for #event_name {
impl self::ethcontract::tokens::MultiDetokenize for #event_name {
fn from_tokens(
tokens: Vec<self::ethcontract::common::abi::Token>,
) -> Result<Self, self::ethcontract::web3::contract::Error> {
if tokens.len() != #params_len {
return Err(self::ethcontract::web3::contract::Error::InvalidOutputType(format!(
"Expected {} tokens, got {}: {:?}",
#params_len,
tokens.len(),
tokens
)));
}

#[allow(unused_mut)]
let mut tokens = tokens.into_iter();
#( #read_param_token )*

) -> Result<Self, self::ethcontract::tokens::Error> {
let (#(#param_names,)*) = self::ethcontract::tokens::MultiDetokenize::from_tokens(tokens)?;
Ok(#data_type_construction)
}
}
Expand Down
11 changes: 6 additions & 5 deletions ethcontract-generate/src/contract/methods.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::contract::{types, Context};
use crate::util;
use anyhow::{anyhow, Context as _, Result};
use ethcontract_common::abi::{Function, Param};
use ethcontract_common::abi::{Function, Param, StateMutability};
use ethcontract_common::abiext::FunctionExt;
use ethcontract_common::hash::H32;
use inflector::Inflector;
Expand Down Expand Up @@ -104,10 +104,11 @@ fn expand_function(cx: &Context, function: &Function, alias: Option<Ident>) -> R

let input = expand_inputs(&function.inputs)?;
let outputs = expand_fn_outputs(&function.outputs)?;
let (method, result_type_name) = if function.constant {
(quote! { view_method }, quote! { DynViewMethodBuilder })
} else {
(quote! { method }, quote! { DynMethodBuilder })
let (method, result_type_name) = match function.state_mutability {
StateMutability::Pure | StateMutability::View => {
(quote! { view_method }, quote! { DynViewMethodBuilder })
}
_ => (quote! { method }, quote! { DynMethodBuilder }),
};
let result = quote! { self::ethcontract::dyns::#result_type_name<#outputs> };
let arg = expand_inputs_call_arg(&function.inputs);
Expand Down
5 changes: 4 additions & 1 deletion ethcontract-generate/src/contract/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
let size = Literal::usize_unsuffixed(*n);
Ok(quote! { [#inner; #size] })
}
ParamType::Tuple(_) => Err(anyhow!("ABIEncoderV2 is currently not supported")),
ParamType::Tuple(t) => {
let inner = t.iter().map(expand).collect::<Result<Vec<_>>>()?;
Ok(quote! { (#(#inner),*) })
}
}
}
1 change: 1 addition & 0 deletions ethcontract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ ws-tokio = ["web3/ws-tokio"]
ws-tls-tokio = ["web3/ws-tls-tokio"]

[dependencies]
arrayvec = "0.5"
ethcontract-common = { version = "0.11.2", path = "../ethcontract-common" }
ethcontract-derive = { version = "0.11.2", path = "../ethcontract-derive", optional = true}
futures = "0.3"
Expand Down
25 changes: 14 additions & 11 deletions ethcontract/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ mod deploy;
mod event;
mod method;

use crate::errors::{DeployError, LinkError};
use crate::tokens::MultiTokenize;
use crate::{
errors::{DeployError, LinkError},
tokens::MultiDetokenize,
};
use ethcontract_common::abi::{Error as AbiError, Result as AbiResult};
use ethcontract_common::abiext::FunctionExt;
use ethcontract_common::hash::H32;
use ethcontract_common::{Abi, Artifact, Bytecode};
use std::collections::HashMap;
use std::hash::Hash;
use web3::api::Web3;
use web3::contract::tokens::{Detokenize, Tokenize};
use web3::types::{Address, Bytes, H256};
use web3::Transport;

Expand All @@ -23,7 +26,7 @@ pub use self::event::{
AllEventsBuilder, Event, EventBuilder, EventMetadata, EventStatus, ParseLog, RawLog,
StreamEvent, Topic,
};
pub use self::method::{Detokenizable, MethodBuilder, MethodDefaults, ViewMethodBuilder, Void};
pub use self::method::{MethodBuilder, MethodDefaults, Return, ViewMethodBuilder, Void};

/// Represents a contract instance at an address. Provides methods for
/// contract interaction.
Expand Down Expand Up @@ -112,7 +115,7 @@ impl<T: Transport> Instance<T> {
params: P,
) -> Result<DeployBuilder<T, Self>, DeployError>
where
P: Tokenize,
P: MultiTokenize,
{
Linker::new(artifact).deploy(web3, params)
}
Expand All @@ -126,7 +129,7 @@ impl<T: Transport> Instance<T> {
libraries: I,
) -> Result<DeployBuilder<T, Self>, DeployError>
where
P: Tokenize,
P: MultiTokenize,
I: Iterator<Item = (&'a str, Address)>,
{
let mut linker = Linker::new(artifact);
Expand Down Expand Up @@ -163,8 +166,8 @@ impl<T: Transport> Instance<T> {
/// actually commit anything to the block chain.
pub fn method<P, R>(&self, signature: H32, params: P) -> AbiResult<MethodBuilder<T, R>>
where
P: Tokenize,
R: Detokenizable,
P: MultiTokenize,
R: Return,
{
let signature = signature.as_ref();
let function = self
Expand All @@ -191,8 +194,8 @@ impl<T: Transport> Instance<T> {
/// state.
pub fn view_method<P, R>(&self, signature: H32, params: P) -> AbiResult<ViewMethodBuilder<T, R>>
where
P: Tokenize,
R: Detokenizable,
P: MultiTokenize,
R: Return,
{
Ok(self.method(signature, params)?.view())
}
Expand Down Expand Up @@ -221,7 +224,7 @@ impl<T: Transport> Instance<T> {
/// that emits events for the specified Solidity event by name.
pub fn event<E>(&self, signature: H256) -> AbiResult<EventBuilder<T, E>>
where
E: Detokenize,
E: MultiDetokenize,
{
let event = self
.events
Expand Down Expand Up @@ -286,7 +289,7 @@ impl Linker {
) -> Result<DeployBuilder<T, Instance<T>>, DeployError>
where
T: Transport,
P: Tokenize,
P: MultiTokenize,
{
DeployBuilder::new(web3, self, params)
}
Expand Down
4 changes: 2 additions & 2 deletions ethcontract/src/contract/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
//! new contracts.
use crate::errors::{DeployError, ExecutionError};
use crate::tokens::MultiTokenize;
use crate::transaction::{Account, GasPrice, TransactionBuilder, TransactionResult};
use ethcontract_common::abi::Error as AbiError;
use ethcontract_common::{Abi, Bytecode};
use std::marker::PhantomData;
use web3::api::Web3;
use web3::contract::tokens::Tokenize;
use web3::types::{Address, Bytes, H256, U256};
use web3::Transport;

Expand Down Expand Up @@ -62,7 +62,7 @@ where
/// deployment (constructor) parameters.
pub fn new<P>(web3: Web3<T>, context: I::Context, params: P) -> Result<Self, DeployError>
where
P: Tokenize,
P: MultiTokenize,
{
// NOTE(nlordell): unfortunately here we have to re-implement some
// `rust-web3` code so that we can add things like signing support;
Expand Down
14 changes: 7 additions & 7 deletions ethcontract/src/contract/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod data;
pub use self::data::{Event, EventMetadata, EventStatus, ParseLog, RawLog, StreamEvent};
use crate::errors::{EventError, ExecutionError};
use crate::log::LogFilterBuilder;
use crate::tokens::{MultiDetokenize, SingleTokenize};
pub use ethcontract_common::abi::Topic;
use ethcontract_common::abi::{Event as AbiEvent, RawTopicFilter, Token};
use futures::future::{self, TryFutureExt as _};
Expand All @@ -14,14 +15,13 @@ use std::cmp;
use std::marker::PhantomData;
use std::time::Duration;
use web3::api::Web3;
use web3::contract::tokens::{Detokenize, Tokenizable};
use web3::types::{Address, BlockNumber, H256};
use web3::Transport;

/// A builder for creating a filtered stream of contract events that are
#[derive(Debug)]
#[must_use = "event builders do nothing unless you stream them"]
pub struct EventBuilder<T: Transport, E: Detokenize> {
pub struct EventBuilder<T: Transport, E: MultiDetokenize> {
/// The underlying web3 instance.
web3: Web3<T>,
/// The event ABI data for encoding topic filters and decoding logs.
Expand All @@ -33,7 +33,7 @@ pub struct EventBuilder<T: Transport, E: Detokenize> {
_event: PhantomData<E>,
}

impl<T: Transport, E: Detokenize> EventBuilder<T, E> {
impl<T: Transport, E: MultiDetokenize> EventBuilder<T, E> {
/// Creates a new event builder from a web3 provider and a contract event
/// and address.
pub fn new(web3: Web3<T>, event: AbiEvent, address: Address) -> Self {
Expand Down Expand Up @@ -71,7 +71,7 @@ impl<T: Transport, E: Detokenize> EventBuilder<T, E> {
/// actually `topic[1]`.
pub fn topic0<P>(mut self, topic: Topic<P>) -> Self
where
P: Tokenizable,
P: SingleTokenize,
{
self.topics.topic0 = tokenize_topic(topic);
self
Expand All @@ -80,7 +80,7 @@ impl<T: Transport, E: Detokenize> EventBuilder<T, E> {
/// Adds a filter for the second indexed topic.
pub fn topic1<P>(mut self, topic: Topic<P>) -> Self
where
P: Tokenizable,
P: SingleTokenize,
{
self.topics.topic1 = tokenize_topic(topic);
self
Expand All @@ -89,7 +89,7 @@ impl<T: Transport, E: Detokenize> EventBuilder<T, E> {
/// Adds a filter for the third indexed topic.
pub fn topic2<P>(mut self, topic: Topic<P>) -> Self
where
P: Tokenizable,
P: SingleTokenize,
{
self.topics.topic2 = tokenize_topic(topic);
self
Expand Down Expand Up @@ -158,7 +158,7 @@ impl<T: Transport, E: Detokenize> EventBuilder<T, E> {
/// Converts a tokenizable topic into a raw topic for filtering.
fn tokenize_topic<P>(topic: Topic<P>) -> Topic<Token>
where
P: Tokenizable,
P: SingleTokenize,
{
topic.map(|parameter| parameter.into_token())
}
Expand Down
6 changes: 2 additions & 4 deletions ethcontract/src/contract/event/data.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! Module contains code for parsing and manipulating event data.
use crate::errors::ExecutionError;
use crate::{errors::ExecutionError, tokens::MultiDetokenize};
use ethcontract_common::abi::{Event as AbiEvent, RawLog as AbiRawLog};
use web3::contract::tokens::Detokenize;
use web3::types::{Log, H256};

/// A contract event
Expand Down Expand Up @@ -172,7 +170,7 @@ impl RawLog {
/// Decode raw log data into a tokenizable for a matching event ABI entry.
pub fn decode<D>(self, event: &AbiEvent) -> Result<D, ExecutionError>
where
D: Detokenize,
D: MultiDetokenize,
{
let event_log = event.parse_log(AbiRawLog {
topics: self.topics,
Expand Down
Loading

0 comments on commit 404b73b

Please sign in to comment.