// 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 .
mod call;
mod events;
mod instantiate;
mod runtime_api;
mod transcode;
mod upload;
#[cfg(test)]
#[cfg(feature = "integration-tests")]
mod integration_tests;
use anyhow::{
anyhow,
Context,
Result,
};
use std::{
fs::File,
path::PathBuf,
};
use self::events::display_events;
use crate::{
crate_metadata::CrateMetadata,
name_value_println,
workspace::ManifestPath,
Verbosity,
VerbosityFlags,
};
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,
};
pub use upload::UploadCommand;
type Balance = u128;
type CodeHash = ::Hash;
type ContractAccount = ::AccountId;
type PairSigner = subxt::PairSigner;
type SignedExtra = subxt::PolkadotExtrinsicParams;
type RuntimeApi = runtime_api::api::RuntimeApi;
/// Arguments required for creating and sending an extrinsic to a substrate node.
#[derive(Clone, Debug, clap::Args)]
pub struct ExtrinsicOpts {
/// Path to the `Cargo.toml` of the contract.
#[clap(long, parse(from_os_str))]
manifest_path: Option,
/// Websockets url of a substrate node.
#[clap(
name = "url",
long,
parse(try_from_str),
default_value = "ws://localhost:9944"
)]
url: url::Url,
/// Secret key URI for the account deploying the contract.
#[clap(name = "suri", long, short)]
suri: String,
/// Password for the secret key.
#[clap(name = "password", long, short)]
password: Option,
#[clap(flatten)]
verbosity: VerbosityFlags,
/// Dry-run the extrinsic via rpc, instead of as an extrinsic. Chain state will not be mutated.
#[clap(long)]
dry_run: bool,
/// The maximum amount of balance that can be charged from the caller to pay for the storage
/// consumed.
#[clap(long, parse(try_from_str = parse_balance))]
storage_deposit_limit: Option,
}
impl ExtrinsicOpts {
pub fn signer(&self) -> Result {
sr25519::Pair::from_string(&self.suri, self.password.as_ref().map(String::as_ref))
.map_err(|_| anyhow::anyhow!("Secret string error"))
}
/// Returns the verbosity
pub fn verbosity(&self) -> Result {
TryFrom::try_from(&self.verbosity)
}
}
/// For a contract project with its `Cargo.toml` at the specified `manifest_path`, load the cargo
/// [`CrateMetadata`] along with the contract metadata [`ink_metadata::InkProject`].
pub fn load_metadata(
manifest_path: Option<&PathBuf>,
) -> Result<(CrateMetadata, ink_metadata::InkProject)> {
let manifest_path = ManifestPath::try_from(manifest_path)?;
let crate_metadata = CrateMetadata::collect(&manifest_path)?;
let path = crate_metadata.metadata_path();
if !path.exists() {
return Err(anyhow!(
"Metadata file not found. Try building with `cargo contract build`."
))
}
let file = File::open(&path)
.context(format!("Failed to open metadata file {}", path.display()))?;
let metadata: contract_metadata::ContractMetadata = serde_json::from_reader(file)
.context(format!(
"Failed to deserialize metadata file {}",
path.display()
))?;
let ink_metadata = serde_json::from_value(serde_json::Value::Object(metadata.abi))
.context(format!(
"Failed to deserialize ink project metadata from file {}",
path.display()
))?;
if let ink_metadata::MetadataVersioned::V3(ink_project) = ink_metadata {
Ok((crate_metadata, ink_project))
} else {
Err(anyhow!("Unsupported ink metadata version. Expected V1"))
}
}
/// Parse Rust style integer balance literals which can contain underscores.
fn parse_balance(input: &str) -> Result {
input
.replace('_', "")
.parse::()
.map_err(Into::into)
}
/// Create a new [`PairSigner`] from the given [`sr25519::Pair`].
pub fn pair_signer(pair: sr25519::Pair) -> PairSigner {
PairSigner::new(pair)
}
const STORAGE_DEPOSIT_KEY: &str = "Storage Deposit";
pub const EXEC_RESULT_MAX_KEY_COL_WIDTH: usize = STORAGE_DEPOSIT_KEY.len() + 1;
/// Print to stdout the fields of the result of a `instantiate` or `call` dry-run via RPC.
pub fn display_contract_exec_result(
result: &ContractResult,
) -> Result<()> {
let mut debug_message_lines = std::str::from_utf8(&result.debug_message)
.context("Error decoding UTF8 debug message bytes")?
.lines();
name_value_println!(
"Gas Consumed",
format!("{:?}", result.gas_consumed),
EXEC_RESULT_MAX_KEY_COL_WIDTH
);
name_value_println!(
"Gas Required",
format!("{:?}", result.gas_required),
EXEC_RESULT_MAX_KEY_COL_WIDTH
);
name_value_println!(
STORAGE_DEPOSIT_KEY,
format!("{:?}", result.storage_deposit),
EXEC_RESULT_MAX_KEY_COL_WIDTH
);
// print debug messages aligned, only first line has key
if let Some(debug_message) = debug_message_lines.next() {
name_value_println!(
"Debug Message",
format!("{}", debug_message),
EXEC_RESULT_MAX_KEY_COL_WIDTH
);
}
for debug_message in debug_message_lines {
name_value_println!(
"",
format!("{}", debug_message),
EXEC_RESULT_MAX_KEY_COL_WIDTH
);
}
Ok(())
}
/// Wait for the transaction to be included successfully into a block.
///
/// # Errors
///
/// If a runtime Module error occurs, this will only display the pallet and error indices. Dynamic
/// lookups of the actual error will be available once the following issue is resolved:
/// .
///
/// # Finality
///
/// Currently this will report success once the transaction is included in a block. In the future
/// there could be a flag to wait for finality before reporting success.
async fn wait_for_success_and_handle_error(
tx_progress: subxt::TransactionProgress<'_, T, RuntimeDispatchError, RuntimeEvent>,
) -> Result>
where
T: Config,
{
tx_progress
.wait_for_in_block()
.await?
.wait_for_success()
.await
.map_err(Into::into)
}