diff --git a/Cargo.lock b/Cargo.lock index 3695755c..0740139c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -698,20 +698,24 @@ dependencies = [ "move-compiler", "move-core-types", "move-package", + "move-resource-viewer", "move-stdlib", "move-symbol-pool", "move-vm-runtime", "move-vm-types", + "net", "once_cell", "pontem", "pontem-client", "rand 0.7.3", "regex", "reqwest", + "resource-viewer", "ring", "rpassword", "semver", "serde 1.0.136", + "serde_json", "smallvec", "structopt", "toml", @@ -720,6 +724,12 @@ dependencies = [ "url", ] +[[package]] +name = "dyn-clone" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + [[package]] name = "ed25519" version = "1.3.0" @@ -1284,6 +1294,7 @@ checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg", "hashbrown 0.11.2", + "serde 1.0.136", ] [[package]] @@ -1383,6 +1394,7 @@ name = "lang" version = "0.1.0" dependencies = [ "anyhow", + "blake2-rfc", "codespan-reporting", "include_dir", "move-binary-format", @@ -1392,6 +1404,7 @@ dependencies = [ "move-ir-types", "move-package", "move-symbol-pool", + "rust-base58", "serde 1.0.136", "tempfile", "walkdir", @@ -2256,6 +2269,24 @@ dependencies = [ "tempfile", ] +[[package]] +name = "net" +version = "0.1.2" +dependencies = [ + "anyhow", + "bcs", + "hex", + "lang", + "log", + "move-binary-format", + "move-core-types", + "move-vm-runtime", + "reqwest", + "serde 1.0.136", + "serde_json", + "url", +] + [[package]] name = "net2" version = "0.2.37" @@ -3153,6 +3184,20 @@ dependencies = [ "winreg", ] +[[package]] +name = "resource-viewer" +version = "1.2.1" +dependencies = [ + "http", + "move-binary-format", + "move-core-types", + "move-resource-viewer", + "net", + "schemars", + "serde 1.0.136", + "serde_json", +] + [[package]] name = "ring" version = "0.16.20" @@ -3218,6 +3263,31 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "schemars" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b5a3c80cea1ab61f4260238409510e814e38b4b563c06044edf91e7dc070e3" +dependencies = [ + "dyn-clone", + "indexmap", + "schemars_derive", + "serde 1.0.136", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b" +dependencies = [ + "proc-macro2 1.0.36", + "quote 1.0.15", + "serde_derive_internals", + "syn 1.0.86", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -3331,6 +3401,17 @@ dependencies = [ "syn 1.0.86", ] +[[package]] +name = "serde_derive_internals" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" +dependencies = [ + "proc-macro2 1.0.36", + "quote 1.0.15", + "syn 1.0.86", +] + [[package]] name = "serde_json" version = "1.0.78" diff --git a/Cargo.toml b/Cargo.toml index c3a841ae..29a6115e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,9 @@ members = [ "dove", "lang", + "net", "common/git-hash", + "resource-viewer", "pontem/client" ] exclude = [ "pontem/hash_project", "pontem/pontemapi"] diff --git a/README.md b/README.md index 990cc778..41d0f796 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,112 @@ dove deploy MODULE_NAME --secret --url https://127.0.0.1:9933 --gas 400 dove deploy PATH/TO/FILE --account //Alice --gas 300 ``` +## Resource Viewer +Move Resource Viewer is a tool to query [BCS](https://github.com/diem/bcs) resources data from blockchain nodes storage and represent them in JSON or human readable format. + +1. The viewer makes a request to the blockchain node by a sending specific query (address + resource type). +2. The viewer send another request to node and query resource type layout. +3. The viewer restores resources using response data and type layout. + +## Usage example + +Query the user's store contract balance: + +```bash +dove view "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY::Store::Store" --api "ws://127.0.0.1:9946" +``` + +### Input parameters + +- `[QUERY]` resource type-path, e.g.: + - `0x1::Account::Balance<0x1::PONT::PONT>` + - `5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY::Store::Store` + - In general: `0xDEADBEEF::Module::Struct< 0xBADBEEF::Mod::Struct<...>, ... >` + - Inner address can be omitted, it's inherited by parent: + `0xDEADBEEF::Module::Struct` expands to `0xDEADBEEF::Module::Struct<0xDEADBEEF::Mod::Struct>` + - Query can ends with index `[42]` for `vec`-resources +- Output options: + - `-o` / `--output` fs-path to output file + - `-j` / `--json` sets output format to json. Can be omitted if output file extension is `.json`, so then json format will be chosen automatically. + - `--json-schema` additional json-schema export, fs-path to output schema file. + +For more info check out `--help`. + +### Output + +Two output formats supported: + +- Move-like text +- JSON + +_The structure of the output in JSON is described in the scheme, which can be obtained by calling with the `--json-schema` parameter._ + +#### Move-like example: + +```rust +resource 00000000::Account::Balance<00000000::Coins::BTC> { + coin: resource 00000000::Dfinance::T<00000000::Coins::BTC> { + value: 1000000000u128 + } +} +``` + +#### JSON example: + +```json +{ + "is_resource": true, + "type": { + "address": "0000000000000000000000000000000000000001", + "module": "Account", + "name": "Balance", + "type_params": [ + { + "Struct": { + "address": "0000000000000000000000000000000000000001", + "module": "Coins", + "name": "BTC", + "type_params": [] + } + } + ] + }, + "value": [ + { + "id": "coin", + "value": { + "Struct": { + "is_resource": true, + "type": { + "address": "0000000000000000000000000000000000000001", + "module": "Dfinance", + "name": "T", + "type_params": [ + { + "Struct": { + "address": "0000000000000000000000000000000000000001", + "module": "Coins", + "name": "BTC", + "type_params": [] + } + } + ] + }, + "value": [ + { + "id": "value", + "value": { + "U128": 1000000000 + } + } + ] + } + } + } + ] +} +``` + ## LICENSE [LICENSE](/LICENSE) diff --git a/dove/Cargo.toml b/dove/Cargo.toml index 8275a285..722b9d45 100644 --- a/dove/Cargo.toml +++ b/dove/Cargo.toml @@ -12,7 +12,9 @@ edition = "2021" [dependencies] # LOCAL git-hash = { path = "../common/git-hash" } +resource-viewer = { path = "../resource-viewer" } lang = { path = "../lang" } +net = { path = "../net" } pontem-client = { path = "../pontem/client" } # DIEM @@ -28,6 +30,7 @@ move-package = { git = "https://github.com/pontem-network/move.git", branch = "r move-command-line-common = { git = "https://github.com/pontem-network/move.git", branch = "release-1.6" } move-cli = { git = "https://github.com/pontem-network/move.git", branch = "release-1.6" } pontem = { git = "https://github.com/pontem-network/move.git", branch = "release-1.6" } +move-resource-viewer = { package = "move-resource-viewer", git = "https://github.com/pontem-network/move.git", branch = "release-1.6" } # third-party dependencies log = "0.4.14" @@ -51,6 +54,7 @@ itertools = "0.9.0" uint = "0.9.1" smallvec = "1.8.0" diem-crypto = "0.0.3" +serde_json = "1.0" # Used for storing access keys aes = "0.7" diff --git a/dove/src/cli.rs b/dove/src/cli.rs index ec52a488..74867976 100644 --- a/dove/src/cli.rs +++ b/dove/src/cli.rs @@ -15,9 +15,9 @@ use crate::cmd::clean::Clean; use crate::cmd::run::Run; use crate::cmd::call::ExecuteTransaction; use crate::cmd::key::Key; -use crate::context::Context; - use crate::cmd::deploy::Deploy; +use crate::cmd::view::View; +use crate::context::Context; use crate::natives::{all_natives, pontem_cost_table}; #[derive(StructOpt)] @@ -91,6 +91,11 @@ pub enum DoveCommands { #[structopt(flatten)] cmd: Key, }, + #[structopt(about = "Resource viewer", display_order = 19)] + View { + #[structopt(flatten)] + cmd: View, + }, } fn preprocess_args(args: Vec) -> Vec { @@ -152,6 +157,7 @@ pub fn execute(args: Vec, cwd: PathBuf) -> Result<()> { DoveCommands::Run { mut cmd } => cmd.apply(&mut ctx), DoveCommands::Call { mut cmd } => cmd.apply(&mut ctx), DoveCommands::Deploy { mut cmd } => cmd.apply(&mut ctx), + DoveCommands::View { mut cmd } => cmd.apply(&mut ctx), DoveCommands::Build | DoveCommands::Test | DoveCommands::Prove diff --git a/dove/src/cmd/mod.rs b/dove/src/cmd/mod.rs index 72a5f38c..0c1b2d3f 100644 --- a/dove/src/cmd/mod.rs +++ b/dove/src/cmd/mod.rs @@ -8,3 +8,5 @@ pub mod deploy; pub mod key; /// Script executor. pub mod run; +/// resource-viewer +pub mod view; diff --git a/dove/src/cmd/view.rs b/dove/src/cmd/view.rs new file mode 100644 index 00000000..8de0d63d --- /dev/null +++ b/dove/src/cmd/view.rs @@ -0,0 +1,168 @@ +use std::path::{Path, PathBuf}; +use anyhow::Error; +use structopt::StructOpt; +use log::{error, info}; +use reqwest::Url; + +use move_core_types::language_storage::TypeTag; +use move_package::source_package::parsed_manifest::{AddressDeclarations, NamedAddress}; + +use lang::ss58::ss58_to_address; +use resource_viewer::ser; +use net::{make_net, NetView}; + +use crate::context::Context; +use crate::call::parser::parse_type_param; + +/// Move Resource Viewer +#[derive(StructOpt, Debug)] +#[structopt(setting(structopt::clap::AppSettings::ColoredHelp))] +#[structopt(usage = "dove view [QUERY] [OPTIONS] + Examples: + $ dove view Account::Store::U64 + $ dove view Account::Store::U64 --api http://127.0.0.1:9933 + $ dove view Account::Store::U64 --api http://127.0.0.1:9933 --json + $ dove view 0x1::Account::Balance<0x1::Coins::ETH> --api http://127.0.0.1:9933 --json --output PATH/SAVE.json +")] +pub struct View { + #[structopt( + display_order = 1, + help = "Fully qualified type description in a form of ADDRESS::MODULE::TYPE_NAME \n\ + Examples: \n\ + Account::Store::U64 \n\ + 0x1::Account::Balance<0x1::Coins::ETH>" + )] + query: String, + + #[structopt( + long, + default_value = "http://127.0.0.1:9933", + display_order = 2, + help = "The url of the substrate node to query. HTTP or HTTPS only" + )] + api: Url, + + #[structopt(long, short, display_order = 3, help = "Sets output format to JSON")] + json: bool, + + #[structopt( + long = "json-schema", + display_order = 4, + help = "Export JSON schema for output format" + )] + json_schema: Option, + + #[structopt(long, short, display_order = 5, help = "Path to output file")] + output: Option, + + #[structopt(long, short, display_order = 6, help = "Block number")] + height: Option, +} + +impl View { + pub fn apply(&mut self, ctx: &mut Context) -> anyhow::Result<()> { + if let Some(path) = self.json_schema.as_ref() { + produce_json_schema(path); + } + + let height = self.height.clone(); + let net = make_net(self.api.clone())?; + let address_map = ctx.manifest.addresses.clone().unwrap_or_default(); + + if !self.query.starts_with("0x") { + if let Some(pos) = self.query.find("::") { + let name_address = &self.query[..pos]; + let address = address_map + .get(&NamedAddress::from(name_address)) + .map(|acc| { + acc.ok_or(anyhow!( + "In Move.toml address not assigned to alias {}", + name_address + )) + }) + .unwrap_or_else(|| ss58_to_address(name_address))?; + + self.query = format!("{}{}", address.to_hex_literal(), &self.query[pos..]); + } + } + let query = parse_query(&address_map, &self.query)?; + + match query { + TypeTag::Struct(st) => { + let addr = st.address; + + net.get_resource(&addr, &st, &height) + .map(|resp| { + let view = NetView::new(net, height); + if let Some(bytes_for_block) = resp { + // Internally produce FatStructType (with layout) for StructTag by + // resolving & de-.. entire deps-chain. + let annotator = move_resource_viewer::MoveValueAnnotator::new(&view); + + annotator + .view_resource(&st, &bytes_for_block.0) + .and_then(|result| { + let height = bytes_for_block.1; + + if self.json { + serde_json::ser::to_string_pretty( + &ser::AnnotatedMoveStructWrapper { height, result }, + ) + .map_err(|err| anyhow!("{}", err)) + } else { + Ok(format!("{}", result)) + } + }) + .map(|result| { + write_output(self.output.as_deref(), &result, "result") + }) + } else { + bail!("Resource not found, result is empty") + } + }) + .and_then(|result| result) + } + TypeTag::Vector(list_types) => bail!("Unsupported root type Vec {:?}", list_types), + _ => bail!("Unsupported type {}", query), + } + } +} + +fn produce_json_schema(path: &Path) { + let schema = ser::produce_json_schema(); + let render = serde_json::to_string_pretty(&schema).unwrap(); + write_output(Some(path), &render, "schema"); +} + +fn write_output(path: Option<&Path>, result: &str, name: &str) { + use std::io::prelude::*; + + if let Some(path) = path { + std::fs::File::create(path) + .and_then(|mut f| f.write_all(result.as_bytes())) + .map_err(|err| error!("Cannot write output: {}", err)) + .map(|_| info!("File with {} was written successfully", name)) + .ok(); + } else { + println!("{}", &result); + } +} + +/// Query parsing +/// addr_map:&AddressDeclarations - To check alias addresses and replace with a hexadecimal address +/// qyery - Query string for parsing +fn parse_query(addr_map: &AddressDeclarations, query: &str) -> Result { + use move_command_line_common::files::FileHash; + use move_compiler::shared::CompilationEnv; + use move_compiler::parser::syntax::Context; + use move_compiler::parser::lexer::Lexer; + use move_compiler::Flags; + + let mut lexer = Lexer::new(query, FileHash::new(query)); + let mut env = CompilationEnv::new(Flags::empty(), Default::default()); + let mut ctx = Context::new(&mut env, &mut lexer); + + ctx.tokens.advance().map_err(|err| anyhow!("{:?}", &err))?; + + parse_type_param(addr_map, &mut ctx) +} diff --git a/lang/Cargo.toml b/lang/Cargo.toml index 81746c86..6a1c4388 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lang" -version = "0.1.0" +version = "0.1.1" authors = [ "Alex Koz. ", "Dm. Yakushev ", @@ -25,6 +25,8 @@ anyhow = "1.0.45" serde = { version = "1.0.130", features = ["derive"] } codespan-reporting = "0.11.1" walkdir = "2.3.1" +rust-base58 = "0.0.4" +blake2-rfc = "0.2" [dev-dependencies] include_dir = "0.6.0" diff --git a/lang/src/compiler/address/ss58.rs b/lang/src/compiler/address/ss58.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/lang/src/lib.rs b/lang/src/lib.rs index 7873f0b7..3cfb4380 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -2,5 +2,5 @@ extern crate anyhow; pub mod bytecode; -pub mod file; pub mod lexer; +pub mod ss58; diff --git a/lang/src/ss58.rs b/lang/src/ss58.rs new file mode 100644 index 00000000..8407db29 --- /dev/null +++ b/lang/src/ss58.rs @@ -0,0 +1,82 @@ +use anyhow::Result; +use move_core_types::account_address::AccountAddress; +use rust_base58::{ToBase58, FromBase58}; + +const SS58_PREFIX: &[u8] = b"SS58PRE"; +const PUB_KEY_LENGTH: usize = 32; +const CHECK_SUM_LEN: usize = 2; + +/// Convert address to ss58 +/// 0xD43593C715FDD31C61141ABD04A99FD6822C8558854CCDE39A5684E7A56DA27D => 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY +pub fn address_to_ss58(account: &AccountAddress) -> String { + let mut ss58_address = [0; 35]; + ss58_address[0] = 42; + ss58_address[1..33].copy_from_slice(&account.into_bytes()); + let hash = ss58hash(&ss58_address[0..33]); + ss58_address[33..35].copy_from_slice(&hash.as_bytes()[0..2]); + ss58_address.to_base58() +} + +fn ss58hash(data: &[u8]) -> blake2_rfc::blake2b::Blake2bResult { + let mut context = blake2_rfc::blake2b::Blake2b::new(64); + context.update(SS58_PREFIX); + context.update(data); + context.finalize() +} + +/// Convert ss58 to address +/// 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY => 0xD43593C715FDD31C61141ABD04A99FD6822C8558854CCDE39A5684E7A56DA27D +pub fn ss58_to_address(ss58: &str) -> Result { + let bs58 = match ss58.from_base58() { + Ok(bs58) => bs58, + Err(err) => return Err(anyhow!("Wrong base58:{}", err)), + }; + ensure!( + bs58.len() > PUB_KEY_LENGTH + CHECK_SUM_LEN, + format!( + "Address length must be equal or greater than {} bytes", + PUB_KEY_LENGTH + CHECK_SUM_LEN + ) + ); + let check_sum = &bs58[bs58.len() - CHECK_SUM_LEN..]; + let address = &bs58[bs58.len() - PUB_KEY_LENGTH - CHECK_SUM_LEN..bs58.len() - CHECK_SUM_LEN]; + + if check_sum != &ss58hash(&bs58[0..bs58.len() - CHECK_SUM_LEN]).as_bytes()[0..CHECK_SUM_LEN] { + return Err(anyhow!("Wrong address checksum")); + } + let mut addr = [0; PUB_KEY_LENGTH]; + addr.copy_from_slice(address); + Ok(AccountAddress::new(addr)) +} + +#[cfg(test)] +mod tests { + use move_core_types::account_address::AccountAddress; + use crate::ss58::{address_to_ss58, ss58_to_address}; + + #[test] + fn test_address_to_ss58() { + let t = AccountAddress::from_hex_literal( + "0xD43593C715FDD31C61141ABD04A99FD6822C8558854CCDE39A5684E7A56DA27D", + ) + .unwrap(); + + assert_eq!( + address_to_ss58(&t), + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + ); + } + + #[test] + fn test_ss58_to_address() { + let t = AccountAddress::from_hex_literal( + "0xD43593C715FDD31C61141ABD04A99FD6822C8558854CCDE39A5684E7A56DA27D", + ) + .unwrap(); + + assert_eq!( + t, + ss58_to_address("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY").unwrap() + ); + } +} diff --git a/net/Cargo.toml b/net/Cargo.toml index 18cd1798..6819f083 100644 --- a/net/Cargo.toml +++ b/net/Cargo.toml @@ -1,21 +1,27 @@ [package] name = "net" -version = "0.1.1" +version = "0.1.2" edition = "2018" [dependencies] +# LOCAL +lang = { path = "../lang" } + +# DIEM +move-core-types = { git = "https://github.com/pontem-network/move.git", branch = "release-1.6" } +move-vm-runtime = { git = "https://github.com/pontem-network/move.git", branch = "release-1.6" } +move-binary-format = { git = "https://github.com/pontem-network/move.git", branch = "release-1.6" } + +# third-party dependencies anyhow = { version = "1.0", default-features = false } -http = "0.2" serde = { version = "1.0.125", features = ["derive", "rc"] } serde_json = "1.0" -log = "0.4" -hex = "0.4" +url = { version = "2.2" } reqwest = { version = "0.10", features = ["blocking", "json"] } -bcs = "0.1.2" - -lang = { path = "../lang" } +log = "0.4" +hex = "0.4.2" +bcs = "0.1.3" -move-core-types = { git = "https://github.com/pontem-network/diem.git", branch = "v1.3-r1" } -move-vm-runtime = { git = "https://github.com/pontem-network/diem.git", branch = "v1.3-r1", features = ["debug_module"] } -move-binary-format = { git = "https://github.com/pontem-network/diem.git", branch = "v1.3-r1" } +[features] +dfinance = [] diff --git a/net/src/dnode/client.rs b/net/src/dnode/client.rs index 1d89be6d..5ed21774 100644 --- a/net/src/dnode/client.rs +++ b/net/src/dnode/client.rs @@ -1,13 +1,13 @@ use log::{trace, debug}; use serde::{Serialize, Deserialize}; use anyhow::{anyhow, Result}; -use http::Uri; +use url::Url; use crate::{Block, BytesForBlock}; pub fn data_request( address: &[u8], path: &[u8], - url: &Uri, + url: &Url, height: &Option, ) -> Result { let url = format!( diff --git a/net/src/dnode/mod.rs b/net/src/dnode/mod.rs index f229b9c3..bf2d6de8 100644 --- a/net/src/dnode/mod.rs +++ b/net/src/dnode/mod.rs @@ -1,16 +1,17 @@ +#![cfg(feature = "dfinance")] + mod client; +use anyhow::Result; +use url::Url; -use crate::{Net, Block, BytesForBlock}; use move_core_types::language_storage::{ModuleId, StructTag}; -use http::Uri; -use anyhow::Result; -use lang::compiler::dialects::Dialect; -use crate::dnode::client::data_request; use move_core_types::account_address::AccountAddress; +use crate::{Net, Block, BytesForBlock}; +use crate::dnode::client::data_request; + pub struct DnodeNet { - pub(crate) dialect: Box, - pub(crate) uri: Uri, + pub(crate) uri: Url, } impl Net for DnodeNet { @@ -19,12 +20,17 @@ impl Net for DnodeNet { module_id: &ModuleId, height: &Option, ) -> Result> { - let address = self.dialect.adapt_address_to_target(*module_id.address()); + // does not work without dialect + // let address = self.dialect.adapt_address_to_target(*module_id.address()); + // remove this + let address = module_id.address().into_bytes(); + let bytes = data_request(&address, &module_id.access_vector(), &self.uri, height).ok(); match bytes { None => Ok(None), - Some(mut bytes) => { - self.dialect.adapt_to_basis(&mut bytes.0)?; + Some(bytes) => { + // does not work without dialect + // self.dialect.adapt_to_basis(&mut bytes.0)?; Ok(Some(BytesForBlock(bytes.0, bytes.1))) } } @@ -36,19 +42,20 @@ impl Net for DnodeNet { tag: &StructTag, height: &Option, ) -> Result> { - let address = self.dialect.adapt_address_to_target(*address); + // does not work without dialect + // let address = self.dialect.adapt_address_to_target(*address); + // remove this + let address = address.into_bytes(); + let access_vector = tag.access_vector(); let bytes = data_request(&address, &access_vector, &self.uri, height).ok(); match bytes { None => Ok(None), - Some(mut bytes) => { - self.dialect.adapt_to_basis(&mut bytes.0)?; + Some(bytes) => { + // does not work without dialect + // self.dialect.adapt_to_basis(&mut bytes.0)?; Ok(Some(BytesForBlock(bytes.0, bytes.1))) } } } - - fn dialect(&self) -> &dyn Dialect { - self.dialect.as_ref() - } } diff --git a/net/src/lib.rs b/net/src/lib.rs index 498b3dbd..b037fb3d 100644 --- a/net/src/lib.rs +++ b/net/src/lib.rs @@ -1,38 +1,27 @@ -use anyhow::{anyhow, Result}; -use http::Uri; +use anyhow::{Error, Result}; +use url::Url; + use move_core_types::account_address::AccountAddress; use move_core_types::language_storage::{ModuleId, StructTag}; -use move_core_types::vm_status::StatusCode; -use move_binary_format::errors::{Location, PartialVMError, PartialVMResult, VMResult}; - -use lang::compiler::dialects::Dialect as DialectTrait; -use lang::compiler::dialects::DialectName; - -use crate::dnode::DnodeNet; -use crate::pont::PontNet; -use move_vm_runtime::data_cache::MoveStorage; +use move_core_types::resolver::{ModuleResolver, ResourceResolver}; +#[cfg(feature = "dfinance")] mod dnode; + mod pont; +use crate::pont::PontNet; pub type Block = String; -pub fn make_net(uri: T, name: DialectName) -> Result> +pub fn make_net(uri: T) -> Result> where - T: Into, + T: Into, { let uri = uri.into(); - match name { - DialectName::Diem => Err(anyhow!("Unexpected dialect")), - DialectName::DFinance => Ok(Box::new(DnodeNet { - dialect: name.get_dialect(), - uri, - })), - DialectName::Pont => Ok(Box::new(PontNet { - dialect: name.get_dialect(), - api: uri.to_string(), - })), - } + + Ok(Box::new(PontNet { + api: uri.to_string(), + })) } #[derive(Debug)] @@ -44,13 +33,13 @@ pub trait Net { module_id: &ModuleId, height: &Option, ) -> Result>; + fn get_resource( &self, address: &AccountAddress, tag: &StructTag, height: &Option, ) -> Result>; - fn dialect(&self) -> &dyn DialectTrait; } pub struct NetView { @@ -68,28 +57,22 @@ impl NetView { } } -impl MoveStorage for NetView { - fn get_module(&self, module_id: &ModuleId) -> VMResult>> { +impl ModuleResolver for NetView { + type Error = anyhow::Error; + + fn get_module(&self, module_id: &ModuleId) -> anyhow::Result>> { self.net .get_module(module_id, &self.block) - .map_err(|err| { - PartialVMError::new(StatusCode::MISSING_DATA) - .with_message(err.to_string()) - .finish(Location::Undefined) - }) .map(|bytes| bytes.map(|bytes| bytes.0)) } +} - fn get_resource( - &self, - address: &AccountAddress, - tag: &StructTag, - ) -> PartialVMResult>> { +impl ResourceResolver for NetView { + type Error = Error; + + fn get_resource(&self, address: &AccountAddress, tag: &StructTag) -> Result>> { self.net .get_resource(address, tag, &self.block) - .map_err(|err| { - PartialVMError::new(StatusCode::MISSING_DATA).with_message(err.to_string()) - }) .map(|bytes| bytes.map(|bytes| bytes.0)) } } diff --git a/net/src/pont/mod.rs b/net/src/pont/mod.rs index 3838a936..9704f596 100644 --- a/net/src/pont/mod.rs +++ b/net/src/pont/mod.rs @@ -1,16 +1,16 @@ use std::fmt::{Display, Formatter}; use anyhow::{bail, Result}; use serde::{Serialize, Deserialize}; -use lang::compiler::dialects::Dialect; -use lang::compiler::address::ss58::address_to_ss58; + use move_core_types::language_storage::{ModuleId, StructTag}; use move_core_types::account_address::AccountAddress; + +use lang::ss58::address_to_ss58; use crate::{Net, BytesForBlock}; pub type Block = String; pub struct PontNet { - pub(crate) dialect: Box, pub(crate) api: String, } @@ -75,7 +75,6 @@ impl Net for PontNet { format!("0x{}", hex::encode(bcs::to_bytes(&tag)?)), ], }; - let mut headers = reqwest::header::HeaderMap::new(); headers.insert( "Content-Type", @@ -109,10 +108,6 @@ impl Net for PontNet { Ok(None) } } - - fn dialect(&self) -> &dyn Dialect { - self.dialect.as_ref() - } } #[derive(Serialize)] @@ -151,30 +146,28 @@ impl Display for ErrorMsg { #[cfg(test)] mod tests { - use std::str::FromStr; - use lang::compiler::dialects::DialectName; use move_core_types::account_address::AccountAddress; use move_core_types::identifier::Identifier; use move_core_types::language_storage::{ModuleId, StructTag}; + + use lang::ss58::ss58_to_address; + use super::PontNet; use crate::Net; - use lang::compiler::address::ss58::ss58_to_address; /// If the node is raised to "localhost:9933". #[ignore] #[test] fn test_get_module() { - let dialect_name = DialectName::from_str("pont").unwrap(); let api = PontNet { - dialect: dialect_name.get_dialect(), api: "http://localhost:9933".to_string(), }; let module = api .get_module( - &ModuleId { - address: AccountAddress::from_hex_literal("0x1").unwrap(), - name: Identifier::new("Hash").unwrap(), - }, + &ModuleId::new( + AccountAddress::from_hex_literal("0x1").unwrap(), + Identifier::new("Hash").unwrap(), + ), &None, ) .unwrap() @@ -196,9 +189,7 @@ mod tests { #[ignore] #[test] fn test_get_resource() { - let dialect_name = DialectName::from_str("pont").unwrap(); let api = PontNet { - dialect: dialect_name.get_dialect(), api: "http://localhost:9933".to_string(), }; diff --git a/resource-viewer/Cargo.toml b/resource-viewer/Cargo.toml index a991960e..32ea7102 100644 --- a/resource-viewer/Cargo.toml +++ b/resource-viewer/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "move-resource-viewer" -version = "1.2.0" +name = "resource-viewer" +version = "1.2.1" authors = [ "Alex Koz. ", "Dm. Yakushev ", @@ -10,34 +10,19 @@ authors = [ edition = "2018" [dependencies] -log = "0.4" -anyhow = { version = "1.0", default-features = false } http = "0.2" serde = { version = "1.0.125", features = ["derive", "rc"] } serde_json = "1.0" schemars = { version = "0.8", features = ["default", "derive", "preserve_order"], optional = true } -hex = "0.4.2" # diem deps: -move-core-types = { git = "https://github.com/pontem-network/diem.git", branch = "v1.3-r1" } -resource-viewer = { git = "https://github.com/pontem-network/diem.git", branch = "v1.3-r1" } -move-ir-types = { git = "https://github.com/pontem-network/diem.git", branch = "v1.3-r1" } -move-lang = { git = "https://github.com/pontem-network/diem.git", branch = "v1.3-r1" } -move-binary-format = { git = "https://github.com/pontem-network/diem.git", branch = "v1.3-r1" } -move-vm-runtime = { git = "https://github.com/pontem-network/diem.git", branch = "v1.3-r1", features = ["debug_module"]} +move-resource-viewer = { package = "move-resource-viewer", git = "https://github.com/pontem-network/move.git", branch = "release-1.6" } +move-core-types = { git = "https://github.com/pontem-network/move.git", branch = "release-1.6" } +move-binary-format = { git = "https://github.com/pontem-network/move.git", branch = "release-1.6" } # move-lang deps: -lang = { path = "../lang" } -git-hash = { path = "../common/git-hash" } -compat = { path = "../lang/compat", package = "move-compat" } net = { path = "../net" } -# cli: -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -anyhow = { version = "1.0", default-features = true } -structopt = "0.3" -env_logger = "0.8.2" - [features] default = [ "json-schema", diff --git a/resource-viewer/README.md b/resource-viewer/README.md deleted file mode 100644 index ac916458..00000000 --- a/resource-viewer/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# Move Resource Viewer - -**Resource viewer is currently out of date and pending migration inside dove in future versions.** - -Move Resource Viewer is a tool to query [BCS](https://github.com/diem/bcs) resources data from blockchain nodes storage and represent them in JSON or human readable format. - -Supported nodes: -* [pontem](https://github.com/pontem-network/pontem) -* [dnode](http://github.com/dfinance/dnode) -* [diem](https://github.com/diem/diem) - -## How does it works? - -1. The viewer makes a request to the blockchain node by a sending specific query (address + resource type). -2. The viewer send another request to node and query resource type layout. -3. The viewer restores resources using response data and type layout. - -## Installation - -Requirements: -- [Rust][] toolchain, the easiest way to get it is to use [Rustup][]. - -Using cargo: - -```bash -cargo install --git https://github.com/pontem-network/move-tools.git move-resource-viewer -``` - -[Rust]: https://www.rust-lang.org -[Rustup]: https://rustup.rs - - -[DFinance]: https://github.com/dfinance -[diem/Diem]: https://github.com/diem -[SS58]: "https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58)" - - -## Usage example - -Query the user's store contract balance: - -```bash -move-resource-viewer --address 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY --query "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY::Store::Store" --api="ws://127.0.0.1:9946" -``` - -### Input parameters - -- `-a` / `--account` can be in Pontem [ss58][], Dfinance [bech32][] or hex `0x…{16-20 bytes}`. -- `-q` / `--query` resource type-path, e.g.: - - `0x1::Account::Balance<0x1::PONT::PONT>` - - `5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY::Store::Store` - - In general: `0xDEADBEEF::Module::Struct< 0xBADBEEF::Mod::Struct<...>, ... >` - - Inner address can be omitted, it's inherited by parent: - `0xDEADBEEF::Module::Struct` expands to `0xDEADBEEF::Module::Struct<0xDEADBEEF::Mod::Struct>` - - Query can ends with index `[42]` for `vec`-resources -- Output options: - - `-o` / `--output` fs-path to output file - - `-j` / `--json` sets output format to json. Can be omitted if output file extension is `.json`, so then json format will be chosen automatically. - - `--json-schema` additional json-schema export, fs-path to output schema file. - -For more info check out `--help`. - -[dnode]: https://github.com/dfinance/dnode -[bech32]: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki - -### Output - -Two output formats supported: - -- Move-like text -- JSON - -_The structure of the output in JSON is described in the scheme, which can be obtained by calling with the `--json-schema` parameter._ - -#### Move-like example: - -```rust -resource 00000000::Account::Balance<00000000::Coins::BTC> { - coin: resource 00000000::Dfinance::T<00000000::Coins::BTC> { - value: 1000000000u128 - } -} -``` - -#### JSON example: - -```json -{ - "is_resource": true, - "type": { - "address": "0000000000000000000000000000000000000001", - "module": "Account", - "name": "Balance", - "type_params": [ - { - "Struct": { - "address": "0000000000000000000000000000000000000001", - "module": "Coins", - "name": "BTC", - "type_params": [] - } - } - ] - }, - "value": [ - { - "id": "coin", - "value": { - "Struct": { - "is_resource": true, - "type": { - "address": "0000000000000000000000000000000000000001", - "module": "Dfinance", - "name": "T", - "type_params": [ - { - "Struct": { - "address": "0000000000000000000000000000000000000001", - "module": "Coins", - "name": "BTC", - "type_params": [] - } - } - ] - }, - "value": [ - { - "id": "value", - "value": { - "U128": 1000000000 - } - } - ] - } - } - } - ] -} -``` diff --git a/resource-viewer/src/lib.rs b/resource-viewer/src/lib.rs index 703c43a4..35b62cb3 100644 --- a/resource-viewer/src/lib.rs +++ b/resource-viewer/src/lib.rs @@ -1,4 +1 @@ -extern crate anyhow; - pub mod ser; -pub mod tte; diff --git a/resource-viewer/src/main.rs b/resource-viewer/src/main.rs deleted file mode 100644 index 79b479a6..00000000 --- a/resource-viewer/src/main.rs +++ /dev/null @@ -1,179 +0,0 @@ -// Simple querie examples: -// "0x1::Account::Balance<0x1::PONT::T>" -// "0x1::Account::Balance<0x1::XFI::T>", -// "0x1::Account::Balance<0x1::Coins::ETH>", -// "0x1::Account::Balance<0x1::Coins::BTC>", -// "0x1::Account::Balance<0x1::Coins::USDT>", -// "0x1::Account::Balance<0x1::Coins::SXFI>", - -#[macro_use] -extern crate log; -use structopt::StructOpt; - -use std::path::{Path, PathBuf}; -use std::str::FromStr; - -use anyhow::{anyhow, Error, Result}; -use http::Uri; -use move_core_types::language_storage::TypeTag; -use resource_viewer as rv; - -use lang::compiler::dialects::DialectName; -use move_resource_viewer::{ser, tte}; -use net::{make_net, NetView}; - -#[cfg(feature = "json-schema")] -const STDOUT_PATH: &str = "-"; -const VERSION: &str = git_hash::crate_version_with_git_hash_short!(); - -#[derive(StructOpt, Debug)] -#[structopt(name = "Move resource viewer", version = VERSION)] -struct Cfg { - /// Owner's address - #[structopt(long, short)] - address: String, - #[structopt(default_value = "pont")] - dialect: String, - - /// Query in `TypeTag` format, - /// one-line address+type description. - /// Mainly, in most cases should be StructTag. - /// Additionaly can contain index at the end. - /// Query examples: - /// "0x1::Account::Balance<0x1::XFI::T>", - /// "0x1::Account::Balance<0x1::Coins::ETH>" - #[structopt(long, short)] - query: tte::TypeTagQuery, - - /// Time: maximum block number - #[structopt(long, short)] - height: Option, - - /// Output file path. - /// Special value for write to stdout: "-" - #[structopt(long, short)] - #[structopt(default_value = STDOUT_PATH)] - output: PathBuf, - - /// Sets output format to JSON. - /// Optional, `true` if output file extension is .json - #[structopt(long, short)] - json: Option, - - /// Node REST API address - #[structopt(long)] - api: Uri, - - /// Enables compatibility mode - #[structopt(long, short)] - compat: bool, - - /// Export JSON schema for output format. - /// Special value for write to stdout: "-" - #[cfg(feature = "json-schema")] - #[structopt(long = "json-schema")] - json_schema: Option, -} - -fn main() -> Result<(), Error> { - init_logger() - .map_err(|err| eprintln!("Error: {}", err)) - .ok(); - run().map_err(|err| { - error!("{}", err); - err - }) -} - -fn init_logger() -> Result<(), impl std::error::Error> { - use env_logger::*; - - let mut builder = Builder::from_env(Env::default()); - builder.format_timestamp(None); - builder.try_init() -} - -fn run() -> Result<(), Error> { - let cfg = Cfg::from_args(); - - let dialect_name = DialectName::from_str(&cfg.dialect)?; - - produce_json_schema(&cfg); - let net = make_net(cfg.api, dialect_name)?; - let dialect = dialect_name.get_dialect(); - - let output = cfg.output; - let height = cfg.height; - let json = cfg.json.unwrap_or_else(|| { - output - .extension() - .map(|ext| ext == "json") - .unwrap_or_default() - }); - let (tte, index) = cfg.query.into_inner(); - let addr = dialect.as_ref().parse_address(&cfg.address)?; - - match tte { - TypeTag::Struct(st) => { - net.get_resource(&addr, &st, &height) - .map(|resp| { - let view = NetView::new(net, height); - if let Some(bytes_for_block) = resp { - // Internally produce FatStructType (with layout) for StructTag by - // resolving & de-.. entire deps-chain. - let annotator = rv::MoveValueAnnotator::new_no_stdlib(&view); - - annotator - .view_resource(&st, &bytes_for_block.0) - .and_then(|result| { - let height = bytes_for_block.1; - - if json { - serde_json::ser::to_string_pretty( - &ser::AnnotatedMoveStructWrapper { height, result }, - ) - .map_err(|err| anyhow!("{}", err)) - } else { - Ok(format!("{}", result)) - } - }) - .map(|result| write_output(&output, &result, "result")) - } else { - Err(anyhow!("Resource not found, result is empty")) - } - }) - .and_then(|result| result) - } - - TypeTag::Vector(tt) => Err(anyhow!( - "Unsupported root type Vec<{}>{:?}", - tt, - index.map(|v| [v]).unwrap_or_default() - )), - - _ => Err(anyhow!("Unsupported type {}", tte)), - } -} - -#[allow(unused_variables)] -fn produce_json_schema(cfg: &Cfg) { - #[cfg(feature = "json-schema")] - if let Some(path) = cfg.json_schema.as_ref() { - let schema = ser::produce_json_schema(); - let render = serde_json::to_string_pretty(&schema).unwrap(); - write_output(path, &render, "schema"); - } -} - -fn write_output(path: &Path, result: &str, name: &str) { - use std::io::prelude::*; - if path.as_os_str() == STDOUT_PATH { - println!("{}", &result); - } else { - std::fs::File::create(path) - .and_then(|mut f| f.write_all(result.as_bytes())) - .map_err(|err| error!("Cannot write output: {}", err)) - .map(|_| info!("File with {} was written successfully", name)) - .ok(); - } -} diff --git a/resource-viewer/src/ser.rs b/resource-viewer/src/ser.rs index afa11fd8..9b829000 100644 --- a/resource-viewer/src/ser.rs +++ b/resource-viewer/src/ser.rs @@ -1,7 +1,7 @@ #![allow(clippy::field_reassign_with_default)] use serde::Serialize; -use resource_viewer::{AnnotatedMoveStruct, AnnotatedMoveValue}; +use move_resource_viewer::{AnnotatedMoveStruct, AnnotatedMoveValue}; #[cfg(feature = "json-schema")] use schemars::{JsonSchema, schema_for, schema::RootSchema}; @@ -24,10 +24,9 @@ pub struct AnnotatedMoveStructWrapper { #[serde(with = "AnnotatedMoveStructExt")] pub result: AnnotatedMoveStruct, } - #[derive(Serialize)] #[cfg_attr(feature = "json-schema", derive(JsonSchema))] -#[serde(remote = "resource_viewer::AnnotatedMoveStruct")] +#[serde(remote = "move_resource_viewer::AnnotatedMoveStruct")] struct AnnotatedMoveStructExt { #[serde(with = "schema_support::AbilitySetExt")] abilities: AbilitySet, @@ -44,7 +43,7 @@ struct AnnotatedMoveStructExt { #[derive(Serialize)] #[cfg_attr(feature = "json-schema", derive(JsonSchema))] -#[serde(remote = "resource_viewer::AnnotatedMoveValue")] +#[serde(remote = "move_resource_viewer::AnnotatedMoveValue")] enum AnnotatedMoveValueExt { U8(u8), U64(u64), @@ -72,7 +71,7 @@ struct AccountAddressExt( ); impl AccountAddressExt { pub fn ext_to_u8(addr: &AccountAddress) -> [u8; AccountAddress::LENGTH] { - addr.to_u8() + addr.into_bytes() } } diff --git a/resource-viewer/src/tte.rs b/resource-viewer/src/tte.rs deleted file mode 100644 index adce4ca5..00000000 --- a/resource-viewer/src/tte.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::str::FromStr; - -use anyhow::{Result, Error, anyhow}; - -use move_ir_types::location::Loc; -use move_core_types::language_storage::TypeTag; -use lang::lexer::unwrap_spanned_ty; -use move_lang::parser::lexer::{Lexer, Tok}; -use move_lang::parser::syntax::parse_type; - -#[derive(Debug)] -pub struct TypeTagQuery { - tt: TypeTag, - - /// Index of vector - /// e.g.: `0x0::Mod::Res[i]` - i: Option, -} - -impl FromStr for TypeTagQuery { - type Err = Error; - - fn from_str(s: &str) -> Result { - self::parse(s) - } -} - -impl From for (TypeTag, Option) { - fn from(query: TypeTagQuery) -> Self { - (query.tt, query.i) - } -} - -impl TypeTagQuery { - pub fn into_inner(self) -> (TypeTag, Option) { - self.into() - } -} - -pub fn parse(s: &str) -> Result { - let map_err = |err: Vec<(Loc, String)>| { - anyhow!("Query parsing error:\n\t{:}", { - let strs: Vec<_> = err - .into_iter() - .map(|(loc, msg)| format!("{}: {}", loc.span(), msg)) - .collect(); - strs.join("\n\t") - }) - }; - - let q = { - #[cfg(feature = "ps_address")] - { - let res = lang::compiler::address::ss58::replace_ss58_addresses( - &s, - &mut Default::default(), - ); - log::debug!("in-query address decoded: {:}", res); - res - } - #[cfg(not(feature = "ps_address"))] - { - s - } - }; - - let mut lexer = Lexer::new(q, "query", Default::default()); - lexer.advance().map_err(map_err)?; - - let ty = parse_type(&mut lexer).map_err(map_err)?; - let tt = unwrap_spanned_ty(ty)?; - - let mut i = None; - while lexer.peek() != Tok::EOF { - let tok = lexer.peek(); - lexer.advance().map_err(map_err)?; - - match tok { - Tok::LBracket => { - i = { - assert_eq!(lexer.peek(), Tok::NumValue); - let res = match u128::from_str(lexer.content()) { - Ok(i) => Ok(i), - Err(_) => Err(anyhow!("largest number type 'u128'")), - }; - lexer.advance().map_err(map_err)?; - res.ok() - } - } - _ => break, - } - } - - Ok(TypeTagQuery { tt, i }) -}