refactor(ethers-contract): split to modules
This commit is contained in:
parent
099fa5d7ef
commit
f42aaf2588
|
@ -272,6 +272,7 @@ dependencies = [
|
|||
"once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -19,3 +19,4 @@ syn = "1.0.12"
|
|||
url = "2.1"
|
||||
serde_json = "1.0.53"
|
||||
once_cell = "1.4.0"
|
||||
rustc-hex = { version = "2.1.0", default-features = false }
|
||||
|
|
|
@ -6,12 +6,13 @@ use quote::quote;
|
|||
|
||||
pub(crate) fn imports() -> TokenStream {
|
||||
quote! {
|
||||
// TODO: Can we make this context aware so that it imports either ethers_contract
|
||||
// or ethers::contract?
|
||||
use ethers_contract::{
|
||||
Sender, Event,
|
||||
abi::{Abi, Token, Detokenize, InvalidOutputType, Tokenizable},
|
||||
Contract, Lazy,
|
||||
Contract, ContractCall, Event, Lazy,
|
||||
signers::{Client, Signer},
|
||||
types::*, // import all the types so that we can codegen for everything
|
||||
signers::{Signer, Client},
|
||||
providers::JsonRpcClient,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ fn expand_filter(event: &Event) -> Result<TokenStream> {
|
|||
let ev_name = Literal::string(&event.name);
|
||||
let result = util::ident(&event.name.to_pascal_case());
|
||||
|
||||
let doc = util::expand_doc(&format!("Gets the {} event", event.name));
|
||||
let doc = util::expand_doc(&format!("Gets the contract's `{}` event", event.name));
|
||||
Ok(quote! {
|
||||
|
||||
#doc
|
||||
|
|
|
@ -7,6 +7,7 @@ use anyhow::{anyhow, Context as _, Result};
|
|||
use inflector::Inflector;
|
||||
use proc_macro2::{Literal, TokenStream};
|
||||
use quote::quote;
|
||||
use rustc_hex::ToHex;
|
||||
use syn::Ident;
|
||||
|
||||
/// Expands a context into a method struct containing all the generated bindings
|
||||
|
@ -39,13 +40,17 @@ fn expand_function(function: &Function, alias: Option<Ident>) -> Result<TokenStr
|
|||
let outputs = expand_fn_outputs(&function.outputs)?;
|
||||
|
||||
let result = if function.constant {
|
||||
quote! { Sender<'a, S, P, #outputs> }
|
||||
quote! { ContractCall<'a, S, P, #outputs> }
|
||||
} else {
|
||||
quote! { Sender<'a, S, P, H256> }
|
||||
quote! { ContractCall<'a, S, P, H256> }
|
||||
};
|
||||
|
||||
let arg = expand_inputs_call_arg(&function.inputs);
|
||||
let doc = util::expand_doc(&format!("Calls the contract's {} function", function.name));
|
||||
let doc = util::expand_doc(&format!(
|
||||
"Calls the contract's `{}` (0x{}) function",
|
||||
function.name,
|
||||
function.selector().to_hex::<String>()
|
||||
));
|
||||
Ok(quote! {
|
||||
|
||||
#doc
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
use ethers_abi::{Detokenize, Function};
|
||||
use ethers_providers::JsonRpcClient;
|
||||
use ethers_signers::{Client, Signer};
|
||||
use ethers_types::{Address, BlockNumber, TransactionRequest, H256, U256};
|
||||
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
pub struct ContractCall<'a, S, P, D> {
|
||||
pub(crate) tx: TransactionRequest,
|
||||
pub(crate) function: Function,
|
||||
pub(crate) client: &'a Client<'a, S, P>,
|
||||
pub(crate) block: Option<BlockNumber>,
|
||||
pub(crate) datatype: PhantomData<D>,
|
||||
}
|
||||
|
||||
impl<'a, S, P, D: Detokenize> ContractCall<'a, S, P, D> {
|
||||
/// Sets the `from` field in the transaction to the provided value
|
||||
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
|
||||
self.tx.from = Some(from.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `gas` field in the transaction to the provided value
|
||||
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
|
||||
self.tx.gas = Some(gas.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `gas_price` field in the transaction to the provided value
|
||||
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
|
||||
self.tx.gas_price = Some(gas_price.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `value` field in the transaction to the provided value
|
||||
pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
|
||||
self.tx.value = Some(value.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ThisError, Debug)]
|
||||
// TODO: Can we get rid of this static?
|
||||
pub enum ContractError<P: JsonRpcClient>
|
||||
where
|
||||
P::Error: 'static,
|
||||
{
|
||||
#[error(transparent)]
|
||||
DecodingError(#[from] ethers_abi::Error),
|
||||
#[error(transparent)]
|
||||
DetokenizationError(#[from] ethers_abi::InvalidOutputType),
|
||||
#[error(transparent)]
|
||||
CallError(P::Error),
|
||||
}
|
||||
|
||||
impl<'a, S: Signer, P: JsonRpcClient, D: Detokenize> ContractCall<'a, S, P, D>
|
||||
where
|
||||
P::Error: 'static,
|
||||
{
|
||||
/// Queries the blockchain via an `eth_call` for the provided transaction.
|
||||
///
|
||||
/// If executed on a non-state mutating smart contract function (i.e. `view`, `pure`)
|
||||
/// then it will return the raw data from the chain.
|
||||
///
|
||||
/// If executed on a mutating smart contract function, it will do a "dry run" of the call
|
||||
/// and return the return type of the transaction without mutating the state
|
||||
///
|
||||
/// Note: this function _does not_ send a transaction from your account
|
||||
pub async fn call(self) -> Result<D, ContractError<P>> {
|
||||
let bytes = self
|
||||
.client
|
||||
.call(self.tx, self.block)
|
||||
.await
|
||||
.map_err(ContractError::CallError)?;
|
||||
|
||||
let tokens = self.function.decode_output(&bytes.0)?;
|
||||
|
||||
let data = D::from_tokens(tokens)?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// Signs and broadcasts the provided transaction
|
||||
pub async fn send(self) -> Result<H256, P::Error> {
|
||||
self.client.send_transaction(self.tx, self.block).await
|
||||
}
|
||||
}
|
|
@ -1,19 +1,17 @@
|
|||
use ethers_abi::{
|
||||
Abi, Detokenize, Error, Event as AbiEvent, EventExt, Function, FunctionExt, RawLog, Tokenize,
|
||||
};
|
||||
use ethers_providers::{JsonRpcClient, Provider};
|
||||
use crate::{ContractCall, Event};
|
||||
|
||||
use ethers_abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize};
|
||||
use ethers_providers::JsonRpcClient;
|
||||
use ethers_signers::{Client, Signer};
|
||||
use ethers_types::{
|
||||
Address, BlockNumber, Filter, Selector, TransactionRequest, ValueOrArray, H256, U256,
|
||||
};
|
||||
use ethers_types::{Address, Filter, Selector, TransactionRequest};
|
||||
|
||||
use rustc_hex::ToHex;
|
||||
use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
|
||||
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
/// Represents a contract instance at an address. Provides methods for
|
||||
/// contract interaction.
|
||||
// TODO: Should we separate the lifetimes for the two references?
|
||||
// https://stackoverflow.com/a/29862184
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Contract<'a, S, P> {
|
||||
client: &'a Client<'a, S, P>,
|
||||
|
@ -40,7 +38,7 @@ impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a transaction builder for the provided function name. If there are
|
||||
/// Returns an `Event` builder for the provided event name. If there are
|
||||
/// multiple functions with the same name due to overloading, consider using
|
||||
/// the `method_hash` method instead, since this will use the first match.
|
||||
pub fn event<'b, D: Detokenize>(&'a self, name: &str) -> Result<Event<'a, 'b, P, D>, Error>
|
||||
|
@ -64,7 +62,7 @@ impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> {
|
|||
&self,
|
||||
name: &str,
|
||||
args: T,
|
||||
) -> Result<Sender<'a, S, P, D>, Error> {
|
||||
) -> Result<ContractCall<'a, S, P, D>, Error> {
|
||||
// get the function
|
||||
let function = self.abi.function(name)?;
|
||||
self.method_func(function, args)
|
||||
|
@ -76,7 +74,7 @@ impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> {
|
|||
&self,
|
||||
signature: Selector,
|
||||
args: T,
|
||||
) -> Result<Sender<'a, S, P, D>, Error> {
|
||||
) -> Result<ContractCall<'a, S, P, D>, Error> {
|
||||
let function = self
|
||||
.methods
|
||||
.get(&signature)
|
||||
|
@ -89,7 +87,7 @@ impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> {
|
|||
&self,
|
||||
function: &Function,
|
||||
args: T,
|
||||
) -> Result<Sender<'a, S, P, D>, Error> {
|
||||
) -> Result<ContractCall<'a, S, P, D>, Error> {
|
||||
// create the calldata
|
||||
let data = function.encode_input(&args.into_tokens())?;
|
||||
|
||||
|
@ -100,7 +98,7 @@ impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(Sender {
|
||||
Ok(ContractCall {
|
||||
tx,
|
||||
client: self.client,
|
||||
block: None,
|
||||
|
@ -118,144 +116,6 @@ impl<'a, S: Signer, P: JsonRpcClient> Contract<'a, S, P> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Sender<'a, S, P, D> {
|
||||
tx: TransactionRequest,
|
||||
function: Function,
|
||||
client: &'a Client<'a, S, P>,
|
||||
block: Option<BlockNumber>,
|
||||
datatype: PhantomData<D>,
|
||||
}
|
||||
|
||||
impl<'a, S, P, D: Detokenize> Sender<'a, S, P, D> {
|
||||
/// Sets the `from` field in the transaction to the provided value
|
||||
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
|
||||
self.tx.from = Some(from.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `gas` field in the transaction to the provided value
|
||||
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
|
||||
self.tx.gas = Some(gas.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `gas_price` field in the transaction to the provided value
|
||||
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
|
||||
self.tx.gas_price = Some(gas_price.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `value` field in the transaction to the provided value
|
||||
pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
|
||||
self.tx.value = Some(value.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ThisError, Debug)]
|
||||
// TODO: Can we get rid of this static?
|
||||
pub enum ContractError<P: JsonRpcClient>
|
||||
where
|
||||
P::Error: 'static,
|
||||
{
|
||||
#[error(transparent)]
|
||||
DecodingError(#[from] ethers_abi::Error),
|
||||
#[error(transparent)]
|
||||
DetokenizationError(#[from] ethers_abi::InvalidOutputType),
|
||||
#[error(transparent)]
|
||||
CallError(P::Error),
|
||||
}
|
||||
|
||||
impl<'a, S: Signer, P: JsonRpcClient, D: Detokenize> Sender<'a, S, P, D>
|
||||
where
|
||||
P::Error: 'static,
|
||||
{
|
||||
pub async fn call(self) -> Result<D, ContractError<P>> {
|
||||
let bytes = self
|
||||
.client
|
||||
.call(self.tx, self.block)
|
||||
.await
|
||||
.map_err(ContractError::CallError)?;
|
||||
|
||||
let tokens = self.function.decode_output(&bytes.0)?;
|
||||
|
||||
let data = D::from_tokens(tokens)?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub async fn send(self) -> Result<H256, P::Error> {
|
||||
self.client.send_transaction(self.tx, self.block).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Event<'a, 'b, P, D> {
|
||||
pub filter: Filter,
|
||||
provider: &'a Provider<P>,
|
||||
event: &'b AbiEvent,
|
||||
datatype: PhantomData<D>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, P, D: Detokenize> Event<'a, 'b, P, D> {
|
||||
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||
self.filter.from_block = Some(block.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||
self.filter.to_block = Some(block.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
||||
self.filter.topics.push(topic.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn topics(mut self, topics: &[ValueOrArray<H256>]) -> Self {
|
||||
self.filter.topics.extend_from_slice(topics);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Can we get rid of the static?
|
||||
impl<'a, 'b, P: JsonRpcClient, D: Detokenize> Event<'a, 'b, P, D>
|
||||
where
|
||||
P::Error: 'static,
|
||||
{
|
||||
pub async fn query(self) -> Result<Vec<D>, ContractError<P>> {
|
||||
// get the logs
|
||||
let logs = self
|
||||
.provider
|
||||
.get_logs(&self.filter)
|
||||
.await
|
||||
.map_err(ContractError::CallError)?;
|
||||
|
||||
let events = logs
|
||||
.into_iter()
|
||||
.map(|log| {
|
||||
// ethabi parses the unindexed and indexed logs together to a
|
||||
// vector of tokens
|
||||
let tokens = self
|
||||
.event
|
||||
.parse_log(RawLog {
|
||||
topics: log.topics,
|
||||
data: log.data.0,
|
||||
})?
|
||||
.params
|
||||
.into_iter()
|
||||
.map(|param| param.value)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// convert the tokens to the requested datatype
|
||||
Ok::<_, ContractError<P>>(D::from_tokens(tokens)?)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility function for creating a mapping between a unique signature and a
|
||||
/// name-index pair for accessing contract ABI items.
|
||||
fn create_mapping<T, S, F>(
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
use crate::ContractError;
|
||||
|
||||
use ethers_abi::{Detokenize, Event as AbiEvent, RawLog};
|
||||
use ethers_providers::{JsonRpcClient, Provider};
|
||||
|
||||
use ethers_types::{BlockNumber, Filter, ValueOrArray, H256};
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct Event<'a, 'b, P, D> {
|
||||
pub filter: Filter,
|
||||
pub(crate) provider: &'a Provider<P>,
|
||||
pub(crate) event: &'b AbiEvent,
|
||||
pub(crate) datatype: PhantomData<D>,
|
||||
}
|
||||
|
||||
// TODO: Improve these functions
|
||||
impl<'a, 'b, P, D: Detokenize> Event<'a, 'b, P, D> {
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||
self.filter.from_block = Some(block.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||
self.filter.to_block = Some(block.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
||||
self.filter.topics.push(topic.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn topics(mut self, topics: &[ValueOrArray<H256>]) -> Self {
|
||||
self.filter.topics.extend_from_slice(topics);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Can we get rid of the static?
|
||||
impl<'a, 'b, P: JsonRpcClient, D: Detokenize> Event<'a, 'b, P, D>
|
||||
where
|
||||
P::Error: 'static,
|
||||
{
|
||||
/// Queries the blockchain for the selected filter and returns a vector of matching
|
||||
/// event logs
|
||||
pub async fn query(self) -> Result<Vec<D>, ContractError<P>> {
|
||||
// get the logs
|
||||
let logs = self
|
||||
.provider
|
||||
.get_logs(&self.filter)
|
||||
.await
|
||||
.map_err(ContractError::CallError)?;
|
||||
|
||||
let events = logs
|
||||
.into_iter()
|
||||
.map(|log| {
|
||||
// ethabi parses the unindexed and indexed logs together to a
|
||||
// vector of tokens
|
||||
let tokens = self
|
||||
.event
|
||||
.parse_log(RawLog {
|
||||
topics: log.topics,
|
||||
data: log.data.0,
|
||||
})?
|
||||
.params
|
||||
.into_iter()
|
||||
.map(|param| param.value)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// convert the tokens to the requested datatype
|
||||
Ok::<_, ContractError<P>>(D::from_tokens(tokens)?)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
// TODO: Add filter watchers
|
||||
}
|
|
@ -1,5 +1,11 @@
|
|||
mod contract;
|
||||
pub use contract::*;
|
||||
pub use contract::Contract;
|
||||
|
||||
mod event;
|
||||
pub use event::Event;
|
||||
|
||||
mod call;
|
||||
pub use call::{ContractCall, ContractError};
|
||||
|
||||
#[cfg(feature = "abigen")]
|
||||
pub use ethers_contract_abigen::Builder;
|
||||
|
|
Loading…
Reference in New Issue