ic_web3_rs/contract/deploy.rs
1////! Contract deployment utilities
2//
3//use crate::{
4// api::{Eth, Namespace},
5// confirm,
6// contract::{tokens::Tokenize, Contract, Options},
7// error,
8// transports::ic_http_client::CallOptions,
9// types::{Address, Bytes, TransactionReceipt, TransactionRequest},
10// Transport,
11//};
12//#[cfg(feature = "signing")]
13//use crate::{signing::Key, types::TransactionParameters};
14//use futures::{Future, TryFutureExt};
15//use std::{collections::HashMap, time};
16//
17//pub use crate::contract::error::deploy::Error;
18//
19///// A configuration builder for contract deployment.
20//#[derive(Debug)]
21//pub 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: HashMap<String, Address>,
28//}
29//
30//impl<T: Transport> Builder<T> {
31// /// Number of confirmations required after code deployment.
32// pub fn confirmations(mut self, confirmations: usize) -> Self {
33// self.confirmations = confirmations;
34// self
35// }
36//
37// /// Deployment transaction options.
38// pub fn options(mut self, options: Options) -> Self {
39// self.options = options;
40// self
41// }
42//
43// /// Confirmations poll interval.
44// pub fn poll_interval(mut self, interval: time::Duration) -> Self {
45// self.poll_interval = interval;
46// self
47// }
48//
49// /// Execute deployment passing code and contructor parameters.
50// pub async fn execute<P, V>(
51// self,
52// code: V,
53// params: P,
54// from: Address,
55// options: CallOptions,
56// ) -> Result<Contract<T>, Error>
57// where
58// P: Tokenize,
59// V: AsRef<str>,
60// {
61// let transport = self.eth.transport().clone();
62// let poll_interval = self.poll_interval;
63// let confirmations = self.confirmations;
64//
65// self.do_execute(code, params, from, move |tx| {
66// confirm::send_transaction_with_confirmation(transport, tx, poll_interval, confirmations, options)
67// })
68// .await
69// }
70// /// Execute deployment passing code and constructor parameters.
71// ///
72// /// Unlike the above `execute`, this method uses
73// /// `sign_raw_transaction_with_confirmation` instead of
74// /// `sign_transaction_with_confirmation`, which requires the account from
75// /// which the transaction is sent to be unlocked.
76// pub async fn sign_and_execute<P, V>(
77// self,
78// code: V,
79// params: P,
80// from: Address,
81// password: &str,
82// options: CallOptions,
83// ) -> Result<Contract<T>, Error>
84// where
85// P: Tokenize,
86// V: AsRef<str>,
87// {
88// let transport = self.eth.transport().clone();
89// let poll_interval = self.poll_interval;
90// let confirmations = self.confirmations;
91//
92// self.do_execute(code, params, from, move |tx| {
93// crate::api::Personal::new(transport.clone())
94// .sign_transaction(tx, password, options.clone())
95// .and_then(move |signed_tx| {
96// confirm::send_raw_transaction_with_confirmation(
97// transport,
98// signed_tx.raw,
99// poll_interval,
100// confirmations,
101// options,
102// )
103// })
104// })
105// .await
106// }
107//
108// /// Execute deployment passing code and constructor parameters.
109// ///
110// /// Unlike the above `sign_and_execute`, this method allows the
111// /// caller to pass in a private key to sign the transaction with
112// /// and therefore allows deploying from an account that the
113// /// ethereum node doesn't need to know the private key for.
114// ///
115// /// An optional `chain_id` parameter can be passed to provide
116// /// replay protection for transaction signatures. Passing `None`
117// /// would create a transaction WITHOUT replay protection and
118// /// should be avoided.
119// /// You can obtain `chain_id` of the network you are connected
120// /// to using `web3.eth().chain_id()` method.
121// #[cfg(feature = "signing")]
122// pub async fn sign_with_key_and_execute<P, V, K>(
123// self,
124// code: V,
125// params: P,
126// from: K,
127// chain_id: Option<u64>,
128// ) -> Result<Contract<T>, Error>
129// where
130// P: Tokenize,
131// V: AsRef<str>,
132// K: Key,
133// {
134// let transport = self.eth.transport().clone();
135// let poll_interval = self.poll_interval;
136// let confirmations = self.confirmations;
137//
138// self.do_execute(code, params, from.address(), move |tx| async move {
139// let tx = TransactionParameters {
140// nonce: tx.nonce,
141// to: tx.to,
142// gas: tx.gas.unwrap_or_else(|| 1_000_000.into()),
143// gas_price: tx.gas_price,
144// value: tx.value.unwrap_or_else(|| 0.into()),
145// data: tx
146// .data
147// .expect("Tried to deploy a contract but transaction data wasn't set"),
148// chain_id,
149// transaction_type: tx.transaction_type,
150// access_list: tx.access_list,
151// max_fee_per_gas: tx.max_fee_per_gas,
152// max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
153// };
154// let signed_tx = crate::api::Accounts::new(transport.clone())
155// .sign_transaction(tx, from)
156// .await?;
157// confirm::send_raw_transaction_with_confirmation(
158// transport,
159// signed_tx.raw_transaction,
160// poll_interval,
161// confirmations,
162// )
163// .await
164// })
165// .await
166// }
167//
168// async fn do_execute<P, V, Ft>(
169// self,
170// code: V,
171// params: P,
172// from: Address,
173// send: impl FnOnce(TransactionRequest) -> Ft,
174// ) -> Result<Contract<T>, Error>
175// where
176// P: Tokenize,
177// V: AsRef<str>,
178// Ft: Future<Output = error::Result<TransactionReceipt>>,
179// {
180// let options = self.options;
181// let eth = self.eth;
182// let abi = self.abi;
183//
184// let mut code_hex = code.as_ref().to_string();
185//
186// for (lib, address) in self.linker {
187// if lib.len() > 38 {
188// return Err(Error::Abi(ethabi::Error::InvalidName(
189// "The library name should be under 39 characters.".into(),
190// )));
191// }
192// let replace = format!("__{:_<38}", lib); // This makes the required width 38 characters and will pad with `_` to match it.
193// let address: String = hex::encode(address);
194// code_hex = code_hex.replacen(&replace, &address, 1);
195// }
196// code_hex = code_hex.replace("\"", "").replace("0x", ""); // This is to fix truffle + serde_json redundant `"` and `0x`
197// let code =
198// hex::decode(&code_hex).map_err(|e| ethabi::Error::InvalidName(format!("hex decode error: {}", e)))?;
199//
200// let params = params.into_tokens();
201// let data = match (abi.constructor(), params.is_empty()) {
202// (None, false) => {
203// return Err(Error::Abi(ethabi::Error::InvalidName(
204// "Constructor is not defined in the ABI.".into(),
205// )));
206// }
207// (None, true) => code,
208// (Some(constructor), _) => constructor.encode_input(code, ¶ms)?,
209// };
210//
211// let tx = TransactionRequest {
212// from,
213// to: None,
214// gas: options.gas,
215// gas_price: options.gas_price,
216// value: options.value,
217// nonce: options.nonce,
218// data: Some(Bytes(data)),
219// condition: options.condition,
220// transaction_type: options.transaction_type,
221// access_list: options.access_list,
222// max_fee_per_gas: options.max_fee_per_gas,
223// max_priority_fee_per_gas: options.max_priority_fee_per_gas,
224// };
225// let receipt = send(tx).await?;
226// match receipt.status {
227// Some(status) if status == 0.into() => Err(Error::ContractDeploymentFailure(receipt.transaction_hash)),
228// // If the `status` field is not present we use the presence of `contract_address` to
229// // determine if deployment was successfull.
230// _ => match receipt.contract_address {
231// Some(address) => Ok(Contract::new(eth, address, abi)),
232// None => Err(Error::ContractDeploymentFailure(receipt.transaction_hash)),
233// },
234// }
235// }
236//}
237//
238//#[cfg(test)]
239//mod tests {
240// use crate::{
241// api::{self, Namespace},
242// contract::{Contract, Options},
243// rpc,
244// transports::{ic_http_client::CallOptions, test::TestTransport},
245// types::{Address, Call, U256},
246// };
247// use serde_json::Value;
248// use std::collections::HashMap;
249//
250// #[test]
251// fn should_deploy_a_contract() {
252// // given
253// let mut transport = TestTransport::default();
254// // Transaction Hash
255// transport.add_response(rpc::Value::String(
256// "0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1".into(),
257// ));
258// // BlockFilter
259// transport.add_response(rpc::Value::String("0x0".into()));
260// // getFilterChanges
261// transport.add_response(rpc::Value::Array(vec![rpc::Value::String(
262// "0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c54878f".into(),
263// )]));
264// transport.add_response(rpc::Value::Array(vec![rpc::Value::String(
265// "0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c548790".into(),
266// )]));
267// // receipt
268// let receipt = ::serde_json::from_str::<rpc::Value>(
269// "{\"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\"}"
270// ).unwrap();
271// transport.add_response(receipt.clone());
272// // block number
273// transport.add_response(rpc::Value::String("0x25a".into()));
274// // receipt again
275// transport.add_response(receipt);
276//
277// {
278// let builder = Contract::deploy(api::Eth::new(&transport), include_bytes!("./res/token.json")).unwrap();
279//
280// // when
281// futures::executor::block_on(
282// builder
283// .options(Options::with(|opt| opt.value = Some(5.into())))
284// .confirmations(1)
285// .execute(
286// "0x01020304",
287// (U256::from(1_000_000), "My Token".to_owned(), 3u64, "MT".to_owned()),
288// Address::from_low_u64_be(5),
289// CallOptions::default(),
290// ),
291// )
292// .unwrap()
293// };
294//
295// // then
296// transport.assert_request("eth_sendTransaction", &[
297// "{\"data\":\"0x0102030400000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000084d7920546f6b656e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024d54000000000000000000000000000000000000000000000000000000000000\",\"from\":\"0x0000000000000000000000000000000000000005\",\"value\":\"0x5\"}".into(),
298// ]);
299// transport.assert_request("eth_newBlockFilter", &[]);
300// transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
301// transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
302// transport.assert_request(
303// "eth_getTransactionReceipt",
304// &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
305// );
306// transport.assert_request("eth_blockNumber", &[]);
307// transport.assert_request(
308// "eth_getTransactionReceipt",
309// &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
310// );
311// transport.assert_no_more_requests();
312// }
313//
314// #[test]
315// fn deploy_linked_contract() {
316// use serde_json::{to_string, to_vec};
317// let mut transport = TestTransport::default();
318// let receipt = ::serde_json::from_str::<rpc::Value>(
319// "{\"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\"}"
320// ).unwrap();
321//
322// for _ in 0..2 {
323// transport.add_response(rpc::Value::String(
324// "0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1".into(),
325// ));
326// transport.add_response(rpc::Value::String("0x0".into()));
327// transport.add_response(rpc::Value::Array(vec![rpc::Value::String(
328// "0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c54878f".into(),
329// )]));
330// transport.add_response(rpc::Value::Array(vec![rpc::Value::String(
331// "0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c548790".into(),
332// )]));
333// transport.add_response(receipt.clone());
334// transport.add_response(rpc::Value::String("0x25a".into()));
335// transport.add_response(receipt.clone());
336// }
337//
338// let lib: Value = serde_json::from_slice(include_bytes!("./res/MyLibrary.json")).unwrap();
339// let lib_abi: Vec<u8> = to_vec(&lib["abi"]).unwrap();
340// let lib_code = to_string(&lib["bytecode"]).unwrap();
341//
342// let main: Value = serde_json::from_slice(include_bytes!("./res/Main.json")).unwrap();
343// let main_abi: Vec<u8> = to_vec(&main["abi"]).unwrap();
344// let main_code = to_string(&main["bytecode"]).unwrap();
345//
346// let lib_address;
347// {
348// let builder = Contract::deploy(api::Eth::new(&transport), &lib_abi).unwrap();
349// lib_address =
350// futures::executor::block_on(builder.execute(lib_code, (), Address::zero(), CallOptions::default()))
351// .unwrap()
352// .address();
353// }
354//
355// transport.assert_request("eth_sendTransaction", &[
356// "{\"data\":\"0x60ad61002f600b82828239805160001a6073146000811461001f57610021565bfe5b5030600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106056576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f8a8fd6d14605b575b600080fd5b60616077565b6040518082815260200191505060405180910390f35b600061010090509056fea165627a7a72305820b50091adcb7ef9987dd8daa665cec572801bf8243530d70d52631f9d5ddb943e0029\",\"from\":\"0x0000000000000000000000000000000000000000\"}"
357// .into()]);
358// transport.assert_request("eth_newBlockFilter", &[]);
359// transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
360// transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
361// transport.assert_request(
362// "eth_getTransactionReceipt",
363// &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
364// );
365// transport.assert_request("eth_blockNumber", &[]);
366// transport.assert_request(
367// "eth_getTransactionReceipt",
368// &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
369// );
370// transport.assert_no_more_requests();
371// {
372// let builder = Contract::deploy_from_truffle(api::Eth::new(&transport), &main_abi, {
373// let mut linker = HashMap::new();
374// linker.insert("MyLibrary", lib_address);
375// linker
376// })
377// .unwrap();
378// let _ =
379// futures::executor::block_on(builder.execute(main_code, (), Address::zero(), CallOptions::default()))
380// .unwrap();
381// }
382//
383// transport.assert_request("eth_sendTransaction", &[
384// "{\"data\":\"0x608060405234801561001057600080fd5b5061013f806100206000396000f3fe608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f8a8fd6d14610046575b600080fd5b34801561005257600080fd5b5061005b610071565b6040518082815260200191505060405180910390f35b600073600515dfe465f600f0c9793fa27cd2794f3ec0e163f8a8fd6d6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b1580156100d357600080fd5b505af41580156100e7573d6000803e3d6000fd5b505050506040513d60208110156100fd57600080fd5b810190808051906020019092919050505090509056fea165627a7a72305820580d3776b3d132142f431e141a2e20bd4dd4907fa304feea7b604e8f39ed59520029\",\"from\":\"0x0000000000000000000000000000000000000000\"}"
385// .into()]);
386//
387// transport.assert_request("eth_newBlockFilter", &[]);
388// transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
389// transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
390// transport.assert_request(
391// "eth_getTransactionReceipt",
392// &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
393// );
394// transport.assert_request("eth_blockNumber", &[]);
395// transport.assert_request(
396// "eth_getTransactionReceipt",
397// &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()],
398// );
399// transport.assert_no_more_requests();
400// }
401//}