Unverified Commit bce9301c authored by Sasha Gryaznov's avatar Sasha Gryaznov Committed by GitHub
Browse files

Add `decode` command for event, message and constructor data decoding (#481)

* add contract call data to debug log

* [REVERT!] some debug data to dig into an error

* save

* decode cmd works for both event and message

save: +output tuned

+decoder for message data and specifier of type to DecodeCommand

removed redundant args to decode cmd

* remove some debugging prints

This reverts commit 1375fee8

.

* test fixed

* use util decode_hex()

* moved decode cmd to root (it's not an extrinsic)

* e2e test for decoding message data

* add decoding event to the same e2e test

* Update src/cmd/decode.rs
Co-authored-by: Andrew Jones's avatarAndrew Jones <ascjones@gmail.com>

* added constructor data decoder

* ci fixes

* lil test update

* Partially revert "lil test update" (mainfest fixes)

This reverts commit 1f24d5be.

* Build before test, and test --release

* Revert "Build before test, and test --release"

This reverts commit 7f6ef5ee

.

* Move decode test to an integration test
Co-authored-by: Andrew Jones's avatarAndrew Jones <ascjones@gmail.com>
parent ceebfcff
Pipeline #188147 passed with stages
in 7 minutes and 4 seconds
// 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 <http://www.gnu.org/licenses/>.
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(())
}
}
......@@ -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 {
......
......@@ -66,6 +66,7 @@ pub fn display_events(
if <ContractEmitted as Event>::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,
......
......@@ -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};
......
......@@ -175,7 +175,6 @@ impl<'a> ContractMessageTranscoder<'a> {
// data is an encoded `Vec<u8>` so is prepended with its length `Compact<u32>`, which we
// ignore because the structure of the event data is known for decoding.
let _len = <Compact<u32>>::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<Value> {
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<Value> {
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<Value> {
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(())
}
}
......@@ -15,12 +15,14 @@
// along with cargo-contract. If not, see <http://www.gnu.org/licenses/>.
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;
......
......@@ -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(),
}
}
......
// 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 <http://www.gnu.org/licenses/>.
use std::path::Path;
/// Create a `cargo contract` command
fn cargo_contract<P: AsRef<Path>>(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));
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment