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,
HasModuleError as _,
};
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 = <DefaultConfig as Config>::Hash;
type ContractAccount = <DefaultConfig as Config>::AccountId;
type PairSigner = subxt::PairSigner<DefaultConfig, sp_core::sr25519::Pair>;
type SignedExtra = subxt::PolkadotExtrinsicParams<DefaultConfig>;
type RuntimeApi = runtime_api::api::RuntimeApi<DefaultConfig, SignedExtra>;
#[derive(Clone, Debug, clap::Args)]
pub struct ExtrinsicOpts {
#[clap(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
#[clap(
name = "url",
long,
parse(try_from_str),
default_value = "ws://localhost:9944"
)]
url: url::Url,
#[clap(name = "suri", long, short)]
suri: String,
#[clap(name = "password", long, short)]
password: Option<String>,
#[clap(flatten)]
verbosity: VerbosityFlags,
#[clap(long)]
dry_run: bool,
#[clap(long, parse(try_from_str = parse_balance))]
storage_deposit_limit: Option<Balance>,
}
impl ExtrinsicOpts {
pub fn signer(&self) -> Result<sr25519::Pair> {
sr25519::Pair::from_string(&self.suri, self.password.as_ref().map(String::as_ref))
.map_err(|_| anyhow::anyhow!("Secret string error"))
}
pub fn verbosity(&self) -> Result<Verbosity> {
TryFrom::try_from(&self.verbosity)
}
}
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"))
}
}
fn parse_balance(input: &str) -> Result<Balance> {
input
.replace('_', "")
.parse::<Balance>()
.map_err(Into::into)
}
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;
pub fn display_contract_exec_result<R>(
result: &ContractResult<R, Balance>,
) -> 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
);
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(())
}
async fn wait_for_success_and_handle_error<T>(
tx_progress: subxt::TransactionProgress<'_, T, RuntimeDispatchError, RuntimeEvent>,
) -> Result<subxt::TransactionEvents<'_, T, RuntimeEvent>>
where
T: Config,
{
tx_progress
.wait_for_in_block()
.await?
.wait_for_success()
.await
.map_err(Into::into)
}
async fn dry_run_error_details(
api: &RuntimeApi,
error: &RuntimeDispatchError,
) -> Result<String> {
let error = if let Some(error_data) = error.module_error_data() {
let details = api
.client
.metadata()
.error(error_data.pallet_index, error_data.error_index())?;
format!(
"ModuleError: {}::{}: {:?}",
details.pallet(),
details.error(),
details.description()
)
} else {
format!("{:?}", error)
};
Ok(error)
}