From 941c652caaf661ebac46eec4d3c91ddbd75ad484 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 6 Sep 2023 17:05:29 +0200 Subject: [PATCH 01/11] Add --all argument to info command --- crates/cargo-contract/src/cmd/info.rs | 164 ++++++++++++++------- crates/cargo-contract/src/cmd/mod.rs | 8 + crates/extrinsics/src/integration_tests.rs | 15 ++ crates/extrinsics/src/lib.rs | 29 +++- 4 files changed, 159 insertions(+), 57 deletions(-) diff --git a/crates/cargo-contract/src/cmd/info.rs b/crates/cargo-contract/src/cmd/info.rs index 009a34d1d..a2c755c63 100644 --- a/crates/cargo-contract/src/cmd/info.rs +++ b/crates/cargo-contract/src/cmd/info.rs @@ -16,6 +16,7 @@ use super::{ basic_display_format_contract_info, + display_all_contracts, DefaultConfig, }; use anyhow::{ @@ -23,6 +24,7 @@ use anyhow::{ Result, }; use contract_extrinsics::{ + fetch_all_contracts, fetch_contract_info, fetch_wasm_code, ErrorVariant, @@ -41,8 +43,13 @@ use tokio::runtime::Runtime; #[clap(name = "info", about = "Get infos from a contract")] pub struct InfoCommand { /// The address of the contract to display info of. - #[clap(name = "contract", long, env = "CONTRACT")] - contract: ::AccountId, + #[clap( + name = "contract", + long, + env = "CONTRACT", + required_unless_present = "all" + )] + contract: Option<::AccountId>, /// Websockets url of a substrate node. #[clap( name = "url", @@ -55,74 +62,119 @@ pub struct InfoCommand { #[clap(name = "output-json", long)] output_json: bool, /// Display the contract's Wasm bytecode. - #[clap(name = "binary", long)] + #[clap(name = "binary", long, conflicts_with = "all")] binary: bool, + /// Display all contracts addresses + #[clap(name = "all", long, conflicts_with = "contract")] + all: bool, } impl InfoCommand { pub fn run(&self) -> Result<(), ErrorVariant> { - tracing::debug!( - "Getting contract information for AccountId {:?}", - self.contract - ); - Runtime::new()?.block_on(async { let url = self.url.clone(); let client = OnlineClient::::from_url(url).await?; - let info_result = fetch_contract_info(&self.contract, &client).await?; + if self.all { + tracing::debug!("Getting all contracts"); + let count = 100; + let mut from = None; + let mut contracts_all = Vec::new(); + loop { + let mut contracts = + fetch_all_contracts(&client, count, from.as_ref()).await?; + display_all_contracts(&contracts); + if contracts.len() + < count + .try_into() + .expect("Converting u32 to usize type failed") + { + contracts_all.append(&mut contracts); + break + } else { + from = contracts.last().cloned(); + contracts_all.append(&mut contracts); + } + } + if self.output_json { + let contracts_json = serde_json::json!({ + "contracts": contracts_all + }); + println!( + "{}", + serde_json::to_string_pretty(&contracts_json).map_err(|err| { + anyhow!("JSON serialization failed: {}", err) + })? + ); + } + Ok(()) + } else { + tracing::debug!( + "Getting contract information for AccountId {:?}", + self.contract + ); + let info_result = fetch_contract_info( + self.contract + .as_ref() + .expect("Contract argument was not provided"), + &client, + ) + .await?; - match info_result { - Some(info_to_json) => { - match (self.output_json, self.binary) { - (true, false) => println!("{}", info_to_json.to_json()?), - (false, false) => { - basic_display_format_contract_info(&info_to_json) - } - // Binary flag applied - (_, true) => { - let wasm_code = - fetch_wasm_code(*info_to_json.code_hash(), &client) - .await?; - match (wasm_code, self.output_json) { - (Some(code), false) => { - std::io::stdout() - .write_all(&code) - .expect("Writing to stdout failed") - } - (Some(code), true) => { - let wasm = serde_json::json!({ - "wasm": format!("0x{}", hex::encode(code)) - }); - println!( - "{}", - serde_json::to_string_pretty(&wasm).map_err( - |err| { - anyhow!( - "JSON serialization failed: {}", - err - ) - } - )? - ); - } - (None, _) => { - return Err(anyhow!( - "Contract wasm code was not found" - ) - .into()) + match info_result { + Some(info_to_json) => { + match (self.output_json, self.binary) { + (true, false) => println!("{}", info_to_json.to_json()?), + (false, false) => { + basic_display_format_contract_info(&info_to_json) + } + // Binary flag applied + (_, true) => { + let wasm_code = + fetch_wasm_code(*info_to_json.code_hash(), &client) + .await?; + match (wasm_code, self.output_json) { + (Some(code), false) => { + std::io::stdout() + .write_all(&code) + .expect("Writing to stdout failed") + } + (Some(code), true) => { + let wasm = serde_json::json!({ + "wasm": format!("0x{}", hex::encode(code)) + }); + println!( + "{}", + serde_json::to_string_pretty(&wasm).map_err( + |err| { + anyhow!( + "JSON serialization failed: {}", + err + ) + } + )? + ); + } + (None, _) => { + return Err(anyhow!( + "Contract wasm code was not found" + ) + .into()) + } } } } + Ok(()) + } + None => { + Err(anyhow!( + "No contract information was found for account id {}", + self.contract + .as_ref() + .expect("Contract argument was not provided") + ) + .into()) } - Ok(()) - } - None => { - Err(anyhow!( - "No contract information was found for account id {}", - self.contract - ) - .into()) } } }) diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index a6e556d3f..eac5ceaa2 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -65,6 +65,7 @@ use std::io::{ self, Write, }; +use subxt::utils::AccountId32; pub use subxt::PolkadotConfig as DefaultConfig; /// Arguments required for creating and sending an extrinsic to a substrate node. @@ -230,3 +231,10 @@ pub fn basic_display_format_contract_info(info: &ContractInfo) { MAX_KEY_COL_WIDTH ); } + +/// Display all contracts addresses in a formatted way +pub fn display_all_contracts(contracts: &[AccountId32]) { + contracts.iter().for_each(|e: &AccountId32| { + name_value_println!("Contract", format!("{}", e), MAX_KEY_COL_WIDTH); + }) +} diff --git a/crates/extrinsics/src/integration_tests.rs b/crates/extrinsics/src/integration_tests.rs index 3b9e65c1a..e27efa110 100644 --- a/crates/extrinsics/src/integration_tests.rs +++ b/crates/extrinsics/src/integration_tests.rs @@ -405,6 +405,21 @@ async fn build_upload_instantiate_info() { .assert() .stdout(predicate::str::contains(r#""wasm": "0x"#)); + let output = cargo_contract(project_path.as_path()) + .arg("info") + .arg("--all") + .output() + .expect("failed to execute process"); + let stdout = str::from_utf8(&output.stdout).unwrap(); + let stderr = str::from_utf8(&output.stderr).unwrap(); + assert!( + output.status.success(), + "getting all contracts failed: {stderr}" + ); + + let contract_account_all = extract_contract_address(stdout); + assert_eq!(contract_account_all, contract_account, "{stdout:?}"); + // prevent the node_process from being dropped and killed let _ = node_process; } diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 06f28832d..d9ecc5f2c 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -52,7 +52,10 @@ use scale::{ Decode, Encode, }; -use sp_core::Bytes; +use sp_core::{ + hashing, + Bytes, +}; use subxt::{ blocks, config, @@ -378,6 +381,30 @@ pub async fn fetch_wasm_code(hash: CodeHash, client: &Client) -> Result, +) -> Result> { + let hash_pallet = hashing::twox_128(b"Contracts"); + let hash_map = hashing::twox_128(b"ContractInfoOf"); + let key = [hash_pallet, hash_map].concat(); + + let start_key = from.map(|e| [key.clone(), e.0.to_vec()].concat()); + let keys = client + .rpc() + .storage_keys_paged(key.as_slice(), count, start_key.as_deref(), None) + .await?; + + let contracts = keys + .into_iter() + .map(|e| AccountId32::decode(&mut &e.0[16 + 16 + 8..])) + .collect::>()?; + + Ok(contracts) +} + /// Copy of `pallet_contracts_primitives::StorageDeposit` which implements `Serialize`, /// required for json output. #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, serde::Serialize)] From 74b43b76f89de128fb5963acfefc74e7810de64c Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 7 Sep 2023 11:49:48 +0200 Subject: [PATCH 02/11] Add integration-tests for contract info --all, and code cleanup --- CHANGELOG.md | 1 + crates/cargo-contract/src/cmd/info.rs | 137 +++++++++++--------------- crates/cargo-contract/src/cmd/mod.rs | 16 +-- crates/extrinsics/src/error.rs | 6 ++ crates/extrinsics/src/lib.rs | 18 +++- docs/info.md | 1 + 6 files changed, 86 insertions(+), 93 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1654b08bb..9c61f51a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Detect `INK_STATIC_BUFFER_SIZE` env var - [#1310](https://github.com/paritytech/cargo-contract/pull/1310) - Add `verify` command - [#1306](https://github.com/paritytech/cargo-contract/pull/1306) - Add `--binary` flag for `info` command - [#1311](https://github.com/paritytech/cargo-contract/pull/1311/) +- Add `--all` flag for `info` command - [#1311](https://github.com/paritytech/cargo-contract/pull/1311/) ## [4.0.0-alpha] diff --git a/crates/cargo-contract/src/cmd/info.rs b/crates/cargo-contract/src/cmd/info.rs index a2c755c63..3168ad024 100644 --- a/crates/cargo-contract/src/cmd/info.rs +++ b/crates/cargo-contract/src/cmd/info.rs @@ -65,7 +65,7 @@ pub struct InfoCommand { #[clap(name = "binary", long, conflicts_with = "all")] binary: bool, /// Display all contracts addresses - #[clap(name = "all", long, conflicts_with = "contract")] + #[clap(name = "all", long)] all: bool, } @@ -75,107 +75,80 @@ impl InfoCommand { let url = self.url.clone(); let client = OnlineClient::::from_url(url).await?; + // All flag applied if self.all { - tracing::debug!("Getting all contracts"); - let count = 100; + // Using count of 255 to avoid conversion issue to usize on + // any architecture + let count = 255; let mut from = None; - let mut contracts_all = Vec::new(); + let mut contracts = Vec::new(); loop { - let mut contracts = - fetch_all_contracts(&client, count, from.as_ref()).await?; - display_all_contracts(&contracts); + let len = contracts.len(); + contracts + .append(&mut fetch_all_contracts(&client, count, from).await?); if contracts.len() - < count - .try_into() - .expect("Converting u32 to usize type failed") + < len + + >::try_into(count) + .expect("Conversion from u32 to usize failed") { - contracts_all.append(&mut contracts); break - } else { - from = contracts.last().cloned(); - contracts_all.append(&mut contracts); } + from = contracts.last(); } + if self.output_json { let contracts_json = serde_json::json!({ - "contracts": contracts_all + "contracts": contracts }); - println!( - "{}", - serde_json::to_string_pretty(&contracts_json).map_err(|err| { - anyhow!("JSON serialization failed: {}", err) - })? - ); + println!("{}", serde_json::to_string_pretty(&contracts_json)?); + } else { + display_all_contracts(&contracts) } Ok(()) } else { - tracing::debug!( - "Getting contract information for AccountId {:?}", - self.contract - ); - let info_result = fetch_contract_info( - self.contract - .as_ref() - .expect("Contract argument was not provided"), - &client, - ) - .await?; + // Contract arg shall be always present in this case, it is enforced by + // clap configuration + let contract = self + .contract + .as_ref() + .expect("Contract argument was not provided"); + + let info_to_json = + fetch_contract_info(contract, &client) + .await? + .ok_or(anyhow!( + "No contract information was found for account id {}", + contract + ))?; - match info_result { - Some(info_to_json) => { - match (self.output_json, self.binary) { - (true, false) => println!("{}", info_to_json.to_json()?), - (false, false) => { - basic_display_format_contract_info(&info_to_json) + match (self.output_json, self.binary) { + (true, false) => println!("{}", info_to_json.to_json()?), + (false, false) => basic_display_format_contract_info(&info_to_json), + // Binary flag applied + (_, true) => { + let wasm_code = + fetch_wasm_code(*info_to_json.code_hash(), &client).await?; + match (wasm_code, self.output_json) { + (Some(code), false) => { + std::io::stdout() + .write_all(&code) + .expect("Writing to stdout failed") + } + (Some(code), true) => { + let wasm = serde_json::json!({ + "wasm": format!("0x{}", hex::encode(code)) + }); + println!("{}", serde_json::to_string_pretty(&wasm)?); } - // Binary flag applied - (_, true) => { - let wasm_code = - fetch_wasm_code(*info_to_json.code_hash(), &client) - .await?; - match (wasm_code, self.output_json) { - (Some(code), false) => { - std::io::stdout() - .write_all(&code) - .expect("Writing to stdout failed") - } - (Some(code), true) => { - let wasm = serde_json::json!({ - "wasm": format!("0x{}", hex::encode(code)) - }); - println!( - "{}", - serde_json::to_string_pretty(&wasm).map_err( - |err| { - anyhow!( - "JSON serialization failed: {}", - err - ) - } - )? - ); - } - (None, _) => { - return Err(anyhow!( - "Contract wasm code was not found" - ) - .into()) - } - } + (None, _) => { + return Err( + anyhow!("Contract wasm code was not found").into() + ) } } - Ok(()) - } - None => { - Err(anyhow!( - "No contract information was found for account id {}", - self.contract - .as_ref() - .expect("Contract argument was not provided") - ) - .into()) } } + Ok(()) } }) } diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index eac5ceaa2..36efebf84 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -65,8 +65,10 @@ use std::io::{ self, Write, }; -use subxt::utils::AccountId32; -pub use subxt::PolkadotConfig as DefaultConfig; +pub use subxt::{ + Config, + PolkadotConfig as DefaultConfig, +}; /// Arguments required for creating and sending an extrinsic to a substrate node. #[derive(Clone, Debug, clap::Args)] @@ -233,8 +235,10 @@ pub fn basic_display_format_contract_info(info: &ContractInfo) { } /// Display all contracts addresses in a formatted way -pub fn display_all_contracts(contracts: &[AccountId32]) { - contracts.iter().for_each(|e: &AccountId32| { - name_value_println!("Contract", format!("{}", e), MAX_KEY_COL_WIDTH); - }) +pub fn display_all_contracts(contracts: &[::AccountId]) { + contracts + .iter() + .for_each(|e: &::AccountId| { + name_value_println!("Contract", format!("{}", e), MAX_KEY_COL_WIDTH); + }) } diff --git a/crates/extrinsics/src/error.rs b/crates/extrinsics/src/error.rs index 6a0e79704..92d342c83 100644 --- a/crates/extrinsics/src/error.rs +++ b/crates/extrinsics/src/error.rs @@ -72,6 +72,12 @@ impl From for ErrorVariant { } } +impl From for ErrorVariant { + fn from(error: serde_json::Error) -> Self { + Self::Generic(GenericError::from_message(format!("{error:?}"))) + } +} + #[derive(serde::Serialize)] pub struct ModuleError { pub pallet: String, diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index d9ecc5f2c..9d4af4f51 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -381,22 +381,30 @@ pub async fn fetch_wasm_code(hash: CodeHash, client: &Client) -> Result, ) -> Result> { - let hash_pallet = hashing::twox_128(b"Contracts"); - let hash_map = hashing::twox_128(b"ContractInfoOf"); - let key = [hash_pallet, hash_map].concat(); + // Pallet name Twox128 hash + let pallet_hash = hashing::twox_128(b"Contracts"); + + // Map name Twox128 hash + let map_hash = hashing::twox_128(b"ContractInfoOf"); + let key = [pallet_hash, map_hash].concat(); + + let start_key = from + .map(|e| [key.clone(), hashing::twox_64(&e.0).to_vec(), e.0.to_vec()].concat()); - let start_key = from.map(|e| [key.clone(), e.0.to_vec()].concat()); let keys = client .rpc() .storage_keys_paged(key.as_slice(), count, start_key.as_deref(), None) .await?; + // StorageKey is a concatention of Twox128("Contracts"), Twox128("ContractInfoOf") and + // Twox64Concat(AccountId) let contracts = keys .into_iter() .map(|e| AccountId32::decode(&mut &e.0[16 + 16 + 8..])) diff --git a/docs/info.md b/docs/info.md index 00a278d63..8a779b681 100644 --- a/docs/info.md +++ b/docs/info.md @@ -19,3 +19,4 @@ cargo contract info \ - `--url` the url of the rpc endpoint you want to specify - by default `ws://localhost:9944`. - `--output-json` to export the output as JSON. - `--binary` outputs Wasm code as a binary blob. If used in combination with `--output-json`, outputs Wasm code as JSON object with hex string. +- `--all` outputs all contracts addresses. It can not be used together with `--binary` flag. From efef0b1e0f37a491428284a017c765624c746e32 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 7 Sep 2023 12:07:41 +0200 Subject: [PATCH 03/11] Changelog with info --all support updated --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c61f51a9..51debc367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Detect `INK_STATIC_BUFFER_SIZE` env var - [#1310](https://github.com/paritytech/cargo-contract/pull/1310) - Add `verify` command - [#1306](https://github.com/paritytech/cargo-contract/pull/1306) - Add `--binary` flag for `info` command - [#1311](https://github.com/paritytech/cargo-contract/pull/1311/) -- Add `--all` flag for `info` command - [#1311](https://github.com/paritytech/cargo-contract/pull/1311/) +- Add `--all` flag for `info` command - [#1319](https://github.com/paritytech/cargo-contract/pull/1319) ## [4.0.0-alpha] From 01e768c1f04b82c859675150a45e5b1238dce097 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 7 Sep 2023 14:16:34 +0200 Subject: [PATCH 04/11] Added error when out of range in slice for pallet map key --- crates/extrinsics/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 9d4af4f51..817fe8157 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -407,7 +407,13 @@ pub async fn fetch_all_contracts( // Twox64Concat(AccountId) let contracts = keys .into_iter() - .map(|e| AccountId32::decode(&mut &e.0[16 + 16 + 8..])) + .map(|e| { + let mut account = + e.0.get(16 + 16 + 8..) + .ok_or(anyhow!("Slice out of range"))?; + AccountId32::decode(&mut account) + .map_err(|err| anyhow!("Derialization error: {}", err)) + }) .collect::>()?; Ok(contracts) From 0fdece8b58ffb8b901af6447450d12cb908b54d0 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 7 Sep 2023 14:25:48 +0200 Subject: [PATCH 05/11] Typo fixed in error message --- crates/extrinsics/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 817fe8157..02ec2d3eb 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -412,7 +412,7 @@ pub async fn fetch_all_contracts( e.0.get(16 + 16 + 8..) .ok_or(anyhow!("Slice out of range"))?; AccountId32::decode(&mut account) - .map_err(|err| anyhow!("Derialization error: {}", err)) + .map_err(|err| anyhow!("Deserialization error: {}", err)) }) .collect::>()?; From be5919579ff62f1a62804b99f97f5c03b9270948 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 7 Sep 2023 14:50:57 +0200 Subject: [PATCH 06/11] Made the errors more readable --- crates/extrinsics/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 02ec2d3eb..408de944b 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -410,9 +410,9 @@ pub async fn fetch_all_contracts( .map(|e| { let mut account = e.0.get(16 + 16 + 8..) - .ok_or(anyhow!("Slice out of range"))?; + .ok_or(anyhow!("Unexpected storage key size"))?; AccountId32::decode(&mut account) - .map_err(|err| anyhow!("Deserialization error: {}", err)) + .map_err(|err| anyhow!("AccountId deserialization error: {}", err)) }) .collect::>()?; From a4d93df0ec0612dd0b964204e31bd8e71f667995 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 7 Sep 2023 21:54:07 +0200 Subject: [PATCH 07/11] Code cleanup for info call --- crates/cargo-contract/src/cmd/info.rs | 47 +++++++++++++-------------- crates/extrinsics/src/lib.rs | 5 ++- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/cargo-contract/src/cmd/info.rs b/crates/cargo-contract/src/cmd/info.rs index 3168ad024..24ccdd83f 100644 --- a/crates/cargo-contract/src/cmd/info.rs +++ b/crates/cargo-contract/src/cmd/info.rs @@ -121,32 +121,29 @@ impl InfoCommand { contract ))?; - match (self.output_json, self.binary) { - (true, false) => println!("{}", info_to_json.to_json()?), - (false, false) => basic_display_format_contract_info(&info_to_json), - // Binary flag applied - (_, true) => { - let wasm_code = - fetch_wasm_code(*info_to_json.code_hash(), &client).await?; - match (wasm_code, self.output_json) { - (Some(code), false) => { - std::io::stdout() - .write_all(&code) - .expect("Writing to stdout failed") - } - (Some(code), true) => { - let wasm = serde_json::json!({ - "wasm": format!("0x{}", hex::encode(code)) - }); - println!("{}", serde_json::to_string_pretty(&wasm)?); - } - (None, _) => { - return Err( - anyhow!("Contract wasm code was not found").into() - ) - } - } + // Binary flag applied + if self.binary { + let wasm_code = fetch_wasm_code(&client, info_to_json.code_hash()) + .await? + .ok_or(anyhow!( + "Contract wasm code was not found for account id {}", + contract + ))?; + + if self.output_json { + let wasm = serde_json::json!({ + "wasm": format!("0x{}", hex::encode(wasm_code)) + }); + println!("{}", serde_json::to_string_pretty(&wasm)?); + } else { + std::io::stdout() + .write_all(&wasm_code) + .expect("Writing to stdout failed") } + } else if self.output_json { + println!("{}", info_to_json.to_json()?) + } else { + basic_display_format_contract_info(&info_to_json) } Ok(()) } diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 408de944b..2a9857908 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -367,7 +367,10 @@ impl ContractInfo { } /// Fetch the contract wasm code from the storage using the provided client and code hash. -pub async fn fetch_wasm_code(hash: CodeHash, client: &Client) -> Result>> { +pub async fn fetch_wasm_code( + client: &Client, + hash: &CodeHash, +) -> Result>> { let pristine_code_address = api::storage().contracts().pristine_code(hash); let pristine_bytes = client From a1d059b8c9df8c120d4d8c765c606c82626df2c4 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Fri, 8 Sep 2023 16:15:00 +0200 Subject: [PATCH 08/11] Simplified fetch_all_contracts function --- crates/extrinsics/src/lib.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 2a9857908..215655ce5 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -391,28 +391,26 @@ pub async fn fetch_all_contracts( count: u32, from: Option<&AccountId32>, ) -> Result> { - // Pallet name Twox128 hash - let pallet_hash = hashing::twox_128(b"Contracts"); - - // Map name Twox128 hash - let map_hash = hashing::twox_128(b"ContractInfoOf"); - let key = [pallet_hash, map_hash].concat(); - + let key = api::storage() + .contracts() + .contract_info_of_root() + .to_root_bytes(); let start_key = from .map(|e| [key.clone(), hashing::twox_64(&e.0).to_vec(), e.0.to_vec()].concat()); - let keys = client - .rpc() - .storage_keys_paged(key.as_slice(), count, start_key.as_deref(), None) + .storage() + .at_latest() + .await? + .fetch_keys(key.as_ref(), count, start_key.as_deref()) .await?; - // StorageKey is a concatention of Twox128("Contracts"), Twox128("ContractInfoOf") and + // StorageKey is a concatention of contract_info_of root key and // Twox64Concat(AccountId) let contracts = keys .into_iter() .map(|e| { let mut account = - e.0.get(16 + 16 + 8..) + e.0.get(key.len() + 8..) .ok_or(anyhow!("Unexpected storage key size"))?; AccountId32::decode(&mut account) .map_err(|err| anyhow!("AccountId deserialization error: {}", err)) From d59e201bc05df4c78d1c6ebca704f04a76a06eef Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Mon, 11 Sep 2023 15:15:29 +0200 Subject: [PATCH 09/11] Changed usize conversion and run method to async in info cmd --- crates/cargo-contract/src/cmd/info.rs | 129 ++++++++++++-------------- crates/cargo-contract/src/main.rs | 4 +- 2 files changed, 63 insertions(+), 70 deletions(-) diff --git a/crates/cargo-contract/src/cmd/info.rs b/crates/cargo-contract/src/cmd/info.rs index 24ccdd83f..0f2d67be9 100644 --- a/crates/cargo-contract/src/cmd/info.rs +++ b/crates/cargo-contract/src/cmd/info.rs @@ -37,7 +37,6 @@ use subxt::{ Config, OnlineClient, }; -use tokio::runtime::Runtime; #[derive(Debug, clap::Args)] #[clap(name = "info", about = "Get infos from a contract")] @@ -70,83 +69,75 @@ pub struct InfoCommand { } impl InfoCommand { - pub fn run(&self) -> Result<(), ErrorVariant> { - Runtime::new()?.block_on(async { - let url = self.url.clone(); - let client = OnlineClient::::from_url(url).await?; + pub async fn run(&self) -> Result<(), ErrorVariant> { + let url = self.url.clone(); + let client = OnlineClient::::from_url(url).await?; - // All flag applied - if self.all { - // Using count of 255 to avoid conversion issue to usize on - // any architecture - let count = 255; - let mut from = None; - let mut contracts = Vec::new(); - loop { - let len = contracts.len(); - contracts - .append(&mut fetch_all_contracts(&client, count, from).await?); - if contracts.len() - < len - + >::try_into(count) - .expect("Conversion from u32 to usize failed") - { - break - } - from = contracts.last(); + // All flag applied + if self.all { + // 1000 is max allowed value + const COUNT: u32 = 1000; + let mut from = None; + let mut contracts = Vec::new(); + loop { + let len = contracts.len(); + contracts.append(&mut fetch_all_contracts(&client, COUNT, from).await?); + if contracts.len() < len + COUNT as usize { + break } + from = contracts.last(); + } - if self.output_json { - let contracts_json = serde_json::json!({ - "contracts": contracts - }); - println!("{}", serde_json::to_string_pretty(&contracts_json)?); - } else { - display_all_contracts(&contracts) - } - Ok(()) + if self.output_json { + let contracts_json = serde_json::json!({ + "contracts": contracts + }); + println!("{}", serde_json::to_string_pretty(&contracts_json)?); } else { - // Contract arg shall be always present in this case, it is enforced by - // clap configuration - let contract = self - .contract - .as_ref() - .expect("Contract argument was not provided"); + display_all_contracts(&contracts) + } + Ok(()) + } else { + // Contract arg shall be always present in this case, it is enforced by + // clap configuration + let contract = self + .contract + .as_ref() + .expect("Contract argument was not provided"); - let info_to_json = - fetch_contract_info(contract, &client) - .await? - .ok_or(anyhow!( - "No contract information was found for account id {}", - contract - ))?; + let info_to_json = + fetch_contract_info(contract, &client) + .await? + .ok_or(anyhow!( + "No contract information was found for account id {}", + contract + ))?; - // Binary flag applied - if self.binary { - let wasm_code = fetch_wasm_code(&client, info_to_json.code_hash()) - .await? - .ok_or(anyhow!( - "Contract wasm code was not found for account id {}", - contract - ))?; + // Binary flag applied + if self.binary { + let wasm_code = fetch_wasm_code(&client, info_to_json.code_hash()) + .await? + .ok_or(anyhow!( + "Contract wasm code was not found for account id {}", + contract + ))?; - if self.output_json { - let wasm = serde_json::json!({ - "wasm": format!("0x{}", hex::encode(wasm_code)) - }); - println!("{}", serde_json::to_string_pretty(&wasm)?); - } else { - std::io::stdout() - .write_all(&wasm_code) - .expect("Writing to stdout failed") - } - } else if self.output_json { - println!("{}", info_to_json.to_json()?) + if self.output_json { + let wasm = serde_json::json!({ + "wasm": format!("0x{}", hex::encode(wasm_code)) + }); + println!("{}", serde_json::to_string_pretty(&wasm)?); } else { - basic_display_format_contract_info(&info_to_json) + std::io::stdout() + .write_all(&wasm_code) + .expect("Writing to stdout failed") } - Ok(()) + } else if self.output_json { + println!("{}", info_to_json.to_json()?) + } else { + basic_display_format_contract_info(&info_to_json) } - }) + Ok(()) + } } } diff --git a/crates/cargo-contract/src/main.rs b/crates/cargo-contract/src/main.rs index f2a81258d..9d81f213b 100644 --- a/crates/cargo-contract/src/main.rs +++ b/crates/cargo-contract/src/main.rs @@ -217,7 +217,9 @@ fn exec(cmd: Command) -> Result<()> { .map_err(|err| map_extrinsic_err(err, remove.output_json())) }) } - Command::Info(info) => info.run().map_err(format_err), + Command::Info(info) => { + runtime.block_on(async { info.run().await.map_err(format_err) }) + } Command::Verify(verify) => { let result = verify.run().map_err(format_err)?; From 01d8d5d0b5aa331bfb9e9aff04b4316d473df6a2 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Mon, 11 Sep 2023 17:33:44 +0200 Subject: [PATCH 10/11] Code cleanup, typo in description fixed --- crates/cargo-contract/src/cmd/info.rs | 10 +++---- crates/cargo-contract/src/cmd/mod.rs | 4 +-- crates/extrinsics/src/integration_tests.rs | 3 +-- crates/extrinsics/src/lib.rs | 31 +++++++++++++--------- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/crates/cargo-contract/src/cmd/info.rs b/crates/cargo-contract/src/cmd/info.rs index 0f2d67be9..a0d68a53e 100644 --- a/crates/cargo-contract/src/cmd/info.rs +++ b/crates/cargo-contract/src/cmd/info.rs @@ -70,19 +70,19 @@ pub struct InfoCommand { impl InfoCommand { pub async fn run(&self) -> Result<(), ErrorVariant> { - let url = self.url.clone(); - let client = OnlineClient::::from_url(url).await?; + let client = OnlineClient::::from_url(&self.url).await?; // All flag applied if self.all { // 1000 is max allowed value - const COUNT: u32 = 1000; + const MAX_COUNT: u32 = 1000; let mut from = None; let mut contracts = Vec::new(); loop { let len = contracts.len(); - contracts.append(&mut fetch_all_contracts(&client, COUNT, from).await?); - if contracts.len() < len + COUNT as usize { + contracts + .append(&mut fetch_all_contracts(&client, MAX_COUNT, from).await?); + if contracts.len() < len + MAX_COUNT as usize { break } from = contracts.last(); diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index 36efebf84..6fcac4275 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -238,7 +238,5 @@ pub fn basic_display_format_contract_info(info: &ContractInfo) { pub fn display_all_contracts(contracts: &[::AccountId]) { contracts .iter() - .for_each(|e: &::AccountId| { - name_value_println!("Contract", format!("{}", e), MAX_KEY_COL_WIDTH); - }) + .for_each(|e: &::AccountId| println!("{}", e)) } diff --git a/crates/extrinsics/src/integration_tests.rs b/crates/extrinsics/src/integration_tests.rs index e27efa110..abd0fcadf 100644 --- a/crates/extrinsics/src/integration_tests.rs +++ b/crates/extrinsics/src/integration_tests.rs @@ -417,8 +417,7 @@ async fn build_upload_instantiate_info() { "getting all contracts failed: {stderr}" ); - let contract_account_all = extract_contract_address(stdout); - assert_eq!(contract_account_all, contract_account, "{stdout:?}"); + assert_eq!(stdout.trim_end(), contract_account, "{stdout:?}"); // prevent the node_process from being dropped and killed let _ = node_process; diff --git a/crates/extrinsics/src/lib.rs b/crates/extrinsics/src/lib.rs index 215655ce5..c8c3d3f56 100644 --- a/crates/extrinsics/src/lib.rs +++ b/crates/extrinsics/src/lib.rs @@ -384,18 +384,33 @@ pub async fn fetch_wasm_code( Ok(pristine_bytes) } -/// Fetch all contracts addresses from the storage using the provided client and count of +/// Parse a contract account address from a storage key. Returns error if a key is +/// malformated. +fn parse_contract_account_address( + storage_contract_account_key: &[u8], + storage_contract_root_key_len: usize, +) -> Result { + // storage_contract_account_key is a concatenation of contract_info_of root key and + // Twox64Concat(AccountId) + let mut account = storage_contract_account_key + .get(storage_contract_root_key_len + 8..) + .ok_or(anyhow!("Unexpected storage key size"))?; + AccountId32::decode(&mut account) + .map_err(|err| anyhow!("AccountId deserialization error: {}", err)) +} + +/// Fetch all contract addresses from the storage using the provided client and count of /// requested elements starting from an optional address pub async fn fetch_all_contracts( client: &Client, count: u32, - from: Option<&AccountId32>, + count_from: Option<&AccountId32>, ) -> Result> { let key = api::storage() .contracts() .contract_info_of_root() .to_root_bytes(); - let start_key = from + let start_key = count_from .map(|e| [key.clone(), hashing::twox_64(&e.0).to_vec(), e.0.to_vec()].concat()); let keys = client .storage() @@ -404,17 +419,9 @@ pub async fn fetch_all_contracts( .fetch_keys(key.as_ref(), count, start_key.as_deref()) .await?; - // StorageKey is a concatention of contract_info_of root key and - // Twox64Concat(AccountId) let contracts = keys .into_iter() - .map(|e| { - let mut account = - e.0.get(key.len() + 8..) - .ok_or(anyhow!("Unexpected storage key size"))?; - AccountId32::decode(&mut account) - .map_err(|err| anyhow!("AccountId deserialization error: {}", err)) - }) + .map(|e| parse_contract_account_address(&e.0, key.len())) .collect::>()?; Ok(contracts) From 3b056d57b1585e374486b9bf78f0affa54f4e25b Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Mon, 11 Sep 2023 17:38:33 +0200 Subject: [PATCH 11/11] Variables renamed --- crates/cargo-contract/src/cmd/info.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/cargo-contract/src/cmd/info.rs b/crates/cargo-contract/src/cmd/info.rs index a0d68a53e..a5d5b393c 100644 --- a/crates/cargo-contract/src/cmd/info.rs +++ b/crates/cargo-contract/src/cmd/info.rs @@ -76,16 +76,17 @@ impl InfoCommand { if self.all { // 1000 is max allowed value const MAX_COUNT: u32 = 1000; - let mut from = None; + let mut count_from = None; let mut contracts = Vec::new(); loop { let len = contracts.len(); - contracts - .append(&mut fetch_all_contracts(&client, MAX_COUNT, from).await?); + contracts.append( + &mut fetch_all_contracts(&client, MAX_COUNT, count_from).await?, + ); if contracts.len() < len + MAX_COUNT as usize { break } - from = contracts.last(); + count_from = contracts.last(); } if self.output_json {