Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abiv2 #468

Merged
merged 2 commits into from
Mar 24, 2021
Merged

Abiv2 #468

Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Support abi v2
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.
vkgnosis committed Mar 24, 2021
commit d180c73e7eb5f1cae464ba188ec152c24f7fb8fb
2 changes: 1 addition & 1 deletion ethcontract-common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
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`.
2 changes: 1 addition & 1 deletion ethcontract-common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
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;
@@ -39,6 +39,7 @@ impl Artifact {
functions: HashMap::new(),
events: HashMap::new(),
fallback: false,
receive: false,
},
bytecode: Default::default(),
networks: HashMap::new(),
2 changes: 2 additions & 0 deletions ethcontract-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -342,6 +342,7 @@ impl Parse for Method {
})
.collect::<ParseResult<Vec<_>>>()?;

#[allow(deprecated)]
nlordell marked this conversation as resolved.
Show resolved Hide resolved
Function {
name,
inputs,
@@ -350,6 +351,7 @@ impl Parse for Method {
// affect its signature.
outputs: vec![],
constant: false,
state_mutability: Default::default(),
vkgnosis marked this conversation as resolved.
Show resolved Hide resolved
}
};
let signature = function.abi_signature();
38 changes: 10 additions & 28 deletions ethcontract-generate/src/contract/events.rs
Original file line number Diff line number Diff line change
@@ -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 {
@@ -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! {
@@ -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")
}
}
})
}
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;
@@ -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);
@@ -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! { () }),
vkgnosis marked this conversation as resolved.
Show resolved Hide resolved
1 => types::expand(&outputs[0].kind),
_ => {
let types = outputs
@@ -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>>,
{
@@ -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]
5 changes: 4 additions & 1 deletion ethcontract-generate/src/contract/types.rs
Original file line number Diff line number Diff line change
@@ -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<_>>>()?;
vkgnosis marked this conversation as resolved.
Show resolved Hide resolved
Ok(quote! { (#(#inner,)*) })
}
}
}
1 change: 1 addition & 0 deletions ethcontract/Cargo.toml
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ ws-tokio = ["web3/ws-tokio"]
ws-tls-tokio = ["web3/ws-tls-tokio"]

[dependencies]
arrayvec = "0.5"
nlordell marked this conversation as resolved.
Show resolved Hide resolved
ethcontract-common = { version = "0.11.3", path = "../ethcontract-common" }
ethcontract-derive = { version = "0.11.3", path = "../ethcontract-derive", optional = true}
futures = "0.3"
22 changes: 14 additions & 8 deletions ethcontract/src/contract.rs
Original file line number Diff line number Diff line change
@@ -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;

@@ -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.
@@ -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
@@ -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())
}
@@ -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>>,
{
@@ -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
7 changes: 5 additions & 2 deletions ethcontract/src/contract/deploy.rs
Original file line number Diff line number Diff line change
@@ -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;

@@ -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,
14 changes: 7 additions & 7 deletions ethcontract/src/contract/event.rs
Original file line number Diff line number Diff line change
@@ -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},
@@ -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.
@@ -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 {
@@ -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
@@ -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
@@ -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
@@ -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())
}
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
@@ -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,
@@ -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)
}
Loading