2020-06-10 18:20:47 +00:00
use super ::{ call ::ContractCall , event ::Event } ;
2020-05-27 08:46:16 +00:00
2020-05-31 16:01:34 +00:00
use ethers_core ::{
2020-05-28 09:04:12 +00:00
abi ::{ Abi , Detokenize , Error , EventExt , Function , FunctionExt , Tokenize } ,
2020-06-22 08:44:08 +00:00
types ::{ Address , Filter , NameOrAddress , Selector , TransactionRequest , TxHash } ,
2020-05-28 09:04:12 +00:00
} ;
2020-06-22 08:44:08 +00:00
use ethers_providers ::{ JsonRpcClient , PendingTransaction } ;
2020-05-31 16:01:34 +00:00
use ethers_signers ::{ Client , Signer } ;
2020-05-26 18:57:59 +00:00
use rustc_hex ::ToHex ;
2020-06-22 08:44:08 +00:00
use std ::{ collections ::HashMap , fmt ::Debug , hash ::Hash , marker ::PhantomData , sync ::Arc } ;
2020-05-26 18:57:59 +00:00
2020-06-10 18:20:47 +00:00
/// 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
/// (called storage). Every deployed Contract has an address, which is used to connect
/// to it so that it may be sent messages to call its methods.
///
/// A Contract can emit Events, which can be efficiently observed by applications
/// to be notified when a contract has performed specific operation.
///
/// There are two types of methods that can be called on a Contract:
///
/// 1. A Constant method may not add, remove or change any data in the storage,
/// nor log any events, and may only call Constant methods on other contracts.
/// These methods are free (no Ether is required) to call. The result from them
/// may also be returned to the caller. Constant methods are marked as `pure` and
/// `view` in Solidity.
///
/// 2. A Non-Constant method requires a fee (in Ether) to be paid, but may perform
/// any state-changing operation desired, log events, send ether and call Non-Constant
/// methods on other Contracts. These methods cannot return their result to the caller.
/// These methods must be triggered by a transaction, sent by an Externally Owned Account
/// (EOA) either directly or indirectly (i.e. called from another contract), and are
/// required to be mined before the effects are present. Therefore, the duration
/// required for these operations can vary widely, and depend on the transaction
/// gas price, network congestion and miner priority heuristics.
///
/// The Contract API provides simple way to connect to a Contract and call its methods,
/// as functions on a Rust struct, handling all the binary protocol conversion,
/// internal name mangling and topic construction. This allows a Contract object
/// to be used like any standard Rust struct, without having to worry about the
/// low-level details of the Ethereum Virtual Machine or Blockchain.
///
/// The Contract definition (called an Application Binary Interface, or ABI) must
/// be provided to instantiate a contract and the available methods and events will
/// be made available to call by providing their name as a `str` via the [`method`]
/// and [`event`] methods. If non-existing names are given, the function/event call
/// will fail.
///
/// Alternatively, you can _and should_ use the [`abigen`] macro, or the [`Abigen` builder]
/// to generate type-safe bindings to your contracts.
///
/// # Example
///
/// Assuming we already have our contract deployed at `address`, we'll proceed to
/// interact with its methods and retrieve raw logs it has emitted.
///
/// ```no_run
2020-06-20 13:55:07 +00:00
/// use ethers::{
/// abi::Abi,
/// utils::Solc,
/// types::{Address, H256},
/// contract::Contract,
/// providers::{Provider, Http},
/// signers::Wallet,
/// };
2020-06-10 18:20:47 +00:00
/// use std::convert::TryFrom;
///
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// // this is a fake address used just for this example
/// let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?;
///
/// // (ugly way to write the ABI inline, you can otherwise read it from a file)
/// let abi: Abi = serde_json::from_str(r#"[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":true,"internalType":"address","name":"oldAuthor","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#)?;
///
/// // connect to the network
/// let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
/// let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
/// .parse::<Wallet>()?.connect(provider);
///
/// // create the contract object at the address
2020-06-22 08:44:08 +00:00
/// let contract = Contract::new(address, abi, client);
2020-06-10 18:20:47 +00:00
///
/// // Calling constant methods is done by calling `call()` on the method builder.
/// // (if the function takes no arguments, then you must use `()` as the argument)
/// let init_value: String = contract
/// .method::<_, String>("getValue", ())?
/// .call()
/// .await?;
///
/// // Non-constant methods are executed via the `send()` call on the method builder.
/// let tx_hash = contract
2020-06-22 08:44:08 +00:00
/// .method::<_, H256>("setValue", "hi".to_owned())?.send().await?;
///
/// // `await`ing on the pending transaction resolves to a transaction receipt
/// let receipt = contract.pending_transaction(tx_hash).confirmations(6).await?;
2020-06-10 18:20:47 +00:00
///
/// # Ok(())
/// # }
/// ```
///
/// # Event Logging
/// Querying structured logs requires you to have defined a struct with the expected
/// datatypes and to have implemented `Detokenize` for it. This boilerplate code
/// is generated for you via the [`abigen`] and [`Abigen` builder] utilities.
///
/// ```no_run
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// use ethers_core::{abi::Abi, types::Address};
/// use ethers_contract::Contract;
/// use ethers_providers::{Provider, Http};
/// use ethers_signers::Wallet;
/// use std::convert::TryFrom;
/// use ethers_core::abi::{Detokenize, Token, InvalidOutputType};
/// # // this is a fake address used just for this example
/// # let address = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse::<Address>()?;
/// # let abi: Abi = serde_json::from_str(r#"[]"#)?;
/// # let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
/// # let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc".parse::<Wallet>()?.connect(provider);
2020-06-22 08:44:08 +00:00
/// # let contract = Contract::new(address, abi, client);
2020-06-10 18:20:47 +00:00
///
/// #[derive(Clone, Debug)]
/// struct ValueChanged {
/// old_author: Address,
/// new_author: Address,
/// old_value: String,
/// new_value: String,
/// }
///
/// impl Detokenize for ValueChanged {
/// fn from_tokens(tokens: Vec<Token>) -> Result<ValueChanged, InvalidOutputType> {
/// let old_author: Address = tokens[1].clone().to_address().unwrap();
/// let new_author: Address = tokens[1].clone().to_address().unwrap();
/// let old_value = tokens[2].clone().to_string().unwrap();
/// let new_value = tokens[3].clone().to_string().unwrap();
///
/// Ok(Self {
/// old_author,
/// new_author,
/// old_value,
/// new_value,
/// })
/// }
/// }
///
///
/// let logs: Vec<ValueChanged> = contract
/// .event("ValueChanged")?
/// .from_block(0u64)
/// .query()
/// .await?;
///
/// println!("{:?}", logs);
/// # Ok(())
/// # }
///
/// ```
///
/// _Disclaimer: these above docs have been adapted from the corresponding [ethers.js page](https://docs.ethers.io/ethers.js/html/api-contract.html)_
///
/// [`abigen`]: macro.abigen.html
2020-06-20 13:55:07 +00:00
/// [`Abigen` builder]: crate::Abigen
/// [`event`]: method@crate::Contract::event
/// [`method`]: method@crate::Contract::method
2020-05-26 18:57:59 +00:00
#[ derive(Debug, Clone) ]
2020-06-22 08:44:08 +00:00
pub struct Contract < P , S > {
client : Arc < Client < P , S > > ,
2020-06-15 08:46:07 +00:00
abi : Abi ,
2020-05-26 18:57:59 +00:00
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 ) > ,
}
2020-06-22 08:44:08 +00:00
impl < P , S > Contract < P , S >
2020-06-01 23:15:33 +00:00
where
S : Signer ,
P : JsonRpcClient ,
{
2020-05-26 18:57:59 +00:00
/// Creates a new contract from the provided client, abi and address
2020-06-22 08:44:08 +00:00
pub fn new ( address : Address , abi : Abi , client : impl Into < Arc < Client < P , S > > > ) -> Self {
2020-05-26 18:57:59 +00:00
let methods = create_mapping ( & abi . functions , | function | function . selector ( ) ) ;
Self {
2020-06-22 08:44:08 +00:00
client : client . into ( ) ,
2020-05-26 18:57:59 +00:00
abi ,
address ,
methods ,
}
}
2020-06-20 13:55:07 +00:00
/// Returns an [`Event`](crate::builders::Event) builder for the provided event name.
2020-06-02 11:33:21 +00:00
pub fn event < D : Detokenize > ( & self , name : & str ) -> Result < Event < P , D > , Error > {
2020-05-26 18:57:59 +00:00
// get the event's full name
let event = self . abi . event ( name ) ? ;
Ok ( Event {
provider : & self . client . provider ( ) ,
2020-06-02 11:33:21 +00:00
filter : Filter ::new ( )
. event ( & event . abi_signature ( ) )
. address ( self . address ) ,
2020-05-26 18:57:59 +00:00
event : & event ,
datatype : PhantomData ,
} )
}
/// Returns a transaction builder for the provided function 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 method < T : Tokenize , D : Detokenize > (
& self ,
name : & str ,
args : T ,
2020-06-22 08:44:08 +00:00
) -> Result < ContractCall < P , S , D > , Error > {
2020-05-26 18:57:59 +00:00
// get the function
let function = self . abi . function ( name ) ? ;
self . method_func ( function , args )
}
/// Returns a transaction builder for the selected function signature. This should be
/// preferred if there are overloaded functions in your smart contract
pub fn method_hash < T : Tokenize , D : Detokenize > (
& self ,
signature : Selector ,
args : T ,
2020-06-22 08:44:08 +00:00
) -> Result < ContractCall < P , S , D > , Error > {
2020-05-26 18:57:59 +00:00
let function = self
. methods
. get ( & signature )
. map ( | ( name , index ) | & self . abi . functions [ name ] [ * index ] )
. ok_or_else ( | | Error ::InvalidName ( signature . to_hex ::< String > ( ) ) ) ? ;
self . method_func ( function , args )
}
fn method_func < T : Tokenize , D : Detokenize > (
& self ,
function : & Function ,
args : T ,
2020-06-22 08:44:08 +00:00
) -> Result < ContractCall < P , S , D > , Error > {
2020-05-26 18:57:59 +00:00
// create the calldata
let data = function . encode_input ( & args . into_tokens ( ) ) ? ;
// create the tx object
let tx = TransactionRequest {
2020-05-27 20:43:02 +00:00
to : Some ( NameOrAddress ::Address ( self . address ) ) ,
2020-05-26 18:57:59 +00:00
data : Some ( data . into ( ) ) ,
.. Default ::default ( )
} ;
2020-05-27 08:46:16 +00:00
Ok ( ContractCall {
2020-05-26 18:57:59 +00:00
tx ,
2020-06-22 08:44:08 +00:00
client : Arc ::clone ( & self . client ) , // cheap clone behind the Arc
2020-05-26 18:57:59 +00:00
block : None ,
function : function . to_owned ( ) ,
datatype : PhantomData ,
} )
}
2020-06-02 10:36:02 +00:00
/// Returns a new contract instance at `address`.
///
/// Clones `self` internally
2020-06-21 07:17:11 +00:00
pub fn at < T : Into < Address > > ( & self , address : T ) -> Self
where
P : Clone ,
{
2020-06-02 10:36:02 +00:00
let mut this = self . clone ( ) ;
this . address = address . into ( ) ;
this
}
/// Returns a new contract instance using the provided client
///
/// Clones `self` internally
2020-06-22 08:44:08 +00:00
pub fn connect ( & self , client : Arc < Client < P , S > > ) -> Self
2020-06-21 07:17:11 +00:00
where
P : Clone ,
{
2020-06-02 10:36:02 +00:00
let mut this = self . clone ( ) ;
this . client = client ;
this
}
2020-06-02 11:33:21 +00:00
/// Returns the contract's address
pub fn address ( & self ) -> Address {
self . address
}
/// Returns a reference to the contract's ABI
pub fn abi ( & self ) -> & Abi {
& self . abi
}
/// Returns a reference to the contract's client
pub fn client ( & self ) -> & Client < P , S > {
& self . client
}
2020-06-22 08:44:08 +00:00
/// Helper which creates a pending transaction object from a transaction hash
/// using the provider's polling interval
pub fn pending_transaction ( & self , tx_hash : TxHash ) -> PendingTransaction < '_ , P > {
self . client . provider ( ) . pending_transaction ( tx_hash )
}
2020-05-26 18:57:59 +00:00
}
/// 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 ( )
}