use crate::transaction::{Account, GasPrice, TransactionBuilder, TransactionResult};
use crate::{batch::CallBatch, errors::MethodError, tokens::Tokenize};
use ethcontract_common::abi::{Function, Token};
use std::marker::PhantomData;
use web3::types::{AccessList, Address, BlockId, Bytes, CallRequest, U256};
use web3::Transport;
use web3::{api::Web3, BatchTransport};
#[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: Tokenize> {
web3: Web3<T>,
function: Function,
pub tx: TransactionBuilder<T>,
_result: PhantomData<R>,
}
impl<T: Transport> MethodBuilder<T, ()> {
pub fn fallback(web3: Web3<T>, address: Address, data: Bytes) -> Self {
#[allow(deprecated)]
let function = Function {
name: "fallback".into(),
inputs: vec![],
outputs: vec![],
constant: None,
state_mutability: Default::default(),
};
MethodBuilder::new(web3, function, address, data)
}
}
impl<T: Transport, R: Tokenize> 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 function(&self) -> &Function {
&self.function
}
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 access_list(mut self, value: AccessList) -> Self {
self.tx = self.tx.access_list(value);
self
}
pub fn into_inner(self) -> TransactionBuilder<T> {
self.tx
}
pub async fn send(self) -> Result<TransactionResult, MethodError> {
let Self { function, tx, .. } = self;
tx.send()
.await
.map_err(|err| MethodError::new(&function, err))
}
pub fn view(self) -> ViewMethodBuilder<T, R> {
ViewMethodBuilder::from_method(self)
}
pub async fn call(self) -> Result<R, MethodError> {
self.view().call().await
}
}
#[derive(Debug, Clone)]
#[must_use = "view methods do nothing unless you `.call()` them"]
pub struct ViewMethodBuilder<T: Transport, R: Tokenize> {
pub m: MethodBuilder<T, R>,
pub block: Option<BlockId>,
}
impl<T: Transport, R: Tokenize> 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 function(&self) -> &Function {
&self.m.function
}
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 nonce(mut self, value: U256) -> Self {
self.m = self.m.nonce(value);
self
}
pub fn access_list(mut self, value: AccessList) -> Self {
self.m = self.m.access_list(value);
self
}
pub fn block(mut self, value: BlockId) -> Self {
self.block = Some(value);
self
}
}
impl<T: Transport, R: Tokenize> ViewMethodBuilder<T, R> {
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);
convert_response::<_, R>(future, function).await
}
pub fn batch_call<B: BatchTransport>(
self,
batch: &mut CallBatch<B>,
) -> 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 }
}
fn decompose(self) -> (Function, CallRequest, Option<BlockId>) {
let resolved_gas_price = self
.m
.tx
.gas_price
.map(|gas_price| gas_price.resolve_for_transaction())
.unwrap_or_default();
(
self.m.function,
CallRequest {
from: self.m.tx.from.map(|account| account.address()),
to: self.m.tx.to,
gas: self.m.tx.gas,
gas_price: resolved_gas_price.gas_price,
value: self.m.tx.value,
data: self.m.tx.data,
transaction_type: resolved_gas_price.transaction_type,
access_list: self.m.tx.access_list,
max_fee_per_gas: resolved_gas_price.max_fee_per_gas,
max_priority_fee_per_gas: resolved_gas_price.max_priority_fee_per_gas,
},
self.block,
)
}
}
async fn convert_response<
F: std::future::Future<Output = Result<Bytes, web3::Error>>,
R: Tokenize,
>(
future: F,
function: Function,
) -> Result<R, MethodError> {
let bytes = future
.await
.map_err(|err| MethodError::new(&function, err))?;
let tokens = function
.decode_output(&bytes.0)
.map_err(|err| MethodError::new(&function, err))?;
let token = match tokens.len() {
0 => Token::Tuple(Vec::new()),
1 => tokens.into_iter().next().unwrap(),
_ => Token::Tuple(tokens),
};
let result = R::from_token(token).map_err(|err| MethodError::new(&function, err))?;
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::prelude::*;
use ethcontract_common::abi::{Param, ParamType};
use web3::types::AccessListItem;
fn test_abi_function() -> (Function, Bytes) {
#[allow(deprecated)]
let function = Function {
name: "test".to_owned(),
inputs: Vec::new(),
outputs: vec![Param {
name: "".to_owned(),
kind: ParamType::Uint(256),
internal_type: None,
}],
constant: None,
state_mutability: Default::default(),
};
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.0.into())
.value(28.into())
.nonce(42.into())
.access_list(vec![AccessListItem::default()])
.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.0.into()));
assert_eq!(tx.value, Some(28.into()));
assert_eq!(tx.data, Some(data));
assert_eq!(tx.nonce, Some(42.into()));
assert_eq!(tx.access_list, Some(vec![AccessListItem::default()]));
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.0.into())
.value(28.into())
.block(BlockId::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.0.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.0.into()));
transport.assert_no_more_requests();
}
}