refactor: extract minimal features of Contract into BaseContract (#88)
* refactor: extract minimal features of Contract into BaseContract * refactor: move BaseContract to own file * chore: ensure celo test passes * chore: fix clippy Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
parent
a22f1f9aa0
commit
35e24ed412
|
@ -0,0 +1,74 @@
|
|||
use crate::Contract;
|
||||
|
||||
use ethers_core::{
|
||||
abi::{Abi, FunctionExt},
|
||||
types::{Address, Selector},
|
||||
};
|
||||
use ethers_providers::Middleware;
|
||||
|
||||
use std::{collections::HashMap, fmt::Debug, hash::Hash, sync::Arc};
|
||||
|
||||
/// A reduced form of `Contract` which just takes the `abi` and produces
|
||||
/// ABI encoded data for its functions.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BaseContract {
|
||||
pub(crate) abi: Abi,
|
||||
|
||||
/// A mapping from method signature to a name-index pair for accessing
|
||||
/// functions in the contract ABI. This is used to avoid allocation when
|
||||
/// searching for matching functions by signature.
|
||||
// Adapted from: https://github.com/gnosis/ethcontract-rs/blob/master/src/contract.rs
|
||||
pub(crate) methods: HashMap<Selector, (String, usize)>,
|
||||
}
|
||||
|
||||
impl From<Abi> for BaseContract {
|
||||
/// Creates a new `BaseContract` from the abi.
|
||||
fn from(abi: Abi) -> Self {
|
||||
let methods = create_mapping(&abi.functions, |function| function.selector());
|
||||
Self { abi, methods }
|
||||
}
|
||||
}
|
||||
|
||||
impl BaseContract {
|
||||
/// Returns a reference to the contract's ABI
|
||||
pub fn abi(&self) -> &Abi {
|
||||
&self.abi
|
||||
}
|
||||
|
||||
/// Upgrades a `BaseContract` into a full fledged contract with an address and middleware.
|
||||
pub fn into_contract<M: Middleware>(
|
||||
self,
|
||||
address: Address,
|
||||
client: impl Into<Arc<M>>,
|
||||
) -> Contract<M> {
|
||||
Contract::new(address, self, client)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Abi> for BaseContract {
|
||||
fn as_ref(&self) -> &Abi {
|
||||
self.abi()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>(
|
||||
elements: &HashMap<String, Vec<T>>,
|
||||
signature: F,
|
||||
) -> HashMap<S, (String, usize)>
|
||||
where
|
||||
S: Hash + Eq,
|
||||
F: Fn(&T) -> S,
|
||||
{
|
||||
let signature = &signature;
|
||||
elements
|
||||
.iter()
|
||||
.flat_map(|(name, sub_elements)| {
|
||||
sub_elements
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(move |(index, element)| (signature(element), (name.to_owned(), index)))
|
||||
})
|
||||
.collect()
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
use super::{call::ContractCall, event::Event};
|
||||
use super::{base::BaseContract, call::ContractCall, event::Event};
|
||||
|
||||
use ethers_core::{
|
||||
abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize},
|
||||
abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize},
|
||||
types::{Address, Filter, NameOrAddress, Selector, TransactionRequest, TxHash},
|
||||
};
|
||||
use ethers_providers::{Middleware, PendingTransaction};
|
||||
|
||||
use rustc_hex::ToHex;
|
||||
use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc};
|
||||
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||
|
||||
/// A Contract is an abstraction of an executable program on the Ethereum Blockchain.
|
||||
/// It has code (called byte code) as well as allocated long-term memory
|
||||
|
@ -160,34 +160,25 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData, syn
|
|||
/// [`method`]: method@crate::Contract::method
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Contract<M> {
|
||||
base_contract: BaseContract,
|
||||
client: Arc<M>,
|
||||
abi: Abi,
|
||||
address: Address,
|
||||
|
||||
/// A mapping from method signature to a name-index pair for accessing
|
||||
/// functions in the contract ABI. This is used to avoid allocation when
|
||||
/// searching for matching functions by signature.
|
||||
// Adapted from: https://github.com/gnosis/ethcontract-rs/blob/master/src/contract.rs
|
||||
methods: HashMap<Selector, (String, usize)>,
|
||||
}
|
||||
|
||||
impl<M: Middleware> Contract<M> {
|
||||
/// Creates a new contract from the provided client, abi and address
|
||||
pub fn new(address: Address, abi: Abi, client: impl Into<Arc<M>>) -> Self {
|
||||
let methods = create_mapping(&abi.functions, |function| function.selector());
|
||||
|
||||
pub fn new(address: Address, abi: impl Into<BaseContract>, client: impl Into<Arc<M>>) -> Self {
|
||||
Self {
|
||||
base_contract: abi.into(),
|
||||
client: client.into(),
|
||||
abi,
|
||||
address,
|
||||
methods,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an [`Event`](crate::builders::Event) builder for the provided event name.
|
||||
pub fn event<D: Detokenize>(&self, name: &str) -> Result<Event<M, D>, Error> {
|
||||
// get the event's full name
|
||||
let event = self.abi.event(name)?;
|
||||
let event = self.base_contract.abi.event(name)?;
|
||||
Ok(Event {
|
||||
provider: &self.client,
|
||||
filter: Filter::new()
|
||||
|
@ -207,7 +198,7 @@ impl<M: Middleware> Contract<M> {
|
|||
args: T,
|
||||
) -> Result<ContractCall<M, D>, Error> {
|
||||
// get the function
|
||||
let function = self.abi.function(name)?;
|
||||
let function = self.base_contract.abi.function(name)?;
|
||||
self.method_func(function, args)
|
||||
}
|
||||
|
||||
|
@ -219,9 +210,10 @@ impl<M: Middleware> Contract<M> {
|
|||
args: T,
|
||||
) -> Result<ContractCall<M, D>, Error> {
|
||||
let function = self
|
||||
.base_contract
|
||||
.methods
|
||||
.get(&signature)
|
||||
.map(|(name, index)| &self.abi.functions[name][*index])
|
||||
.map(|(name, index)| &self.base_contract.abi.functions[name][*index])
|
||||
.ok_or_else(|| Error::InvalidName(signature.to_hex::<String>()))?;
|
||||
self.method_func(function, args)
|
||||
}
|
||||
|
@ -283,7 +275,7 @@ impl<M: Middleware> Contract<M> {
|
|||
|
||||
/// Returns a reference to the contract's ABI
|
||||
pub fn abi(&self) -> &Abi {
|
||||
&self.abi
|
||||
&self.base_contract.abi
|
||||
}
|
||||
|
||||
/// Returns a reference to the contract's client
|
||||
|
@ -295,25 +287,3 @@ impl<M: Middleware> Contract<M> {
|
|||
self.client.pending_transaction(tx_hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>(
|
||||
elements: &HashMap<String, Vec<T>>,
|
||||
signature: F,
|
||||
) -> HashMap<S, (String, usize)>
|
||||
where
|
||||
S: Hash + Eq,
|
||||
F: Fn(&T) -> S,
|
||||
{
|
||||
let signature = &signature;
|
||||
elements
|
||||
.iter()
|
||||
.flat_map(|(name, sub_elements)| {
|
||||
sub_elements
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(move |(index, element)| (signature(element), (name.to_owned(), index)))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
mod contract;
|
||||
pub use contract::Contract;
|
||||
|
||||
mod base;
|
||||
pub use base::BaseContract;
|
||||
|
||||
mod call;
|
||||
pub use call::ContractError;
|
||||
|
||||
|
|
|
@ -234,13 +234,13 @@ impl<M: Middleware> Multicall<M> {
|
|||
///
|
||||
/// ```no_run
|
||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// # use ethers::prelude::*;
|
||||
/// # use ethers::{abi::Abi, prelude::*};
|
||||
/// # use std::{sync::Arc, convert::TryFrom};
|
||||
/// #
|
||||
/// # let client = Provider::<Http>::try_from("http://localhost:8545")?;
|
||||
/// # let client = Arc::new(client);
|
||||
/// #
|
||||
/// # let abi = serde_json::from_str("")?;
|
||||
/// # let abi: Abi = serde_json::from_str("")?;
|
||||
/// # let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?;
|
||||
/// # let contract = Contract::<Provider<Http>>::new(address, abi, client.clone());
|
||||
/// #
|
||||
|
|
|
@ -385,7 +385,8 @@ mod celo_tests {
|
|||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let _receipt = contract.pending_transaction(tx_hash).await.unwrap();
|
||||
let receipt = contract.pending_transaction(tx_hash).await.unwrap();
|
||||
assert_eq!(receipt.status.unwrap(), 1.into());
|
||||
|
||||
let value: String = contract
|
||||
.method("getValue", ())
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
//! # Clients for interacting with Ethereum nodes
|
||||
//!
|
||||
//! This crate provides asynchronous [Ethereum JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC)
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![deny(intra_doc_link_resolution_failure)]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||
|
|
Loading…
Reference in New Issue