Skip to content

Commit

Permalink
Basic storage inspection command (#1395)
Browse files Browse the repository at this point in the history
* Add storage command

* WIP separate contract_info.rs

* Extract contract_info queries to separate mod

* Extract contract_artifacts to separate mod

* Delete contract_info.rs

* Fetch contract root storage and wire up cmd

* Fetch contract storage for root

* Comment

* add todo

* Revert info files to `master`

* Move to separate contract_storage.rs mod.

* WIP getting keys from contract metadata storage layout

* Get root storage key from metadata

* Make it generic

* Recurse

* WIP fetching all keys for contract

* Use prefix

* Fetch all storage entries raw

* BTreeMap

* Display enriched contract storage

* Remove unused stuff

* WIP query root keys

* Query root keys

* Warnings and don't serialize if none

* Clippy

* Use Display for TrieId

* Display raw storage

* Flatten TrieId properly

* Clippy

* CHANGELOG.md

* README.md

* Make `Client` generic

* Don't need `required_unless_present = "all"`

* Fmt
  • Loading branch information
ascjones authored Nov 27, 2023
1 parent 83b941d commit dbfa9ad
Show file tree
Hide file tree
Showing 14 changed files with 554 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add workspace support -[#1358](https://github.com/paritytech/cargo-contract/pull/1358)
- Add `Storage Total Deposit` to `info` command output - [#1347](https://github.com/paritytech/cargo-contract/pull/1347)
- Add dynamic types support - [#1399](https://github.com/paritytech/cargo-contract/pull/1399)
- Basic storage inspection command - [#1395](https://github.com/paritytech/cargo-contract/pull/1395)

### Fixed
- Do not allow to execute calls on immutable contract messages - [#1397](https://github.com/paritytech/cargo-contract/pull/1397)
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ Remove a contract from a `pallet-contracts` enabled chain. See [extrinsics](crat

Fetch and display contract information of a contract on chain. See [info](docs/info.md).

##### `cargo contract storage`

Fetch and display the storage of a contract on chain.

## Publishing

In order to publish a new version of `cargo-contract`:
Expand Down
5 changes: 3 additions & 2 deletions crates/cargo-contract/src/cmd/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use contract_extrinsics::{
CodeHash,
ContractInfo,
ErrorVariant,
TrieId,
};
use std::{
fmt::Debug,
Expand Down Expand Up @@ -139,7 +140,7 @@ impl InfoCommand {

#[derive(serde::Serialize)]
pub struct ExtendedContractInfo {
pub trie_id: String,
pub trie_id: TrieId,
pub code_hash: CodeHash,
pub storage_items: u32,
pub storage_items_deposit: Balance,
Expand All @@ -154,7 +155,7 @@ impl ExtendedContractInfo {
None => "Unknown".to_string(),
};
ExtendedContractInfo {
trie_id: contract_info.trie_id().to_string(),
trie_id: contract_info.trie_id().clone(),
code_hash: *contract_info.code_hash(),
storage_items: contract_info.storage_items(),
storage_items_deposit: contract_info.storage_items_deposit(),
Expand Down
4 changes: 3 additions & 1 deletion crates/cargo-contract/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod info;
pub mod instantiate;
pub mod remove;
pub mod schema;
pub mod storage;
pub mod upload;
pub mod verify;

Expand All @@ -42,6 +43,7 @@ pub(crate) use self::{
GenerateSchemaCommand,
VerifySchemaCommand,
},
storage::StorageCommand,
upload::UploadCommand,
verify::VerifyCommand,
};
Expand Down Expand Up @@ -223,7 +225,7 @@ 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) {
name_value_println!("TrieId", format!("{}", info.trie_id), MAX_KEY_COL_WIDTH);
name_value_println!("TrieId", info.trie_id, MAX_KEY_COL_WIDTH);
name_value_println!(
"Code Hash",
format!("{:?}", info.code_hash),
Expand Down
108 changes: 108 additions & 0 deletions crates/cargo-contract/src/cmd/storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright 2018-2023 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 <http://www.gnu.org/licenses/>.

use super::DefaultConfig;
use anyhow::Result;
use colored::Colorize;
use contract_extrinsics::{
ContractArtifacts,
ContractStorage,
ContractStorageRpc,
ErrorVariant,
};
use std::{
fmt::Debug,
path::PathBuf,
};
use subxt::Config;

#[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: <DefaultConfig as Config>::AccountId,
/// Fetch the "raw" storage keys and values for the contract.
#[clap(long)]
raw: bool,
/// Path to a contract build artifact file: a raw `.wasm` file, a `.contract` bundle,
/// or a `.json` metadata file.
#[clap(value_parser, conflicts_with = "manifest_path")]
file: Option<PathBuf>,
/// Path to the `Cargo.toml` of the contract.
#[clap(long, value_parser)]
manifest_path: Option<PathBuf>,
/// Websockets url of a substrate node.
#[clap(
name = "url",
long,
value_parser,
default_value = "ws://localhost:9944"
)]
url: url::Url,
}

impl StorageCommand {
pub async fn run(&self) -> Result<(), ErrorVariant> {
let rpc = ContractStorageRpc::<DefaultConfig>::new(&self.url).await?;
let storage_layout = ContractStorage::<DefaultConfig>::new(rpc);

if self.raw {
let storage_data = storage_layout
.load_contract_storage_data(&self.contract)
.await?;
println!(
"{json}",
json = serde_json::to_string_pretty(&storage_data)?
);
return Ok(())
}

let contract_artifacts = ContractArtifacts::from_manifest_or_file(
self.manifest_path.as_ref(),
self.file.as_ref(),
);

match contract_artifacts {
Ok(contract_artifacts) => {
let ink_metadata = contract_artifacts.ink_project_metadata()?;
let contract_storage = storage_layout
.load_contract_storage_with_layout(&ink_metadata, &self.contract)
.await?;
println!(
"{json}",
json = serde_json::to_string_pretty(&contract_storage)?
);
}
Err(_) => {
eprintln!(
"{} Displaying raw storage: no valid contract metadata artifacts found",
"Info:".cyan().bold(),
);
let storage_data = storage_layout
.load_contract_storage_data(&self.contract)
.await?;
println!(
"{json}",
json = serde_json::to_string_pretty(&storage_data)?
);
return Ok(())
}
}

Ok(())
}
}
7 changes: 7 additions & 0 deletions crates/cargo-contract/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use self::cmd::{
InfoCommand,
InstantiateCommand,
RemoveCommand,
StorageCommand,
UploadCommand,
VerifyCommand,
VerifySchemaCommand,
Expand Down Expand Up @@ -140,6 +141,9 @@ enum Command {
/// Display information about a contract
#[clap(name = "info")]
Info(InfoCommand),
/// Inspect the on-chain storage of a contract.
#[clap(name = "storage")]
Storage(StorageCommand),
/// Verifies that a given contract binary matches the build result of the specified
/// workspace.
#[clap(name = "verify")]
Expand Down Expand Up @@ -228,6 +232,9 @@ fn exec(cmd: Command) -> Result<()> {
Command::Info(info) => {
runtime.block_on(async { info.run().await.map_err(format_err) })
}
Command::Storage(storage) => {
runtime.block_on(async { storage.run().await.map_err(format_err) })
}
Command::Verify(verify) => {
let result = verify.run().map_err(format_err)?;

Expand Down
1 change: 1 addition & 0 deletions crates/extrinsics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ contract-metadata = { version = "4.0.0-alpha", path = "../metadata" }
contract-transcode = { version = "4.0.0-alpha", path = "../transcode" }

anyhow = "1.0.75"
blake2 = { version = "0.10.6", default-features = false }
clap = { version = "4.4.8", features = ["derive", "env"] }
futures = { version = "0.3.29", default-features = false, features = ["std"] }
tracing = "0.1.40"
Expand Down
15 changes: 15 additions & 0 deletions crates/extrinsics/src/contract_artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use anyhow::{
Result,
};
use colored::Colorize;
use ink_metadata::InkProject;
use std::path::{
Path,
PathBuf,
Expand Down Expand Up @@ -142,6 +143,20 @@ impl ContractArtifacts {
})
}

/// Get the deserialized [`InkProject`] metadata.
///
/// ## Errors
/// - No contract metadata could be found.
/// - Invalid contract metadata.
pub fn ink_project_metadata(&self) -> Result<InkProject> {
let metadata = self.metadata()?;
let ink_project = serde_json::from_value(serde_json::Value::Object(metadata.abi))
.context(
"Failed to deserialize ink project metadata from contract metadata",
)?;
Ok(ink_project)
}

/// Get the code hash from the contract metadata.
pub fn code_hash(&self) -> Result<[u8; 32]> {
let metadata = self.metadata()?;
Expand Down
Loading

0 comments on commit dbfa9ad

Please sign in to comment.