use crate::abicompat::AbiCompat;
use crate::errors::{revert, ExecutionError, MethodError};
use crate::future::CompatCallFuture;
use crate::transaction::send::SendFuture;
use crate::transaction::{Account, GasPrice, TransactionBuilder};
use ethcontract_common::abi::{Function, Token};
use futures::compat::Future01CompatExt;
use pin_project::pin_project;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use web3::api::Web3;
use web3::contract::tokens::Detokenize;
use web3::contract::Error as Web3ContractError;
use web3::types::{Address, BlockNumber, Bytes, CallRequest, U256};
use web3::Transport;
pub struct Void(());
pub trait Detokenizable {
type Output;
fn from_tokens(tokens: Vec<Token>) -> Result<Self::Output, ExecutionError>;
fn is_void() -> bool {
false
}
}
impl Detokenizable for Void {
type Output = ();
fn from_tokens(tokens: Vec<Token>) -> Result<Self::Output, ExecutionError> {
if !tokens.is_empty() {
return Err(Web3ContractError::InvalidOutputType(format!(
"Expected no elements, got tokens: {:?}",
tokens
))
.into());
}
Ok(())
}
fn is_void() -> bool {
true
}
}
impl<T: Detokenize> Detokenizable for T {
type Output = Self;
fn from_tokens(tokens: Vec<Token>) -> Result<Self::Output, ExecutionError> {
let tokens = match tokens.compat() {
Some(tokens) => tokens,
None => return Err(ExecutionError::UnsupportedToken),
};
let result = <T as Detokenize>::from_tokens(tokens)?;
Ok(result)
}
}
#[derive(Clone, Debug, Default)]
pub struct MethodDefaults {
pub from: Option<Account>,
pub gas: Option<U256>,
pub gas_price: Option<GasPrice>,
}
#[derive(Debug, Clone)]
#[must_use = "methods do nothing unless you `.call()` or `.send()` them"]
pub struct MethodBuilder<T: Transport, R: Detokenizable> {
web3: Web3<T>,
function: Function,
pub tx: TransactionBuilder<T>,
_result: PhantomData<R>,
}
impl<T: Transport> MethodBuilder<T, Void> {
pub fn fallback(web3: Web3<T>, address: Address, data: Bytes) -> Self {
let function = Function {
name: "fallback".into(),
inputs: vec![],
outputs: vec![],
constant: false,
};
MethodBuilder::new(web3, function, address, data)
}
}
impl<T: Transport, R: Detokenizable> MethodBuilder<T, R> {
pub fn new(web3: Web3<T>, function: Function, address: Address, data: Bytes) -> Self {
MethodBuilder {
web3: web3.clone(),
function,
tx: TransactionBuilder::new(web3).to(address).data(data),
_result: PhantomData,
}
}
pub fn with_defaults(mut self, defaults: &MethodDefaults) -> Self {
self.tx.from = self.tx.from.or_else(|| defaults.from.clone());
self.tx.gas = self.tx.gas.or(defaults.gas);
self.tx.gas_price = self.tx.gas_price.or(defaults.gas_price);
self
}
pub fn from(mut self, value: Account) -> Self {
self.tx = self.tx.from(value);
self
}
pub fn gas(mut self, value: U256) -> Self {
self.tx = self.tx.gas(value);
self
}
pub fn gas_price(mut self, value: GasPrice) -> Self {
self.tx = self.tx.gas_price(value);
self
}
pub fn value(mut self, value: U256) -> Self {
self.tx = self.tx.value(value);
self
}
pub fn nonce(mut self, value: U256) -> Self {
self.tx = self.tx.nonce(value);
self
}
pub fn confirmations(mut self, value: usize) -> Self {
self.tx = self.tx.confirmations(value);
self
}
pub fn into_inner(self) -> TransactionBuilder<T> {
self.tx
}
pub fn send(self) -> MethodSendFuture<T> {
MethodFuture::new(self.function, self.tx.send())
}
pub fn view(self) -> ViewMethodBuilder<T, R> {
ViewMethodBuilder::from_method(self)
}
pub fn call(self) -> CallFuture<T, R> {
self.view().call()
}
}
#[pin_project]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct MethodFuture<F> {
function: Function,
#[pin]
inner: F,
}
impl<F> MethodFuture<F> {
fn new(function: Function, inner: F) -> Self {
MethodFuture { function, inner }
}
}
impl<T, F> Future for MethodFuture<F>
where
F: Future<Output = Result<T, ExecutionError>>,
{
type Output = Result<T, MethodError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let mut this = self.project();
this.inner
.as_mut()
.poll(cx)
.map(|result| result.map_err(|err| MethodError::new(&this.function, err)))
}
}
pub type MethodSendFuture<T> = MethodFuture<SendFuture<T>>;
#[derive(Debug, Clone)]
#[must_use = "view methods do nothing unless you `.call()` them"]
pub struct ViewMethodBuilder<T: Transport, R: Detokenizable> {
pub m: MethodBuilder<T, R>,
pub block: Option<BlockNumber>,
}
impl<T: Transport, R: Detokenizable> ViewMethodBuilder<T, R> {
pub fn from_method(method: MethodBuilder<T, R>) -> Self {
ViewMethodBuilder {
m: method,
block: None,
}
}
pub fn with_defaults(mut self, defaults: &MethodDefaults) -> Self {
self.m = self.m.with_defaults(defaults);
self
}
pub fn from(mut self, value: Address) -> Self {
self.m = self.m.from(Account::Local(value, None));
self
}
pub fn gas(mut self, value: U256) -> Self {
self.m = self.m.gas(value);
self
}
pub fn gas_price(mut self, value: GasPrice) -> Self {
self.m = self.m.gas_price(value);
self
}
pub fn value(mut self, value: U256) -> Self {
self.m = self.m.value(value);
self
}
pub fn block(mut self, value: BlockNumber) -> Self {
self.block = Some(value);
self
}
}
impl<T: Transport, R: Detokenizable> ViewMethodBuilder<T, R> {
pub fn call(self) -> CallFuture<T, R> {
CallFuture::from_builder(self)
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[pin_project]
pub struct CallFuture<T: Transport, R: Detokenizable> {
function: Function,
#[pin]
call: CompatCallFuture<T, Bytes>,
_result: PhantomData<Box<R>>,
}
impl<T: Transport, R: Detokenizable> CallFuture<T, R> {
fn from_builder(builder: ViewMethodBuilder<T, R>) -> Self {
CallFuture {
function: builder.m.function,
call: builder
.m
.web3
.eth()
.call(
CallRequest {
from: builder.m.tx.from.map(|account| account.address()),
to: builder.m.tx.to.unwrap_or_default(),
gas: builder.m.tx.gas,
gas_price: builder
.m
.tx
.gas_price
.and_then(|gas_price| gas_price.value()),
value: builder.m.tx.value,
data: builder.m.tx.data,
},
builder.block,
)
.compat(),
_result: PhantomData,
}
}
}
impl<T: Transport, R: Detokenizable> Future for CallFuture<T, R> {
type Output = Result<R::Output, MethodError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let mut this = self.project();
this.call.as_mut().poll(cx).map(|result| {
result
.map_err(ExecutionError::from)
.and_then(|bytes| decode_geth_call_result::<R>(&this.function, bytes.0))
.map_err(|err| MethodError::new(&this.function, err))
})
}
}
fn decode_geth_call_result<R: Detokenizable>(
function: &Function,
bytes: Vec<u8>,
) -> Result<R::Output, ExecutionError> {
if let Some(reason) = revert::decode_reason(&bytes) {
Err(ExecutionError::Revert(Some(reason)))
} else if !R::is_void() && bytes.is_empty() {
Err(ExecutionError::InvalidOpcode)
} else {
let tokens = function.decode_output(&bytes)?;
let result = R::from_tokens(tokens)?;
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::prelude::*;
use ethcontract_common::abi::{Param, ParamType};
fn test_abi_function() -> (Function, Bytes) {
let function = Function {
name: "test".to_owned(),
inputs: Vec::new(),
outputs: vec![Param {
name: "".to_owned(),
kind: ParamType::Uint(256),
}],
constant: false,
};
let data = function
.encode_input(&[])
.expect("error encoding empty input");
(function, Bytes(data))
}
#[test]
fn method_tx_options() {
let transport = TestTransport::new();
let web3 = Web3::new(transport.clone());
let address = addr!("0x0123456789012345678901234567890123456789");
let from = addr!("0x9876543210987654321098765432109876543210");
let (function, data) = test_abi_function();
let tx = MethodBuilder::<_, U256>::new(web3, function, address, data.clone())
.from(Account::Local(from, None))
.gas(1.into())
.gas_price(2.into())
.value(28.into())
.nonce(42.into())
.into_inner();
assert_eq!(tx.from.map(|a| a.address()), Some(from));
assert_eq!(tx.to, Some(address));
assert_eq!(tx.gas, Some(1.into()));
assert_eq!(tx.gas_price, Some(2.into()));
assert_eq!(tx.value, Some(28.into()));
assert_eq!(tx.data, Some(data));
assert_eq!(tx.nonce, Some(42.into()));
transport.assert_no_more_requests();
}
#[test]
fn view_method_call() {
let mut transport = TestTransport::new();
let web3 = Web3::new(transport.clone());
let address = addr!("0x0123456789012345678901234567890123456789");
let from = addr!("0x9876543210987654321098765432109876543210");
let (function, data) = test_abi_function();
let tx = ViewMethodBuilder::<_, U256>::from_method(MethodBuilder::new(
web3,
function,
address,
data.clone(),
))
.from(from)
.gas(1.into())
.gas_price(2.into())
.value(28.into())
.block(BlockNumber::Number(100.into()));
transport.add_response(json!(
"0x000000000000000000000000000000000000000000000000000000000000002a"
));
let result = tx.call().immediate().expect("call error");
assert_eq!(result, 42.into());
transport.assert_request(
"eth_call",
&[
json!({
"from": from,
"to": address,
"gas": "0x1",
"gasPrice": "0x2",
"value": "0x1c",
"data": data,
}),
json!("0x64"),
],
);
transport.assert_no_more_requests();
}
#[test]
fn method_to_view_method_preserves_options() {
let mut transport = TestTransport::new();
let web3 = Web3::new(transport.clone());
let address = addr!("0x0123456789012345678901234567890123456789");
let (function, data) = test_abi_function();
let tx = MethodBuilder::<_, U256>::new(web3, function, address, data.clone())
.gas(42.into())
.view();
transport.add_response(json!(
"0x0000000000000000000000000000000000000000000000000000000000000000"
));
tx.call().immediate().expect("call error");
transport.assert_request(
"eth_call",
&[
json!({
"to": address,
"gas": "0x2a",
"data": data,
}),
json!("latest"),
],
);
transport.assert_no_more_requests();
}
#[test]
fn method_defaults_are_applied() {
let transport = TestTransport::new();
let web3 = Web3::new(transport.clone());
let from = addr!("0x9876543210987654321098765432109876543210");
let address = addr!("0x0123456789012345678901234567890123456789");
let (function, data) = test_abi_function();
let tx = MethodBuilder::<_, U256>::new(web3, function, address, data)
.with_defaults(&MethodDefaults {
from: Some(Account::Local(from, None)),
gas: Some(1.into()),
gas_price: Some(2.into()),
})
.into_inner();
assert_eq!(tx.from.map(|a| a.address()), Some(from));
assert_eq!(tx.gas, Some(1.into()));
assert_eq!(tx.gas_price, Some(2.into()));
transport.assert_no_more_requests();
}
#[test]
fn method_call_geth_revert_with_message() {
let mut transport = TestTransport::new();
let web3 = Web3::new(transport.clone());
let address = addr!("0x0123456789012345678901234567890123456789");
let (function, data) = test_abi_function();
let tx = ViewMethodBuilder::<_, U256>::from_method(MethodBuilder::new(
web3, function, address, data,
));
transport.add_response(json!(revert::encode_reason_hex("message")));
let result = tx.call().immediate();
assert!(
match &result {
Err(MethodError {
inner: ExecutionError::Revert(Some(ref reason)),
..
}) if reason == "message" => true,
_ => false,
},
"unexpected result {:?}",
result
);
}
#[test]
fn method_call_geth_revert() {
let mut transport = TestTransport::new();
let web3 = Web3::new(transport.clone());
let address = addr!("0x0123456789012345678901234567890123456789");
let (function, data) = test_abi_function();
let tx = ViewMethodBuilder::<_, U256>::from_method(MethodBuilder::new(
web3, function, address, data,
));
transport.add_response(json!("0x"));
let result = tx.call().immediate();
assert!(
match &result {
Err(MethodError {
inner: ExecutionError::InvalidOpcode,
..
}) => true,
_ => false,
},
"unexpected result {:?}",
result
);
}
}