Skip to content

Commit

Permalink
Remove Return and Void trait
Browse files Browse the repository at this point in the history
We no longer need them because we represent no return type as an empty
tuple instead of a placeholder type.
  • Loading branch information
vkgnosis committed Mar 11, 2021
1 parent fbcc415 commit c7c9f9d
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 78 deletions.
10 changes: 3 additions & 7 deletions ethcontract-generate/src/contract/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,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 @@ -170,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 @@ -219,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
8 changes: 4 additions & 4 deletions ethcontract/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub use self::event::{
AllEventsBuilder, Event, EventBuilder, EventMetadata, EventStatus, ParseLog, RawLog,
StreamEvent, Topic,
};
pub use self::method::{MethodBuilder, MethodDefaults, Return, 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 @@ -166,7 +166,7 @@ impl<T: Transport> Instance<T> {
pub fn method<P, R>(&self, signature: H32, params: P) -> AbiResult<MethodBuilder<T, R>>
where
P: Tokenize,
R: Return,
R: Tokenize,
{
let signature = signature.as_ref();
let function = self
Expand Down Expand Up @@ -198,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: Return,
R: Tokenize,
{
Ok(self.method(signature, params)?.view())
}
Expand All @@ -208,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 Down
81 changes: 20 additions & 61 deletions ethcontract/src/contract/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,6 @@ use web3::types::{Address, BlockId, Bytes, CallRequest, U256};
use web3::Transport;
use web3::{api::Web3, BatchTransport};

/// A solidity return type. We need a separate trait for this so that we can represent functions
/// that return nothing.
pub trait Return {
/// The output type.
type Output: Tokenize;
/// Is this the void return type?
fn is_void() -> bool;
/// Convert tokens into self.
fn from_tokens(tokens: Vec<Token>) -> Result<Self::Output, TokenError>;
}

/// The return type of a solidity function that returns nothing.
pub struct Void;
impl Return for Void {
type Output = ();

fn is_void() -> bool {
true
}

fn from_tokens(tokens: Vec<Token>) -> Result<Self::Output, TokenError> {
if !tokens.is_empty() {
return Err(TokenError::MultiDetokenizeLengthMismatch);
}
Ok(())
}
}

impl<T> Return for T
where
T: Tokenize,
{
type Output = T;

fn is_void() -> bool {
false
}

fn from_tokens(tokens: Vec<Token>) -> Result<Self::Output, TokenError> {
if tokens.len() != 1 {
return Err(TokenError::MultiDetokenizeLengthMismatch);
}
Self::Output::from_token(tokens.into_iter().next().unwrap())
}
}

/// Default options to be applied to `MethodBuilder` or `ViewMethodBuilder`.
#[derive(Clone, Debug, Default)]
pub struct MethodDefaults {
Expand All @@ -76,15 +30,15 @@ pub struct MethodDefaults {
/// transactions. This is useful when dealing with view functions.
#[derive(Debug, Clone)]
#[must_use = "methods do nothing unless you `.call()` or `.send()` them"]
pub struct MethodBuilder<T: Transport, R: Return> {
pub struct MethodBuilder<T: Transport, R: Tokenize> {
web3: Web3<T>,
function: Function,
/// transaction parameters
pub tx: TransactionBuilder<T>,
_result: PhantomData<R>,
}

impl<T: Transport> MethodBuilder<T, Void> {
impl<T: Transport> MethodBuilder<T, ()> {
/// Creates a new builder for a transaction invoking the fallback method.
pub fn fallback(web3: Web3<T>, address: Address, data: Bytes) -> Self {
// NOTE: We create a fake `Function` entry for the fallback method. This
Expand All @@ -102,7 +56,7 @@ impl<T: Transport> MethodBuilder<T, Void> {
}
}

impl<T: Transport, R: Return> MethodBuilder<T, R> {
impl<T: Transport, R: Tokenize> MethodBuilder<T, R> {
/// Creates a new builder for a transaction.
pub fn new(web3: Web3<T>, function: Function, address: Address, data: Bytes) -> Self {
MethodBuilder {
Expand Down Expand Up @@ -188,7 +142,7 @@ impl<T: Transport, R: Return> MethodBuilder<T, R> {
/// as such do not require gas or signing. Note that doing a call with a
/// block number requires first demoting the `MethodBuilder` into a
/// `ViewMethodBuilder` and setting the block number for the call.
pub async fn call(self) -> Result<R::Output, MethodError> {
pub async fn call(self) -> Result<R, MethodError> {
self.view().call().await
}
}
Expand All @@ -197,14 +151,14 @@ impl<T: Transport, R: Return> MethodBuilder<T, R> {
/// directly send transactions and is for read only method calls.
#[derive(Debug, Clone)]
#[must_use = "view methods do nothing unless you `.call()` them"]
pub struct ViewMethodBuilder<T: Transport, R: Return> {
pub struct ViewMethodBuilder<T: Transport, R: Tokenize> {
/// method parameters
pub m: MethodBuilder<T, R>,
/// optional block number
pub block: Option<BlockId>,
}

impl<T: Transport, R: Return> ViewMethodBuilder<T, R> {
impl<T: Transport, R: Tokenize> ViewMethodBuilder<T, R> {
/// Create a new `ViewMethodBuilder` by demoting a `MethodBuilder`.
pub fn from_method(method: MethodBuilder<T, R>) -> Self {
ViewMethodBuilder {
Expand Down Expand Up @@ -254,10 +208,10 @@ impl<T: Transport, R: Return> ViewMethodBuilder<T, R> {
}
}

impl<T: Transport, R: Return> ViewMethodBuilder<T, R> {
impl<T: Transport, R: Tokenize> ViewMethodBuilder<T, R> {
/// Call a contract method. Contract calls do not modify the blockchain and
/// as such do not require gas or signing.
pub async fn call(self) -> Result<R::Output, MethodError> {
pub async fn call(self) -> Result<R, MethodError> {
let eth = &self.m.web3.eth();
let (function, call, block) = self.decompose();
let future = eth.call(call, block);
Expand All @@ -270,7 +224,7 @@ impl<T: Transport, R: Return> ViewMethodBuilder<T, R> {
pub fn batch_call<B: BatchTransport>(
self,
batch: &mut CallBatch<B>,
) -> impl std::future::Future<Output = Result<R::Output, MethodError>> {
) -> impl std::future::Future<Output = Result<R, MethodError>> {
let (function, call, block) = self.decompose();
let future = batch.push(call, block);
async move { convert_response::<_, R>(future, function).await }
Expand All @@ -294,11 +248,11 @@ impl<T: Transport, R: Return> ViewMethodBuilder<T, R> {

async fn convert_response<
F: std::future::Future<Output = Result<Bytes, web3::Error>>,
R: Return,
R: Tokenize,
>(
future: F,
function: Function,
) -> Result<R::Output, MethodError> {
) -> Result<R, MethodError> {
let bytes = future
.await
.map_err(|err| MethodError::new(&function, err))?;
Expand All @@ -316,14 +270,15 @@ async fn convert_response<
/// encode this information in a JSON RPC error. On a revert or invalid opcode,
/// the result is `0x` (empty data), while on a revert with message, it is an
/// ABI encoded `Error(string)` function call data.
fn decode_geth_call_result<R: Return>(
fn decode_geth_call_result<R: Tokenize>(
function: &Function,
bytes: Vec<u8>,
) -> Result<R::Output, ExecutionError> {
) -> Result<R, ExecutionError> {
let is_void = R::from_token(Token::Tuple(Vec::new())).is_ok();
if let Some(reason) = revert::decode_reason(&bytes) {
// This is an encoded revert message from Geth nodes.
Err(ExecutionError::Revert(Some(reason)))
} else if !R::is_void() && bytes.is_empty() {
} else if !is_void && bytes.is_empty() {
// Geth does this on `revert()` without a message and `invalid()`,
// just treat them all as `invalid()` as generally contracts revert
// with messages. Unfortunately, for methods with empty return types
Expand All @@ -333,7 +288,11 @@ fn decode_geth_call_result<R: Return>(
} else {
// just a plain ol' regular result, try and decode it
let tokens = function.decode_output(&bytes)?;
let result = R::from_tokens(tokens)?;
let result = match tokens.len() {
0 => R::from_token(Token::Tuple(Vec::new())),
1 => R::from_token(tokens.into_iter().next().unwrap()),
_ => Err(TokenError::WrongNumberOfTokensReturned),
}?;
Ok(result)
}
}
Expand Down
4 changes: 1 addition & 3 deletions ethcontract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,7 @@ pub mod prelude {
//! A prelude module for importing commonly used types when interacting with
//! generated contracts.
pub use crate::contract::{
Event, EventMetadata, EventStatus, RawLog, StreamEvent, Topic, Void,
};
pub use crate::contract::{Event, EventMetadata, EventStatus, RawLog, StreamEvent, Topic};
pub use crate::int::I256;
pub use crate::secret::{Password, PrivateKey};
pub use crate::transaction::{Account, GasPrice};
Expand Down
8 changes: 5 additions & 3 deletions ethcontract/src/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ pub enum Error {
/// Tokenize::from_token token is tuple with wrong length.
#[error("expected a different number of tokens in tuple")]
TupleLengthMismatch,
/// MultiDetokenize::from_tokens has wrong number of tokens.
#[error("expected a different number of tokens when detokenizing multiple tokens")]
MultiDetokenizeLengthMismatch,
/// Methods should only ever return 0 or 1 token because multiple return types are grouped into
/// into a tuple which is a single token. If a method nonetheless returns more than 1 token this
/// error occurs.
#[error("a method returned an unexpected number of tokens")]
WrongNumberOfTokensReturned,
}

/// Rust type and single token conversion.
Expand Down

0 comments on commit c7c9f9d

Please sign in to comment.