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::{
|
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},
|
types::{Address, Filter, NameOrAddress, Selector, TransactionRequest, TxHash},
|
||||||
};
|
};
|
||||||
use ethers_providers::{Middleware, PendingTransaction};
|
use ethers_providers::{Middleware, PendingTransaction};
|
||||||
|
|
||||||
use rustc_hex::ToHex;
|
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.
|
/// 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
|
/// 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
|
/// [`method`]: method@crate::Contract::method
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Contract<M> {
|
pub struct Contract<M> {
|
||||||
|
base_contract: BaseContract,
|
||||||
client: Arc<M>,
|
client: Arc<M>,
|
||||||
abi: Abi,
|
|
||||||
address: Address,
|
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> {
|
impl<M: Middleware> Contract<M> {
|
||||||
/// Creates a new contract from the provided client, abi and address
|
/// Creates a new contract from the provided client, abi and address
|
||||||
pub fn new(address: Address, abi: Abi, client: impl Into<Arc<M>>) -> Self {
|
pub fn new(address: Address, abi: impl Into<BaseContract>, client: impl Into<Arc<M>>) -> Self {
|
||||||
let methods = create_mapping(&abi.functions, |function| function.selector());
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
base_contract: abi.into(),
|
||||||
client: client.into(),
|
client: client.into(),
|
||||||
abi,
|
|
||||||
address,
|
address,
|
||||||
methods,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an [`Event`](crate::builders::Event) builder for the provided event name.
|
/// 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> {
|
pub fn event<D: Detokenize>(&self, name: &str) -> Result<Event<M, D>, Error> {
|
||||||
// get the event's full name
|
// get the event's full name
|
||||||
let event = self.abi.event(name)?;
|
let event = self.base_contract.abi.event(name)?;
|
||||||
Ok(Event {
|
Ok(Event {
|
||||||
provider: &self.client,
|
provider: &self.client,
|
||||||
filter: Filter::new()
|
filter: Filter::new()
|
||||||
|
@ -207,7 +198,7 @@ impl<M: Middleware> Contract<M> {
|
||||||
args: T,
|
args: T,
|
||||||
) -> Result<ContractCall<M, D>, Error> {
|
) -> Result<ContractCall<M, D>, Error> {
|
||||||
// get the function
|
// get the function
|
||||||
let function = self.abi.function(name)?;
|
let function = self.base_contract.abi.function(name)?;
|
||||||
self.method_func(function, args)
|
self.method_func(function, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,9 +210,10 @@ impl<M: Middleware> Contract<M> {
|
||||||
args: T,
|
args: T,
|
||||||
) -> Result<ContractCall<M, D>, Error> {
|
) -> Result<ContractCall<M, D>, Error> {
|
||||||
let function = self
|
let function = self
|
||||||
|
.base_contract
|
||||||
.methods
|
.methods
|
||||||
.get(&signature)
|
.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>()))?;
|
.ok_or_else(|| Error::InvalidName(signature.to_hex::<String>()))?;
|
||||||
self.method_func(function, args)
|
self.method_func(function, args)
|
||||||
}
|
}
|
||||||
|
@ -283,7 +275,7 @@ impl<M: Middleware> Contract<M> {
|
||||||
|
|
||||||
/// Returns a reference to the contract's ABI
|
/// Returns a reference to the contract's ABI
|
||||||
pub fn abi(&self) -> &Abi {
|
pub fn abi(&self) -> &Abi {
|
||||||
&self.abi
|
&self.base_contract.abi
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the contract's client
|
/// Returns a reference to the contract's client
|
||||||
|
@ -295,25 +287,3 @@ impl<M: Middleware> Contract<M> {
|
||||||
self.client.pending_transaction(tx_hash)
|
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;
|
mod contract;
|
||||||
pub use contract::Contract;
|
pub use contract::Contract;
|
||||||
|
|
||||||
|
mod base;
|
||||||
|
pub use base::BaseContract;
|
||||||
|
|
||||||
mod call;
|
mod call;
|
||||||
pub use call::ContractError;
|
pub use call::ContractError;
|
||||||
|
|
||||||
|
|
|
@ -234,13 +234,13 @@ impl<M: Middleware> Multicall<M> {
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// # use ethers::prelude::*;
|
/// # use ethers::{abi::Abi, prelude::*};
|
||||||
/// # use std::{sync::Arc, convert::TryFrom};
|
/// # use std::{sync::Arc, convert::TryFrom};
|
||||||
/// #
|
/// #
|
||||||
/// # let client = Provider::<Http>::try_from("http://localhost:8545")?;
|
/// # let client = Provider::<Http>::try_from("http://localhost:8545")?;
|
||||||
/// # let client = Arc::new(client);
|
/// # let client = Arc::new(client);
|
||||||
/// #
|
/// #
|
||||||
/// # let abi = serde_json::from_str("")?;
|
/// # let abi: Abi = serde_json::from_str("")?;
|
||||||
/// # let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?;
|
/// # let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?;
|
||||||
/// # let contract = Contract::<Provider<Http>>::new(address, abi, client.clone());
|
/// # let contract = Contract::<Provider<Http>>::new(address, abi, client.clone());
|
||||||
/// #
|
/// #
|
||||||
|
|
|
@ -385,7 +385,8 @@ mod celo_tests {
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.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
|
let value: String = contract
|
||||||
.method("getValue", ())
|
.method("getValue", ())
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
#![deny(intra_doc_link_resolution_failure)]
|
|
||||||
//! # Clients for interacting with Ethereum nodes
|
//! # Clients for interacting with Ethereum nodes
|
||||||
//!
|
//!
|
||||||
//! This crate provides asynchronous [Ethereum JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC)
|
//! This crate provides asynchronous [Ethereum JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC)
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
rust_2018_idioms,
|
rust_2018_idioms,
|
||||||
unreachable_pub
|
unreachable_pub
|
||||||
)]
|
)]
|
||||||
#![deny(intra_doc_link_resolution_failure)]
|
|
||||||
#![doc(test(
|
#![doc(test(
|
||||||
no_crate_inject,
|
no_crate_inject,
|
||||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||||
|
|
Loading…
Reference in New Issue