ethcontract/contract/
deploy.rs

1//! Implementation for creating instances for deployed contracts and deploying
2//! new contracts.
3
4use crate::errors::{DeployError, ExecutionError};
5use crate::tokens::Tokenize;
6use crate::transaction::{Account, GasPrice, TransactionBuilder, TransactionResult};
7use ethcontract_common::abi::Error as AbiError;
8use ethcontract_common::{Abi, Bytecode};
9use std::marker::PhantomData;
10use web3::api::Web3;
11use web3::types::{Address, Bytes, H256, U256};
12use web3::Transport;
13
14/// a factory trait for deployable contract instances. this traits provides
15/// functionality for building a deployment and creating instances of a
16/// contract type at a given address.
17///
18/// this allows generated contracts to be deployable without having to create
19/// new builder and future types.
20pub trait Deploy<T: Transport>: Sized {
21    /// The type of the contract instance being created.
22    type Context;
23
24    /// Gets a reference to the contract bytecode.
25    fn bytecode(cx: &Self::Context) -> &Bytecode;
26
27    /// Gets a reference the contract ABI.
28    fn abi(cx: &Self::Context) -> &Abi;
29
30    /// Create a contract instance from the specified deployment.
31    fn from_deployment(
32        web3: Web3<T>,
33        address: Address,
34        transaction_hash: H256,
35        cx: Self::Context,
36    ) -> Self;
37}
38
39/// Builder for specifying options for deploying a linked contract.
40#[derive(Debug, Clone)]
41#[must_use = "deploy builers do nothing unless you `.deploy()` them"]
42pub struct DeployBuilder<T, I>
43where
44    T: Transport,
45    I: Deploy<T>,
46{
47    /// The underlying `web3` provider.
48    web3: Web3<T>,
49    /// The factory context.
50    context: I::Context,
51    /// The underlying transaction used t
52    tx: TransactionBuilder<T>,
53    _instance: PhantomData<I>,
54}
55
56impl<T, I> DeployBuilder<T, I>
57where
58    T: Transport,
59    I: Deploy<T>,
60{
61    /// Create a new deploy builder from a `web3` provider, contract data and
62    /// deployment (constructor) parameters.
63    pub fn new<P>(web3: Web3<T>, context: I::Context, params: P) -> Result<Self, DeployError>
64    where
65        P: Tokenize,
66    {
67        // NOTE(nlordell): unfortunately here we have to re-implement some
68        //   `rust-web3` code so that we can add things like signing support;
69        //   luckily most of complicated bits can be reused from the tx code
70
71        let bytecode = I::bytecode(&context);
72        if bytecode.is_empty() {
73            return Err(DeployError::EmptyBytecode);
74        }
75
76        let code = bytecode.to_bytes()?;
77        let params = match params.into_token() {
78            ethcontract_common::abi::Token::Tuple(tokens) => tokens,
79            _ => unreachable!("function arguments are always tuples"),
80        };
81        let data = match (I::abi(&context).constructor(), params.is_empty()) {
82            (None, false) => return Err(AbiError::InvalidData.into()),
83            (None, true) => code,
84            (Some(ctor), _) => Bytes(ctor.encode_input(code.0, &params)?),
85        };
86
87        Ok(DeployBuilder {
88            web3: web3.clone(),
89            context,
90            tx: TransactionBuilder::new(web3).data(data).confirmations(0),
91            _instance: PhantomData,
92        })
93    }
94
95    /// Specify the signing method to use for the transaction, if not specified
96    /// the the transaction will be locally signed with the default user.
97    pub fn from(mut self, value: Account) -> Self {
98        self.tx = self.tx.from(value);
99        self
100    }
101
102    /// Secify amount of gas to use, if not specified then a gas estimate will
103    /// be used.
104    pub fn gas(mut self, value: U256) -> Self {
105        self.tx = self.tx.gas(value);
106        self
107    }
108
109    /// Specify the gas price to use, if not specified then the estimated gas
110    /// price will be used.
111    pub fn gas_price(mut self, value: GasPrice) -> Self {
112        self.tx = self.tx.gas_price(value);
113        self
114    }
115
116    /// Specify what how much ETH to transfer with the transaction, if not
117    /// specified then no ETH will be sent.
118    pub fn value(mut self, value: U256) -> Self {
119        self.tx = self.tx.value(value);
120        self
121    }
122
123    /// Specify the nonce for the transation, if not specified will use the
124    /// current transaction count for the signing account.
125    pub fn nonce(mut self, value: U256) -> Self {
126        self.tx = self.tx.nonce(value);
127        self
128    }
129
130    /// Specify the number of confirmations to wait for when confirming the
131    /// transaction, if not specified will wait for the transaction to be mined
132    /// without any extra confirmations.
133    pub fn confirmations(mut self, value: usize) -> Self {
134        self.tx = self.tx.confirmations(value);
135        self
136    }
137
138    /// Extract inner `TransactionBuilder` from this `DeployBuilder`. This
139    /// exposes `TransactionBuilder` only APIs.
140    pub fn into_inner(self) -> TransactionBuilder<T> {
141        self.tx
142    }
143
144    /// Sign (if required) and execute the transaction. Returns the transaction
145    /// hash that can be used to retrieve transaction information.
146    pub async fn deploy(self) -> Result<I, DeployError> {
147        let tx = match self.tx.send().await? {
148            TransactionResult::Receipt(tx) => tx,
149            TransactionResult::Hash(tx) => return Err(DeployError::Pending(tx)),
150        };
151
152        let transaction_hash = tx.transaction_hash;
153        let address = tx
154            .contract_address
155            .ok_or_else(|| ExecutionError::Failure(Box::new(tx)))?;
156
157        Ok(I::from_deployment(
158            self.web3,
159            address,
160            transaction_hash,
161            self.context,
162        ))
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::contract::{Instance, Linker};
170    use crate::test::prelude::*;
171    use ethcontract_common::Contract;
172
173    type InstanceDeployBuilder<T> = DeployBuilder<T, Instance<T>>;
174
175    #[test]
176    fn deploy_tx_options() {
177        let transport = TestTransport::new();
178        let web3 = Web3::new(transport.clone());
179
180        let from = addr!("0x9876543210987654321098765432109876543210");
181        let bytecode = Bytecode::from_hex_str("0x42").unwrap();
182        let contract = Contract {
183            bytecode: bytecode.clone(),
184            ..Contract::empty()
185        };
186        let linker = Linker::new(contract);
187        let tx = InstanceDeployBuilder::new(web3, linker, ())
188            .expect("error creating deploy builder")
189            .from(Account::Local(from, None))
190            .gas(1.into())
191            .gas_price(2.0.into())
192            .value(28.into())
193            .nonce(42.into())
194            .into_inner();
195
196        assert_eq!(tx.from.map(|a| a.address()), Some(from));
197        assert_eq!(tx.to, None);
198        assert_eq!(tx.gas, Some(1.into()));
199        assert_eq!(tx.gas_price, Some(2.0.into()));
200        assert_eq!(tx.value, Some(28.into()));
201        assert_eq!(tx.data, Some(bytecode.to_bytes().unwrap()));
202        assert_eq!(tx.nonce, Some(42.into()));
203        transport.assert_no_more_requests();
204    }
205
206    #[test]
207    fn deploy() {
208        // TODO(nlordell): implement this test - there is an open issue for this
209        //   on github
210    }
211
212    #[test]
213    fn deploy_fails_on_empty_bytecode() {
214        let transport = TestTransport::new();
215        let web3 = Web3::new(transport.clone());
216
217        let contract = Contract::empty();
218        let linker = Linker::new(contract);
219        let error = InstanceDeployBuilder::new(web3, linker, ()).err().unwrap();
220
221        assert_eq!(error.to_string(), DeployError::EmptyBytecode.to_string());
222        transport.assert_no_more_requests();
223    }
224}