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