Skip to content

Commit

Permalink
Add --output-json for call, instantiate & upload commands (#722)
Browse files Browse the repository at this point in the history
* seralisable models for contract call

* displaying events from the object

* displaying calls results and fixing serialisation

* fix typos

* json output when calling a contract

* json output for dry-run

* display json for instantiate function and fix typos

* renaming contract_hash to contract for consistency

* serialise storage deposit changes

* printing error jsons for InstantiateCommand & fmt

* json formatting generic errors

* Update CHANGELOG.md

* code refactoring

* explicit default initialisation of fields

* cargo fmt

* set correct verbosity flags

* Suggested refactorings for PR #722 (`--output-json`) (#730)

* Convert subxt error to custom ErrorVariant

* Refactor displaying of extrinsic result events

* dispalying extrinsic errors, refactoring

* json output for upload command

* wrapper for generic errors at top level

* cargo fmt

* skip serializing for hex bytes

* gitignore vscode folder

* Suggested unification of error handling for #722 (#736)

* Move extrinsics error to its own file, update instantiate

* Update upload to unify errors

* Update call to unify errors

* Handle json errors at the top level

* refactoring Andrew changes

Co-authored-by: Andrew Jones <[email protected]>
  • Loading branch information
German and ascjones authored Sep 12, 2022
1 parent a1307e2 commit 8ca0b03
Show file tree
Hide file tree
Showing 12 changed files with 667 additions and 248 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target
**/*.rs.bk
.vscode
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
- `--output-json` support for `call`, `instantiate` and `upload` commands - [#722](https://github.com/paritytech/cargo-contract/pull/722)

## [2.0.0-alpha.2] - 2022-09-02

### Fixed
### Fixed
- Sync version of transcode crate to fix metadata parsing - [#723](https://githubcom/paritytech/cargo-contract/pull/723)
- Fix numbering of steps during `build` - [#715](https://githubcom/paritytech/cargo-contract/pull/715)
- Fix numbering of steps during `build` - [#715](https://github.com/paritytech/cargo-contract/pull/715)

## [2.0.0-alpha.1] - 2022-08-24

Expand Down
140 changes: 104 additions & 36 deletions crates/cargo-contract/src/cmd/extrinsics/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

use super::{
display_contract_exec_result,
display_events,
error_details,
parse_balance,
prompt_confirm_tx,
state_call,
Expand All @@ -31,7 +29,13 @@ use super::{
PairSigner,
MAX_KEY_COL_WIDTH,
};

use crate::{
cmd::extrinsics::{
display_contract_exec_result_debug,
events::DisplayEvents,
ErrorVariant,
},
name_value_println,
DEFAULT_KEY_COL_WIDTH,
};
Expand All @@ -40,8 +44,12 @@ use anyhow::{
Result,
};

use pallet_contracts_primitives::ContractExecResult;
use pallet_contracts_primitives::{
ContractExecResult,
StorageDeposit,
};
use scale::Encode;
use transcode::Value;

use std::fmt::Debug;
use subxt::{
Expand Down Expand Up @@ -70,10 +78,17 @@ pub struct CallCommand {
/// The value to be transferred as part of the call.
#[clap(name = "value", long, parse(try_from_str = parse_balance), default_value = "0")]
value: Balance,
/// Export the call output in JSON format.
#[clap(long, conflicts_with = "verbose")]
output_json: bool,
}

impl CallCommand {
pub fn run(&self) -> Result<()> {
pub fn is_json(&self) -> bool {
self.output_json
}

pub fn run(&self) -> Result<(), ErrorVariant> {
let crate_metadata = CrateMetadata::from_manifest_path(
self.extrinsic_opts.manifest_path.as_ref(),
)?;
Expand All @@ -94,27 +109,36 @@ impl CallCommand {
Ok(ref ret_val) => {
let value = transcoder
.decode_return(&self.message, &mut &ret_val.data.0[..])?;
name_value_println!(
"Result",
String::from("Success!"),
DEFAULT_KEY_COL_WIDTH
);
name_value_println!(
"Reverted",
format!("{:?}", ret_val.did_revert()),
DEFAULT_KEY_COL_WIDTH
);
name_value_println!(
"Data",
format!("{}", value),
DEFAULT_KEY_COL_WIDTH
);
display_contract_exec_result::<_, DEFAULT_KEY_COL_WIDTH>(&result)
let dry_run_result = CallDryRunResult {
result: String::from("Success!"),
reverted: ret_val.did_revert(),
data: value,
gas_consumed: result.gas_consumed,
gas_required: result.gas_required,
storage_deposit: result.storage_deposit.clone(),
};
if self.output_json {
println!("{}", dry_run_result.to_json()?);
} else {
dry_run_result.print();
display_contract_exec_result_debug::<_, DEFAULT_KEY_COL_WIDTH>(
&result,
)?;
}
Ok(())
}
Err(ref err) => {
let err = error_details(err, &client.metadata())?;
name_value_println!("Result", err, MAX_KEY_COL_WIDTH);
display_contract_exec_result::<_, MAX_KEY_COL_WIDTH>(&result)
let metadata = client.metadata();
let object = ErrorVariant::from_dispatch_error(err, &metadata)?;
if self.output_json {
Err(object)
} else {
name_value_println!("Result", object, MAX_KEY_COL_WIDTH);
display_contract_exec_result::<_, MAX_KEY_COL_WIDTH>(
&result,
)?;
Ok(())
}
}
}
} else {
Expand Down Expand Up @@ -148,7 +172,7 @@ impl CallCommand {
data: Vec<u8>,
signer: &PairSigner,
transcoder: &ContractMessageTranscoder,
) -> Result<()> {
) -> Result<(), ErrorVariant> {
tracing::debug!("calling contract {:?}", self.contract);

let gas_limit = self
Expand Down Expand Up @@ -177,12 +201,17 @@ impl CallCommand {

let result = submit_extrinsic(client, &call, signer).await?;

display_events(
&result,
transcoder,
&client.metadata(),
&self.extrinsic_opts.verbosity()?,
)
let display_events =
DisplayEvents::from_events(&result, transcoder, &client.metadata())?;

let output = if self.output_json {
display_events.to_json()?
} else {
display_events.display_events(self.extrinsic_opts.verbosity()?)
};
println!("{}", output);

Ok(())
}

/// Dry run the call before tx submission. Returns the gas required estimate.
Expand All @@ -202,19 +231,27 @@ impl CallCommand {
}
}
}
super::print_dry_running_status(&self.message);
if !self.output_json {
super::print_dry_running_status(&self.message);
}
let call_result = self.call_dry_run(data, signer).await?;
match call_result.result {
Ok(_) => {
super::print_gas_required_success(call_result.gas_required);
if !self.output_json {
super::print_gas_required_success(call_result.gas_required);
}
let gas_limit = self.gas_limit.unwrap_or(call_result.gas_required);
Ok(gas_limit)
}
Err(ref err) => {
let err = error_details(err, &client.metadata())?;
name_value_println!("Result", err, MAX_KEY_COL_WIDTH);
display_contract_exec_result::<_, MAX_KEY_COL_WIDTH>(&call_result)?;
Err(anyhow!("Pre-submission dry-run failed. Use --skip-dry-run to skip this step."))
let object = ErrorVariant::from_dispatch_error(err, &client.metadata())?;
if self.output_json {
Err(anyhow!("{}", serde_json::to_string_pretty(&object)?))
} else {
name_value_println!("Result", object, MAX_KEY_COL_WIDTH);
display_contract_exec_result::<_, MAX_KEY_COL_WIDTH>(&call_result)?;
Err(anyhow!("Pre-submission dry-run failed. Use --skip-dry-run to skip this step."))
}
}
}
}
Expand All @@ -232,3 +269,34 @@ pub struct CallRequest {
storage_deposit_limit: Option<Balance>,
input_data: Vec<u8>,
}

/// Result of the contract call
#[derive(serde::Serialize)]
pub struct CallDryRunResult {
/// Result of a dry run
pub result: String,
/// Was the operation reverted
pub reverted: bool,
pub data: Value,
pub gas_consumed: u64,
pub gas_required: u64,
/// Storage deposit after the operation
pub storage_deposit: StorageDeposit<Balance>,
}

impl CallDryRunResult {
/// Returns a result in json format
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string_pretty(self)?)
}

pub fn print(&self) {
name_value_println!("Result", self.result, DEFAULT_KEY_COL_WIDTH);
name_value_println!(
"Reverted",
format!("{:?}", self.reverted),
DEFAULT_KEY_COL_WIDTH
);
name_value_println!("Data", format!("{:?}", self.data), DEFAULT_KEY_COL_WIDTH);
}
}
109 changes: 109 additions & 0 deletions crates/cargo-contract/src/cmd/extrinsics/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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::fmt::Display;
use subxt::ext::sp_runtime::DispatchError;

#[derive(serde::Serialize)]
pub enum ErrorVariant {
#[serde(rename = "module_error")]
Module(ModuleError),
#[serde(rename = "generic_error")]
Generic(GenericError),
}

impl From<subxt::Error> for ErrorVariant {
fn from(error: subxt::Error) -> Self {
match error {
subxt::Error::Runtime(subxt::error::DispatchError::Module(module_err)) => {
ErrorVariant::Module(ModuleError {
pallet: module_err.pallet.clone(),
error: module_err.error.clone(),
docs: module_err.description,
})
}
err => ErrorVariant::Generic(GenericError::from_message(err.to_string())),
}
}
}

impl From<anyhow::Error> for ErrorVariant {
fn from(error: anyhow::Error) -> Self {
Self::Generic(GenericError::from_message(format!("{}", error)))
}
}

impl From<&str> for ErrorVariant {
fn from(err: &str) -> Self {
Self::Generic(GenericError::from_message(err.to_owned()))
}
}

#[derive(serde::Serialize)]
pub struct ModuleError {
pub pallet: String,
pub error: String,
pub docs: Vec<String>,
}

#[derive(serde::Serialize)]
pub struct GenericError {
error: String,
}

impl GenericError {
pub fn from_message(error: String) -> Self {
GenericError { error }
}
}

impl ErrorVariant {
pub fn from_dispatch_error(
error: &DispatchError,
metadata: &subxt::Metadata,
) -> anyhow::Result<ErrorVariant> {
match error {
DispatchError::Module(err) => {
let details = metadata.error(err.index, err.error)?;
Ok(ErrorVariant::Module(ModuleError {
pallet: details.pallet().to_owned(),
error: details.error().to_owned(),
docs: details.docs().to_owned(),
}))
}
err => {
Ok(ErrorVariant::Generic(GenericError::from_message(format!(
"DispatchError: {:?}",
err
))))
}
}
}
}

impl Display for ErrorVariant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorVariant::Module(err) => {
f.write_fmt(format_args!(
"ModuleError: {}::{}: {:?}",
err.pallet, err.error, err.docs
))
}
ErrorVariant::Generic(err) => write!(f, "{}", err.error),
}
}
}
Loading

0 comments on commit 8ca0b03

Please sign in to comment.