From 35e24ed412aa0ae7a823b532db7f4787346b01d7 Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Tue, 27 Oct 2020 16:27:01 +0530 Subject: [PATCH] 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 --- ethers-contract/src/base.rs | 74 ++++++++++++++++++++++++++++ ethers-contract/src/contract.rs | 52 +++++-------------- ethers-contract/src/lib.rs | 3 ++ ethers-contract/src/multicall/mod.rs | 4 +- ethers-contract/tests/contract.rs | 3 +- ethers-providers/src/lib.rs | 1 - ethers/src/lib.rs | 1 - 7 files changed, 92 insertions(+), 46 deletions(-) create mode 100644 ethers-contract/src/base.rs diff --git a/ethers-contract/src/base.rs b/ethers-contract/src/base.rs new file mode 100644 index 00000000..7946a95d --- /dev/null +++ b/ethers-contract/src/base.rs @@ -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, +} + +impl From 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( + self, + address: Address, + client: impl Into>, + ) -> Contract { + Contract::new(address, self, client) + } +} + +impl AsRef 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( + elements: &HashMap>, + signature: F, +) -> HashMap +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() +} diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs index 861c086d..14b5838d 100644 --- a/ethers-contract/src/contract.rs +++ b/ethers-contract/src/contract.rs @@ -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 { + base_contract: BaseContract, client: Arc, - 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, } impl Contract { /// Creates a new contract from the provided client, abi and address - pub fn new(address: Address, abi: Abi, client: impl Into>) -> Self { - let methods = create_mapping(&abi.functions, |function| function.selector()); - + pub fn new(address: Address, abi: impl Into, client: impl Into>) -> 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(&self, name: &str) -> Result, 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 Contract { args: T, ) -> Result, 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 Contract { args: T, ) -> Result, 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::()))?; self.method_func(function, args) } @@ -283,7 +275,7 @@ impl Contract { /// 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 Contract { 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( - elements: &HashMap>, - signature: F, -) -> HashMap -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() -} diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs index 0aedc737..bf04d17c 100644 --- a/ethers-contract/src/lib.rs +++ b/ethers-contract/src/lib.rs @@ -16,6 +16,9 @@ mod contract; pub use contract::Contract; +mod base; +pub use base::BaseContract; + mod call; pub use call::ContractError; diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs index d33931e9..cae07d3f 100644 --- a/ethers-contract/src/multicall/mod.rs +++ b/ethers-contract/src/multicall/mod.rs @@ -234,13 +234,13 @@ impl Multicall { /// /// ```no_run /// # async fn foo() -> Result<(), Box> { - /// # use ethers::prelude::*; + /// # use ethers::{abi::Abi, prelude::*}; /// # use std::{sync::Arc, convert::TryFrom}; /// # /// # let client = Provider::::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::
()?; /// # let contract = Contract::>::new(address, abi, client.clone()); /// # diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs index 4dec14ae..4d342353 100644 --- a/ethers-contract/tests/contract.rs +++ b/ethers-contract/tests/contract.rs @@ -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", ()) diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs index 4cb54a1d..6172fd30 100644 --- a/ethers-providers/src/lib.rs +++ b/ethers-providers/src/lib.rs @@ -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) diff --git a/ethers/src/lib.rs b/ethers/src/lib.rs index 3d19be87..195da973 100644 --- a/ethers/src/lib.rs +++ b/ethers/src/lib.rs @@ -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))