Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 22 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions src/cmd/decode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// 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(long)]
data: String,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ArgEnum)]
enum DataType {
Event,
Message,
}

impl DecodeCommand {
pub fn run(&self) -> Result<()> {
ascjones marked this conversation as resolved.
Show resolved Hide resolved
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)?[..]),
};

println!(
"{:>width$} {}",
"Decoded data:".bright_green().bold(),
decoded_data,
width = DEFAULT_KEY_COL_WIDTH
);

Ok(())
}
}

#[cfg(test)]
mod tests {
use crate::util::tests::with_new_contract_project;
use std::path::Path;

/// Create a `cargo contract` command
fn cargo_contract(path: &Path) -> assert_cmd::Command {
let mut cmd = assert_cmd::Command::cargo_bin("cargo-contract").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]
pub mod switcher {
#[ink(event)]
pub struct Switched {
new_value: bool,
ascjones marked this conversation as resolved.
Show resolved Hide resolved
}

#[ink(storage)]
pub struct Switcher {
value: bool,
ascjones marked this conversation as resolved.
Show resolved Hide resolved
}

impl Switcher {
#[ink(constructor)]
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;
}
ascjones marked this conversation as resolved.
Show resolved Hide resolved
}
}"#;

// when
// contract is built
with_new_contract_project(|manifest_path| {
let project_dir = manifest_path.directory().expect("directory must exist");
let lib = project_dir.join("lib.rs");
std::fs::write(&lib, contract)?;

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").assert().success();

let msg_data: &str = "babebabe01";
let msg_decoded: &str =
r#"Ok(Map(Map { ident: Some("switch"), map: {String("value"): Bool(true)} }))"#;
ascjones marked this conversation as resolved.
Show resolved Hide resolved

// then
// message data is being decoded properly
ascjones marked this conversation as resolved.
Show resolved Hide resolved
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#"Ok(Map(Map { ident: Some("Switched"), map: {String("new_value"): Bool(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));

Ok(())
})
}
}
2 changes: 2 additions & 0 deletions src/cmd/extrinsics/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions src/cmd/extrinsics/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion src/cmd/extrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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};
Expand Down
43 changes: 42 additions & 1 deletion src/cmd/extrinsics/transcode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -203,6 +202,37 @@ 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
.metadata
.spec()
.messages()
ascjones marked this conversation as resolved.
Show resolved Hide resolved
.iter()
.filter(|x| msg_selector == x.selector().to_bytes())
.next()
.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_return(&self, name: &str, data: &mut &[u8]) -> Result<Value> {
let msg_spec = self
.find_message_spec(name)
Expand Down Expand Up @@ -391,4 +421,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(())
}
}
2 changes: 2 additions & 0 deletions src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 6 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -454,6 +454,9 @@ enum Command {
/// Call a contract
#[clap(name = "call")]
Call(CallCommand),
/// Decode a contract input data
#[clap(name = "decode")]
Decode(DecodeCommand),
}

fn main() {
Expand Down Expand Up @@ -511,6 +514,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(),
}
}

Expand Down