use crate::{
chain::quantus_subxt,
cli::exercise::{report::Report, runner::ExerciseCtx},
error::{QuantusError, Result},
exercise_step,
};
pub async fn run(ctx: &mut ExerciseCtx, report: &mut Report, phase: &str) -> Result<()> {
let post_upgrade = phase.starts_with("post-upgrade:");
exercise_step!(report, phase, "runtime_version", runtime_version(ctx, post_upgrade));
exercise_step!(report, phase, "metadata_pallets", metadata_pallets(ctx));
exercise_step!(report, phase, "chain_properties", chain_properties(ctx));
exercise_step!(report, phase, "latest_block_and_events", latest_block_and_events(ctx));
exercise_step!(report, phase, "treasury_info", treasury_info(ctx));
exercise_step!(report, phase, "high_security_status", high_security_status(ctx));
exercise_step!(report, phase, "scheduler_agenda", scheduler_agenda(ctx));
exercise_step!(report, phase, "account_balances", account_balances(ctx));
Ok(())
}
async fn runtime_version(ctx: &ExerciseCtx, post_upgrade: bool) -> Result<String> {
let (spec, tx) = ctx.client.get_runtime_version().await?;
let compatible = crate::config::is_runtime_compatible(spec, tx);
if compatible {
return Ok(format!("spec {spec} / tx {tx}, compatible with CLI bindings"));
}
if post_upgrade {
if crate::config::COMPATIBLE_RUNTIMES.iter().any(|r| r.transaction_version == tx) {
return Ok(format!(
"spec {spec} / tx {tx}: candidate runtime not in the compatibility table \
(expected after an upgrade); tx version unchanged"
));
}
return Err(QuantusError::Generic(format!(
"candidate runtime changed transaction_version to {tx}, which is not in the \
CLI compatibility table; signed extrinsics from this CLI are invalid against it"
)));
}
Err(QuantusError::Generic(format!(
"runtime spec {spec} / tx {tx} is not in the CLI compatibility table"
)))
}
async fn metadata_pallets(ctx: &ExerciseCtx) -> Result<String> {
let metadata = ctx.client.client().metadata();
let pallets: Vec<String> = metadata.pallets().map(|p| p.name().to_string()).collect();
if pallets.is_empty() {
return Err(QuantusError::Generic("metadata contains no pallets".to_string()));
}
for required in ["System", "Balances", "ReversibleTransfers", "Multisig", "TechReferenda"] {
if !pallets.iter().any(|p| p == required) {
return Err(QuantusError::Generic(format!("pallet {required} missing from metadata")));
}
}
Ok(format!("{} pallets exposed: {}", pallets.len(), pallets.join(", ")))
}
async fn chain_properties(ctx: &ExerciseCtx) -> Result<String> {
let (symbol, decimals) = crate::cli::send::get_chain_properties(&ctx.client).await?;
Ok(format!("token {symbol} with {decimals} decimals"))
}
async fn latest_block_and_events(ctx: &ExerciseCtx) -> Result<String> {
let latest_hash = ctx.client.get_latest_block().await?;
let block = ctx.client.client().blocks().at(latest_hash).await?;
let number = block.number();
let extrinsics = block.extrinsics().await?;
let events = ctx.client.client().events().at(latest_hash).await?;
let mut decoded = 0usize;
for event in events.iter() {
event?;
decoded += 1;
}
Ok(format!("block #{number}: {} extrinsics, {decoded} events decoded", extrinsics.len()))
}
async fn treasury_info(ctx: &ExerciseCtx) -> Result<String> {
let latest = ctx.client.get_latest_block().await?;
let storage_at = ctx.client.client().storage().at(latest);
let account_addr = quantus_subxt::api::storage().treasury_pallet().treasury_account();
let account = storage_at
.fetch(&account_addr)
.await?
.ok_or_else(|| QuantusError::Generic("treasury account not set".to_string()))?;
let portion_addr = quantus_subxt::api::storage().treasury_pallet().treasury_portion();
let portion = storage_at.fetch(&portion_addr).await?.map(|p| p.0).unwrap_or(0);
let balance_addr = quantus_subxt::api::storage().system().account(account);
let info = storage_at.fetch_or_default(&balance_addr).await?;
Ok(format!(
"portion {:.2}%, free balance {} raw units",
portion as f64 / 10_000.0,
info.data.free
))
}
async fn high_security_status(ctx: &ExerciseCtx) -> Result<String> {
let root = crate::cli::exercise::runner::account_id_of(&ctx.root)?;
let addr = quantus_subxt::api::storage()
.reversible_transfers()
.high_security_accounts(root);
let latest = ctx.client.get_latest_block().await?;
let value = ctx.client.client().storage().at(latest).fetch(&addr).await?;
Ok(match value {
Some(_) => "root account has high-security enabled".to_string(),
None => "root account has high-security disabled (expected default)".to_string(),
})
}
async fn scheduler_agenda(ctx: &ExerciseCtx) -> Result<String> {
let timestamp = crate::cli::scheduler::get_last_processed_timestamp(&ctx.client).await?;
Ok(format!("scheduler last processed timestamp: {timestamp:?}"))
}
async fn account_balances(ctx: &ExerciseCtx) -> Result<String> {
let balance = ctx.free_balance(&ctx.root_ss58).await?;
if balance == 0 {
return Err(QuantusError::Generic(
"the root funding account has zero free balance".to_string(),
));
}
Ok(format!("root account free balance: {balance} raw units"))
}