docs(contract): expand contract docs
This commit is contained in:
parent
1adbca67b0
commit
030fc671fe
|
@ -299,6 +299,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustc-hex",
|
"rustc-hex",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
@ -1142,9 +1143,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.53"
|
version = "1.0.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2"
|
checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
|
|
@ -20,6 +20,7 @@ tokio = { version = "0.2.21", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "0.2.21", default-features = false, features = ["macros"] }
|
tokio = { version = "0.2.21", default-features = false, features = ["macros"] }
|
||||||
|
serde_json = "1.0.55"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["abigen"]
|
default = ["abigen"]
|
||||||
|
|
|
@ -10,32 +10,45 @@ use std::{fmt::Debug, marker::PhantomData};
|
||||||
use thiserror::Error as ThisError;
|
use thiserror::Error as ThisError;
|
||||||
|
|
||||||
#[derive(ThisError, Debug)]
|
#[derive(ThisError, Debug)]
|
||||||
|
/// An Error which is thrown when interacting with a smart contract
|
||||||
pub enum ContractError {
|
pub enum ContractError {
|
||||||
|
/// Thrown when the ABI decoding fails
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
DecodingError(#[from] AbiError),
|
DecodingError(#[from] AbiError),
|
||||||
|
|
||||||
|
/// Thrown when detokenizing an argument
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
DetokenizationError(#[from] InvalidOutputType),
|
DetokenizationError(#[from] InvalidOutputType),
|
||||||
|
|
||||||
|
/// Thrown when a client call fails
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ClientError(#[from] ClientError),
|
ClientError(#[from] ClientError),
|
||||||
|
|
||||||
|
/// Thrown when a provider call fails
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ProviderError(#[from] ProviderError),
|
ProviderError(#[from] ProviderError),
|
||||||
|
|
||||||
|
/// Thrown during deployment if a constructor argument was passed in the `deploy`
|
||||||
|
/// call but a constructor was not present in the ABI
|
||||||
#[error("constructor is not defined in the ABI")]
|
#[error("constructor is not defined in the ABI")]
|
||||||
ConstructorError,
|
ConstructorError,
|
||||||
|
|
||||||
|
/// Thrown if a contract address is not found in the deployment transaction's
|
||||||
|
/// receipt
|
||||||
#[error("Contract was not deployed")]
|
#[error("Contract was not deployed")]
|
||||||
ContractNotDeployed,
|
ContractNotDeployed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
/// Helper for managing a transaction before submitting it to a node
|
||||||
pub struct ContractCall<'a, P, S, D> {
|
pub struct ContractCall<'a, P, S, D> {
|
||||||
pub(crate) tx: TransactionRequest,
|
/// The raw transaction object
|
||||||
pub(crate) function: Function,
|
pub tx: TransactionRequest,
|
||||||
|
/// The ABI of the function being called
|
||||||
|
pub function: Function,
|
||||||
|
/// Optional block number to be used when calculating the transaction's gas and nonce
|
||||||
|
pub block: Option<BlockNumber>,
|
||||||
pub(crate) client: &'a Client<P, S>,
|
pub(crate) client: &'a Client<P, S>,
|
||||||
pub(crate) block: Option<BlockNumber>,
|
|
||||||
pub(crate) datatype: PhantomData<D>,
|
pub(crate) datatype: PhantomData<D>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{ContractCall, Event};
|
use super::{call::ContractCall, event::Event};
|
||||||
|
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize},
|
abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize},
|
||||||
|
@ -10,10 +10,153 @@ use ethers_signers::{Client, Signer};
|
||||||
use rustc_hex::ToHex;
|
use rustc_hex::ToHex;
|
||||||
use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
|
use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
|
||||||
|
|
||||||
/// Represents a contract instance at an address. Provides methods for
|
/// A Contract is an abstraction of an executable program on the Ethereum Blockchain.
|
||||||
/// contract interaction.
|
/// It has code (called byte code) as well as allocated long-term memory
|
||||||
// TODO: Should we separate the lifetimes for the two references?
|
/// (called storage). Every deployed Contract has an address, which is used to connect
|
||||||
// https://stackoverflow.com/a/29862184
|
/// 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
|
||||||
|
/// use ethers_core::{abi::Abi, utils::Solc, types::{Address, H256}};
|
||||||
|
/// use ethers_contract::Contract;
|
||||||
|
/// use ethers_providers::{Provider, Http};
|
||||||
|
/// use ethers_signers::Wallet;
|
||||||
|
/// 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
|
||||||
|
/// let contract = Contract::new(address, &abi, &client);
|
||||||
|
///
|
||||||
|
/// // 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
|
||||||
|
/// .method::<_, H256>("setValue", "hi".to_owned())?
|
||||||
|
/// .send()
|
||||||
|
/// .await?;
|
||||||
|
///
|
||||||
|
/// # 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);
|
||||||
|
/// # let contract = Contract::new(address, &abi, &client);
|
||||||
|
///
|
||||||
|
/// #[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
|
||||||
|
/// [`Abigen` builder]: struct.Abigen.html
|
||||||
|
/// [`event`]: struct.Contract.html#method.event
|
||||||
|
/// [`method`]: struct.Contract.html#method.method
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Contract<'a, P, S> {
|
pub struct Contract<'a, P, S> {
|
||||||
client: &'a Client<P, S>,
|
client: &'a Client<P, S>,
|
||||||
|
|
|
@ -9,42 +9,51 @@ use ethers_core::{
|
||||||
|
|
||||||
use std::{collections::HashMap, marker::PhantomData};
|
use std::{collections::HashMap, marker::PhantomData};
|
||||||
|
|
||||||
|
/// Helper for managing the event filter before querying or streaming its logs
|
||||||
pub struct Event<'a: 'b, 'b, P, D> {
|
pub struct Event<'a: 'b, 'b, P, D> {
|
||||||
|
/// The event filter's state
|
||||||
pub filter: Filter,
|
pub filter: Filter,
|
||||||
|
/// The ABI of the event which is being filtered
|
||||||
|
pub event: &'b AbiEvent,
|
||||||
pub(crate) provider: &'a Provider<P>,
|
pub(crate) provider: &'a Provider<P>,
|
||||||
pub(crate) event: &'b AbiEvent,
|
|
||||||
pub(crate) datatype: PhantomData<D>,
|
pub(crate) datatype: PhantomData<D>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Improve these functions
|
// TODO: Improve these functions
|
||||||
impl<P, D: Detokenize> Event<'_, '_, P, D> {
|
impl<P, D: Detokenize> Event<'_, '_, P, D> {
|
||||||
|
/// Sets the filter's `from` block
|
||||||
#[allow(clippy::wrong_self_convention)]
|
#[allow(clippy::wrong_self_convention)]
|
||||||
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
self.filter.from_block = Some(block.into());
|
self.filter.from_block = Some(block.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the filter's `to` block
|
||||||
#[allow(clippy::wrong_self_convention)]
|
#[allow(clippy::wrong_self_convention)]
|
||||||
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
self.filter.to_block = Some(block.into());
|
self.filter.to_block = Some(block.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the filter's 0th topic (typically the event name for non-anonymous events)
|
||||||
pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
||||||
self.filter.topics[0] = Some(topic.into());
|
self.filter.topics[0] = Some(topic.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the filter's 1st topic
|
||||||
pub fn topic1<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
pub fn topic1<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
||||||
self.filter.topics[1] = Some(topic.into());
|
self.filter.topics[1] = Some(topic.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the filter's 2nd topic
|
||||||
pub fn topic2<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
pub fn topic2<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
||||||
self.filter.topics[2] = Some(topic.into());
|
self.filter.topics[2] = Some(topic.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the filter's 3rd topic
|
||||||
pub fn topic3<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
pub fn topic3<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
|
||||||
self.filter.topics[3] = Some(topic.into());
|
self.filter.topics[3] = Some(topic.into());
|
||||||
self
|
self
|
||||||
|
|
|
@ -11,13 +11,14 @@ use std::time::Duration;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
|
||||||
/// Poll for tx confirmation once every 7 seconds.
|
/// Poll for tx confirmation once every 7 seconds.
|
||||||
/// TODO: Can this be improved by replacing polling with an "on new block" subscription?
|
// TODO: Can this be improved by replacing polling with an "on new block" subscription?
|
||||||
const POLL_INTERVAL: u64 = 7000;
|
const POLL_INTERVAL: u64 = 7000;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
/// Helper which manages the deployment transaction of a smart contract
|
||||||
pub struct Deployer<'a, P, S> {
|
pub struct Deployer<'a, P, S> {
|
||||||
|
abi: &'a Abi,
|
||||||
client: &'a Client<P, S>,
|
client: &'a Client<P, S>,
|
||||||
pub abi: &'a Abi,
|
|
||||||
tx: TransactionRequest,
|
tx: TransactionRequest,
|
||||||
confs: usize,
|
confs: usize,
|
||||||
poll_interval: Duration,
|
poll_interval: Duration,
|
||||||
|
@ -28,16 +29,22 @@ where
|
||||||
S: Signer,
|
S: Signer,
|
||||||
P: JsonRpcClient,
|
P: JsonRpcClient,
|
||||||
{
|
{
|
||||||
|
/// Sets the poll frequency for checking the number of confirmations for
|
||||||
|
/// the contract deployment transaction
|
||||||
pub fn poll_interval<T: Into<Duration>>(mut self, interval: T) -> Self {
|
pub fn poll_interval<T: Into<Duration>>(mut self, interval: T) -> Self {
|
||||||
self.poll_interval = interval.into();
|
self.poll_interval = interval.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the number of confirmations to wait for the contract deployment transaction
|
||||||
pub fn confirmations<T: Into<usize>>(mut self, confirmations: T) -> Self {
|
pub fn confirmations<T: Into<usize>>(mut self, confirmations: T) -> Self {
|
||||||
self.confs = confirmations.into();
|
self.confs = confirmations.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Broadcasts the contract deployment transaction and after waiting for it to
|
||||||
|
/// be sufficiently confirmed (default: 1), it returns a [`Contract`](./struct.Contract.html)
|
||||||
|
/// struct at the deployed contract's address.
|
||||||
pub async fn send(self) -> Result<Contract<'a, P, S>, ContractError> {
|
pub async fn send(self) -> Result<Contract<'a, P, S>, ContractError> {
|
||||||
let tx_hash = self.client.send_transaction(self.tx, None).await?;
|
let tx_hash = self.client.send_transaction(self.tx, None).await?;
|
||||||
|
|
||||||
|
@ -58,16 +65,60 @@ where
|
||||||
Ok(contract)
|
Ok(contract)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the deployer's ABI
|
||||||
pub fn abi(&self) -> &Abi {
|
pub fn abi(&self) -> &Abi {
|
||||||
&self.abi
|
&self.abi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the deployer's client
|
||||||
pub fn client(&self) -> &Client<P, S> {
|
pub fn client(&self) -> &Client<P, S> {
|
||||||
&self.client
|
&self.client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
/// To deploy a contract to the Ethereum network, a `ContractFactory` can be
|
||||||
|
/// created which manages the Contract bytecode and Application Binary Interface
|
||||||
|
/// (ABI), usually generated from the Solidity compiler.
|
||||||
|
///
|
||||||
|
/// Once the factory's deployment transaction is mined with sufficient confirmations,
|
||||||
|
/// the [`Contract`](./struct.Contract.html) object is returned.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use ethers_core::utils::Solc;
|
||||||
|
/// use ethers_contract::ContractFactory;
|
||||||
|
/// use ethers_providers::{Provider, Http};
|
||||||
|
/// use ethers_signers::Wallet;
|
||||||
|
/// use std::convert::TryFrom;
|
||||||
|
///
|
||||||
|
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// // first we'll compile the contract (you can alternatively compile it yourself
|
||||||
|
/// // and pass the ABI/Bytecode
|
||||||
|
/// let compiled = Solc::new("./tests/contract.sol").build().unwrap();
|
||||||
|
/// let contract = compiled
|
||||||
|
/// .get("SimpleStorage")
|
||||||
|
/// .expect("could not find contract");
|
||||||
|
///
|
||||||
|
/// // connect to the network
|
||||||
|
/// let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
|
||||||
|
/// let client = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
|
||||||
|
/// .parse::<Wallet>()?.connect(provider);
|
||||||
|
///
|
||||||
|
/// // create a factory which will be used to deploy instances of the contract
|
||||||
|
/// let factory = ContractFactory::new(&contract.abi, &contract.bytecode, &client);
|
||||||
|
///
|
||||||
|
/// // The deployer created by the `deploy` call exposes a builder which gets consumed
|
||||||
|
/// // by the async `send` call
|
||||||
|
/// let contract = factory
|
||||||
|
/// .deploy("initial value".to_string())?
|
||||||
|
/// .confirmations(0usize)
|
||||||
|
/// .send()
|
||||||
|
/// .await?;
|
||||||
|
/// println!("{}", contract.address());
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
pub struct ContractFactory<'a, P, S> {
|
pub struct ContractFactory<'a, P, S> {
|
||||||
client: &'a Client<P, S>,
|
client: &'a Client<P, S>,
|
||||||
abi: &'a Abi,
|
abi: &'a Abi,
|
||||||
|
@ -79,8 +130,10 @@ where
|
||||||
S: Signer,
|
S: Signer,
|
||||||
P: JsonRpcClient,
|
P: JsonRpcClient,
|
||||||
{
|
{
|
||||||
/// Instantiate a new contract factory
|
/// Creates a factory for deployment of the Contract with bytecode, and the
|
||||||
pub fn new(client: &'a Client<P, S>, abi: &'a Abi, bytecode: &'a Bytes) -> Self {
|
/// constructor defined in the abi. The client will be used to send any deployment
|
||||||
|
/// transaction.
|
||||||
|
pub fn new(abi: &'a Abi, bytecode: &'a Bytes, client: &'a Client<P, S>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
abi,
|
abi,
|
||||||
|
@ -88,8 +141,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deploys an instance of the contract with the provider constructor arguments
|
/// Constructs the deployment transaction based on the provided constructor
|
||||||
/// and returns the contract's instance
|
/// arguments and returns a `Deployer` instance. You must call `send()` in order
|
||||||
|
/// to actually deploy the contract.
|
||||||
|
///
|
||||||
|
/// Notes:
|
||||||
|
/// 1. If there are no constructor arguments, you should pass `()` as the argument.
|
||||||
|
/// 1. The default poll duration is 7 seconds.
|
||||||
|
/// 1. The default number of confirmations is 1 block.
|
||||||
pub fn deploy<T: Tokenize>(
|
pub fn deploy<T: Tokenize>(
|
||||||
&self,
|
&self,
|
||||||
constructor_args: T,
|
constructor_args: T,
|
||||||
|
|
|
@ -1,24 +1,28 @@
|
||||||
mod contract;
|
mod contract;
|
||||||
pub use contract::Contract;
|
pub use contract::Contract;
|
||||||
|
|
||||||
mod event;
|
|
||||||
pub use event::Event;
|
|
||||||
|
|
||||||
mod call;
|
mod call;
|
||||||
pub use call::{ContractCall, ContractError};
|
pub use call::ContractError;
|
||||||
|
|
||||||
mod factory;
|
mod factory;
|
||||||
pub use factory::ContractFactory;
|
pub use factory::ContractFactory;
|
||||||
|
|
||||||
|
mod event;
|
||||||
|
|
||||||
|
/// This module exposes low lever builder structures which are only consumed by the
|
||||||
|
/// type-safe ABI bindings generators.
|
||||||
|
pub mod builders {
|
||||||
|
pub use super::call::ContractCall;
|
||||||
|
pub use super::event::Event;
|
||||||
|
pub use super::factory::Deployer;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "abigen")]
|
#[cfg(feature = "abigen")]
|
||||||
pub use ethers_contract_abigen::Abigen;
|
pub use ethers_contract_abigen::Abigen;
|
||||||
|
|
||||||
#[cfg(feature = "abigen")]
|
#[cfg(feature = "abigen")]
|
||||||
pub use ethers_contract_derive::abigen;
|
pub use ethers_contract_derive::abigen;
|
||||||
|
|
||||||
// re-export for convenience
|
// Hide the Lazy re-export, it's just for convenience
|
||||||
pub use ethers_core::abi;
|
#[doc(hidden)]
|
||||||
pub use ethers_core::types;
|
|
||||||
pub use ethers_providers as providers;
|
|
||||||
pub use ethers_signers as signers;
|
|
||||||
pub use once_cell::sync::Lazy;
|
pub use once_cell::sync::Lazy;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use ethers_contract::ContractFactory;
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Detokenize, InvalidOutputType, Token},
|
abi::{Detokenize, InvalidOutputType, Token},
|
||||||
types::{Address, H256},
|
types::{Address, H256},
|
||||||
utils::{GanacheBuilder, Solc},
|
utils::{Ganache, Solc},
|
||||||
};
|
};
|
||||||
use ethers_providers::{Http, Provider};
|
use ethers_providers::{Http, Provider};
|
||||||
use ethers_signers::Wallet;
|
use ethers_signers::Wallet;
|
||||||
|
@ -19,7 +19,7 @@ async fn deploy_and_call_contract() {
|
||||||
// launch ganache
|
// launch ganache
|
||||||
let port = 8546u64;
|
let port = 8546u64;
|
||||||
let url = format!("http://localhost:{}", port).to_string();
|
let url = format!("http://localhost:{}", port).to_string();
|
||||||
let _ganache = GanacheBuilder::new().port(port)
|
let _ganache = Ganache::new().port(port)
|
||||||
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
|
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
|
||||||
.spawn();
|
.spawn();
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ async fn deploy_and_call_contract() {
|
||||||
let client2 = wallet2.connect(provider);
|
let client2 = wallet2.connect(provider);
|
||||||
|
|
||||||
// create a factory which will be used to deploy instances of the contract
|
// create a factory which will be used to deploy instances of the contract
|
||||||
let factory = ContractFactory::new(&client, &contract.abi, &contract.bytecode);
|
let factory = ContractFactory::new(&contract.abi, &contract.bytecode, &client);
|
||||||
|
|
||||||
// `send` consumes the deployer so it must be cloned for later re-use
|
// `send` consumes the deployer so it must be cloned for later re-use
|
||||||
// (practically it's not expected that you'll need to deploy multiple instances of
|
// (practically it's not expected that you'll need to deploy multiple instances of
|
||||||
|
|
Loading…
Reference in New Issue