CLI Tool Explore Command: Runtime APIs, Events, Colorized outputs, scale-typegen integration (#1290)

* restructure cli commands

* config: Add `SkipCheckIfFeeless` signed extension (#1264)

* config: Add `SkipCheckIfFeeless` signed extension

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* config: Add extra extension to the default params

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* examples: Adjust signed extension example

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* config: Extend SkipCheckIfFeeless with inner signed extension

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* config: Configure SkipCheck with inner signed extension params

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* config: Implement Deafult for SkipCheckIfFeelessParams with Option

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* examples: Fix example with proper extension

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* config: Extend <T as Config>::AssetId with EncodeAsType and Clone

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* config: Add SkipCheck with AssetTx

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* config: Encode as type from metadata the inner signed extensions

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Adjust examples

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* blocks: Use `SkipCheckIfFeeless` for decoding the tip of extensions

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* config: Decode `SkipCheckIfFeeless` with `Self`

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* tests: Adjust testing

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* config: Descriptive errors for building `SkipCheckIfFeeless`

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* config: Add docs for extra error types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Add extra derives to signed extensions

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* config: Use `Default::default` to simplify type init

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Replace removed lint (#1270)

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Add support for multi-chain usecase (#1238)

* lightclient: Make `smoldot::chainID` part of the RPC requests

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Make `BackgroundTask` generic over `PlatformRef` and chain

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Construct from raw smoldot and target different chains

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Update cargo lock for wasm tests

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Reuse `new_from_client` method and removed unused imports

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Reexport smoldot client and RPC objects used in pub
interface

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Adjust `new_from_client` interface

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Extend background to poll over multiple RPC objects

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Build light client from raw and target different chains

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Add demo chain specs

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Move artifacts to dedicated folder

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Use SelectAll to drive all streams

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Fetch initial data from the target chain

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Reexport other smoldot objects

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Target chain with potentially different config

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt/rpc: Log chainID for debugging

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt/examples: Add smoldot client with parachain example

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Propagate chain ID together with rpc responses object

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Multiplex responses by request ID and chain ID

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Add raw light client builder

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Add cargo feature flag for parachains example

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Derive default for internal structure

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Guard reexports by std feature flag

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update subxt/src/client/light_client/mod.rs

Co-authored-by: James Wilson <james@jsdw.me>

* lightclient: Update the builder pattern and chain targetting

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Fix documentation

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Provide more insightful docs wrt native/wasm panics

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* examples: Adjust comment location

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Refactor UniqueChainId into the background task

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update lightclient/src/background.rs

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>

* Update subxt/src/client/light_client/builder.rs

Co-authored-by: James Wilson <james@jsdw.me>

* lightclient: Update docs wrt panics

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Update docs wrt to smoldot instance -> client

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Use IntoIter instead of Iterator

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Adjsut docs wrt [`Self::new_from_client`]

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Remove RawRpc from LightClient in favor of chainID

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Reexport everything under smoldot module

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Use stateRootHash instead of genesis.raw

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: James Wilson <james@jsdw.me>
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>

* Bump futures from 0.3.28 to 0.3.29 (#1272)

Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.28 to 0.3.29.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.28...0.3.29)

---
updated-dependencies:
- dependency-name: futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump zeroize from 1.6.0 to 1.7.0 (#1274)

Bumps [zeroize](https://github.com/RustCrypto/utils) from 1.6.0 to 1.7.0.
- [Commits](https://github.com/RustCrypto/utils/commits)

---
updated-dependencies:
- dependency-name: zeroize
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump tracing-subscriber from 0.3.17 to 0.3.18 (#1275)

Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.17 to 0.3.18.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.17...tracing-subscriber-0.3.18)

---
updated-dependencies:
- dependency-name: tracing-subscriber
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump tracing-subscriber from 0.3.17 to 0.3.18 (#1275)

Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.17 to 0.3.18.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.17...tracing-subscriber-0.3.18)

---
updated-dependencies:
- dependency-name: tracing-subscriber
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump getrandom from 0.2.10 to 0.2.11 (#1273)

Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.2.10 to 0.2.11.
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.2.10...v0.2.11)

---
updated-dependencies:
- dependency-name: getrandom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* impl RpcClientT for Arc<T> and Box<T> (#1277)

* impl RpcClientT for Arc<WsClient>

* fix grumbles: impl for Box<T> and Arc<T>

* grumbles: move RpcClientT impls

* first iteration of using scale_typegen

* introduce indoc for formatting

* calls, constants and home are cleaner now

* added event subcommand

* show runtime apis working

* add better code formatting

* fix style

* adjust tests, use owo_colorize to not add extra dependency

* fmt

* adjust docs

* move scale-typegen-description dependency to workspace

* improve `substrate-compat` (#1265)

* improve `substrate-compat`

* From => Into

---------

Co-authored-by: James Wilson <james@jsdw.me>

* Bump proc-macro2 from 1.0.69 to 1.0.70 (#1292)

Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.69 to 1.0.70.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.69...1.0.70)

---
updated-dependencies:
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump serde from 1.0.192 to 1.0.193 (#1291)

Bumps [serde](https://github.com/serde-rs/serde) from 1.0.192 to 1.0.193.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.192...v1.0.193)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* lightclient: Fix wasm socket closure called after being dropped (#1289)

* lightclient: Close wasm socket while dropping from connecting state

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Construct one time only closures

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Enable console logs for lightclient WASM testing

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Separate wakes and check connectivity on poll_read

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Close the socket depending on internal state

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Revert "lightclient: Separate wakes and check connectivity on poll_read"

This reverts commit 866094001d4c0b119a80ed681a74b323f74eae1b.

* lightclient: Return pending if socket is opening from poll_read

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Close the socket on `poll_close`

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Reset closures on Drop to avoid recursive invokation

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Close the socket if not already closing

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* workflows: Install rustup component for building substrate (#1295)

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Command to fetch chainSpec and optimise its size (#1278)

* cli: Add chainSpec command

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli/chainSpec: Move to dedicated module

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Compute the state root hash

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Remove code substitutes

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Update polkadot.json

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* scripts: Generate the chain spec

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Remove testing artifacts

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Fix clippy

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Apply rustfmt

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Introduce feature flag for smoldot dependency

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli: Rename chain-spec to chain-spec-pruning

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* scripts: Update chain-spec command

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* update to new scale-typegen interfaces

* use released version of scale-typegen

* Merge branch 'master' into tadeohepperle/cli-support-runtime-apis

* remove unused debug file

* resolve merge errors

* adjustments

* constants file adjustment

* method renaming

* fix issue with encoding runtime api params

* Add logging to submit_transaction and unstable driver, and ensure unpin evs complete

* panic if None returned from subscription too, also with stats

* change panic to Err just to be on the safe side

* clippy

* make long tests run only after clippy + fmt pass

* megre in light client test change pr

* chore(subxt/src): typo fix (#1370)

* rpcmethods

* followstr

* mod and else

* Weekly Cronjob fetching artifacts and generating polkadot.rs file. (#1352)

* github CI action cronjob

* add commit message

* fix the CI yml files

* binary crate for CI script with substrate-runner

* update the CI script

* correct the artifacts script

* remove bash script

* lightclient(fix): Ensure lightclient chainSpec is at least one block old (#1372)

* testing(fix): Ensure lightclient chainSpec is at least one block old

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Revert "testing(fix): Ensure lightclient chainSpec is at least one block old"

This reverts commit 0eafcb2ca59d1f1cd2cef86b770f5a0401cce59f.

* lightclient(fix): Ensure lightclient chainSpec is at least one block old

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Link smoldot issue

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Use tokio under lightclient feature flag

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Do not sleep on errors to fetch the chainSpec

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* artifacts: Remove test file

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Subscribe to two finalized blocks

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* subxt: Revert cargo toml

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* ci: Reduce the light client timeout to 15 minutes (#1373)

* ci: Reduce the light client timpeut to 15 seconds

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* ci: Use ubuntu-latest for light-client tests

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* actually only wait for machete+fmt, clippy can be much slower

* update CI file from Alex PR

* resolve clippy err

* Try a few RPC nodes in case one of them is not working

* fix submit_transaction debug logging of message

* Improve Signed Extension and Block Decoding Examples/Book (#1357)

* asset hub example and book adjustment

* formatting

* recursive derives

* polkadot monitor example and book adjustments

* formatting

* adjust docs and examples, add dynamic example

* james suggestions

* fmt

* chore(subxt/src): typo fix (#1370)

* rpcmethods

* followstr

* mod and else

* Weekly Cronjob fetching artifacts and generating polkadot.rs file. (#1352)

* github CI action cronjob

* add commit message

* fix the CI yml files

* binary crate for CI script with substrate-runner

* update the CI script

* correct the artifacts script

* remove bash script

---------

Co-authored-by: James Wilson <james@jsdw.me>
Co-authored-by: Pan chao <152830401+Pan-chao@users.noreply.github.com>

* fix formatting of returned sections

* make storage use execute flag as well

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
Co-authored-by: James Wilson <james@jsdw.me>
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: yjh <yjh465402634@gmail.com>
Co-authored-by: Pan chao <152830401+Pan-chao@users.noreply.github.com>
This commit is contained in:
Tadeo Hepperle
2024-01-19 17:57:16 +01:00
committed by GitHub
parent 0f48d54209
commit 05ced83f74
15 changed files with 1345 additions and 1135 deletions
+332 -122
View File
@@ -1,159 +1,240 @@
use crate::utils::{print_first_paragraph_with_indent, validate_url_security, FileOrUrl};
use clap::{Parser as ClapParser, Subcommand};
use crate::utils::validate_url_security;
use crate::utils::FileOrUrl;
use clap::{command, Parser, Subcommand};
use codec::Decode;
use color_eyre::eyre::eyre;
use color_eyre::owo_colors::OwoColorize;
use indoc::writedoc;
use std::fmt::Write;
use std::write;
use codec::Decode;
use color_eyre::eyre::eyre;
use crate::commands::explore::calls::{explore_calls, CallsSubcommand};
use crate::commands::explore::constants::{explore_constants, ConstantsSubcommand};
use crate::commands::explore::storage::{explore_storage, StorageSubcommand};
use subxt::Metadata;
mod calls;
mod constants;
mod storage;
use self::pallets::PalletSubcommand;
mod pallets;
mod runtime_apis;
/// Explore pallets, calls, call parameters, storage entries and constants. Also allows for creating (unsigned) extrinsics.
///
/// # Example
///
/// ## Pallets
///
/// Show the pallets that are available:
/// Show the pallets and runtime apis that are available:
/// ```text
/// subxt explore --file=polkadot_metadata.scale
/// ```
///
/// ## Calls
/// ## Pallets
///
/// each pallet has `calls`, `constants`, `storage` and `events` that can be explored.
///
/// ### Calls
///
/// Show the calls in a pallet:
///
/// ```text
/// subxt explore Balances calls
/// subxt explore pallet Balances calls
/// ```
///
/// Show the call parameters a call expects:
///
/// ```text
/// subxt explore Balances calls transfer
/// subxt explore pallet Balances calls transfer
/// ```
///
/// Create an unsigned extrinsic from a scale value, validate it and output its hex representation
///
/// ```text
/// subxt explore Grandpa calls note_stalled { "delay": 5, "best_finalized_block_number": 5 }
/// subxt explore pallet Grandpa calls note_stalled { "delay": 5, "best_finalized_block_number": 5 }
/// # Encoded call data:
/// # 0x2c0411020500000005000000
/// subxt explore Balances calls transfer "{ \"dest\": v\"Raw\"((255, 255, 255)), \"value\": 0 }"
/// subxt explore pallet Balances calls transfer "{ \"dest\": v\"Raw\"((255, 255, 255)), \"value\": 0 }"
/// # Encoded call data:
/// # 0x24040607020cffffff00
/// ```
/// ## Constants
///
/// ### Constants
///
/// Show the constants in a pallet:
///
/// ```text
/// subxt explore Balances constants
/// subxt explore pallet Balances constants
/// ```
/// ## Storage
///
/// ### Storage
///
/// Show the storage entries in a pallet
///
/// ```text
/// subxt explore Alliance storage
/// ```
/// Show the types and value of a specific storage entry
/// ```text
/// subxt explore Alliance storage Announcements [KEY_SCALE_VALUE]
/// subxt explore pallet Alliance storage
/// ```
///
#[derive(Debug, ClapParser)]
/// Show the types and value of a specific storage entry
///
/// ```text
/// subxt explore pallet Alliance storage Announcements [KEY_SCALE_VALUE]
/// ```
///
/// ### Events
///
/// ```text
/// subxt explore pallet Balances events
/// ```
///
/// Show the type of a specific event
///
/// ```text
/// subxt explore pallet Balances events frozen
/// ```
///
/// ## Runtime APIs
/// Show the input and output types of a runtime api method.
/// In this example "core" is the name of the runtime api and "version" is a method on it:
///
/// ```text
/// subxt explore api core version
/// ```
///
/// Execute a runtime API call with the `--execute` (`-e`) flag, to see the return value.
/// For example here we get the "version", via the "core" runtime API from the connected node:
///
/// ```text
/// subxt explore api core version --execute
/// ```
///
#[derive(Debug, Parser)]
pub struct Opts {
#[command(flatten)]
file_or_url: FileOrUrl,
pallet: Option<String>,
#[command(subcommand)]
pallet_subcommand: Option<PalletSubcommand>,
subcommand: Option<PalletOrRuntimeApi>,
/// Allow insecure URLs e.g. URLs starting with ws:// or http:// without SSL encryption
#[clap(long, short)]
allow_insecure: bool,
}
#[derive(Debug, Clone, Subcommand)]
pub enum PalletSubcommand {
Calls(CallsSubcommand),
Constants(ConstantsSubcommand),
Storage(StorageSubcommand),
#[derive(Debug, Subcommand)]
pub enum PalletOrRuntimeApi {
Pallet(PalletOpts),
Api(RuntimeApiOpts),
}
#[derive(Debug, Parser)]
pub struct PalletOpts {
pub name: Option<String>,
#[command(subcommand)]
pub subcommand: Option<PalletSubcommand>,
}
#[derive(Debug, Parser)]
pub struct RuntimeApiOpts {
pub name: Option<String>,
#[clap(required = false)]
pub method: Option<String>,
#[clap(long, short, action)]
pub execute: bool,
#[clap(required = false)]
trailing_args: Vec<String>,
}
pub async fn run(opts: Opts, output: &mut impl std::io::Write) -> color_eyre::Result<()> {
validate_url_security(opts.file_or_url.url.as_ref(), opts.allow_insecure)?;
// get the metadata
let bytes = opts.file_or_url.fetch().await?;
let file_or_url = opts.file_or_url;
let bytes = file_or_url.fetch().await?;
let metadata = Metadata::decode(&mut &bytes[..])?;
// if no pallet specified, show user the pallets to choose from:
let Some(pallet_name) = opts.pallet else {
let available_pallets = print_available_pallets(&metadata);
writeln!(output, "Usage:",)?;
writeln!(output, " subxt explore <PALLET>",)?;
writeln!(output, " explore a specific pallet",)?;
writeln!(output, "\n{available_pallets}",)?;
let pallet_placeholder = "<PALLET>".blue();
let runtime_api_placeholder = "<RUNTIME_API>".blue();
// if no pallet/runtime_api specified, show user the pallets/runtime_apis to choose from:
let Some(pallet_or_runtime_api) = opts.subcommand else {
let pallets = pallets_as_string(&metadata);
let runtime_apis = runtime_apis_as_string(&metadata);
writedoc! {output, "
Usage:
subxt explore pallet {pallet_placeholder}
explore a specific pallet
subxt explore api {runtime_api_placeholder}
explore a specific runtime api
{pallets}
{runtime_apis}
"}?;
return Ok(());
};
// if specified pallet is wrong, show user the pallets to choose from (but this time as an error):
let Some(pallet_metadata) = metadata
.pallets()
.find(|pallet| pallet.name().to_lowercase() == pallet_name.to_lowercase())
else {
return Err(eyre!(
"pallet \"{}\" not found in metadata!\n{}",
pallet_name,
print_available_pallets(&metadata)
));
};
match pallet_or_runtime_api {
PalletOrRuntimeApi::Pallet(opts) => {
let Some(name) = opts.name else {
let pallets = pallets_as_string(&metadata);
writedoc! {output, "
Usage:
subxt explore pallet {pallet_placeholder}
explore a specific pallet
{pallets}
"}?;
return Ok(());
};
// if correct pallet was specified but no subcommand, instruct the user how to proceed:
let Some(pallet_subcomand) = opts.pallet_subcommand else {
let docs_string = print_first_paragraph_with_indent(pallet_metadata.docs(), 4);
if !docs_string.is_empty() {
writeln!(output, "Description:\n{docs_string}")?;
if let Some(pallet) = metadata
.pallets()
.find(|e| e.name().eq_ignore_ascii_case(&name))
{
pallets::run(opts.subcommand, pallet, &metadata, file_or_url, output).await
} else {
Err(eyre!(
"pallet \"{name}\" not found in metadata!\n{}",
pallets_as_string(&metadata),
))
}
}
writeln!(output, "Usage:")?;
writeln!(output, " subxt explore {pallet_name} calls")?;
writeln!(
output,
" explore the calls that can be made into this pallet"
)?;
writeln!(output, " subxt explore {pallet_name} constants")?;
writeln!(output, " explore the constants held in this pallet")?;
writeln!(output, " subxt explore {pallet_name} storage")?;
writeln!(
output,
" explore the storage values held in this pallet"
)?;
return Ok(());
};
PalletOrRuntimeApi::Api(opts) => {
let Some(name) = opts.name else {
let runtime_apis = runtime_apis_as_string(&metadata);
writedoc! {output, "
Usage:
subxt explore api {runtime_api_placeholder}
explore a specific runtime api
match pallet_subcomand {
PalletSubcommand::Calls(command) => {
explore_calls(command, &metadata, pallet_metadata, output)
}
PalletSubcommand::Constants(command) => {
explore_constants(command, &metadata, pallet_metadata, output)
}
PalletSubcommand::Storage(command) => {
// if the metadata is in some url, we use that same url to make storage calls against.
let node_url = opts.file_or_url.url.map(|url| url.to_string());
explore_storage(command, &metadata, pallet_metadata, node_url, output).await
{runtime_apis}
"}?;
return Ok(());
};
if let Some(runtime_api) = metadata
.runtime_api_traits()
.find(|e| e.name().eq_ignore_ascii_case(&name))
{
runtime_apis::run(
opts.method,
opts.execute,
opts.trailing_args,
runtime_api,
&metadata,
file_or_url,
output,
)
.await
} else {
Err(eyre!(
"runtime api \"{name}\" not found in metadata!\n{}",
runtime_apis_as_string(&metadata),
))
}
}
}
}
fn print_available_pallets(metadata: &Metadata) -> String {
fn pallets_as_string(metadata: &Metadata) -> String {
let pallet_placeholder = "<PALLET>".blue();
if metadata.pallets().len() == 0 {
"There are no <PALLET> values available.".to_string()
format!("There are no {pallet_placeholder}'s available.")
} else {
let mut output = "Available <PALLET> values are:".to_string();
let mut output = format!("Available {pallet_placeholder}'s are:");
let mut strings: Vec<_> = metadata.pallets().map(|p| p.name()).collect();
strings.sort();
for pallet in strings {
@@ -163,8 +244,27 @@ fn print_available_pallets(metadata: &Metadata) -> String {
}
}
pub fn runtime_apis_as_string(metadata: &Metadata) -> String {
let runtime_api_placeholder = "<RUNTIME_API>".blue();
if metadata.runtime_api_traits().len() == 0 {
format!("There are no {runtime_api_placeholder}'s available.")
} else {
let mut output = format!("Available {runtime_api_placeholder}'s are:");
let mut strings: Vec<_> = metadata.runtime_api_traits().map(|p| p.name()).collect();
strings.sort();
for api in strings {
write!(output, "\n {}", api).unwrap();
}
output
}
}
#[cfg(test)]
pub mod tests {
use indoc::formatdoc;
use pretty_assertions::assert_eq;
use super::Opts;
async fn run(cli_command: &str) -> color_eyre::Result<String> {
@@ -173,9 +273,25 @@ pub mod tests {
args.append(&mut split);
let opts: Opts = clap::Parser::try_parse_from(args)?;
let mut output: Vec<u8> = Vec::new();
super::run(opts, &mut output)
let r = super::run(opts, &mut output)
.await
.map(|_| String::from_utf8(output).unwrap())
.map(|_| String::from_utf8(output).unwrap())?;
Ok(r)
}
trait StripAnsi: ToString {
fn strip_ansi(&self) -> String {
let bytes = strip_ansi_escapes::strip(self.to_string().as_bytes());
String::from_utf8(bytes).unwrap()
}
}
impl<T: ToString> StripAnsi for T {}
macro_rules! assert_eq_start {
($a:expr, $b:expr) => {
assert_eq!(&$a[0..$b.len()], &$b[..]);
};
}
async fn run_against_file(cli_command: &str) -> color_eyre::Result<String> {
@@ -187,44 +303,138 @@ pub mod tests {
#[tokio::test]
async fn test_commands() {
// show pallets:
let output = run_against_file("").await;
assert_eq!(output.unwrap(), "Usage:\n subxt explore <PALLET>\n explore a specific pallet\n\nAvailable <PALLET> values are:\n Balances\n Multisig\n ParaInherent\n System\n Timestamp\n");
// shows pallets and runtime apis:
let output = run_against_file("").await.unwrap().strip_ansi();
let expected_output = formatdoc! {
"Usage:
subxt explore pallet <PALLET>
explore a specific pallet
subxt explore api <RUNTIME_API>
explore a specific runtime api
Available <PALLET>'s are:
Balances
Multisig
ParaInherent
System
Timestamp
Available <RUNTIME_API>'s are:
AccountNonceApi
AuthorityDiscoveryApi
BabeApi
BeefyApi
BeefyMmrApi
BlockBuilder
Core
GenesisBuilder
GrandpaApi
Metadata
MmrApi
OffchainWorkerApi
ParachainHost
SessionKeys
TaggedTransactionQueue
TransactionPaymentApi
"};
assert_eq!(output, expected_output);
// if incorrect pallet, error:
let output = run_against_file("abc123").await;
assert!(output.is_err());
// if correct pallet, show options (calls, constants, storage)
let output = run_against_file("Balances").await;
assert_eq!(output.unwrap(), "Usage:\n subxt explore Balances calls\n explore the calls that can be made into this pallet\n subxt explore Balances constants\n explore the constants held in this pallet\n subxt explore Balances storage\n explore the storage values held in this pallet\n");
let output = run_against_file("pallet Balances")
.await
.unwrap()
.strip_ansi();
let expected_output = formatdoc! {"
Usage:
subxt explore pallet Balances calls
explore the calls that can be made into a pallet
subxt explore pallet Balances constants
explore the constants of a pallet
subxt explore pallet Balances storage
explore the storage values of a pallet
subxt explore pallet Balances events
explore the events of a pallet
"};
assert_eq!(output, expected_output);
// check that exploring calls, storage entries and constants is possible:
let output = run_against_file("Balances calls").await;
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances calls <CALL>\n explore a specific call within this pallet\n\nAvailable <CALL>'s in the \"Balances\" pallet:\n"));
let output = run_against_file("Balances storage").await;
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances storage <STORAGE_ENTRY>\n view details for a specific storage entry\n\nAvailable <STORAGE_ENTRY>'s in the \"Balances\" pallet:\n"));
let output = run_against_file("Balances constants").await;
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances constants <CONSTANT>\n explore a specific call within this pallet\n\nAvailable <CONSTANT>'s in the \"Balances\" pallet:\n"));
let output = run_against_file("pallet Balances calls")
.await
.unwrap()
.strip_ansi();
let start = formatdoc! {"
Usage:
subxt explore pallet Balances calls <CALL>
explore a specific call of this pallet
Available <CALL>'s in the \"Balances\" pallet:"};
assert_eq_start!(output, start);
let output = run_against_file("pallet Balances storage")
.await
.unwrap()
.strip_ansi();
let start = formatdoc! {"
Usage:
subxt explore pallet Balances storage <STORAGE_ENTRY>
explore a specific storage entry of this pallet
Available <STORAGE_ENTRY>'s in the \"Balances\" pallet:
"};
assert_eq_start!(output, start);
let output = run_against_file("pallet Balances constants")
.await
.unwrap()
.strip_ansi();
let start = formatdoc! {"
Usage:
subxt explore pallet Balances constants <CONSTANT>
explore a specific constant of this pallet
Available <CONSTANT>'s in the \"Balances\" pallet:
"};
assert_eq_start!(output, start);
let output = run_against_file("pallet Balances events")
.await
.unwrap()
.strip_ansi();
let start = formatdoc! {"
Usage:
subxt explore pallet Balances events <EVENT>
explore a specific event of this pallet
Available <EVENT>'s in the \"Balances\" pallet:
"};
assert_eq_start!(output, start);
// check that invalid subcommands don't work:
let output = run_against_file("Balances abc123").await;
let output = run_against_file("pallet Balances abc123").await;
assert!(output.is_err());
// check that we can explore a certain call:
let output = run_against_file("Balances calls transfer_allow_death").await;
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances calls transfer_allow_death <SCALE_VALUE>\n construct the call by providing a valid argument\n\nThe call expect expects a <SCALE_VALUE> with this shape:\n {\n dest: enum MultiAddress"));
// check that unsigned extrinsic can be constructed:
let output = run_against_file(
"Balances calls transfer_allow_death {\"dest\":v\"Raw\"((255,255, 255)),\"value\":0}",
)
.await;
assert_eq!(
output.unwrap(),
"Encoded call data:\n 0x24040400020cffffff00\n"
);
// check that we can explore a certain constant:
let output = run_against_file("Balances constants ExistentialDeposit").await;
assert_eq!(output.unwrap(), "Description:\n The minimum amount required to keep an account open. MUST BE GREATER THAN ZERO!\n\nThe constant has the following shape:\n u128\n\nThe value of the constant is:\n 33333333\n");
// check that we can explore a certain storage entry:
let output = run_against_file("System storage Account").await;
assert!(output.unwrap().starts_with("Usage:\n subxt explore System storage Account <KEY_VALUE>\n\nDescription:\n The full account information for a particular account ID."));
// in the future we could also integrate with substrate-testrunner to spawn up a node and send an actual storage query to it: e.g. `subxt explore System storage Digest`
let output = run_against_file("pallet Balances calls transfer_keep_alive")
.await
.unwrap()
.strip_ansi();
// Note: at some point we want to switch to new metadata in the artifacts folder which has e.g. transfer_keep_alive instead of transfer.
let start = formatdoc! {"
Usage:
subxt explore pallet Balances calls transfer_keep_alive <SCALE_VALUE>
construct the call by providing a valid argument
"};
assert_eq_start!(output, start);
// check that we can see methods of a runtime api:
let output = run_against_file("api metadata").await.unwrap().strip_ansi();
let start = formatdoc! {"
Description:
The `Metadata` api trait that returns metadata for the runtime.
Usage:
subxt explore api Metadata <METHOD>
explore a specific runtime api method
Available <METHOD>'s available for the \"Metadata\" runtime api:
"};
assert_eq_start!(output, start);
}
#[tokio::test]