diff --git a/.gitignore b/.gitignore index 5dde1f4..ae32ba2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ data .env scripts/out *.gz +metadata.* diff --git a/Cargo.lock b/Cargo.lock index 3d4d5f0..5997f1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -335,25 +335,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" -[[package]] -name = "calm_io" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ea0608700fe42d90ec17ad0f86335cf229b67df2e34e7f463e8241ce7b8fa5f" -dependencies = [ - "calmio_filters", -] - -[[package]] -name = "calmio_filters" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "846501f4575cd66766a40bb7ab6d8e960adc7eb49f753c8232bd8e0e09cf6ca2" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "cc" version = "1.0.77" @@ -3532,9 +3513,9 @@ dependencies = [ name = "subwasmlib" version = "0.18.0" dependencies = [ - "calm_io", "color-eyre", "frame-metadata", + "hex", "ipfs-hasher", "log", "num-format", diff --git a/README.md b/README.md index 9ab8118..9891577 100644 --- a/README.md +++ b/README.md @@ -63,209 +63,143 @@ MacOS Homebrew users can use: ### Command: --help - subwasm 0.18.0 - chevdor :Wilfried Kopp - - OPTIONS: - -h, --help Print help information - -j, --json Output as json - -q, --quiet Less output - -V, --version Print version information - - SUBCOMMANDS: - compress Compress a given runtime wasm file. You will get an error if you try - compressing a runtime that is already compressed - decompress Decompress a given runtime wasm file. You may pass a runtime that is - uncompressed already. In that case, you will get the same content as output. - This is useful if you want to decompress "no matter what" and don't really - know whether the input will be compressed or not - diff Compare 2 runtimes - get Get/Download the runtime wasm from a running node through rpc - help Print this message or the help of the given subcommand(s) - info The `info` command returns summarized information about a runtime - metadata Returns the metadata as a json object. You may also use the "meta" alias - version The `version` command returns summarized information about the versions of a - runtime + `subwasm` allows fetching, parsing and calling some methods on WASM runtimes of Substrate based chains + + Usage: subwasm [OPTIONS] + + Commands: + get Get/Download the runtime wasm from a running node through rpc + info The `info` command returns summarized information about a runtime + version The `version` command returns summarized information about the versions of a runtime + metadata Returns the metadata as a json object. You may also use the "meta" alias + diff Compare 2 runtimes + compress Compress a given runtime wasm file. You will get an error if you try compressing a runtime that is already compressed + decompress Decompress a given runtime wasm file. You may pass a runtime that is uncompressed already. In that case, you will get the same content as output. This is useful if you want to decompress "no matter what" and don't really know whether the input will be compressed or not + help Print this message or the help of the given subcommand(s) + + Options: + -j, --json Output as json + -q, --quiet Less output + -h, --help Print help information + -V, --version Print version information ### Command: get - subwasm-get 0.18.0 - chevdor :Wilfried Kopp The node url including (mandatory) the port number. Example: ws://localhost:9944 or - http://localhost:9933 [default: http://localhost:9933] - - OPTIONS: - -b, --block The optional block where to fetch the runtime. That allows fetching - older runtimes but you will need to connect to archive nodes. - Currently, you must pass a block hash. Passing the block numbers is not - supported - --chain Provide the name of a chain and a random url amongst a list of known - nodes will be used. If you pass a valid --chain, --url will be ignored - --chain local = http://localhost:9933 - -h, --help Print help information - -j, --json Output as json - -o, --output You may specifiy the output filename where the runtime will be saved. - If not provided, we will figure out an appropriate default name based - on a counter: runtime_NNN.wasm where NNN is incrementing to make sure - you do not override previous runtime. If you specify an existing file - as output, it will be overwritten - -V, --version Print version information + Usage: subwasm get [OPTIONS] [URL] + + Arguments: + [URL] The node url including (mandatory) the port number. Example: ws://localhost:9944 or http://localhost:9933 [default: http://localhost:9933] + + Options: + --chain Provide the name of a chain and a random url amongst a list of known nodes will be used. If you pass a valid --chain, --url will be ignored --chain local = http://localhost:9933 + -j, --json Output as json + -b, --block The optional block where to fetch the runtime. That allows fetching older runtimes but you will need to connect to archive nodes. Currently, you must pass a block hash. Passing the block numbers is not supported + -o, --output You may specifiy the output filename where the runtime will be saved. If not provided, we will figure out an appropriate default name based on a counter: runtime_NNN.wasm where NNN is incrementing to make sure you do not override previous runtime. If you specify an existing file as output, it will be overwritten + -h, --help Print help information + -V, --version Print version information ### Command: info - subwasm-info 0.18.0 - chevdor :Wilfried Kopp The wasm file to load. It can be a path on your local filesystem such as - /tmp/runtime.wasm or a node url such as http://localhost:9933 or - ws://localhost:9944 [default: runtime_000.wasm] - - OPTIONS: - -b, --block The optional block where to fetch the runtime. That allows fetching older - runtimes but you will need to connect to archive nodes. Currently, you - must pass a block hash. Passing the block numbers is not supported - --chain Provide the name of a chain and a random url amongst a list of known - nodes will be used. If you pass a valid --chain, --url will be ignored - --chain local = http://localhost:9933 - -h, --help Print help information - -j, --json Output as json - -V, --version Print version information + Usage: subwasm info [OPTIONS] [SOURCE] + + Arguments: + [SOURCE] The wasm file to load. It can be a path on your local filesystem such as /tmp/runtime.wasm or a node url such as http://localhost:9933 or ws://localhost:9944 [default: runtime_000.wasm] + + Options: + --chain Provide the name of a chain and a random url amongst a list of known nodes will be used. If you pass a valid --chain, --url will be ignored --chain local = http://localhost:9933 + -j, --json Output as json + -b, --block The optional block where to fetch the runtime. That allows fetching older runtimes but you will need to connect to archive nodes. Currently, you must pass a block hash. Passing the block numbers is not supported + -h, --help Print help information + -V, --version Print version information By default, the ID for the Parachain pallet is expected to be `0x01` and the call ID for `authorize_upgrade` is expected to be `0x03`. This default behavior can be overriden by setting the `PARACHAIN_PALLET_ID` to the ID of your parachain pallet and the `AUTHORIZE_UPGRADE_PREFIX` to the ID of your choice. ### Command: version - subwasm-version 0.18.0 - chevdor :Wilfried Kopp The wasm file to load. It can be a path on your local filesystem such as - /tmp/runtime.wasm or a node url such as http://localhost:9933 or - ws://localhost:9944 [default: runtime_000.wasm] - - OPTIONS: - -b, --block The optional block where to fetch the runtime. That allows fetching older - runtimes but you will need to connect to archive nodes. Currently, you - must pass a block hash. Passing the block numbers is not supported - --chain Provide the name of a chain and a random url amongst a list of known - nodes will be used. If you pass a valid --chain, --url will be ignored - --chain local = http://localhost:9933 - -h, --help Print help information - -j, --json Output as json - -V, --version Print version information + Usage: subwasm version [OPTIONS] [SOURCE] + + Arguments: + [SOURCE] The wasm file to load. It can be a path on your local filesystem such as /tmp/runtime.wasm or a node url such as http://localhost:9933 or ws://localhost:9944 [default: runtime_000.wasm] + + Options: + --chain Provide the name of a chain and a random url amongst a list of known nodes will be used. If you pass a valid --chain, --url will be ignored --chain local = http://localhost:9933 + -j, --json Output as json + -b, --block The optional block where to fetch the runtime. That allows fetching older runtimes but you will need to connect to archive nodes. Currently, you must pass a block hash. Passing the block numbers is not supported + -h, --help Print help information + -V, --version Print version information ### Command: meta - subwasm-metadata 0.18.0 - chevdor :Wilfried Kopp The wasm file to load. It can be a path on your local filesystem such as - /tmp/runtime.wasm or a node url such as http://localhost:9933 or - ws://localhost:9944 [default: runtime_000.wasm] - - OPTIONS: - -b, --block The optional block where to fetch the runtime. That allows fetching - older runtimes but you will need to connect to archive nodes. - Currently, you must pass a block hash. Passing the block numbers is not - supported - --chain Provide the name of a chain and a random url amongst a list of known - nodes will be used. If you pass a valid --chain, --url will be ignored - --chain local = http://localhost:9933 - -h, --help Print help information - -j, --json Output as json - -m, --module Without this flag, the metadata command display the list of all - modules. Using this flag, you will only see the module of your choice - and a few details about it - -V, --version Print version information + Usage: subwasm metadata [OPTIONS] [SOURCE] + + Arguments: + [SOURCE] The wasm file to load. It can be a path on your local filesystem such as /tmp/runtime.wasm or a node url such as http://localhost:9933 or ws://localhost:9944 [default: runtime_000.wasm] + + Options: + --chain Provide the name of a chain and a random url amongst a list of known nodes will be used. If you pass a valid --chain, --url will be ignored --chain local = http://localhost:9933 + -j, --json Output as json + -m, --module Without this flag, the metadata command display the list of all modules. Using this flag, you will only see the module of your choice and a few details about it + -b, --block The optional block where to fetch the runtime. That allows fetching older runtimes but you will need to connect to archive nodes. Currently, you must pass a block hash. Passing the block numbers is not supported + -f, --format You may specifiy the output format. One of "human", "scale", "json", "json+scale", "hex+scale" [default: human] + -o, --output You may specifiy the output filename where the metadata will be saved. Alternatively, you may use `auto` and an appropriate name will be generated according to the `format` your chose + -h, --help Print help information + -V, --version Print version information ### Command: diff - subwasm-diff 0.18.0 - chevdor :Wilfried Kopp The first source [default: runtime_000.wasm] - The second source [default: runtime_001.wasm] + Arguments: + [SRC_A] The first source [default: runtime_000.wasm] + [SRC_B] The second source [default: runtime_001.wasm] - OPTIONS: - -a, --chain-a Provide the name of a chain and a random url amongst a list of known - nodes will be used. If you pass a valid --chain, --url will be - ignored --chain local = http://localhost:9933 - -b, --chain-b Provide the name of a chain and a random url amongst a list of known - nodes will be used. If you pass a valid --chain, --url will be - ignored --chain local = http://localhost:9933 - -h, --help Print help information - -j, --json Output as json - -V, --version Print version information + Options: + -a, --chain-a Provide the name of a chain and a random url amongst a list of known nodes will be used. If you pass a valid --chain, --url will be ignored --chain local = http://localhost:9933 + -j, --json Output as json + -b, --chain-b Provide the name of a chain and a random url amongst a list of known nodes will be used. If you pass a valid --chain, --url will be ignored --chain local = http://localhost:9933 + -h, --help Print help information + -V, --version Print version information ### Command: compress - subwasm-compress 0.18.0 - chevdor :Wilfried Kopp + Usage: subwasm compress [OPTIONS] - ARGS: - The path of uncompressed wasm file to load - The path of the file where the compressed runtime will be stored + Arguments: + The path of uncompressed wasm file to load + The path of the file where the compressed runtime will be stored - OPTIONS: - -h, --help Print help information - -j, --json Output as json - -V, --version Print version information + Options: + -j, --json Output as json + -h, --help Print help information + -V, --version Print version information ### Command: decompress - subwasm-decompress 0.18.0 - chevdor :Wilfried Kopp + Usage: subwasm decompress [OPTIONS] - ARGS: - The path of the compressed or uncompressed wasm file to load - The path of the file where the uncompressed runtime will be stored + Arguments: + The path of the compressed or uncompressed wasm file to load + The path of the file where the uncompressed runtime will be stored - OPTIONS: - -h, --help Print help information - -j, --json Output as json - -V, --version Print version information + Options: + -j, --json Output as json + -h, --help Print help information + -V, --version Print version information ### Environment variables @@ -321,10 +255,9 @@ Here is a list of other projects allowing to get the raw metadata through a rpc - [PolkadotJS](https://github.com/polkadot-js/apps) from Jaco / Parity - [subsee](https://github.com/ascjones/subsee) from Andrew / Parity + - [substrate-api-client](https://github.com/scs/substrate-api-client) from SCS - [subxt](https://github.com/paritytech/substrate-subxt) from Parity All those alternatives require a running node and access it via jsonrpc. - -- [smoldot](https://github.com/paritytech/smoldot) light client from Parity allows to [get metadata](https://github.com/AcalaNetwork/chopsticks/pull/80) from wasm diff --git a/cli/src/main.rs b/cli/src/main.rs index ade0cc7..da0b4fc 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,5 +1,7 @@ mod opts; +use std::io::Write; + use clap::{crate_name, crate_version, Parser}; // use color_eyre::owo_colors::OwoColorize; use env_logger::Env; @@ -20,6 +22,8 @@ macro_rules! noquiet { fn main() -> color_eyre::Result<()> { env_logger::Builder::from_env(Env::default().default_filter_or("none")).init(); let opts: Opts = Opts::parse(); + color_eyre::install()?; + noquiet!(opts, println!("Running {} v{}", crate_name!(), crate_version!())); match opts.subcmd { @@ -57,13 +61,43 @@ fn main() -> color_eyre::Result<()> { info!("⏱️ Loading WASM from {:?}", &source); let subwasm = Subwasm::new(&source); - if let Some(filter) = meta_opts.module { - subwasm.display_module(filter); - } else if opts.json { - subwasm.display_metadata_json() - } else { - subwasm.display_modules_list() + let mut fmt: OutputFormat = meta_opts.format.unwrap_or_else(|| "human".into()).into(); + if opts.json { + eprintln!("--json is DEPRECATED, use --format=json instead"); + fmt = OutputFormat::Json; } + + let mut output = meta_opts.output; + if let Some(out) = &output { + if out.is_empty() || out == "auto" { + match fmt { + OutputFormat::Human => output = Some("metadata.txt".into()), + OutputFormat::Json => output = Some("metadata.json".into()), + OutputFormat::Scale => output = Some("metadata.scale".into()), + OutputFormat::HexScale => output = Some("metadata.hex".into()), + OutputFormat::JsonScale => output = Some("metadata.jscale".into()), + } + } + } + + let mut out: Box = if let Some(output) = &output { + Box::new(std::fs::File::create(output)?) + } else { + Box::new(std::io::stdout()) + }; + + match subwasm.write_metadata(fmt, meta_opts.module, &mut out) { + Ok(_) => Ok(()), + Err(e) => { + if let Some(e) = e.root_cause().downcast_ref::() { + if e.kind() == std::io::ErrorKind::BrokenPipe { + log::debug!("ignoring broken pipe error: {:?}", e); + return Ok(()); + } + } + Err(e) + } + }? } SubCommand::Diff(diff_opts) => { @@ -77,11 +111,11 @@ fn main() -> color_eyre::Result<()> { } SubCommand::Compress(copts) => { - compress(copts.input, copts.output).unwrap(); + compress(copts.input, copts.output)?; } SubCommand::Decompress(dopts) => { - decompress(dopts.input, dopts.output).unwrap(); + decompress(dopts.input, dopts.output)?; } }; diff --git a/cli/src/opts.rs b/cli/src/opts.rs index e3c4922..bf49a6d 100644 --- a/cli/src/opts.rs +++ b/cli/src/opts.rs @@ -132,6 +132,15 @@ pub struct MetaOpts { /// Currently, you must pass a block hash. Passing the block numbers is not supported. #[clap(short, long)] pub block: Option, // TODO: can do better... + + /// You may specifiy the output format. One of "human", "scale", "json", "json+scale", "hex+scale" + #[clap(long, short, default_value = "human")] + pub format: Option, + + /// You may specifiy the output filename where the metadata will be saved. + /// Alternatively, you may use `auto` and an appropriate name will be generated according to the `format` your chose. + #[clap(short, long)] + pub output: Option, } /// Compare 2 runtimes diff --git a/cli/tests/test.rs b/cli/tests/test.rs index 02ca14e..7db1e5c 100644 --- a/cli/tests/test.rs +++ b/cli/tests/test.rs @@ -32,7 +32,7 @@ mod cli_tests { fn it_gets_a_runtime() { let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - let assert = cmd.args(&["get", "--output", "runtime.wasm", "wss://rpc.polkadot.io:443"]).assert(); + let assert = cmd.args(["get", "--output", "runtime.wasm", "wss://rpc.polkadot.io:443"]).assert(); assert.success().code(0); assert!(Path::new("runtime.wasm").exists()); } @@ -41,7 +41,7 @@ mod cli_tests { fn it_fails_on_bad_chain() { let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - let assert = cmd.args(&["get", "--chain", "foobar"]).assert(); + let assert = cmd.args(["get", "--chain", "foobar"]).assert(); assert.failure().code(101); } } @@ -53,11 +53,11 @@ mod cli_tests { #[test] fn it_shows_metadata() { let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - let assert = cmd.args(&["get", "wss://rpc.polkadot.io:443", "--output", "runtime.wasm"]).assert(); + let assert = cmd.args(["get", "wss://rpc.polkadot.io:443", "--output", "runtime.wasm"]).assert(); assert.success().code(0); let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - let assert = cmd.args(&["meta", "runtime.wasm"]).assert(); + let assert = cmd.args(["meta", "runtime.wasm"]).assert(); assert.success().code(0); } } @@ -69,27 +69,27 @@ mod cli_tests { #[test] fn it_does_basic_compress_decompress() { let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - let assert = cmd.args(&["get", "wss://rpc.polkadot.io:443", "--output", "compressed.wasm"]).assert(); + let assert = cmd.args(["get", "wss://rpc.polkadot.io:443", "--output", "compressed.wasm"]).assert(); assert.success().code(0); let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - cmd.args(&["decompress", "compressed.wasm", "decompressed.wasm"]).assert().success().code(0); + cmd.args(["decompress", "compressed.wasm", "decompressed.wasm"]).assert().success().code(0); let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - cmd.args(&["compress", "decompressed.wasm", "new_compressed.wasm"]).assert().success().code(0); + cmd.args(["compress", "decompressed.wasm", "new_compressed.wasm"]).assert().success().code(0); } #[test] fn it_does_decompress_on_already() { let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - let assert = cmd.args(&["get", "wss://rpc.polkadot.io:443", "--output", "compressed.wasm"]).assert(); + let assert = cmd.args(["get", "wss://rpc.polkadot.io:443", "--output", "compressed.wasm"]).assert(); assert.success().code(0); let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - cmd.args(&["decompress", "compressed.wasm", "decompressed.wasm"]).assert().success().code(0); + cmd.args(["decompress", "compressed.wasm", "decompressed.wasm"]).assert().success().code(0); let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - cmd.args(&["decompress", "decompressed.wasm", "new_decompressed.wasm"]).assert().success().code(0); + cmd.args(["decompress", "decompressed.wasm", "new_decompressed.wasm"]).assert().success().code(0); } } } diff --git a/doc/usage_meta.adoc b/doc/usage_meta.adoc index 596cbf3..b2c08ba 100644 --- a/doc/usage_meta.adoc +++ b/doc/usage_meta.adoc @@ -10,5 +10,7 @@ Options: -j, --json Output as json -m, --module Without this flag, the metadata command display the list of all modules. Using this flag, you will only see the module of your choice and a few details about it -b, --block The optional block where to fetch the runtime. That allows fetching older runtimes but you will need to connect to archive nodes. Currently, you must pass a block hash. Passing the block numbers is not supported + -f, --format You may specifiy the output format. One of "human", "scale", "json", "json+scale", "hex+scale" [default: human] + -o, --output You may specifiy the output filename where the metadata will be saved. Alternatively, you may use `auto` and an appropriate name will be generated according to the `format` your chose -h, --help Print help information -V, --version Print version information diff --git a/lib/Cargo.toml b/lib/Cargo.toml index af169d2..91a7b14 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -18,7 +18,6 @@ repository = "https://github.com/chevdor/subwasm" version = "0.18.0" [dependencies] -calm_io = "0.1" color-eyre = "0.6" frame-metadata = { version = "15", package = "frame-metadata", features = [ "v12", @@ -39,3 +38,4 @@ substrate-differ = { version = "0.18.0", path = "../libs/substrate-differ" } wasm-loader = { version = "0.18.0", path = "../libs/wasm-loader" } wasm-testbed = { version = "0.18.0", path = "../libs/wasm-testbed" } sp-version = { tag = "monthly-2023-01", git = "https://github.com/paritytech/substrate" } +hex = "0.4" diff --git a/lib/src/lib.rs b/lib/src/lib.rs index a655bc9..24bb409 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,3 +1,4 @@ +use color_eyre::eyre::eyre; use std::path::Path; use std::{fs::File, path::PathBuf}; use std::{io::prelude::*, str::FromStr}; @@ -17,6 +18,7 @@ mod subwasm; mod types; pub use chain_info::*; use log::{debug, info}; +pub use metadata_wrapper::OutputFormat; pub use runtime_info::*; pub use subwasm::*; pub use types::*; @@ -67,7 +69,7 @@ pub fn download_runtime(url: &str, block_ref: Option, output: Option

NodeEndpoint::WebSocket(url.to_string()), url if url.starts_with("http") => NodeEndpoint::Http(url.to_string()), - _ => panic!("The url should either start with http or ws"), + _ => return Err(eyre!("The url should either start with http or ws")), }; let reference = OnchainBlock { endpoint: url, block_ref }; @@ -129,41 +131,41 @@ pub fn diff(src_a: Source, src_b: Source) { /// Compress a given runtime into a new file. You cannot compress /// a runtime that is already compressed. -pub fn compress(input: PathBuf, output: PathBuf) -> Result<(), String> { - let wasm = WasmLoader::load_from_source(&Source::File(input)).unwrap(); +pub fn compress(input: PathBuf, output: PathBuf) -> color_eyre::Result<()> { + let wasm = WasmLoader::load_from_source(&Source::File(input))?; if wasm.compression().compressed() { - return Err("The input is already compressed".into()); + return Err(eyre!("The input is already compressed")); } - let bytes_compressed = Compression::compress(wasm.original_bytes()).unwrap(); + let bytes_compressed = Compression::compress(wasm.original_bytes()).map_err(|e| eyre!(e))?; debug!("original = {:?}", wasm.original_bytes().len()); debug!("compressed = {:?}", bytes_compressed.len()); info!("Saving compressed runtime to {:?}", output); - let mut buffer = File::create(output).unwrap(); - buffer.write_all(&bytes_compressed.to_vec()).unwrap(); + let mut buffer = File::create(output)?; + buffer.write_all(&bytes_compressed.to_vec())?; Ok(()) } /// Decompress a given runtime file. It is fine decompressing an already /// decompressed runtime, you will just get the same. -pub fn decompress(input: PathBuf, output: PathBuf) -> Result<(), String> { - let wasm = WasmLoader::load_from_source(&Source::File(input)).unwrap(); +pub fn decompress(input: PathBuf, output: PathBuf) -> color_eyre::Result<()> { + let wasm = WasmLoader::load_from_source(&Source::File(input))?; let bytes_decompressed = match wasm.compression().compressed() { false => wasm.original_bytes().clone(), - true => Compression::decompress(wasm.original_bytes()).unwrap(), + true => Compression::decompress(wasm.original_bytes()).map_err(|e| eyre!(e))?, }; debug!("original = {:?}", wasm.original_bytes().len()); debug!("decompressed = {:?}", bytes_decompressed.len()); info!("Saving decompressed runtime to {:?}", output); - let mut buffer = File::create(output).unwrap(); - buffer.write_all(&bytes_decompressed.to_vec()).unwrap(); + let mut buffer = File::create(output)?; + buffer.write_all(&bytes_decompressed.to_vec())?; Ok(()) } diff --git a/lib/src/macros.rs b/lib/src/macros.rs index 9957b8a..73b1996 100644 --- a/lib/src/macros.rs +++ b/lib/src/macros.rs @@ -1,51 +1,77 @@ #[macro_export] -macro_rules! display_module { - ($modules: expr, $filter: ident) => { - let meta = $modules - .iter() - .find(|module| { - let name_str = convert(&module.name).to_lowercase(); - name_str == $filter.to_lowercase() - }) - .expect("pallet not found in metadata"); - - println!("Module {:02}: {}", meta.index, convert(&meta.name)); - - println!("🤙 Calls:"); - if let Some(item) = meta.calls.as_ref() { - let calls = convert(&item); - for call in calls { - println!(" - {}", convert(&call.name)); +macro_rules! write_module { + ($modules: expr, $filter: ident, $out: ident) => { + || -> color_eyre::Result<()> { + use color_eyre::eyre::eyre; + + let meta = $modules + .iter() + .find(|module| { + let name_str = convert(&module.name).to_lowercase(); + name_str == $filter.to_lowercase() + }) + .ok_or_else(|| eyre!("pallet not found in metadata"))?; + + writeln!($out, "Module {:02}: {}", meta.index, convert(&meta.name))?; + + writeln!($out, "🤙 Calls:")?; + if let Some(item) = meta.calls.as_ref() { + let calls = convert(&item); + for call in calls { + writeln!($out, " - {}", convert(&call.name))?; + } } - } - println!("📢 Events:"); - if let Some(item) = meta.event.as_ref() { - let events = convert(&item); - for event in events { - println!(" - {}", convert(&event.name)); + writeln!($out, "📢 Events:")?; + if let Some(item) = meta.event.as_ref() { + let events = convert(&item); + for event in events { + writeln!($out, " - {}", convert(&event.name))?; + } } - } + Ok(()) + }()? }; } #[macro_export] -macro_rules! display_v14_meta { - ($v14: expr, $meta: expr, $type: ident) => { - if let Some(metadata) = &$meta.$type { - let type_id = metadata.ty.id(); - // log::debug!("type_id: {:?}", type_id); - let registry = &$v14.types; - - let type_info = registry.resolve(type_id).unwrap(); - match type_info.type_def() { - scale_info::TypeDef::Variant(v) => { - for variant in v.variants() { - println!("- {:?}: {}", variant.index(), variant.name()); +macro_rules! write_v14_meta { + ($v14: expr, $meta: expr, $type: ident, $out: ident) => { + || -> color_eyre::Result<()> { + use color_eyre::eyre::eyre; + + if let Some(metadata) = &$meta.$type { + let type_id = metadata.ty.id(); + // log::debug!("type_id: {:?}", type_id); + let registry = &$v14.types; + + let type_info = registry.resolve(type_id).unwrap(); + match type_info.type_def() { + scale_info::TypeDef::Variant(v) => { + for variant in v.variants() { + write!($out, "- {:?}: {}\n", variant.index(), variant.name())?; + } } + o => return Err(eyre!("Unsupported variant: {:?}", o)), } - o => panic!("Unsupported variant: {:?}", o), + } else { + return Err(eyre!("No metadata found\n")); } - } + Ok(()) + }()? + }; +} + +#[macro_export] +macro_rules! display_module { + ($modules: expr, $filter: ident) => { + $crate::write_module!($modules, $filter, std::io::stdout()); + }; +} + +#[macro_export] +macro_rules! display_v14_meta { + ($v14: expr, $meta: expr, $type: ident) => { + $crate::write_v14_meta!($v14, $meta, $type, std::io::stdout()); }; } diff --git a/lib/src/metadata_wrapper.rs b/lib/src/metadata_wrapper.rs index b724ceb..6f197b2 100644 --- a/lib/src/metadata_wrapper.rs +++ b/lib/src/metadata_wrapper.rs @@ -1,45 +1,132 @@ +use std::io::Write; + +use color_eyre::eyre::eyre; use frame_metadata::RuntimeMetadata; use log::debug; +use scale_info::scale::Encode; + +use crate::{convert::convert, write_module, write_v14_meta}; + +/// The output format for the metadata +#[derive(Debug, Clone, Copy)] +pub enum OutputFormat { + /// Output the metadata in a human readable format + Human, + /// Output the metadata in json format + Json, + /// Output the metadata in raw scale format + Scale, + /// Output the metadata in hex encoded scale format + HexScale, + /// Output the metadata in a json object containing the hex encoded scale encoding of the metadata + JsonScale, +} + +impl> From for OutputFormat { + fn from(s: S) -> Self { + match s.as_ref() { + "human" => OutputFormat::Human, + "json" => OutputFormat::Json, + "scale" => OutputFormat::Scale, + "hex+scale" | "scale+hex" => OutputFormat::HexScale, + "json+scale" | "scale+json" => OutputFormat::JsonScale, + _ => panic!("Unknown output format"), + } + } +} -use crate::{convert::convert, display_module, display_v14_meta}; pub struct MetadataWrapper<'a>(pub &'a RuntimeMetadata); impl<'a> MetadataWrapper<'a> { + pub fn write(&self, fmt: OutputFormat, filter: Option, out: &mut O) -> color_eyre::Result<()> { + debug!("Writing metadata: fmt={:?}, filter={:?}", fmt, filter); + + match fmt { + OutputFormat::Human => { + if let Some(filter) = filter { + self.write_single_module(&filter, out)?; + } else { + self.write_modules_list(out)?; + } + } + OutputFormat::Json => { + if filter.is_some() { + return Err(eyre!("Cannot filter metadata in json format")); + } else { + serde_json::to_writer_pretty(out, &self.0)?; + } + } + OutputFormat::Scale => { + if filter.is_some() { + return Err(eyre!("Cannot filter metadata in scale format")); + } else { + out.write_all(&self.0.encode())?; + } + } + OutputFormat::HexScale => { + if filter.is_some() { + return Err(eyre!("Cannot filter metadata in hex+scale format")); + } else { + let encoded = self.0.encode(); + write!(out, "0x{}", hex::encode(encoded))?; + } + } + OutputFormat::JsonScale => { + if filter.is_some() { + return Err(eyre!("Cannot filter metadata in json+scale format")); + } else { + let encoded = self.0.encode(); + let hex = format!("0x{}", hex::encode(encoded)); + let json = serde_json::to_string_pretty(&serde_json::json!({ "result": hex }))?; + write!(out, "{json}")?; + } + } + } + Ok(()) + } + /// Display a simple list of the modules. /// Starting with V12, modules are identified by indexes so /// the order they appear in the metadata no longer matters and we sort them by indexes. - pub fn display_modules_list(&self) { + pub fn write_modules_list(&self, out: &mut O) -> color_eyre::Result<()> { match &self.0 { RuntimeMetadata::V12(v12) => { let mut modules = convert(&v12.modules).clone(); modules.sort_by(|a, b| a.index.cmp(&b.index)); - modules.iter().for_each(|module| println!(" - {:02}: {}", module.index, convert(&module.name))); + modules.iter().try_for_each(|module| -> std::io::Result<()> { + writeln!(out, " - {:02}: {}", module.index, convert(&module.name)) + })?; } RuntimeMetadata::V13(v13) => { let mut modules = convert(&v13.modules).clone(); modules.sort_by(|a, b| a.index.cmp(&b.index)); - modules.iter().for_each(|module| println!(" - {:02}: {}", module.index, convert(&module.name))); + modules.iter().try_for_each(|module| -> std::io::Result<()> { + writeln!(out, " - {:02}: {}", module.index, convert(&module.name)) + })?; } RuntimeMetadata::V14(v14) => { let mut pallets = v14.pallets.clone(); // pallets.sort_by(|a,b| a.index.cmp(&b.index)); pallets.sort_by_key(|p| p.index); - pallets.iter().for_each(|pallet| println!(" - {:02}: {}", pallet.index, pallet.name)); + pallets.iter().try_for_each(|pallet| -> std::io::Result<()> { + writeln!(out, " - {:02}: {}", pallet.index, pallet.name) + })?; } - _ => panic!("Runtime not supported. Subwasm supports V12 and above."), + _ => return Err(eyre!("Runtime not supported. Subwasm supports V12 and above.")), }; + Ok(()) } /// Display a single module - pub fn display_single_module(&self, filter: &str) { - debug!("metadata_wapper::display_module with filter: {:?}", filter); + pub fn write_single_module(&self, filter: &str, out: &mut O) -> color_eyre::Result<()> { + debug!("metadata_wapper::write_module with filter: {:?}", filter); match &self.0 { RuntimeMetadata::V12(v12) => { - display_module!(convert(&v12.modules), filter); + write_module!(convert(&v12.modules), filter, out); } RuntimeMetadata::V13(v13) => { - display_module!(convert(&v13.modules), filter); + write_module!(convert(&v13.modules), filter, out); } RuntimeMetadata::V14(v14) => { let meta = v14 @@ -49,32 +136,33 @@ impl<'a> MetadataWrapper<'a> { let name_str = pallet.name.to_lowercase(); name_str == filter.to_lowercase() }) - .expect("pallet not found in metadata"); + .ok_or_else(|| eyre!("Pallet not found in metadata"))?; - println!("Module {:02}: {}", meta.index, &meta.name); + writeln!(out, "Module {:02}: {}", meta.index, &meta.name)?; - println!("🤙 Calls:"); - display_v14_meta!(v14, meta, calls); + writeln!(out, "🤙 Calls:")?; + write_v14_meta!(v14, meta, calls, out); - println!("📢 Events:"); - display_v14_meta!(v14, meta, event); + writeln!(out, "📢 Events:")?; + write_v14_meta!(v14, meta, event, out); - println!("⛔️ Errors:"); - display_v14_meta!(v14, meta, error); + writeln!(out, "⛔️ Errors:")?; + write_v14_meta!(v14, meta, error, out); - println!("📦 Storage:"); + writeln!(out, "📦 Storage:")?; if let Some(meta) = &meta.storage { for entry in &meta.entries { - println!("- {}", entry.name); + writeln!(out, "- {}", entry.name)?; } } - println!("💎 Constants:"); + writeln!(out, "💎 Constants:")?; for item in &meta.constants { - println!("- {}", item.name); + writeln!(out, "- {}", item.name)?; } } - _ => panic!("Runtime not supported"), + _ => return Err(eyre!("Runtime not supported")), }; + Ok(()) } } diff --git a/lib/src/subwasm.rs b/lib/src/subwasm.rs index 11734a1..60c1d56 100644 --- a/lib/src/subwasm.rs +++ b/lib/src/subwasm.rs @@ -1,6 +1,10 @@ -use crate::{convert::convert, metadata_wrapper::MetadataWrapper, RuntimeInfo}; -use calm_io::stdoutln; -use frame_metadata::{decode_different::DecodeDifferent, RuntimeMetadata}; +use std::io::Write; + +use crate::{ + metadata_wrapper::{self, MetadataWrapper}, + RuntimeInfo, +}; + use wasm_loader::Source; use wasm_testbed::{WasmTestBed, WasmTestbedError}; @@ -55,61 +59,14 @@ impl Subwasm { // Ok(()) // } - pub fn display_module(&self, filter: String) { + pub fn write_metadata( + &self, + fmt: metadata_wrapper::OutputFormat, + filter: Option, + out: &mut O, + ) -> color_eyre::Result<()> { let metadata = self.testbed.runtime_metadata_prefixed(); let wrapper = MetadataWrapper(&metadata.1); - wrapper.display_single_module(&filter); - } - - pub fn display_modules_list(&self) { - let metadata = self.testbed.runtime_metadata_prefixed(); - let wrapper = MetadataWrapper(&metadata.1); - wrapper.display_modules_list(); - } - - /// Display the metadata as json - pub fn display_metadata_json(&self) { - let pallet_filter: Option = None; - let metadata = self.testbed.metadata(); - - let serialized = if let Some(ref pallet) = pallet_filter { - match metadata { - RuntimeMetadata::V12(v12) => { - let modules = convert(&v12.modules); - - let pallet_metadata = modules - .iter() - .find(|module| module.name == DecodeDifferent::Decoded(pallet.into())) - .expect("pallet not found in metadata"); - serde_json::to_string_pretty(&pallet_metadata) - } - // RuntimeMetadata::V13(v13) => { - // let pallet = v13 - // .modules - // .iter() - // .find(|m| &m.name == pallet) - // .ok_or_else(|| eyre::eyre!("pallet not found in metadata"))?; - // serde_json::to_string_pretty(&pallet)? - // } - _ => panic!("Unsupported metadata version"), - } - } else { - serde_json::to_string_pretty(&metadata) - }; - - // The following fails if piped to another command that truncates the output. - // Typical use case here is: subwasm meta | head - // The failure is due to https://github.com/rust-lang/rust/issues/46016 - // TODO: Once the above is fixed, we can remove the dependency on calm_io - // println!("{}", serialized); - - let serialized = serialized.unwrap(); - let _ = match stdoutln!("{}", serialized) { - Ok(_) => Ok(()), - Err(e) => match e.kind() { - std::io::ErrorKind::BrokenPipe => Ok(()), - _ => Err(e), - }, - }; + wrapper.write(fmt, filter, out) } } diff --git a/libs/wasm-testbed/src/lib.rs b/libs/wasm-testbed/src/lib.rs index b406634..bf8aae1 100644 --- a/libs/wasm-testbed/src/lib.rs +++ b/libs/wasm-testbed/src/lib.rs @@ -231,7 +231,7 @@ mod tests { #[ignore = "local data"] fn it_loads_v12() { let runtime = WasmTestBed::new(&Source::File(PathBuf::from(RUNTIME_V12))).unwrap(); - println!("{:#?}", runtime); + println!("{runtime:#?}"); assert!(runtime.metadata_version == 12); assert!(runtime.is_supported()); } @@ -240,7 +240,7 @@ mod tests { #[ignore = "local data"] fn it_loads_v13() { let runtime = WasmTestBed::new(&Source::File(PathBuf::from(RUNTIME_V13))).unwrap(); - println!("{:#?}", runtime); + println!("{runtime:#?}"); assert!(runtime.metadata_version == 13); assert!(runtime.is_supported()); } @@ -269,7 +269,7 @@ mod tests { #[ignore = "local data"] fn it_loads_kusama_1050() { let runtime = WasmTestBed::new(&Source::File(PathBuf::from(KUSAMA_1050_VXX))).unwrap(); - println!("{:#?}", runtime); + println!("{runtime:#?}"); assert!(runtime.metadata_version == 11); assert!(runtime.is_supported()); } @@ -279,7 +279,7 @@ mod tests { #[ignore = "local data"] fn it_loads_kusama_1062() { let runtime = WasmTestBed::new(&Source::File(PathBuf::from(KUSAMA_1062_VXX))).unwrap(); - println!("{:#?}", runtime); + println!("{runtime:#?}"); assert!(runtime.metadata_version == 11); assert!(runtime.is_supported()); @@ -296,7 +296,7 @@ mod tests { #[ignore = "local data"] fn it_loads_kusama_metdata() { let runtime = WasmTestBed::new(&Source::File(PathBuf::from(KUSAMA_2030_VXX))).unwrap(); - println!("{:#?}", runtime); + println!("{runtime:#?}"); assert!(runtime.metadata_version == 12); assert!(runtime.is_supported()); } @@ -305,7 +305,7 @@ mod tests { #[ignore = "local data"] fn it_loads_kusama_2030() { let runtime = WasmTestBed::new(&Source::File(PathBuf::from(KUSAMA_2030_VXX))).unwrap(); - println!("{:#?}", runtime); + println!("{runtime:#?}"); assert!(runtime.metadata_version == 12); assert!(runtime.is_supported()); @@ -328,7 +328,7 @@ mod tests { #[ignore = "local data"] fn it_loads_polkadot_01() { let runtime = WasmTestBed::new(&Source::File(PathBuf::from(POLKADOT_01_V11))).unwrap(); - println!("{:#?}", runtime); + println!("{runtime:#?}"); assert!(runtime.metadata_version == 11); assert!(runtime.is_supported()); } @@ -338,7 +338,7 @@ mod tests { fn it_loads_polkadot_29() { let runtime = WasmTestBed::new(&Source::File(PathBuf::from(POLKADOT_29_V12))).unwrap(); - println!("{:#?}", runtime); + println!("{runtime:#?}"); assert!(runtime.metadata_version == 12); assert!(runtime.is_supported()); @@ -353,7 +353,7 @@ mod tests { #[ignore = "local data"] fn it_loads_westend_30() { let runtime = WasmTestBed::new(&Source::File(PathBuf::from(WESTEND_V30_V12))).unwrap(); - println!("{:#?}", runtime); + println!("{runtime:#?}"); assert!(runtime.metadata_version == 12); assert!(runtime.is_supported()); } @@ -367,7 +367,7 @@ mod tests { #[ignore = "local data"] fn it_loads_polkadot_dev() { let runtime = WasmTestBed::new(&Source::File(PathBuf::from(POLKADOT_DEV))).unwrap(); - println!("{:#?}", runtime); + println!("{runtime:#?}"); assert!(runtime.metadata_version == 12); assert!(runtime.is_supported()); }