// Copyright 2018-2020 Parity Technologies (UK) Ltd. // This file is part of cargo-contract. // // cargo-contract is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // cargo-contract is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . use super::{ display_contract_exec_result, display_events, dry_run_error_details, parse_balance, runtime_api::api, wait_for_success_and_handle_error, Balance, CodeHash, ContractAccount, ContractMessageTranscoder, ExtrinsicOpts, PairSigner, RuntimeApi, RuntimeDispatchError, EXEC_RESULT_MAX_KEY_COL_WIDTH, }; use crate::{ name_value_println, util::decode_hex, Verbosity, }; use anyhow::{ anyhow, Context, Result, }; use jsonrpsee::{ core::client::ClientT, rpc_params, ws_client::WsClientBuilder, }; use pallet_contracts_primitives::{ ContractResult, InstantiateReturnValue, }; use serde::Serialize; use sp_core::{ crypto::Ss58Codec, Bytes, }; use std::{ fs, path::{ Path, PathBuf, }, result, }; use subxt::{ rpc::NumberOrHex, ClientBuilder, Config, DefaultConfig, }; type ContractInstantiateResult = ContractResult< result::Result, RuntimeDispatchError>, Balance, >; #[derive(Debug, clap::Args)] pub struct InstantiateCommand { /// Path to Wasm contract code, defaults to `./target/ink/.wasm`. /// Use to instantiate contracts which have not yet been uploaded. /// If the contract has already been uploaded use `--code-hash` instead. #[clap(parse(from_os_str))] wasm_path: Option, /// The hash of the smart contract code already uploaded to the chain. /// If the contract has not already been uploaded use `--wasm-path` or run the `upload` command /// first. #[clap(long, parse(try_from_str = parse_code_hash))] code_hash: Option<::Hash>, /// The name of the contract constructor to call #[clap(name = "constructor", long, default_value = "new")] constructor: String, /// The constructor arguments, encoded as strings #[clap(long, multiple_values = true)] args: Vec, #[clap(flatten)] extrinsic_opts: ExtrinsicOpts, /// Transfers an initial balance to the instantiated contract #[clap(name = "value", long, default_value = "0", parse(try_from_str = parse_balance))] value: Balance, /// Maximum amount of gas to be used for this command #[clap(name = "gas", long, default_value = "50000000000")] gas_limit: u64, /// A salt used in the address derivation of the new contract. Use to create multiple instances /// of the same contract code from the same account. #[clap(long, parse(try_from_str = parse_hex_bytes))] salt: Option, } /// Parse a hex encoded 32 byte hash. Returns error if not exactly 32 bytes. fn parse_code_hash(input: &str) -> Result<::Hash> { let bytes = decode_hex(input)?; if bytes.len() != 32 { anyhow::bail!("Code hash should be 32 bytes in length") } let mut arr = [0u8; 32]; arr.copy_from_slice(&bytes); Ok(arr.into()) } /// Parse hex encoded bytes. fn parse_hex_bytes(input: &str) -> Result { let bytes = decode_hex(input)?; Ok(bytes.into()) } impl InstantiateCommand { /// Instantiate a contract stored at the supplied code hash. /// Returns the account id of the instantiated contract if successful. /// /// Creates an extrinsic with the `Contracts::instantiate` Call, submits via RPC, then waits for /// the `ContractsEvent::Instantiated` event. pub fn run(&self) -> Result<()> { let (crate_metadata, contract_metadata) = super::load_metadata(self.extrinsic_opts.manifest_path.as_ref())?; let transcoder = ContractMessageTranscoder::new(&contract_metadata); let data = transcoder.encode(&self.constructor, &self.args)?; let signer = super::pair_signer(self.extrinsic_opts.signer()?); let url = self.extrinsic_opts.url_to_string(); let verbosity = self.extrinsic_opts.verbosity()?; fn load_code(wasm_path: &Path) -> Result { log::info!("Contract code path: {}", wasm_path.display()); let code = fs::read(&wasm_path) .context(format!("Failed to read from {}", wasm_path.display()))?; Ok(Code::Upload(code.into())) } let code = match (self.wasm_path.as_ref(), self.code_hash.as_ref()) { (Some(_), Some(_)) => { Err(anyhow!( "Specify either `--wasm-path` or `--code-hash` but not both" )) } (Some(wasm_path), None) => load_code(wasm_path), (None, None) => { // default to the target contract wasm in the current project, // inferred via the crate metadata. load_code(&crate_metadata.dest_wasm) } (None, Some(code_hash)) => Ok(Code::Existing(*code_hash)), }?; let salt = self.salt.clone().unwrap_or_else(|| Bytes(Vec::new())); let args = InstantiateArgs { value: self.value, gas_limit: self.gas_limit, storage_deposit_limit: self.extrinsic_opts.storage_deposit_limit, data, salt, }; let exec = Exec { args, url, verbosity, signer, transcoder, }; async_std::task::block_on(async move { exec.exec(code, self.extrinsic_opts.dry_run).await }) } } struct InstantiateArgs { value: super::Balance, gas_limit: u64, storage_deposit_limit: Option, data: Vec, salt: Bytes, } pub struct Exec<'a> { args: InstantiateArgs, verbosity: Verbosity, url: String, signer: PairSigner, transcoder: ContractMessageTranscoder<'a>, } impl<'a> Exec<'a> { async fn subxt_api(&self) -> Result { let api = ClientBuilder::new() .set_url(&self.url) .build() .await? .to_runtime_api::(); Ok(api) } async fn exec(&self, code: Code, dry_run: bool) -> Result<()> { log::debug!("instantiate data {:?}", self.args.data); if dry_run { let result = self.instantiate_dry_run(code).await?; match result.result { Ok(ref ret_val) => { name_value_println!( "Result", String::from("Success!"), EXEC_RESULT_MAX_KEY_COL_WIDTH ); name_value_println!( "Contract", ret_val.account_id.to_ss58check(), EXEC_RESULT_MAX_KEY_COL_WIDTH ); name_value_println!( "Reverted", format!("{:?}", ret_val.result.did_revert()), EXEC_RESULT_MAX_KEY_COL_WIDTH ); name_value_println!( "Data", format!("{:?}", ret_val.result.data), EXEC_RESULT_MAX_KEY_COL_WIDTH ); } Err(ref err) => { let err = dry_run_error_details(&self.subxt_api().await?, err).await?; name_value_println!("Result", err, EXEC_RESULT_MAX_KEY_COL_WIDTH); } } display_contract_exec_result(&result)?; return Ok(()) } match code { Code::Upload(code) => { let (code_hash, contract_account) = self.instantiate_with_code(code).await?; name_value_println!("Code hash", format!("{:?}", code_hash)); name_value_println!("Contract", contract_account.to_ss58check()); } Code::Existing(code_hash) => { let contract_account = self.instantiate(code_hash).await?; name_value_println!("Contract", contract_account.to_ss58check()); } } Ok(()) } async fn instantiate_with_code( &self, code: Bytes, ) -> Result<(CodeHash, ContractAccount)> { let api = self.subxt_api().await?; let tx_progress = api .tx() .contracts() .instantiate_with_code( self.args.value, self.args.gas_limit, self.args.storage_deposit_limit, code.to_vec(), self.args.data.clone(), self.args.salt.0.clone(), )? .sign_and_submit_then_watch_default(&self.signer) .await?; let result = wait_for_success_and_handle_error(tx_progress).await?; let metadata = api.client.metadata(); display_events(&result, &self.transcoder, metadata, &self.verbosity)?; let code_stored = result .find_first::()? .ok_or_else(|| anyhow!("Failed to find CodeStored event"))?; let instantiated = result .find_first::()? .ok_or_else(|| anyhow!("Failed to find Instantiated event"))?; Ok((code_stored.code_hash, instantiated.contract)) } async fn instantiate(&self, code_hash: CodeHash) -> Result { let api = self.subxt_api().await?; let tx_progress = api .tx() .contracts() .instantiate( self.args.value, self.args.gas_limit, self.args.storage_deposit_limit, code_hash, self.args.data.clone(), self.args.salt.0.clone(), )? .sign_and_submit_then_watch_default(&self.signer) .await?; let result = wait_for_success_and_handle_error(tx_progress).await?; let metadata = api.client.metadata(); display_events(&result, &self.transcoder, metadata, &self.verbosity)?; let instantiated = result .find_first::()? .ok_or_else(|| anyhow!("Failed to find Instantiated event"))?; Ok(instantiated.contract) } async fn instantiate_dry_run(&self, code: Code) -> Result { let cli = WsClientBuilder::default().build(&self.url).await?; let storage_deposit_limit = self .args .storage_deposit_limit .as_ref() .map(|limit| NumberOrHex::Hex((*limit).into())); let call_request = InstantiateRequest { origin: self.signer.account_id().clone(), value: NumberOrHex::Hex(self.args.value.into()), gas_limit: NumberOrHex::Number(self.args.gas_limit), storage_deposit_limit, code, data: self.args.data.clone().into(), salt: self.args.salt.clone(), }; let params = rpc_params![call_request]; let result: ContractInstantiateResult = cli .request("contracts_instantiate", params) .await .context("contracts_instantiate RPC error")?; Ok(result) } } /// A struct that encodes RPC parameters required to instantiate a new smart contract. #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct InstantiateRequest { origin: ::AccountId, value: NumberOrHex, gas_limit: NumberOrHex, storage_deposit_limit: Option, code: Code, data: Bytes, salt: Bytes, } /// Reference to an existing code hash or a new Wasm module. #[derive(Serialize)] #[serde(rename_all = "camelCase")] enum Code { /// A Wasm module as raw bytes. Upload(Bytes), /// The code hash of an on-chain Wasm blob. Existing(::Hash), } #[cfg(test)] mod tests { use super::*; #[test] fn parse_code_hash_works() { // with 0x prefix assert!(parse_code_hash( "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" ) .is_ok()); // without 0x prefix assert!(parse_code_hash( "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" ) .is_ok()) } }