diff --git a/src/cmd/decode.rs b/src/cmd/decode.rs
new file mode 100644
index 000000000..b0e94f7e8
--- /dev/null
+++ b/src/cmd/decode.rs
@@ -0,0 +1,67 @@
+// Copyright 2018-2020 Parity Technologies (UK) Ltd.
+// This file is part of cargo-contract.
+//
+// cargo-contract is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// cargo-contract is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with cargo-contract. If not, see .
+
+use crate::{
+ cmd::extrinsics::{load_metadata, ContractMessageTranscoder},
+ util::decode_hex,
+ DEFAULT_KEY_COL_WIDTH,
+};
+use anyhow::{Context, Result};
+use colored::Colorize as _;
+
+#[derive(Debug, Clone, clap::Args)]
+#[clap(name = "decode", about = "Decode input_data for a contract")]
+pub struct DecodeCommand {
+ /// Type of data
+ #[clap(arg_enum, short, long)]
+ r#type: DataType,
+ /// The data to decode
+ #[clap(short, long)]
+ data: String,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ArgEnum)]
+enum DataType {
+ Event,
+ Message,
+ Constructor,
+}
+
+impl DecodeCommand {
+ pub fn run(&self) -> Result<()> {
+ let (_, contract_metadata) = load_metadata(None)?;
+ let transcoder = ContractMessageTranscoder::new(&contract_metadata);
+
+ const ERR_MSG: &str = "Failed to decode specified data as a hex value";
+ let decoded_data = match self.r#type {
+ DataType::Event => transcoder
+ .decode_contract_event(&mut &decode_hex(&self.data).context(ERR_MSG)?[..])?,
+ DataType::Message => transcoder
+ .decode_contract_message(&mut &decode_hex(&self.data).context(ERR_MSG)?[..])?,
+ DataType::Constructor => transcoder
+ .decode_contract_constructor(&mut &decode_hex(&self.data).context(ERR_MSG)?[..])?,
+ };
+
+ println!(
+ "{:>width$} {}",
+ "Decoded data:".bright_green().bold(),
+ decoded_data,
+ width = DEFAULT_KEY_COL_WIDTH
+ );
+
+ Ok(())
+ }
+}
diff --git a/src/cmd/extrinsics/call.rs b/src/cmd/extrinsics/call.rs
index 70bd194f1..e6d255509 100644
--- a/src/cmd/extrinsics/call.rs
+++ b/src/cmd/extrinsics/call.rs
@@ -56,6 +56,8 @@ impl CallCommand {
let (_, contract_metadata) = load_metadata(self.extrinsic_opts.manifest_path.as_ref())?;
let transcoder = ContractMessageTranscoder::new(&contract_metadata);
let call_data = transcoder.encode(&self.message, &self.args)?;
+ log::debug!("message data: {:?}", hex::encode(&call_data));
+
let signer = super::pair_signer(self.extrinsic_opts.signer()?);
async_std::task::block_on(async {
diff --git a/src/cmd/extrinsics/events.rs b/src/cmd/extrinsics/events.rs
index 845e3cc69..98031c9bc 100644
--- a/src/cmd/extrinsics/events.rs
+++ b/src/cmd/extrinsics/events.rs
@@ -66,6 +66,7 @@ pub fn display_events(
if ::is_event(&event.pallet, &event.variant)
&& field.name() == Some(&"data".to_string())
{
+ log::debug!("event data: {:?}", hex::encode(&event_data));
let contract_event = transcoder.decode_contract_event(event_data)?;
maybe_println!(
verbosity,
diff --git a/src/cmd/extrinsics/mod.rs b/src/cmd/extrinsics/mod.rs
index 8a544772b..158a7a14c 100644
--- a/src/cmd/extrinsics/mod.rs
+++ b/src/cmd/extrinsics/mod.rs
@@ -28,7 +28,7 @@ mod integration_tests;
use anyhow::{anyhow, Context, Result};
use std::{fs::File, path::PathBuf};
-use self::{events::display_events, transcode::ContractMessageTranscoder};
+use self::events::display_events;
use crate::{
crate_metadata::CrateMetadata, name_value_println, workspace::ManifestPath, Verbosity,
VerbosityFlags,
@@ -37,6 +37,7 @@ use pallet_contracts_primitives::ContractResult;
use sp_core::{crypto::Pair, sr25519};
use subxt::{Config, DefaultConfig};
+pub use self::transcode::ContractMessageTranscoder;
pub use call::CallCommand;
pub use instantiate::InstantiateCommand;
pub use runtime_api::api::{DispatchError as RuntimeDispatchError, Event as RuntimeEvent};
diff --git a/src/cmd/extrinsics/transcode/mod.rs b/src/cmd/extrinsics/transcode/mod.rs
index 4c12c2f4e..421b2e585 100644
--- a/src/cmd/extrinsics/transcode/mod.rs
+++ b/src/cmd/extrinsics/transcode/mod.rs
@@ -175,7 +175,6 @@ impl<'a> ContractMessageTranscoder<'a> {
// data is an encoded `Vec` so is prepended with its length `Compact`, which we
// ignore because the structure of the event data is known for decoding.
let _len = >::decode(data)?;
-
let variant_index = data.read_byte()?;
let event_spec = self
.metadata
@@ -203,6 +202,60 @@ impl<'a> ContractMessageTranscoder<'a> {
Ok(Value::Map(map))
}
+ pub fn decode_contract_message(&self, data: &mut &[u8]) -> Result {
+ let mut msg_selector = [0u8; 4];
+ data.read(&mut msg_selector)?;
+ let msg_spec = self
+ .messages()
+ .find(|x| msg_selector == x.selector().to_bytes())
+ .ok_or_else(|| {
+ anyhow::anyhow!(
+ "Message with selector {} not found in contract metadata",
+ hex::encode(&msg_selector)
+ )
+ })?;
+ log::debug!("decoding contract message '{}'", msg_spec.label());
+
+ let mut args = Vec::new();
+ for arg in msg_spec.args() {
+ let name = arg.label().to_string();
+ let value = self.transcoder.decode(arg.ty().ty().id(), data)?;
+ args.push((Value::String(name), value));
+ }
+
+ let name = msg_spec.label().to_string();
+ let map = Map::new(Some(&name), args.into_iter().collect());
+
+ Ok(Value::Map(map))
+ }
+
+ pub fn decode_contract_constructor(&self, data: &mut &[u8]) -> Result {
+ let mut msg_selector = [0u8; 4];
+ data.read(&mut msg_selector)?;
+ let msg_spec = self
+ .constructors()
+ .find(|x| msg_selector == x.selector().to_bytes())
+ .ok_or_else(|| {
+ anyhow::anyhow!(
+ "Constructor with selector {} not found in contract metadata",
+ hex::encode(&msg_selector)
+ )
+ })?;
+ log::debug!("decoding contract constructor '{}'", msg_spec.label());
+
+ let mut args = Vec::new();
+ for arg in msg_spec.args() {
+ let name = arg.label().to_string();
+ let value = self.transcoder.decode(arg.ty().ty().id(), data)?;
+ args.push((Value::String(name), value));
+ }
+
+ let name = msg_spec.label().to_string();
+ let map = Map::new(Some(&name), args.into_iter().collect());
+
+ Ok(Value::Map(map))
+ }
+
pub fn decode_return(&self, name: &str, data: &mut &[u8]) -> Result {
let msg_spec = self
.find_message_spec(name)
@@ -391,4 +444,15 @@ mod tests {
Ok(())
}
+
+ #[test]
+ fn decode_contract_message() -> Result<()> {
+ let metadata = generate_metadata();
+ let transcoder = ContractMessageTranscoder::new(&metadata);
+
+ let encoded_bytes = hex::decode("633aa551").unwrap();
+ let _ = transcoder.decode_contract_message(&mut &encoded_bytes[..])?;
+
+ Ok(())
+ }
}
diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs
index aadc8619e..afee32e88 100644
--- a/src/cmd/mod.rs
+++ b/src/cmd/mod.rs
@@ -15,12 +15,14 @@
// along with cargo-contract. If not, see .
pub mod build;
+pub mod decode;
pub mod metadata;
pub mod new;
pub mod test;
pub(crate) use self::{
build::{BuildCommand, CheckCommand},
+ decode::DecodeCommand,
test::TestCommand,
};
mod extrinsics;
diff --git a/src/main.rs b/src/main.rs
index 848a75ac3..5711fce6a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -22,8 +22,8 @@ mod workspace;
use self::{
cmd::{
- metadata::MetadataResult, BuildCommand, CallCommand, CheckCommand, InstantiateCommand,
- TestCommand, UploadCommand,
+ metadata::MetadataResult, BuildCommand, CallCommand, CheckCommand, DecodeCommand,
+ InstantiateCommand, TestCommand, UploadCommand,
},
util::DEFAULT_KEY_COL_WIDTH,
workspace::ManifestPath,
@@ -447,6 +447,9 @@ enum Command {
/// Call a contract
#[clap(name = "call")]
Call(CallCommand),
+ /// Decode a contract input data
+ #[clap(name = "decode")]
+ Decode(DecodeCommand),
}
fn main() {
@@ -504,6 +507,7 @@ fn exec(cmd: Command) -> Result<()> {
Command::Upload(upload) => upload.run(),
Command::Instantiate(instantiate) => instantiate.run(),
Command::Call(call) => call.run(),
+ Command::Decode(decode) => decode.run(),
}
}
diff --git a/tests/decode.rs b/tests/decode.rs
new file mode 100644
index 000000000..970458d66
--- /dev/null
+++ b/tests/decode.rs
@@ -0,0 +1,134 @@
+// Copyright 2018-2020 Parity Technologies (UK) Ltd.
+// This file is part of cargo-contract.
+//
+// cargo-contract is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// cargo-contract is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with cargo-contract. If not, see .
+
+use std::path::Path;
+
+/// Create a `cargo contract` command
+fn cargo_contract>(path: P) -> assert_cmd::Command {
+ let mut cmd = assert_cmd::Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap();
+ cmd.current_dir(path).arg("contract");
+ cmd
+}
+
+#[test]
+fn decode_works() {
+ // given
+ let contract = r#"
+ #![cfg_attr(not(feature = "std"), no_std)]
+
+ use ink_lang as ink;
+
+ #[ink::contract]
+ mod switcher {
+ #[ink(event)]
+ pub struct Switched {
+ new_value: bool,
+ }
+
+ #[ink(storage)]
+ pub struct Switcher {
+ value: bool,
+ }
+
+ impl Switcher {
+ #[ink(constructor, selector = 0xBABEBABE)]
+ pub fn new(init_value: bool) -> Self {
+ Self { value: init_value }
+ }
+
+ #[ink(message, selector = 0xBABEBABE)]
+ pub fn switch(&mut self, value: bool) {
+ self.value = value;
+ }
+ }
+ }"#;
+
+ let tmp_dir = tempfile::Builder::new()
+ .prefix("cargo-contract.cli.test.")
+ .tempdir()
+ .expect("temporary directory creation failed");
+
+ // cargo contract new decode_test
+ cargo_contract(tmp_dir.path())
+ .arg("new")
+ .arg("switcher")
+ .assert()
+ .success();
+
+ let project_dir = tmp_dir.path().to_path_buf().join("switcher");
+
+ let lib = project_dir.join("lib.rs");
+ std::fs::write(&lib, contract).expect("Failed to write contract lib.rs");
+
+ assert_cmd::Command::new("rustup")
+ .arg("override")
+ .arg("set")
+ .arg("nightly")
+ .assert()
+ .success();
+
+ log::info!("Building contract in {}", project_dir.to_string_lossy());
+ cargo_contract(&project_dir)
+ .arg("build")
+ .arg("--skip-linting")
+ .assert()
+ .success();
+
+ let msg_data: &str = "babebabe01";
+ let msg_decoded: &str = r#"switch { value: true }"#;
+
+ // then
+ // message data is being decoded properly
+ cargo_contract(&project_dir)
+ .arg("decode")
+ .arg("--data")
+ .arg(msg_data)
+ .arg("-t")
+ .arg("message")
+ .assert()
+ .success()
+ .stdout(predicates::str::contains(msg_decoded));
+
+ let event_data: &str = "080001";
+ let event_decoded: &str = r#"Switched { new_value: true }"#;
+
+ // and
+ // event data is being decoded properly
+ cargo_contract(&project_dir)
+ .arg("decode")
+ .arg("--data")
+ .arg(event_data)
+ .arg("-t")
+ .arg("event")
+ .assert()
+ .success()
+ .stdout(predicates::str::contains(event_decoded));
+
+ let constructor_data: &str = "babebabe00";
+ let constructor_decoded: &str = r#"new { init_value: false }"#;
+
+ // and
+ // event data is being decoded properly
+ cargo_contract(&project_dir)
+ .arg("decode")
+ .arg("-d")
+ .arg(constructor_data)
+ .arg("-t")
+ .arg("constructor")
+ .assert()
+ .success()
+ .stdout(predicates::str::contains(constructor_decoded));
+}