Skip to content

Commit

Permalink
Abiv2 (#468)
Browse files Browse the repository at this point in the history
Edit: Ready for review, scroll down for update.

Nick pointed out that there is an ethabi fork that already supports abiv2. This PR is using this.

Where I am currently stuck is on the tokenization for web3. Web3 has several related traits https://docs.rs/web3/0.15.0/web3/contract/tokens/index.html . The trait that is usually accepted and used by ethcontract is `Tokenize`.
In my solidity example we have
```solidity
struct S {
  uint8 u0;
  uint16 u1;
}

function takeStruct(S calldata s) public view {}
```
In the abi this becomes `Tuple(uint8, uint16)` which gets turned by ethcontract in this PR into `s : (u8, u16)`.

Web3 has one trait `Tokenizable` for things that can get turned into a single `Token` and another trait `Tokenize` that turns things into `Vec<Token>`. The latter is implemented probably for convenience  so that users of web3 can pass tuples of different tokenziable things to it. This has now become problematic because it prevents tuples of Tokenizable to themself implement Tokenizable (by turning into Token::Tuple) because of ambiguous implementations. Imo the correct way to fix this is to get rid of the Tokenize trait so that we can have the mentioned tuple impl. This involves getting this merged in web3 first.
I am not sure if there is a way to achieve the same thing without touching web3. The only alternative I see is to duplicate Tokenize into ethcontract and implemented it correctly for tuples and then pass our Tokenize around instead of web3's.
  • Loading branch information
vkgnosis authored Mar 24, 2021
1 parent fc0d679 commit 39d40e0
Show file tree
Hide file tree
Showing 23 changed files with 605 additions and 189 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-fork-ethcontract = "13.0"
hex = "0.4"
serde = "1.0"
serde_derive = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion ethcontract-common/src/abiext.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! This module implements extensions to the `ethabi` API.
use crate::abi::{Event, Function, ParamType};
use crate::errors::ParseParamTypeError;
use crate::hash::{self, H32};
use ethabi::{Event, Function, ParamType};
use serde_json::json;

/// Extension trait for `ethabi::Function`.
Expand Down
2 changes: 1 addition & 1 deletion ethcontract-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub mod truffle;
pub use crate::abiext::FunctionExt;
pub use crate::bytecode::Bytecode;
pub use crate::truffle::Artifact;
pub use ethabi::{self as abi, Contract as Abi};
pub use ethabi_fork_ethcontract::{self as abi, Contract as Abi};
use serde::Deserialize;
pub use web3::types::Address;
pub use web3::types::H256 as TransactionHash;
Expand Down
3 changes: 2 additions & 1 deletion ethcontract-common/src/truffle.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Module for reading and examining data produced by truffle.
use crate::abi::Contract as Abi;
use crate::errors::ArtifactError;
use crate::{bytecode::Bytecode, DeploymentInformation};
use ethabi::Contract as Abi;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs::File;
Expand Down 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
38 changes: 10 additions & 28 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,25 +92,17 @@ fn expand_data_type(event: &Event, event_derives: &[Path]) -> Result<TokenStream
}
}

impl self::ethcontract::web3::contract::tokens::Detokenize 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 )*

