1use crate::{
4 api::{Eth, Namespace},
5 confirm,
6 contract::{tokens::Tokenize, Contract, Options},
7 error,
8 types::{Address, Bytes, TransactionReceipt, TransactionRequest},
9 Transport,
10};
11#[cfg(feature = "signing")]
12use crate::{signing::Key, types::TransactionParameters};
13use futures::{Future, TryFutureExt};
14use std::{collections::HashMap, time};
15
16pub use crate::contract::error::deploy::Error;
17
18#[derive(Debug)]
20pub struct Builder<T: Transport> {
21 pub(crate) eth: Eth<T>,
22 pub(crate) abi: ethabi::Contract,
23 pub(crate) options: Options,
24 pub(crate) confirmations: usize,
25 pub(crate) poll_interval: time::Duration,
26 pub(crate) linker: HashMap<String, Address>,
27}
28
29impl<T: Transport> Builder<T> {
30 pub fn confirmations(mut self, confirmations: usize) -> Self {
32 self.confirmations = confirmations;
33 self
34 }
35
36 pub fn options(mut self, options: Options) -> Self {
38 self.options = options;
39 self
40 }
41
42 pub fn poll_interval(mut self, interval: time::Duration) -> Self {
44 self.poll_interval = interval;
45 self
46 }
47
48 pub async fn execute<P, V>(self, code: V, params: P, from: Address) -> Result<Contract<T>, Error>
50 where
51 P: Tokenize,
52 V: AsRef<str>,
53 {
54 let transport = self.eth.transport().clone();
55 let poll_interval = self.poll_interval;
56 let confirmations = self.confirmations;
57
58 self.do_execute(code, params, from, move |tx| {
59 confirm::send_transaction_with_confirmation(transport, tx, poll_interval, confirmations)
60 })
61 .await
62 }
63 pub async fn sign_and_execute<P, V>(
70 self,
71 code: V,
72 params: P,
73 from: Address,
74 password: &str,
75 ) -> Result<Contract<T>, Error>
76 where
77 P: Tokenize,
78 V: AsRef<str>,
79 {
80 let transport = self.eth.transport().clone();
81 let poll_interval = self.poll_interval;
82 let confirmations = self.confirmations;
83
84 self.do_execute(code, params, from, move |tx| {
85 crate::api::Personal::new(transport.clone())
86 .sign_transaction(tx, password)
87 .and_then(move |signed_tx| {
88 confirm::send_raw_transaction_with_confirmation(
89 transport,
90 signed_tx.raw,
91 poll_interval,
92 confirmations,
93 )
94 })
95 })
96 .await
97 }
98
99 #[cfg(feature = "signing")]
113 pub async fn sign_with_key_and_execute<P, V, K>(
114 self,
115 code: V,
116 params: P,
117 from: K,
118 chain_id: Option<u64>,
119 ) -> Result<Contract<T>, Error>
120 where
121 P: Tokenize,
122 V: AsRef<str>,
123 K: Key,
124 {
125 let transport = self.eth.transport().clone();
126 let poll_interval = self.poll_interval;
127 let confirmations = self.confirmations;
128
129 self.do_execute(code, params, from.address(), move |tx| async move {
130 let tx = TransactionParameters {
131 nonce: tx.nonce,
132 to: tx.to,
133 gas: tx.gas.unwrap_or_else(|| 1_000_000.into()),
134 gas_price: tx.gas_price,
135 value: tx.value.unwrap_or_else(|| 0.into()),
136 data: tx
137 .data
138 .expect("Tried to deploy a contract but transaction data wasn't set"),
139 chain_id,
140 transaction_type: tx.transaction_type,
141 access_list: tx.access_list,
142 max_fee_per_gas: tx.max_fee_per_gas,
143 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
144 };
145 let signed_tx = crate::api::Accounts::new(transport.clone())
146 .sign_transaction(tx, from)
147 .await?;
148 confirm::send_raw_transaction_with_confirmation(
149 transport,
150 signed_tx.raw_transaction,
151 poll_interval,
152 confirmations,
153 )
154 .await
155 })
156 .await
157 }
158
159 async fn do_execute<P, V, Ft>(
160 self,
161 code: V,
162 params: P,
163 from: Address,
164 send: impl FnOnce(TransactionRequest) -> Ft,
165 ) -> Result<Contract<T>, Error>
166 where
167 P: Tokenize,
168 V: AsRef<str>,
169 Ft: Future<Output = error::Result<TransactionReceipt>>,
170 {
171 let options = self.options;
172 let eth = self.eth;
173 let abi = self.abi;
174
175 let mut code_hex = code.as_ref().to_string();
176
177 for (lib, address) in self.linker {
178 if lib.len() > 38 {
179 return Err(Error::Abi(ethabi::Error::InvalidName(
180 "The library name should be under 39 characters.".into(),
181 )));
182 }
183 let replace = format!("__{:_<38}", lib); let address: String = hex::encode(address);
185 code_hex = code_hex.replacen(&replace, &address, 1);
186 }
187 code_hex = code_hex.replace("\"", "").replace("0x", ""); let code =
189 hex::decode(&code_hex).map_err(|e| ethabi::Error::InvalidName(format!("hex decode error: {}", e)))?;
190
191 let params = params.into_tokens();
192 let data = match (abi.constructor(), params.is_empty()) {
193 (None, false) => {
194 return Err(Error::Abi(ethabi::Error::InvalidName(
195 "Constructor is not defined in the ABI.".into(),
196 )));
197 }
198 (None, true) => code,
199 (Some(constructor), _) => constructor.encode_input(code, ¶ms)?,
200 };
201
202 let tx = TransactionRequest {
203 from,
204 to: None,
205 gas: options.gas,
206 gas_price: options.gas_price,
207 value: options.value,
208 nonce: options.nonce,
209 data: Some(Bytes(data)),
210 condition: options.condition,
211 transaction_type: options.transaction_type,
212 access_list: options.access_list,
213 max_fee_per_gas: options.max_fee_per_gas,
214 max_priority_fee_per_gas: options.max_priority_fee_per_gas,
215 };
216 let receipt = send(tx).await?;
217 match receipt.status {
218 Some(status) if status == 0.into() => Err(Error::ContractDeploymentFailure(receipt.transaction_hash)),
219 _ => match receipt.contract_address {
222 Some(address) => Ok(Contract::new(eth, address, abi)),
223 None => Err(Error::ContractDeploymentFailure(receipt.transaction_hash)),
224 },
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use crate::{
232 api::{self, Namespace},
233 contract::{Contract, Options},
234 rpc,
235 transports::test::TestTransport,
236 types::{Address, U256},
237 };
238 use serde_json::Value;
239 use std::collections::HashMap;
240
241 #[test]
242 fn should_deploy_a_contract() {
243 let mut transport = TestTransport::default();
245 transport.add_response(rpc::Value::String(
247 "0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1".into(),
248 ));
249 transport.add_response(rpc::Value::String("0x0".into()));
251 transport.add_response(rpc::Value::Array(vec![rpc::Value::String(
253 "0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c54878f".into(),
254 )]));
255 transport.add_response(rpc::Value::Array(vec![rpc::Value::String(
256 "0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c548790".into(),
257 )]));
258 let receipt = ::serde_json::from_str::<rpc::Value>(
260 "{\"blockHash\":\"0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c54878f\",\"blockNumber\":\"0x256\",\"contractAddress\":\"0x600515dfe465f600f0c9793fa27cd2794f3ec0e1\",\"from\": \"0x407d73d8a49eeb85d32cf465507dd71d507100c1\",\"cumulativeGasUsed\":\"0xe57e0\",\"gasUsed\":\"0xe57e0\",\"logs\":[],\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"root\":null,\"transactionHash\":\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\",\"transactionIndex\":\"0x0\", \"status\": \"0x1\", \"effectiveGasPrice\": \"0x100\"}"
261 ).unwrap();
262 transport.add_response(receipt.clone());
263 transport.add_response(rpc::Value::String("0x25a".into()));
265 transport.add_response(receipt);
267
268 {
269 let builder = Contract::deploy(api::Eth::new(&transport), include_bytes!("./res/token.json")).unwrap();
270
271 futures::executor::block_on(
273 builder
274 .options(Options::with(|opt| opt.value = Some(5.into())))
275 .confirmations(1)
276 .execute(
277 "0x01020304",
278 (U256::from(1_000_000), "My Token".to_owned(), 3u64, "MT".to_owned()),
279 Address::from_low_u64_be(5),
280 ),
281 )
282 .unwrap()
283 };
284
285 transport.assert_request("eth_sendTransaction", &[
287 "{\"data\":\"0x0102030400000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000084d7920546f6b656e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024d54000000000000000000000000000000000000000000000000000000000000\",\"from\":\"0x0000000000000000000000000000000000000005\",\"value\":\"0x5\"}".into(),
288 ]);
289 transport.assert_request("eth_newBlockFilter", &[]);
290 transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
291 transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
292 transport.assert_request(
293 "eth_getTransactionReceipt",
294 &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
295 );
296 transport.assert_request("eth_blockNumber", &[]);
297 transport.assert_request(
298 "eth_getTransactionReceipt",
299 &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
300 );
301 transport.assert_no_more_requests();
302 }
303
304 #[test]
305 fn deploy_linked_contract() {
306 use serde_json::{to_string, to_vec};
307 let mut transport = TestTransport::default();
308 let receipt = ::serde_json::from_str::<rpc::Value>(
309 "{\"blockHash\":\"0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c54878f\",\"blockNumber\":\"0x256\",\"contractAddress\":\"0x600515dfe465f600f0c9793fa27cd2794f3ec0e1\",\"from\":\"0x407d73d8a49eeb85d32cf465507dd71d507100c1\",\"cumulativeGasUsed\":\"0xe57e0\",\"gasUsed\":\"0xe57e0\",\"logs\":[],\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"root\":null,\"transactionHash\":\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\",\"transactionIndex\":\"0x0\", \"status\": \"0x1\", \"effectiveGasPrice\": \"0x100\"}"
310 ).unwrap();
311
312 for _ in 0..2 {
313 transport.add_response(rpc::Value::String(
314 "0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1".into(),
315 ));
316 transport.add_response(rpc::Value::String("0x0".into()));
317 transport.add_response(rpc::Value::Array(vec![rpc::Value::String(
318 "0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c54878f".into(),
319 )]));
320 transport.add_response(rpc::Value::Array(vec![rpc::Value::String(
321 "0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c548790".into(),
322 )]));
323 transport.add_response(receipt.clone());
324 transport.add_response(rpc::Value::String("0x25a".into()));
325 transport.add_response(receipt.clone());
326 }
327
328 let lib: Value = serde_json::from_slice(include_bytes!("./res/MyLibrary.json")).unwrap();
329 let lib_abi: Vec<u8> = to_vec(&lib["abi"]).unwrap();
330 let lib_code = to_string(&lib["bytecode"]).unwrap();
331
332 let main: Value = serde_json::from_slice(include_bytes!("./res/Main.json")).unwrap();
333 let main_abi: Vec<u8> = to_vec(&main["abi"]).unwrap();
334 let main_code = to_string(&main["bytecode"]).unwrap();
335
336 let lib_address;
337 {
338 let builder = Contract::deploy(api::Eth::new(&transport), &lib_abi).unwrap();
339 lib_address = futures::executor::block_on(builder.execute(lib_code, (), Address::zero()))
340 .unwrap()
341 .address();
342 }
343
344 transport.assert_request("eth_sendTransaction", &[
345 "{\"data\":\"0x60ad61002f600b82828239805160001a6073146000811461001f57610021565bfe5b5030600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106056576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f8a8fd6d14605b575b600080fd5b60616077565b6040518082815260200191505060405180910390f35b600061010090509056fea165627a7a72305820b50091adcb7ef9987dd8daa665cec572801bf8243530d70d52631f9d5ddb943e0029\",\"from\":\"0x0000000000000000000000000000000000000000\"}"
346 .into()]);
347 transport.assert_request("eth_newBlockFilter", &[]);
348 transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
349 transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
350 transport.assert_request(
351 "eth_getTransactionReceipt",
352 &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
353 );
354 transport.assert_request("eth_blockNumber", &[]);
355 transport.assert_request(
356 "eth_getTransactionReceipt",
357 &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
358 );
359 transport.assert_no_more_requests();
360 {
361 let builder = Contract::deploy_from_truffle(api::Eth::new(&transport), &main_abi, {
362 let mut linker = HashMap::new();
363 linker.insert("MyLibrary", lib_address);
364 linker
365 })
366 .unwrap();
367 let _ = futures::executor::block_on(builder.execute(main_code, (), Address::zero())).unwrap();
368 }
369
370 transport.assert_request("eth_sendTransaction", &[
371 "{\"data\":\"0x608060405234801561001057600080fd5b5061013f806100206000396000f3fe608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f8a8fd6d14610046575b600080fd5b34801561005257600080fd5b5061005b610071565b6040518082815260200191505060405180910390f35b600073600515dfe465f600f0c9793fa27cd2794f3ec0e163f8a8fd6d6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b1580156100d357600080fd5b505af41580156100e7573d6000803e3d6000fd5b505050506040513d60208110156100fd57600080fd5b810190808051906020019092919050505090509056fea165627a7a72305820580d3776b3d132142f431e141a2e20bd4dd4907fa304feea7b604e8f39ed59520029\",\"from\":\"0x0000000000000000000000000000000000000000\"}"
372 .into()]);
373
374 transport.assert_request("eth_newBlockFilter", &[]);
375 transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
376 transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
377 transport.assert_request(
378 "eth_getTransactionReceipt",
379 &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
380 );
381 transport.assert_request("eth_blockNumber", &[]);
382 transport.assert_request(
383 "eth_getTransactionReceipt",
384 &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
385 );
386 transport.assert_no_more_requests();
387 }
388}