Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 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
179 changes: 179 additions & 0 deletions src/cmd/decode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// 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(())
}
}

#[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(env!("CARGO_PKG_NAME")).unwrap();
cmd.current_dir(path).arg("contract");
cmd
}
Copy link
Contributor

@athei athei Apr 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to compile cargo contract here. So you essentially want to run cargo run --release -- <args> here. You can get the correct directory where to run it by reading CARGO_MANIFEST_PATH env var.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that assert_cmd assumes it is an integration test (therefore the binary would already have been built).

I've moved this test to integration test to see if it works now.


#[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;
}
}
}"#;

// 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#"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));

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
66 changes: 65 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,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)
Expand Down Expand Up @@ -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(())
}
}
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 @@ -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() {
Expand Down Expand Up @@ -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(),
}
}

Expand Down