impl self::ethcontract::tokens::Tokenize for #event_name {
fn from_token(
token: self::ethcontract::common::abi::Token,
) -> Result<Self, self::ethcontract::tokens::Error> {
let (#(#param_names,)*) = self::ethcontract::tokens::Tokenize::from_token(token)?;
Ok(#data_type_construction)
}

fn into_token(self) -> self::ethcontract::common::abi::Token {
unimplemented!("events are only decoded, not encoded")
}
}
})
}
Expand Down
21 changes: 9 additions & 12 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 Expand Up @@ -144,7 +145,7 @@ pub(crate) fn expand_inputs_call_arg(inputs: &[Param]) -> TokenStream {

fn expand_fn_outputs(outputs: &[Param]) -> Result<TokenStream> {
match outputs.len() {
0 => Ok(quote! { self::ethcontract::Void }),
0 => Ok(quote! { () }),
1 => types::expand(&outputs[0].kind),
_ => {
let types = outputs
Expand All @@ -169,9 +170,7 @@ fn expand_fallback(cx: &Context) -> TokenStream {
impl Contract {
/// Returns a method builder to setup a call to a smart
/// contract's fallback function.
pub fn fallback<D>(&self, data: D) -> self::ethcontract::dyns::DynMethodBuilder<
self::ethcontract::Void,
>
pub fn fallback<D>(&self, data: D) -> self::ethcontract::dyns::DynMethodBuilder<()>
where
D: Into<Vec<u8>>,
{
Expand Down Expand Up @@ -218,9 +217,7 @@ mod tests {

#[test]
fn expand_fn_outputs_empty() {
assert_quote!(expand_fn_outputs(&[],).unwrap(), {
self::ethcontract::Void
});
assert_quote!(expand_fn_outputs(&[],).unwrap(), { () });
}

#[test]
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.3", path = "../ethcontract-common" }
ethcontract-derive = { version = "0.11.3", path = "../ethcontract-derive", optional = true}
futures = "0.3"
Expand Down
22 changes: 14 additions & 8 deletions ethcontract/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ mod deploy;
mod event;
mod method;

use crate::errors::{DeployError, LinkError};
use crate::{
errors::{DeployError, LinkError},
tokens::Tokenize,
};
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, DeploymentInformation};
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 +25,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, ViewMethodBuilder};

/// Represents a contract instance at an address. Provides methods for
/// contract interaction.
Expand Down Expand Up @@ -164,15 +166,19 @@ impl<T: Transport> Instance<T> {
pub fn method<P, R>(&self, signature: H32, params: P) -> AbiResult<MethodBuilder<T, R>>
where
P: Tokenize,
R: Detokenizable,
R: Tokenize,
{
let signature = signature.as_ref();
let function = self
.methods
.get(signature)
.map(|(name, index)| &self.abi.functions[name][*index])
.ok_or_else(|| AbiError::InvalidName(hex::encode(&signature)))?;
let data = function.encode_input(&params.into_tokens())?;
let tokens = match params.into_token() {
ethcontract_common::abi::Token::Tuple(tokens) => tokens,
_ => unreachable!("function arguments are always tuples"),
};
let data = function.encode_input(&tokens)?;

// take ownership here as it greatly simplifies dealing with futures
// lifetime as it would require the contract Instance to live until
Expand All @@ -192,7 +198,7 @@ impl<T: Transport> Instance<T> {
pub fn view_method<P, R>(&self, signature: H32, params: P) -> AbiResult<ViewMethodBuilder<T, R>>
where
P: Tokenize,
R: Detokenizable,
R: Tokenize,
{
Ok(self.method(signature, params)?.view())
}
Expand All @@ -202,7 +208,7 @@ impl<T: Transport> Instance<T> {
///
/// This method will error if the ABI does not contain an entry for a
/// fallback function.
pub fn fallback<D>(&self, data: D) -> AbiResult<MethodBuilder<T, Void>>
pub fn fallback<D>(&self, data: D) -> AbiResult<MethodBuilder<T, ()>>
where
D: Into<Vec<u8>>,
{
Expand All @@ -221,7 +227,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: Tokenize,
{
let event = self
.events
Expand Down
7 changes: 5 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::Tokenize;
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 @@ -74,7 +74,10 @@ where
}

let code = bytecode.to_bytes()?;
let params = params.into_tokens();
let params = match params.into_token() {
ethcontract_common::abi::Token::Tuple(tokens) => tokens,
_ => unreachable!("function arguments are always tuples"),
};
let data = match (I::abi(&context).constructor(), params.is_empty()) {
(None, false) => return Err(AbiError::InvalidData.into()),
(None, true) => code,
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::Tokenize;
pub use ethcontract_common::abi::Topic;
use ethcontract_common::{
abi::{Event as AbiEvent, RawTopicFilter, Token},
Expand All @@ -17,14 +18,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: Tokenize> {
/// The underlying web3 instance.
web3: Web3<T>,
/// The event ABI data for encoding topic filters and decoding logs.
Expand All @@ -36,7 +36,7 @@ pub struct EventBuilder<T: Transport, E: Detokenize> {
_event: PhantomData<E>,
}

impl<T: Transport, E: Detokenize> EventBuilder<T, E> {
impl<T: Transport, E: Tokenize> 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 @@ -74,7 +74,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: Tokenize,
{
self.topics.topic0 = tokenize_topic(topic);
self
Expand All @@ -83,7 +83,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: Tokenize,
{
self.topics.topic1 = tokenize_topic(topic);
self
Expand All @@ -92,7 +92,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: Tokenize,
{
self.topics.topic2 = tokenize_topic(topic);
self
Expand Down Expand Up @@ -161,7 +161,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: Tokenize,
{
topic.map(|parameter| parameter.into_token())
}
Expand Down
10 changes: 4 additions & 6 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 ethcontract_common::abi::{Event as AbiEvent, RawLog as AbiRawLog};
use web3::contract::tokens::Detokenize;
use crate::{errors::ExecutionError, tokens::Tokenize};
use ethcontract_common::abi::{Event as AbiEvent, RawLog as AbiRawLog, Token};
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: Tokenize,
{
let event_log = event.parse_log(AbiRawLog {
topics: self.topics,
Expand All @@ -184,7 +182,7 @@ impl RawLog {
.into_iter()
.map(|param| param.value)
.collect::<Vec<_>>();
let data = D::from_tokens(tokens)?;
let data = D::from_token(Token::Tuple(tokens))?;

Ok(data)
}
Expand Down
Loading

0 comments on commit 39d40e0

Please sign in to comment.