diff --git a/Cargo.lock b/Cargo.lock index 03c85511a..6e584c624 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -753,6 +753,18 @@ dependencies = [ "syn_derive", ] +[[package]] +name = "bounded-collections" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca548b6163b872067dc5eb82fd130c56881435e30367d2073594a3d9744120dd" +dependencies = [ + "log", + "parity-scale-codec", + "scale-info", + "serde", +] + [[package]] name = "bounded-collections" version = "0.2.0" @@ -881,11 +893,10 @@ dependencies = [ "semver", "serde", "serde_json", - "sp-core", - "sp-weights", + "sp-core 30.0.0", + "sp-weights 29.0.0", "substrate-build-script-utils", "subxt", - "subxt-signer", "tempfile", "tokio", "tracing", @@ -1176,7 +1187,6 @@ dependencies = [ "anyhow", "assert_cmd", "blake2", - "clap", "colored", "contract-build", "contract-metadata", @@ -1196,9 +1206,9 @@ dependencies = [ "scale-info", "serde", "serde_json", - "sp-core", - "sp-runtime", - "sp-weights", + "sp-core 30.0.0", + "sp-runtime 33.0.0", + "sp-weights 29.0.0", "subxt", "subxt-signer", "tempfile", @@ -1244,7 +1254,7 @@ dependencies = [ "scale-info", "serde", "serde_json", - "sp-core", + "sp-core 30.0.0", "sp-keyring", "strsim 0.11.0", "thiserror", @@ -4936,6 +4946,20 @@ dependencies = [ "sha-1", ] +[[package]] +name = "sp-application-crypto" +version = "30.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4fe7a9b7fa9da76272b201e2fb3c7900d97d32a46b66af9a04dad457f73c71" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-std", +] + [[package]] name = "sp-application-crypto" version = "32.0.0" @@ -4945,9 +4969,24 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-core", - "sp-io", + "sp-core 30.0.0", + "sp-io 32.0.0", + "sp-std", +] + +[[package]] +name = "sp-arithmetic" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42721f072b421f292a072e8f52a3b3c0fbc27428f0c9fe24067bc47046bad63" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", "sp-std", + "static_assertions", ] [[package]] @@ -4965,6 +5004,52 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "sp-core" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f230cb12575455070da0fc174815958423a0b9a641d5e304a9457113c7cb4007" +dependencies = [ + "array-bytes", + "bip39", + "bitflags 1.3.2", + "blake2", + "bounded-collections 0.1.9", + "bs58", + "dyn-clonable", + "ed25519-zebra 3.1.0", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde", + "itertools 0.10.5", + "libsecp256k1", + "log", + "merlin", + "parity-scale-codec", + "parking_lot", + "paste", + "primitive-types", + "rand", + "scale-info", + "schnorrkel", + "secp256k1", + "secrecy", + "serde", + "sp-core-hashing", + "sp-debug-derive", + "sp-externalities 0.25.0", + "sp-runtime-interface 24.0.0", + "sp-std", + "sp-storage 19.0.0", + "ss58-registry", + "substrate-bip39", + "thiserror", + "tracing", + "w3f-bls", + "zeroize", +] + [[package]] name = "sp-core" version = "30.0.0" @@ -4975,7 +5060,7 @@ dependencies = [ "bip39", "bitflags 1.3.2", "blake2", - "bounded-collections", + "bounded-collections 0.2.0", "bs58", "dyn-clonable", "ed25519-zebra 3.1.0", @@ -4999,10 +5084,10 @@ dependencies = [ "serde", "sp-crypto-hashing", "sp-debug-derive", - "sp-externalities", - "sp-runtime-interface", + "sp-externalities 0.27.0", + "sp-runtime-interface 26.0.0", "sp-std", - "sp-storage", + "sp-storage 20.0.0", "ss58-registry", "substrate-bip39", "thiserror", @@ -5050,6 +5135,18 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "sp-externalities" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63867ec85950ced90d4ab1bba902a47db1b1efdf2829f653945669b2bb470a9c" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-std", + "sp-storage 19.0.0", +] + [[package]] name = "sp-externalities" version = "0.27.0" @@ -5059,7 +5156,32 @@ dependencies = [ "environmental", "parity-scale-codec", "sp-std", - "sp-storage", + "sp-storage 20.0.0", +] + +[[package]] +name = "sp-io" +version = "30.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55f26d89feedaf0faf81688b6e1e1e81329cd8b4c6a4fd6c5b97ed9dd068b8a" +dependencies = [ + "bytes", + "ed25519-dalek", + "libsecp256k1", + "log", + "parity-scale-codec", + "rustversion", + "secp256k1", + "sp-core 28.0.0", + "sp-externalities 0.25.0", + "sp-keystore 0.34.0", + "sp-runtime-interface 24.0.0", + "sp-state-machine 0.35.0", + "sp-std", + "sp-tracing", + "sp-trie 29.0.0", + "tracing", + "tracing-core", ] [[package]] @@ -5075,15 +5197,15 @@ dependencies = [ "parity-scale-codec", "rustversion", "secp256k1", - "sp-core", + "sp-core 30.0.0", "sp-crypto-hashing", - "sp-externalities", - "sp-keystore", - "sp-runtime-interface", - "sp-state-machine", + "sp-externalities 0.27.0", + "sp-keystore 0.36.0", + "sp-runtime-interface 26.0.0", + "sp-state-machine 0.37.0", "sp-std", "sp-tracing", - "sp-trie", + "sp-trie 31.0.0", "tracing", "tracing-core", ] @@ -5094,11 +5216,24 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f9c74648e593b45309dfddf34f4edfd0a91816d1d97dd5e0bd93c46e7cdb0d6" dependencies = [ - "sp-core", - "sp-runtime", + "sp-core 30.0.0", + "sp-runtime 33.0.0", "strum 0.24.1", ] +[[package]] +name = "sp-keystore" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96806a28a62ed9ddecd0b28857b1344d029390f7c5c42a2ff9199cbf5638635c" +dependencies = [ + "parity-scale-codec", + "parking_lot", + "sp-core 28.0.0", + "sp-externalities 0.25.0", + "thiserror", +] + [[package]] name = "sp-keystore" version = "0.36.0" @@ -5107,8 +5242,8 @@ checksum = "bd4bf9e5fa486416c92c2bb497b7ce2c43eac80cbdc407ffe2d34b365694ac29" dependencies = [ "parity-scale-codec", "parking_lot", - "sp-core", - "sp-externalities", + "sp-core 30.0.0", + "sp-externalities 0.27.0", ] [[package]] @@ -5122,6 +5257,31 @@ dependencies = [ "regex", ] +[[package]] +name = "sp-runtime" +version = "31.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3bb49a4475d390198dfd3d41bef4564ab569fbaf1b5e38ae69b35fc01199d91" +dependencies = [ + "docify", + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "paste", + "rand", + "scale-info", + "serde", + "simple-mermaid", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-std", + "sp-weights 27.0.0", +] + [[package]] name = "sp-runtime" version = "33.0.0" @@ -5139,12 +5299,31 @@ dependencies = [ "scale-info", "serde", "simple-mermaid", - "sp-application-crypto", - "sp-arithmetic", - "sp-core", - "sp-io", + "sp-application-crypto 32.0.0", + "sp-arithmetic 25.0.0", + "sp-core 30.0.0", + "sp-io 32.0.0", "sp-std", - "sp-weights", + "sp-weights 29.0.0", +] + +[[package]] +name = "sp-runtime-interface" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66b66d8cec3d785fa6289336c1d9cbd4305d5d84f7134378c4d79ed7983e6fb" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "primitive-types", + "sp-externalities 0.25.0", + "sp-runtime-interface-proc-macro 17.0.0", + "sp-std", + "sp-storage 19.0.0", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", ] [[package]] @@ -5158,15 +5337,29 @@ dependencies = [ "parity-scale-codec", "polkavm-derive 0.8.0", "primitive-types", - "sp-externalities", - "sp-runtime-interface-proc-macro", + "sp-externalities 0.27.0", + "sp-runtime-interface-proc-macro 18.0.0", "sp-std", - "sp-storage", + "sp-storage 20.0.0", "sp-tracing", "sp-wasm-interface", "static_assertions", ] +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfaf6e85b2ec12a4b99cd6d8d57d083e30c94b7f1b0d8f93547121495aae6f0c" +dependencies = [ + "Inflector", + "expander", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "sp-runtime-interface-proc-macro" version = "18.0.0" @@ -5181,6 +5374,28 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "sp-state-machine" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718c779ad1d6fcc0be64c7ce030b33fa44b5c8914b3a1319ef63bb5f27fb98df" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "parking_lot", + "rand", + "smallvec", + "sp-core 28.0.0", + "sp-externalities 0.25.0", + "sp-panic-handler", + "sp-std", + "sp-trie 29.0.0", + "thiserror", + "tracing", + "trie-db", +] + [[package]] name = "sp-state-machine" version = "0.37.0" @@ -5193,11 +5408,11 @@ dependencies = [ "parking_lot", "rand", "smallvec", - "sp-core", - "sp-externalities", + "sp-core 30.0.0", + "sp-externalities 0.27.0", "sp-panic-handler", "sp-std", - "sp-trie", + "sp-trie 31.0.0", "thiserror", "tracing", "trie-db", @@ -5209,6 +5424,20 @@ version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" +[[package]] +name = "sp-storage" +version = "19.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb92d7b24033a8a856d6e20dd980b653cbd7af7ec471cc988b1b7c1d2e3a32b" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", + "sp-std", +] + [[package]] name = "sp-storage" version = "20.0.0" @@ -5236,6 +5465,31 @@ dependencies = [ "tracing-subscriber 0.2.25", ] +[[package]] +name = "sp-trie" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e4d24d84a0beb44a71dcac1b41980e1edf7fb722c7f3046710136a283cd479b" +dependencies = [ + "ahash 0.8.11", + "hash-db", + "lazy_static", + "memory-db", + "nohash-hasher", + "parity-scale-codec", + "parking_lot", + "rand", + "scale-info", + "schnellru", + "sp-core 28.0.0", + "sp-externalities 0.25.0", + "sp-std", + "thiserror", + "tracing", + "trie-db", + "trie-root", +] + [[package]] name = "sp-trie" version = "31.0.0" @@ -5252,8 +5506,8 @@ dependencies = [ "rand", "scale-info", "schnellru", - "sp-core", - "sp-externalities", + "sp-core 30.0.0", + "sp-externalities 0.27.0", "sp-std", "thiserror", "tracing", @@ -5275,18 +5529,34 @@ dependencies = [ "wasmtime", ] +[[package]] +name = "sp-weights" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e874bdf9dd3fd3242f5b7867a4eaedd545b02f29041a46d222a9d9d5caaaa5c" +dependencies = [ + "bounded-collections 0.1.9", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic 23.0.0", + "sp-debug-derive", + "sp-std", +] + [[package]] name = "sp-weights" version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab8a9c7a1b64fa7dba38622ad1de26f0b2e595727c0e42c7b109ecb8e7120688" dependencies = [ - "bounded-collections", + "bounded-collections 0.2.0", "parity-scale-codec", "scale-info", "serde", "smallvec", - "sp-arithmetic", + "sp-arithmetic 25.0.0", "sp-debug-derive", "sp-std", ] @@ -5460,7 +5730,9 @@ dependencies = [ "scale-value", "serde", "serde_json", + "sp-core 28.0.0", "sp-core-hashing", + "sp-runtime 31.0.1", "subxt-lightclient", "subxt-macro", "subxt-metadata", diff --git a/crates/cargo-contract/Cargo.toml b/crates/cargo-contract/Cargo.toml index 85c6a967f..668936caa 100644 --- a/crates/cargo-contract/Cargo.toml +++ b/crates/cargo-contract/Cargo.toml @@ -43,11 +43,10 @@ comfy-table = "7.1.0" # dependencies for extrinsics (deploying and calling a contract) tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -subxt = "0.34.0" +subxt = { version = "0.34.0", features = ["substrate-compat"] } sp-core = "30.0.0" sp-weights = "29.0.0" hex = "0.4.3" -subxt-signer = { version = "0.34.0", features = ["subxt", "sr25519"] } [build-dependencies] anyhow = "1.0.81" diff --git a/crates/cargo-contract/src/cmd/call.rs b/crates/cargo-contract/src/cmd/call.rs index fc1b94e1d..a0939bff3 100644 --- a/crates/cargo-contract/src/cmd/call.rs +++ b/crates/cargo-contract/src/cmd/call.rs @@ -14,20 +14,29 @@ // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . -use crate::ErrorVariant; +use crate::{ + call_with_config, + ErrorVariant, +}; use contract_build::util::DEFAULT_KEY_COL_WIDTH; -use ink_env::{ - DefaultEnvironment, - Environment, +use ink_env::Environment; +use serde::Serialize; +use std::{ + fmt::{ + Debug, + Display, + }, + str::FromStr, }; -use std::fmt::Debug; use super::{ - create_signer, + config::SignerConfig, display_contract_exec_result, display_contract_exec_result_debug, display_dry_run_result_warning, + parse_account, + parse_balance, print_dry_running_status, print_gas_required_success, prompt_confirm_tx, @@ -42,7 +51,6 @@ use anyhow::{ use contract_build::name_value_println; use contract_extrinsics::{ pallet_contracts_primitives::StorageDeposit, - BalanceVariant, CallCommandBuilder, CallExec, DisplayEvents, @@ -52,16 +60,20 @@ use contract_extrinsics::{ use contract_transcode::Value; use sp_weights::Weight; use subxt::{ + config::ExtrinsicParams, + ext::{ + scale_decode::IntoVisitor, + scale_encode::EncodeAsType, + }, Config, - PolkadotConfig as DefaultConfig, }; -use subxt_signer::sr25519::Keypair; + #[derive(Debug, clap::Args)] #[clap(name = "call", about = "Call a contract")] pub struct CallCommand { /// The address of the the contract to call. #[clap(name = "contract", long, env = "CONTRACT")] - contract: ::AccountId, + contract: String, /// The name of the contract message to call. #[clap(long, short)] message: String, @@ -82,10 +94,13 @@ pub struct CallCommand { proof_size: Option, /// The value to be transferred as part of the call. #[clap(name = "value", long, default_value = "0")] - value: BalanceVariant<::Balance>, + value: String, /// Export the call output in JSON format. #[clap(long, conflicts_with = "verbose")] output_json: bool, + /// The chain config to be used as part of the call. + #[clap(name = "config", long, default_value = "Polkadot")] + config: String, } impl CallCommand { @@ -95,31 +110,50 @@ impl CallCommand { } pub async fn handle(&self) -> Result<(), ErrorVariant> { - let token_metadata = - TokenMetadata::query::(&self.extrinsic_cli_opts.url).await?; + call_with_config!(self, run, self.config.as_str()) + } - let signer = create_signer(&self.extrinsic_cli_opts.suri)?; + async fn run>( + &self, + ) -> Result<(), ErrorVariant> + where + ::AccountId: IntoVisitor + FromStr + EncodeAsType, + <::AccountId as FromStr>::Err: Display, + C::Balance: From + Display + Default + FromStr + Serialize + Debug, + >::OtherParams: Default, + { + let contract = parse_account(&self.contract) + .map_err(|e| anyhow::anyhow!("Failed to parse contract option: {}", e))?; + let signer = C::Signer::from_str(&self.extrinsic_cli_opts.suri) + .map_err(|_| anyhow::anyhow!("Failed to parse suri option"))?; + let token_metadata = + TokenMetadata::query::(&self.extrinsic_cli_opts.url).await?; + let storage_deposit_limit = self + .extrinsic_cli_opts + .storage_deposit_limit + .clone() + .map(|b| parse_balance(&b, &token_metadata)) + .transpose() + .map_err(|e| { + anyhow::anyhow!("Failed to parse storage_deposit_limit option: {}", e) + })?; + let value = parse_balance(&self.value, &token_metadata) + .map_err(|e| anyhow::anyhow!("Failed to parse value option: {}", e))?; let extrinsic_opts = ExtrinsicOptsBuilder::new(signer) .file(self.extrinsic_cli_opts.file.clone()) .manifest_path(self.extrinsic_cli_opts.manifest_path.clone()) .url(self.extrinsic_cli_opts.url.clone()) - .storage_deposit_limit( - self.extrinsic_cli_opts - .storage_deposit_limit - .clone() - .map(|bv| bv.denominate_balance(&token_metadata)) - .transpose()?, - ) + .storage_deposit_limit(storage_deposit_limit) .verbosity(self.extrinsic_cli_opts.verbosity()?) .done(); - let call_exec = - CallCommandBuilder::new(self.contract.clone(), &self.message, extrinsic_opts) - .args(self.args.clone()) - .gas_limit(self.gas_limit) - .proof_size(self.proof_size) - .value(self.value.denominate_balance(&token_metadata)?) - .done() - .await?; + + let call_exec = CallCommandBuilder::new(contract, &self.message, extrinsic_opts) + .args(self.args.clone()) + .gas_limit(self.gas_limit) + .proof_size(self.proof_size) + .value(value) + .done() + .await?; let metadata = call_exec.client().metadata(); if !self.extrinsic_cli_opts.execute { @@ -147,7 +181,7 @@ impl CallCommand { println!("{}", dry_run_result.to_json()?); } else { dry_run_result.print(); - display_contract_exec_result_debug::<_, DEFAULT_KEY_COL_WIDTH>( + display_contract_exec_result_debug::<_, DEFAULT_KEY_COL_WIDTH, _>( &result, )?; display_dry_run_result_warning("message"); @@ -159,7 +193,7 @@ impl CallCommand { return Err(object) } else { name_value_println!("Result", object, MAX_KEY_COL_WIDTH); - display_contract_exec_result::<_, MAX_KEY_COL_WIDTH>(&result)?; + display_contract_exec_result::<_, MAX_KEY_COL_WIDTH, _>(&result)?; } } } @@ -190,15 +224,13 @@ impl CallCommand { })?; } let events = call_exec.call(Some(gas_limit)).await?; - let display_events = DisplayEvents::from_events::< - DefaultConfig, - DefaultEnvironment, - >(&events, None, &metadata)?; + let display_events = + DisplayEvents::from_events::(&events, None, &metadata)?; let output = if self.output_json() { display_events.to_json()? } else { - display_events.display_events::( + display_events.display_events::( self.extrinsic_cli_opts.verbosity().unwrap(), &token_metadata, )? @@ -210,11 +242,17 @@ impl CallCommand { } /// A helper function to estimate the gas required for a contract call. -async fn pre_submit_dry_run_gas_estimate_call( - call_exec: &CallExec, +async fn pre_submit_dry_run_gas_estimate_call( + call_exec: &CallExec, output_json: bool, skip_dry_run: bool, -) -> Result { +) -> Result +where + Signer: subxt::tx::Signer + Clone, + ::AccountId: IntoVisitor + EncodeAsType, + C::Balance: Debug, + >::OtherParams: Default, +{ if skip_dry_run { return match (call_exec.gas_limit(), call_exec.proof_size()) { (Some(ref_time), Some(proof_size)) => Ok(Weight::from_parts(ref_time, proof_size)), @@ -250,7 +288,7 @@ async fn pre_submit_dry_run_gas_estimate_call( Err(anyhow!("{}", serde_json::to_string_pretty(&object)?)) } else { name_value_println!("Result", object, MAX_KEY_COL_WIDTH); - display_contract_exec_result::<_, MAX_KEY_COL_WIDTH>(&call_result)?; + display_contract_exec_result::<_, MAX_KEY_COL_WIDTH, _>(&call_result)?; Err(anyhow!("Pre-submission dry-run failed. Use --skip-dry-run to skip this step.")) } @@ -260,17 +298,17 @@ async fn pre_submit_dry_run_gas_estimate_call( /// Result of the contract call #[derive(serde::Serialize)] -pub struct CallDryRunResult { +pub struct CallDryRunResult { /// Was the operation reverted pub reverted: bool, pub data: Value, pub gas_consumed: Weight, pub gas_required: Weight, /// Storage deposit after the operation - pub storage_deposit: StorageDeposit<::Balance>, + pub storage_deposit: StorageDeposit, } -impl CallDryRunResult { +impl CallDryRunResult { /// Returns a result in json format pub fn to_json(&self) -> Result { Ok(serde_json::to_string_pretty(self)?) diff --git a/crates/cargo-contract/src/cmd/config.rs b/crates/cargo-contract/src/cmd/config.rs new file mode 100644 index 000000000..57e02a97f --- /dev/null +++ b/crates/cargo-contract/src/cmd/config.rs @@ -0,0 +1,250 @@ +// Copyright (C) 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 ink_env::{ + DefaultEnvironment, + Environment, +}; +use std::{ + fmt::Debug, + str::FromStr, +}; +use subxt::{ + config::{ + PolkadotExtrinsicParams, + SubstrateExtrinsicParams, + }, + ext::{ + sp_core, + sp_core::Pair, + }, + tx::{ + PairSigner, + Signer as SignerT, + }, + Config, + PolkadotConfig, + SubstrateConfig, +}; + +/// Configuration for signer +pub trait SignerConfig { + type Signer: SignerT + FromStr + Clone; +} + +/// A runtime configuration for the ecdsa test chain. +/// This thing is not meant to be instantiated; it is just a collection of types. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Ecdsachain {} + +impl Config for Ecdsachain { + type Hash = ::Hash; + type AccountId = ::AccountId; + type Address = ::Address; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type ExtrinsicParams = SubstrateExtrinsicParams; + type AssetId = ::AssetId; +} + +impl Environment for Ecdsachain { + const MAX_EVENT_TOPICS: usize = ::MAX_EVENT_TOPICS; + type AccountId = ::AccountId; + type Balance = ::Balance; + type Hash = ::Hash; + type Timestamp = ::Timestamp; + type BlockNumber = ::BlockNumber; + type ChainExtension = ::ChainExtension; +} + +impl SignerConfig for Ecdsachain +where + ::Signature: From, +{ + type Signer = SignerEcdsa; +} + +/// A runtime configuration for the Substrate based chain. +/// This thing is not meant to be instantiated; it is just a collection of types. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Substrate {} + +impl Config for Substrate { + type Hash = ::Hash; + type AccountId = ::AccountId; + type Address = ::Address; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type ExtrinsicParams = SubstrateExtrinsicParams; + type AssetId = ::AssetId; +} + +impl Environment for Substrate { + const MAX_EVENT_TOPICS: usize = ::MAX_EVENT_TOPICS; + type AccountId = ::AccountId; + type Balance = ::Balance; + type Hash = ::Hash; + type Timestamp = ::Timestamp; + type BlockNumber = ::BlockNumber; + type ChainExtension = ::ChainExtension; +} + +impl SignerConfig for Substrate { + type Signer = SignerSR25519; +} + +/// A runtime configuration for the Polkadot based chain. +/// This thing is not meant to be instantiated; it is just a collection of types. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Polkadot {} + +impl Config for Polkadot { + type Hash = ::Hash; + type AccountId = ::AccountId; + type Address = ::Address; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type ExtrinsicParams = PolkadotExtrinsicParams; + type AssetId = ::AssetId; +} + +impl Environment for Polkadot { + const MAX_EVENT_TOPICS: usize = ::MAX_EVENT_TOPICS; + type AccountId = ::AccountId; + type Balance = ::Balance; + type Hash = ::Hash; + type Timestamp = ::Timestamp; + type BlockNumber = ::BlockNumber; + type ChainExtension = ::ChainExtension; +} + +impl SignerConfig for Polkadot { + type Signer = SignerSR25519; +} + +/// Struct representing the implementation of the sr25519 signer +#[derive(Clone)] +pub struct SignerSR25519(pub PairSigner); + +impl FromStr for SignerSR25519 +where + ::AccountId: From, +{ + type Err = anyhow::Error; + + /// Attempts to parse the Signer suri string + fn from_str(input: &str) -> Result, Self::Err> { + let keypair = sp_core::sr25519::Pair::from_string(input, None)?; + let signer = PairSigner::::new(keypair); + Ok(Self(signer)) + } +} + +impl SignerT for SignerSR25519 +where + ::Signature: From, +{ + fn account_id(&self) -> ::AccountId { + self.0.account_id().clone() + } + + fn address(&self) -> C::Address { + self.0.address() + } + + fn sign(&self, signer_payload: &[u8]) -> C::Signature { + self.0.sign(signer_payload) + } +} + +/// Struct representing the implementation of the ecdsa signer +#[derive(Clone)] +pub struct SignerEcdsa(pub PairSigner); + +impl FromStr for SignerEcdsa +where + // Requirements of the `PairSigner where: + // T::AccountId: From` + ::AccountId: From, +{ + type Err = anyhow::Error; + + /// Attempts to parse the Signer suri string + fn from_str(input: &str) -> Result, Self::Err> { + let keypair = sp_core::ecdsa::Pair::from_string(input, None)?; + let signer = PairSigner::::new(keypair); + Ok(Self(signer)) + } +} + +impl SignerT for SignerEcdsa +where + ::Signature: From, +{ + fn account_id(&self) -> ::AccountId { + self.0.account_id().clone() + } + + fn address(&self) -> C::Address { + self.0.address() + } + + fn sign(&self, signer_payload: &[u8]) -> C::Signature { + self.0.sign(signer_payload) + } +} + +#[macro_export] +macro_rules! call_with_config_internal { + ($obj:tt ,$function:tt, $config_name:expr, $($config:ty),*) => { + match $config_name { + $( + stringify!($config) => $obj.$function::<$config>().await, + )* + _ => { + + let configs = vec![$(stringify!($config)),*].iter() + .map(|s| s.trim_start_matches("crate::cmd::config::")) + .collect::>() + .join(", "); + Err(ErrorVariant::Generic( + contract_extrinsics::GenericError::from_message( + format!("Chain configuration not found, Allowed configurations: {configs}") + ))) + }, + } + }; +} + +/// Macro that allows calling the command member function with chain configuration +#[macro_export] +macro_rules! call_with_config { + ($obj:tt, $function:ident, $config_name:expr) => {{ + let config_name = format!("crate::cmd::config::{}", $config_name); + $crate::call_with_config_internal!( + $obj, + $function, + config_name.as_str(), + // All available chain configs need to be specified here + $crate::cmd::config::Polkadot, + $crate::cmd::config::Substrate, + $crate::cmd::config::Ecdsachain + ) + }}; +} diff --git a/crates/cargo-contract/src/cmd/info.rs b/crates/cargo-contract/src/cmd/info.rs index e9e19f49b..73a9573ec 100644 --- a/crates/cargo-contract/src/cmd/info.rs +++ b/crates/cargo-contract/src/cmd/info.rs @@ -14,10 +14,12 @@ // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . +use crate::call_with_config; + use super::{ basic_display_format_extended_contract_info, display_all_contracts, - DefaultConfig, + parse_account, }; use anyhow::Result; use contract_analyze::determine_language; @@ -30,19 +32,25 @@ use contract_extrinsics::{ ErrorVariant, TrieId, }; -use ink_env::{ - DefaultEnvironment, - Environment, -}; +use ink_env::Environment; +use serde::Serialize; use std::{ - fmt::Debug, + fmt::{ + Debug, + Display, + }, io::Write, + str::FromStr, }; use subxt::{ backend::{ legacy::LegacyRpcMethods, rpc::RpcClient, }, + ext::{ + codec::Decode, + scale_decode::IntoVisitor, + }, Config, OnlineClient, }; @@ -57,7 +65,7 @@ pub struct InfoCommand { env = "CONTRACT", required_unless_present = "all" )] - contract: Option<::AccountId>, + contract: Option, /// Websockets url of a substrate node. #[clap( name = "url", @@ -75,14 +83,28 @@ pub struct InfoCommand { /// Display all contracts addresses #[clap(name = "all", long)] all: bool, + /// The chain config to be used as part of the call. + #[clap(name = "config", long, default_value = "Polkadot")] + config: String, } impl InfoCommand { - pub async fn run(&self) -> Result<(), ErrorVariant> { + pub async fn handle(&self) -> Result<(), ErrorVariant> { + call_with_config!(self, run, self.config.as_str()) + } + + pub async fn run(&self) -> Result<(), ErrorVariant> + where + ::AccountId: + Serialize + Display + IntoVisitor + Decode + AsRef<[u8]> + FromStr, + ::Hash: IntoVisitor + Display, + ::Balance: Serialize + Debug + IntoVisitor, + <::AccountId as FromStr>::Err: + Into> + Display, + { let rpc_cli = RpcClient::from_url(url_to_string(&self.url)).await?; - let client = - OnlineClient::::from_rpc_client(rpc_cli.clone()).await?; - let rpc = LegacyRpcMethods::::new(rpc_cli.clone()); + let client = OnlineClient::::from_rpc_client(rpc_cli.clone()).await?; + let rpc = LegacyRpcMethods::::new(rpc_cli.clone()); // All flag applied if self.all { @@ -103,12 +125,12 @@ impl InfoCommand { let contract = self .contract .as_ref() - .expect("Contract argument was not provided"); + .map(|c| parse_account(c)) + .transpose()? + .expect("Contract argument shall be present"); - let info_to_json = fetch_contract_info::( - contract, &rpc, &client, - ) - .await?; + let info_to_json = + fetch_contract_info::(&contract, &rpc, &client).await?; let wasm_code = fetch_wasm_code(&client, &rpc, info_to_json.code_hash()).await?; @@ -128,16 +150,16 @@ impl InfoCommand { println!( "{}", serde_json::to_string_pretty(&ExtendedContractInfo::< - ::Hash, - ::Balance, + ::Hash, + C::Balance, >::new( info_to_json, &wasm_code ))? ) } else { basic_display_format_extended_contract_info(&ExtendedContractInfo::< - ::Hash, - ::Balance, + ::Hash, + C::Balance, >::new( info_to_json, &wasm_code )) diff --git a/crates/cargo-contract/src/cmd/instantiate.rs b/crates/cargo-contract/src/cmd/instantiate.rs index 58de3da4b..0f4d7c96b 100644 --- a/crates/cargo-contract/src/cmd/instantiate.rs +++ b/crates/cargo-contract/src/cmd/instantiate.rs @@ -15,10 +15,11 @@ // along with cargo-contract. If not, see . use super::{ - create_signer, + config::SignerConfig, display_contract_exec_result, display_contract_exec_result_debug, display_dry_run_result_warning, + parse_balance, print_dry_running_status, print_gas_required_success, prompt_confirm_tx, @@ -27,6 +28,7 @@ use super::{ }; use crate::{ anyhow, + call_with_config, ErrorVariant, InstantiateExec, Weight, @@ -41,7 +43,6 @@ use contract_build::{ Verbosity, }; use contract_extrinsics::{ - BalanceVariant, Code, DisplayEvents, ExtrinsicOptsBuilder, @@ -50,14 +51,25 @@ use contract_extrinsics::{ InstantiateExecResult, TokenMetadata, }; -use ink_env::{ - DefaultEnvironment, - Environment, -}; +use ink_env::Environment; +use serde::Serialize; use sp_core::Bytes; -use std::fmt::Debug; -use subxt::PolkadotConfig as DefaultConfig; -use subxt_signer::sr25519::Keypair; +use std::{ + fmt::{ + Debug, + Display, + }, + str::FromStr, +}; +use subxt::{ + config::ExtrinsicParams, + ext::{ + codec::Decode, + scale_decode::IntoVisitor, + scale_encode::EncodeAsType, + }, + Config, +}; #[derive(Debug, clap::Args)] pub struct InstantiateCommand { @@ -71,7 +83,7 @@ pub struct InstantiateCommand { extrinsic_cli_opts: CLIExtrinsicOpts, /// Transfers an initial balance to the instantiated contract #[clap(name = "value", long, default_value = "0")] - value: BalanceVariant<::Balance>, + value: String, /// Maximum amount of gas to be used for this command. /// If not specified will perform a dry-run to estimate the gas consumed for the /// instantiation. @@ -88,6 +100,9 @@ pub struct InstantiateCommand { /// Export the instantiate output in JSON format. #[clap(long, conflicts_with = "verbose")] output_json: bool, + /// The chain config to be used as part of the call. + #[clap(name = "config", long, default_value = "Polkadot")] + config: String, } /// Parse hex encoded bytes. @@ -103,35 +118,52 @@ impl InstantiateCommand { } pub async fn handle(&self) -> Result<(), ErrorVariant> { - let token_metadata = - TokenMetadata::query::(&self.extrinsic_cli_opts.url).await?; + call_with_config!(self, run, self.config.as_str()) + } - let signer = create_signer(&self.extrinsic_cli_opts.suri)?; + async fn run>( + &self, + ) -> Result<(), ErrorVariant> + where + >::Signer: subxt::tx::Signer + Clone + FromStr, + ::AccountId: IntoVisitor + FromStr + EncodeAsType + Decode + Display, + <::AccountId as FromStr>::Err: Display, + C::Balance: From + Display + Default + FromStr + Serialize + Debug, + >::OtherParams: Default, + ::Hash: From<[u8; 32]> + IntoVisitor + EncodeAsType, + { + let signer = C::Signer::from_str(&self.extrinsic_cli_opts.suri) + .map_err(|_| anyhow::anyhow!("Failed to parse suri option"))?; + let token_metadata = + TokenMetadata::query::(&self.extrinsic_cli_opts.url).await?; + let storage_deposit_limit = self + .extrinsic_cli_opts + .storage_deposit_limit + .clone() + .map(|b| parse_balance(&b, &token_metadata)) + .transpose() + .map_err(|e| { + anyhow::anyhow!("Failed to parse storage_deposit_limit option: {}", e) + })?; + let value = parse_balance(&self.value, &token_metadata) + .map_err(|e| anyhow::anyhow!("Failed to parse value option: {}", e))?; let extrinsic_opts = ExtrinsicOptsBuilder::new(signer) .file(self.extrinsic_cli_opts.file.clone()) .manifest_path(self.extrinsic_cli_opts.manifest_path.clone()) .url(self.extrinsic_cli_opts.url.clone()) - .storage_deposit_limit( - self.extrinsic_cli_opts - .storage_deposit_limit - .clone() - .map(|bv| bv.denominate_balance(&token_metadata)) - .transpose()?, - ) + .storage_deposit_limit(storage_deposit_limit) .done(); - let instantiate_exec: InstantiateExec< - DefaultConfig, - DefaultEnvironment, - Keypair, - > = InstantiateCommandBuilder::new(extrinsic_opts) - .constructor(self.constructor.clone()) - .args(self.args.clone()) - .value(self.value.denominate_balance(&token_metadata)?) - .gas_limit(self.gas_limit) - .proof_size(self.proof_size) - .salt(self.salt.clone()) - .done() - .await?; + + let instantiate_exec: InstantiateExec = + InstantiateCommandBuilder::new(extrinsic_opts) + .constructor(self.constructor.clone()) + .args(self.args.clone()) + .value(value) + .gas_limit(self.gas_limit) + .proof_size(self.proof_size) + .salt(self.salt.clone()) + .done() + .await?; if !self.extrinsic_cli_opts.execute { let result = instantiate_exec.instantiate_dry_run().await?; @@ -141,7 +173,7 @@ impl InstantiateCommand { println!("{}", dry_run_result.to_json()?); } else { print_instantiate_dry_run_result(&dry_run_result); - display_contract_exec_result_debug::<_, DEFAULT_KEY_COL_WIDTH>( + display_contract_exec_result_debug::<_, DEFAULT_KEY_COL_WIDTH, _>( &result, )?; display_dry_run_result_warning("instantiate"); @@ -153,7 +185,7 @@ impl InstantiateCommand { return Err(object) } else { name_value_println!("Result", object, MAX_KEY_COL_WIDTH); - display_contract_exec_result::<_, MAX_KEY_COL_WIDTH>(&result)?; + display_contract_exec_result::<_, MAX_KEY_COL_WIDTH, _>(&result)?; } Err(object) } @@ -196,11 +228,20 @@ impl InstantiateCommand { } /// A helper function to estimate the gas required for a contract instantiation. -async fn pre_submit_dry_run_gas_estimate_instantiate( - instantiate_exec: &InstantiateExec, +async fn pre_submit_dry_run_gas_estimate_instantiate< + C: Config + Environment + SignerConfig, +>( + instantiate_exec: &InstantiateExec, output_json: bool, skip_dry_run: bool, -) -> Result { +) -> Result +where + C::Signer: subxt::tx::Signer + Clone, + ::AccountId: IntoVisitor + Display + Decode, + ::Hash: IntoVisitor + EncodeAsType, + C::Balance: Serialize + Debug, + >::OtherParams: Default, +{ if skip_dry_run { return match (instantiate_exec.args().gas_limit(), instantiate_exec.args().proof_size()) { (Some(ref_time), Some(proof_size)) => Ok(Weight::from_parts(ref_time, proof_size)), @@ -240,7 +281,7 @@ async fn pre_submit_dry_run_gas_estimate_instantiate( Err(anyhow!("{}", serde_json::to_string_pretty(&object)?)) } else { name_value_println!("Result", object, MAX_KEY_COL_WIDTH); - display_contract_exec_result::<_, MAX_KEY_COL_WIDTH>( + display_contract_exec_result::<_, MAX_KEY_COL_WIDTH, _>( &instantiate_result, )?; @@ -252,14 +293,20 @@ async fn pre_submit_dry_run_gas_estimate_instantiate( /// Displays the results of contract instantiation, including contract address, /// events, and optional code hash. -pub async fn display_result( - instantiate_exec: &InstantiateExec, - instantiate_exec_result: InstantiateExecResult, +pub async fn display_result>( + instantiate_exec: &InstantiateExec, + instantiate_exec_result: InstantiateExecResult, token_metadata: &TokenMetadata, output_json: bool, verbosity: Verbosity, -) -> Result<(), ErrorVariant> { - let events = DisplayEvents::from_events::( +) -> Result<(), ErrorVariant> +where + ::AccountId: IntoVisitor + EncodeAsType + Display + Decode, + ::Hash: IntoVisitor + EncodeAsType, + C::Balance: Serialize + From + Display, + >::OtherParams: Default, +{ + let events = DisplayEvents::from_events::( &instantiate_exec_result.events, Some(instantiate_exec.transcoder()), &instantiate_exec.client().metadata(), @@ -275,10 +322,7 @@ pub async fn display_result( }; println!("{}", display_instantiate_result.to_json()?) } else { - println!( - "{}", - events.display_events::(verbosity, token_metadata)? - ); + println!("{}", events.display_events::(verbosity, token_metadata)?); if let Some(code_hash) = instantiate_exec_result.code_hash { name_value_println!("Code hash", format!("{code_hash:?}")); } @@ -287,10 +331,16 @@ pub async fn display_result( Ok(()) } -pub fn print_default_instantiate_preview( - instantiate_exec: &InstantiateExec, +pub fn print_default_instantiate_preview>( + instantiate_exec: &InstantiateExec, gas_limit: Weight, -) { +) where + C::Signer: subxt::tx::Signer + Clone, + ::AccountId: IntoVisitor + EncodeAsType + Display + Decode, + ::Hash: IntoVisitor + EncodeAsType, + C::Balance: Serialize, + >::OtherParams: Default, +{ name_value_println!( "Constructor", instantiate_exec.args().constructor(), @@ -323,8 +373,8 @@ impl InstantiateResult { } } -pub fn print_instantiate_dry_run_result( - result: &InstantiateDryRunResult<::Balance>, +pub fn print_instantiate_dry_run_result( + result: &InstantiateDryRunResult, ) { name_value_println!( "Result", diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index f0fb9f125..b38c6a6e4 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . +mod config; + pub mod build; pub mod call; pub mod decode; @@ -70,23 +72,19 @@ pub(crate) use contract_extrinsics::ErrorVariant; use contract_extrinsics::{ pallet_contracts_primitives::ContractResult, BalanceVariant, + TokenMetadata, }; -use core::fmt; -use ink_env::{ - DefaultEnvironment, - Environment, -}; -use std::io::{ - self, - Write, -}; -pub use subxt::{ - Config, - PolkadotConfig as DefaultConfig, -}; -use subxt_signer::{ - sr25519::Keypair, - SecretUri, + +use std::{ + fmt::{ + Debug, + Display, + }, + io::{ + self, + Write, + }, + str::FromStr, }; /// Arguments required for creating and sending an extrinsic to a substrate node. @@ -122,8 +120,7 @@ pub struct CLIExtrinsicOpts { /// The maximum amount of balance that can be charged from the caller to pay for the /// storage. consumed. #[clap(long)] - storage_deposit_limit: - Option::Balance>>, + storage_deposit_limit: Option, /// Before submitting a transaction, do not dry-run it via RPC first. #[clap(long)] skip_dry_run: bool, @@ -143,9 +140,12 @@ const STORAGE_DEPOSIT_KEY: &str = "Storage Total Deposit"; pub const MAX_KEY_COL_WIDTH: usize = STORAGE_DEPOSIT_KEY.len() + 1; /// Print to stdout the fields of the result of a `instantiate` or `call` dry-run via RPC. -pub fn display_contract_exec_result( - result: &ContractResult::Balance, ()>, -) -> Result<()> { +pub fn display_contract_exec_result( + result: &ContractResult, +) -> Result<()> +where + Balance: Debug, +{ let mut debug_message_lines = std::str::from_utf8(&result.debug_message) .context("Error decoding UTF8 debug message bytes")? .lines(); @@ -168,8 +168,8 @@ pub fn display_contract_exec_result( Ok(()) } -pub fn display_contract_exec_result_debug( - result: &ContractResult::Balance, ()>, +pub fn display_contract_exec_result_debug( + result: &ContractResult, ) -> Result<()> { let mut debug_message_lines = std::str::from_utf8(&result.debug_message) .context("Error decoding UTF8 debug message bytes")? @@ -235,10 +235,11 @@ pub fn print_gas_required_success(gas: Weight) { } /// Display contract information in a formatted way -pub fn basic_display_format_extended_contract_info( - info: &ExtendedContractInfo::Balance>, +pub fn basic_display_format_extended_contract_info( + info: &ExtendedContractInfo, ) where - Hash: fmt::Debug, + Hash: Debug, + Balance: Debug, { name_value_println!("TrieId", info.trie_id, MAX_KEY_COL_WIDTH); name_value_println!( @@ -269,21 +270,37 @@ pub fn basic_display_format_extended_contract_info( } /// Display all contracts addresses in a formatted way -pub fn display_all_contracts(contracts: &[::AccountId]) { - contracts - .iter() - .for_each(|e: &::AccountId| println!("{}", e)) +pub fn display_all_contracts(contracts: &[AccountId]) +where + AccountId: Display, +{ + contracts.iter().for_each(|e: &AccountId| println!("{}", e)) } -/// Create a Signer from a secret URI. -pub fn create_signer(suri: &str) -> Result { - let uri = ::from_str(suri)?; - let keypair = Keypair::from_uri(&uri)?; - Ok(keypair) +/// Parse a balance from string format +pub fn parse_balance + Clone>( + balance: &str, + token_metadata: &TokenMetadata, +) -> Result { + BalanceVariant::from_str(balance) + .map_err(|e| anyhow!("Balance parsing failed: {e}")) + .and_then(|bv| bv.denominate_balance(token_metadata)) +} + +/// Parse a account from string format +pub fn parse_account(account: &str) -> Result +where + ::Err: Display, +{ + AccountId::from_str(account) + .map_err(|e| anyhow::anyhow!("Account address parsing failed: {e}")) } /// Parse a hex encoded 32 byte hash. Returns error if not exactly 32 bytes. -pub fn parse_code_hash(input: &str) -> Result<::Hash> { +pub fn parse_code_hash(input: &str) -> Result +where + Hash: From<[u8; 32]>, +{ let bytes = contract_build::util::decode_hex(input)?; if bytes.len() != 32 { anyhow::bail!("Code hash should be 32 bytes in length") @@ -295,17 +312,22 @@ pub fn parse_code_hash(input: &str) -> Result<::Hash> { #[cfg(test)] mod tests { + use subxt::{ + Config, + SubstrateConfig, + }; + use super::*; #[test] fn parse_code_hash_works() { // with 0x prefix - assert!(parse_code_hash( + assert!(parse_code_hash::<::Hash>( "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" ) .is_ok()); // without 0x prefix - assert!(parse_code_hash( + assert!(parse_code_hash::<::Hash>( "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" ) .is_ok()) @@ -314,7 +336,7 @@ mod tests { #[test] fn parse_incorrect_len_code_hash_fails() { // with len not equal to 32 - assert!(parse_code_hash( + assert!(parse_code_hash::<::Hash>( "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da2" ) .is_err()) @@ -323,7 +345,7 @@ mod tests { #[test] fn parse_bad_format_code_hash_fails() { // with bad format - assert!(parse_code_hash( + assert!(parse_code_hash::<::Hash>( "x43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" ) .is_err()) diff --git a/crates/cargo-contract/src/cmd/remove.rs b/crates/cargo-contract/src/cmd/remove.rs index ad7c7e8d1..ef619b124 100644 --- a/crates/cargo-contract/src/cmd/remove.rs +++ b/crates/cargo-contract/src/cmd/remove.rs @@ -14,11 +14,21 @@ // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . -use crate::ErrorVariant; -use std::fmt::Debug; +use crate::{ + call_with_config, + ErrorVariant, +}; +use std::{ + fmt::{ + Debug, + Display, + }, + str::FromStr, +}; use super::{ - create_signer, + config::SignerConfig, + parse_balance, parse_code_hash, CLIExtrinsicOpts, }; @@ -31,24 +41,31 @@ use contract_extrinsics::{ RemoveExec, TokenMetadata, }; -use ink_env::DefaultEnvironment; +use ink_env::Environment; +use serde::Serialize; use subxt::{ + config::ExtrinsicParams, + ext::{ + scale_decode::IntoVisitor, + scale_encode::EncodeAsType, + }, Config, - PolkadotConfig as DefaultConfig, }; -use subxt_signer::sr25519::Keypair; #[derive(Debug, clap::Args)] #[clap(name = "remove", about = "Remove a contract's code")] pub struct RemoveCommand { /// The hash of the smart contract code already uploaded to the chain. - #[clap(long, value_parser = parse_code_hash)] - code_hash: Option<::Hash>, + #[clap(long)] + code_hash: Option, #[clap(flatten)] extrinsic_cli_opts: CLIExtrinsicOpts, /// Export the call output as JSON. #[clap(long, conflicts_with = "verbose")] output_json: bool, + /// The chain config to be used as part of the call. + #[clap(name = "config", long, default_value = "Polkadot")] + config: String, } impl RemoveCommand { @@ -58,44 +75,67 @@ impl RemoveCommand { } pub async fn handle(&self) -> Result<(), ErrorVariant> { - let token_metadata = - TokenMetadata::query::(&self.extrinsic_cli_opts.url).await?; + call_with_config!(self, run, self.config.as_str()) + } - let signer: Keypair = create_signer(&self.extrinsic_cli_opts.suri)?; + async fn run>( + &self, + ) -> Result<(), ErrorVariant> + where + ::AccountId: IntoVisitor + FromStr + EncodeAsType, + <::AccountId as FromStr>::Err: Display, + C::Balance: + Into + From + Display + Default + FromStr + Serialize + Debug, + >::OtherParams: Default, + ::Hash: IntoVisitor + EncodeAsType + From<[u8; 32]>, + { + let signer = C::Signer::from_str(&self.extrinsic_cli_opts.suri) + .map_err(|_| anyhow::anyhow!("Failed to parse suri option"))?; + let token_metadata = + TokenMetadata::query::(&self.extrinsic_cli_opts.url).await?; + let storage_deposit_limit = self + .extrinsic_cli_opts + .storage_deposit_limit + .clone() + .map(|b| parse_balance(&b, &token_metadata)) + .transpose() + .map_err(|e| { + anyhow::anyhow!("Failed to parse storage_deposit_limit option: {}", e) + })?; + let code_hash = self + .code_hash + .clone() + .map(|h| parse_code_hash(&h)) + .transpose() + .map_err(|e| anyhow::anyhow!("Failed to parse code_hash option: {}", e))?; let extrinsic_opts = ExtrinsicOptsBuilder::new(signer) .file(self.extrinsic_cli_opts.file.clone()) .manifest_path(self.extrinsic_cli_opts.manifest_path.clone()) .url(self.extrinsic_cli_opts.url.clone()) - .storage_deposit_limit( - self.extrinsic_cli_opts - .storage_deposit_limit - .clone() - .map(|bv| bv.denominate_balance(&token_metadata)) - .transpose()?, - ) + .storage_deposit_limit(storage_deposit_limit) .done(); - let remove_exec: RemoveExec = - RemoveCommandBuilder::new(extrinsic_opts) - .code_hash(self.code_hash) - .done() - .await?; + + let remove_exec: RemoveExec = RemoveCommandBuilder::new(extrinsic_opts) + .code_hash(code_hash) + .done() + .await?; let remove_result = remove_exec.remove_code().await?; - let display_events = - DisplayEvents::from_events::( - &remove_result.events, - Some(remove_exec.transcoder()), - &remove_exec.client().metadata(), - )?; + let display_events = DisplayEvents::from_events::( + &remove_result.events, + Some(remove_exec.transcoder()), + &remove_exec.client().metadata(), + )?; + let output_events = if self.output_json() { display_events.to_json()? } else { - display_events.display_events::( + display_events.display_events::( self.extrinsic_cli_opts.verbosity().unwrap(), &token_metadata, )? }; if let Some(code_removed) = remove_result.code_removed { - let remove_result: ::Hash = code_removed.code_hash; + let remove_result: ::Hash = code_removed.code_hash; if self.output_json() { // Create a JSON object with the events and the removed code hash. diff --git a/crates/cargo-contract/src/cmd/storage.rs b/crates/cargo-contract/src/cmd/storage.rs index 73d198f7a..73806a87f 100644 --- a/crates/cargo-contract/src/cmd/storage.rs +++ b/crates/cargo-contract/src/cmd/storage.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . -use super::DefaultConfig; use anyhow::Result; use colored::Colorize; use comfy_table::{ @@ -28,16 +27,28 @@ use contract_extrinsics::{ ContractStorageRpc, ErrorVariant, }; -use ink_env::DefaultEnvironment; -use std::path::PathBuf; -use subxt::Config; +use ink_env::Environment; +use serde::Serialize; +use std::{ + fmt::Display, + path::PathBuf, + str::FromStr, +}; +use subxt::{ + ext::scale_decode::IntoVisitor, + Config, +}; + +use crate::call_with_config; + +use super::parse_account; #[derive(Debug, clap::Args)] #[clap(name = "storage", about = "Inspect contract storage")] pub struct StorageCommand { /// The address of the contract to inspect storage of. #[clap(name = "contract", long, env = "CONTRACT")] - contract: ::AccountId, + contract: String, /// Fetch the "raw" storage keys and values for the contract. #[clap(long)] raw: bool, @@ -59,18 +70,32 @@ pub struct StorageCommand { default_value = "ws://localhost:9944" )] url: url::Url, + /// The chain config to be used as part of the call. + #[clap(name = "config", long, default_value = "Polkadot")] + config: String, } impl StorageCommand { - pub async fn run(&self) -> Result<(), ErrorVariant> { - let rpc = ContractStorageRpc::::new(&self.url).await?; - let storage_layout = - ContractStorage::::new(rpc); + pub async fn handle(&self) -> Result<(), ErrorVariant> { + call_with_config!(self, run, self.config.as_str()) + } + + pub async fn run(&self) -> Result<(), ErrorVariant> + where + ::AccountId: Display + IntoVisitor + AsRef<[u8]> + FromStr, + <::AccountId as FromStr>::Err: + Into> + Display, + C::Balance: Serialize + IntoVisitor, + ::Hash: IntoVisitor, + { + let rpc = ContractStorageRpc::::new(&self.url).await?; + let storage_layout = ContractStorage::::new(rpc); + let contract = parse_account(&self.contract) + .map_err(|e| anyhow::anyhow!("Failed to parse contract option: {}", e))?; if self.raw { - let storage_data = storage_layout - .load_contract_storage_data(&self.contract) - .await?; + let storage_data = + storage_layout.load_contract_storage_data(&contract).await?; println!( "{json}", json = serde_json::to_string_pretty(&storage_data)? @@ -87,7 +112,7 @@ impl StorageCommand { Ok(contract_artifacts) => { let transcoder = contract_artifacts.contract_transcoder()?; let contract_storage = storage_layout - .load_contract_storage_with_layout(&self.contract, &transcoder) + .load_contract_storage_with_layout(&contract, &transcoder) .await?; if self.output_json { println!( @@ -104,14 +129,12 @@ impl StorageCommand { "{} Displaying raw storage: no valid contract metadata artifacts found", "Info:".cyan().bold(), ); - let storage_data = storage_layout - .load_contract_storage_data(&self.contract) - .await?; + let storage_data = + storage_layout.load_contract_storage_data(&contract).await?; println!( "{json}", json = serde_json::to_string_pretty(&storage_data)? ); - return Ok(()) } } diff --git a/crates/cargo-contract/src/cmd/upload.rs b/crates/cargo-contract/src/cmd/upload.rs index 9fdd40762..b047e7925 100644 --- a/crates/cargo-contract/src/cmd/upload.rs +++ b/crates/cargo-contract/src/cmd/upload.rs @@ -14,12 +14,22 @@ // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . -use crate::ErrorVariant; -use std::fmt::Debug; +use crate::{ + call_with_config, + ErrorVariant, +}; +use std::{ + fmt::{ + Debug, + Display, + }, + str::FromStr, +}; use super::{ - create_signer, + config::SignerConfig, display_dry_run_result_warning, + parse_balance, CLIExtrinsicOpts, }; use anyhow::Result; @@ -31,15 +41,16 @@ use contract_extrinsics::{ UploadCommandBuilder, UploadExec, }; -use ink_env::{ - DefaultEnvironment, - Environment, -}; +use ink_env::Environment; +use serde::Serialize; use subxt::{ + config::ExtrinsicParams, + ext::{ + scale_decode::IntoVisitor, + scale_encode::EncodeAsType, + }, Config, - PolkadotConfig as DefaultConfig, }; -use subxt_signer::sr25519::Keypair; #[derive(Debug, clap::Args)] #[clap(name = "upload", about = "Upload a contract's code")] @@ -49,6 +60,9 @@ pub struct UploadCommand { /// Export the call output in JSON format. #[clap(long, conflicts_with = "verbose")] output_json: bool, + /// The chain config to be used as part of the call. + #[clap(name = "config", long, default_value = "Polkadot")] + config: String, } impl UploadCommand { @@ -58,25 +72,42 @@ impl UploadCommand { } pub async fn handle(&self) -> Result<(), ErrorVariant> { - let token_metadata = - TokenMetadata::query::(&self.extrinsic_cli_opts.url).await?; + call_with_config!(self, run, self.config.as_str()) + } - let signer = create_signer(&self.extrinsic_cli_opts.suri)?; + async fn run>( + &self, + ) -> Result<(), ErrorVariant> + where + ::AccountId: IntoVisitor + FromStr + EncodeAsType, + <::AccountId as FromStr>::Err: Display, + C::Balance: + Into + From + Display + Default + FromStr + Serialize + Debug, + >::OtherParams: Default, + ::Hash: IntoVisitor + EncodeAsType + From<[u8; 32]>, + { + let signer = C::Signer::from_str(&self.extrinsic_cli_opts.suri) + .map_err(|_| anyhow::anyhow!("Failed to parse suri option"))?; + let token_metadata = + TokenMetadata::query::(&self.extrinsic_cli_opts.url).await?; + let storage_deposit_limit = self + .extrinsic_cli_opts + .storage_deposit_limit + .clone() + .map(|b| parse_balance(&b, &token_metadata)) + .transpose() + .map_err(|e| { + anyhow::anyhow!("Failed to parse storage_deposit_limit option: {}", e) + })?; let extrinsic_opts = ExtrinsicOptsBuilder::new(signer) .file(self.extrinsic_cli_opts.file.clone()) .manifest_path(self.extrinsic_cli_opts.manifest_path.clone()) .url(self.extrinsic_cli_opts.url.clone()) - .storage_deposit_limit( - self.extrinsic_cli_opts - .storage_deposit_limit - .clone() - .map(|bv| bv.denominate_balance(&token_metadata)) - .transpose()?, - ) + .storage_deposit_limit(storage_deposit_limit) .done(); - let upload_exec: UploadExec = - UploadCommandBuilder::new(extrinsic_opts).done().await?; + let upload_exec: UploadExec = + UploadCommandBuilder::new(extrinsic_opts).done().await?; let code_hash = upload_exec.code().code_hash(); let metadata = upload_exec.client().metadata(); @@ -106,20 +137,21 @@ impl UploadCommand { } } else { let upload_result = upload_exec.upload_code().await?; - let display_events = DisplayEvents::from_events::< - DefaultConfig, - DefaultEnvironment, - >(&upload_result.events, None, &metadata)?; + let display_events = DisplayEvents::from_events::( + &upload_result.events, + None, + &metadata, + )?; let output_events = if self.output_json() { display_events.to_json()? } else { - display_events.display_events::( + display_events.display_events::( self.extrinsic_cli_opts.verbosity()?, &token_metadata, )? }; if let Some(code_stored) = upload_result.code_stored { - let code_hash: ::Hash = code_stored.code_hash; + let code_hash: ::Hash = code_stored.code_hash; if self.output_json() { // Create a JSON object with the events and the code hash. let json_object = serde_json::json!({ @@ -144,13 +176,16 @@ impl UploadCommand { } #[derive(serde::Serialize)] -pub struct UploadDryRunResult { +pub struct UploadDryRunResult { pub result: String, pub code_hash: String, - pub deposit: ::Balance, + pub deposit: Balance, } -impl UploadDryRunResult { +impl UploadDryRunResult +where + Balance: Debug + Serialize, +{ pub fn to_json(&self) -> Result { Ok(serde_json::to_string_pretty(self)?) } diff --git a/crates/cargo-contract/src/main.rs b/crates/cargo-contract/src/main.rs index 916dd9d41..4ab46a13e 100644 --- a/crates/cargo-contract/src/main.rs +++ b/crates/cargo-contract/src/main.rs @@ -234,10 +234,10 @@ fn exec(cmd: Command) -> Result<()> { }) } Command::Info(info) => { - runtime.block_on(async { info.run().await.map_err(format_err) }) + runtime.block_on(async { info.handle().await.map_err(format_err) }) } Command::Storage(storage) => { - runtime.block_on(async { storage.run().await.map_err(format_err) }) + runtime.block_on(async { storage.handle().await.map_err(format_err) }) } Command::Verify(verify) => { let result = verify.run().map_err(format_err)?; diff --git a/crates/extrinsics/Cargo.toml b/crates/extrinsics/Cargo.toml index f710c0208..38820efe9 100644 --- a/crates/extrinsics/Cargo.toml +++ b/crates/extrinsics/Cargo.toml @@ -21,7 +21,6 @@ contract-transcode = { version = "4.0.2", path = "../transcode" } anyhow = "1.0.81" blake2 = { version = "0.10.6", default-features = false } -clap = { version = "4.5.3", features = ["derive", "env"] } futures = { version = "0.3.30", default-features = false, features = ["std"] } itertools = { version = "0.12", default-features = false } tracing = "0.1.40" diff --git a/crates/metadata/src/byte_str.rs b/crates/metadata/src/byte_str.rs index 49743efa9..32959cae8 100644 --- a/crates/metadata/src/byte_str.rs +++ b/crates/metadata/src/byte_str.rs @@ -23,7 +23,7 @@ where { if bytes.is_empty() { // Return empty string without prepended `0x`. - return serializer.serialize_str(""); + return serializer.serialize_str("") } serde_hex::serialize(bytes, serializer) } diff --git a/crates/metadata/src/compatibility.rs b/crates/metadata/src/compatibility.rs index b49596dd4..66160e19c 100644 --- a/crates/metadata/src/compatibility.rs +++ b/crates/metadata/src/compatibility.rs @@ -97,7 +97,7 @@ pub fn check_contract_ink_compatibility( .iter() .any(|req| req.matches(ink_version)) { - return Some(ver); + return Some(ver) } None }) diff --git a/crates/transcode/src/lib.rs b/crates/transcode/src/lib.rs index a349ab6a2..a58da3780 100644 --- a/crates/transcode/src/lib.rs +++ b/crates/transcode/src/lib.rs @@ -236,7 +236,7 @@ impl ContractMessageTranscoder { return Err(anyhow::anyhow!( "No constructor or message with the name '{name}' found.\n{help_txt}", - )); + )) } };