use crate::{
cli,
cli::{Output, SpellCheckParams, SpellProveParams},
spell::{
NormalizedSpell, ProveRequest, ProveSpellTx, ProveSpellTxImpl, adjust_coin_contents,
ensure_all_prev_txs_are_present, ensure_exact_app_binaries, from_strings,
read_private_inputs,
},
};
use anyhow::{Result, ensure};
use charms_app_runner::AppRunner;
use charms_client::{
CURRENT_VERSION,
tx::{Chain, Tx, by_txid},
};
use charms_data::{UtxoId, util};
use charms_lib::SPELL_VK;
use serde_json::json;
use std::{future::Future, io::Write, str::FromStr};
pub trait Check {
fn check(&self, params: SpellCheckParams) -> Result<()>;
}
pub trait Prove {
fn prove(&self, params: SpellProveParams) -> impl Future<Output = Result<()>>;
}
pub struct SpellCli {
pub app_runner: AppRunner,
}
impl SpellCli {
pub(crate) fn print_vk(&self, mock: bool) -> Result<()> {
#[cfg(feature = "prover")]
let is_prover = true;
#[cfg(not(feature = "prover"))]
let is_prover = false;
let json = match mock {
true => json!({
"mock": true,
"prover": is_prover,
"version": CURRENT_VERSION,
"vk": SPELL_VK.to_string(),
}),
false => json!({
"prover": is_prover,
"version": CURRENT_VERSION,
"vk": SPELL_VK.to_string(),
}),
};
println!("{}", json);
Ok(())
}
}
impl Prove for SpellCli {
async fn prove(&self, params: SpellProveParams) -> Result<()> {
let SpellProveParams {
spell,
payload,
output: format,
private_inputs,
beamed_from,
prev_txs,
app_bins,
change_address,
fee_rate,
chain,
mock,
collateral_utxo,
} = params;
let spell_prover = ProveSpellTxImpl::new(mock);
let collateral_utxo = collateral_utxo
.map(|utxo| UtxoId::from_str(&utxo))
.transpose()?;
ensure!(fee_rate >= 1.0, "fee rate must be >= 1.0");
let norm_spell: NormalizedSpell = serde_yaml::from_slice(&std::fs::read(spell)?)?;
let app_private_inputs = private_inputs
.map(|p| read_private_inputs(&p))
.transpose()?
.unwrap_or_default();
let tx_ins_beamed_source_utxos = beamed_from
.map(|s| serde_yaml::from_str(&s))
.transpose()?
.unwrap_or_default();
let prev_txs = from_strings(&prev_txs)?;
let binaries = cli::app::binaries_by_vk(&self.app_runner, app_bins)?;
let app_input = match binaries.is_empty() {
true => None,
false => Some(charms_data::AppInput {
app_binaries: binaries.clone(),
app_private_inputs: app_private_inputs.clone(),
}),
};
let mut prove_request = ProveRequest {
spell: norm_spell,
app_private_inputs,
tx_ins_beamed_source_utxos,
binaries,
prev_txs,
change_address,
fee_rate,
chain,
collateral_utxo,
};
adjust_coin_contents(&mut prove_request.spell, chain)?;
if payload {
ensure!(
charms_client::is_correct(
&prove_request.spell,
&prove_request.prev_txs,
app_input,
SPELL_VK,
&prove_request.tx_ins_beamed_source_utxos,
)?,
"spell verification failed"
);
match format {
Output::Json => println!("{}", serde_json::to_string(&prove_request)?),
Output::Cbor => {
let bytes = util::write(&prove_request)?;
std::io::stdout().write_all(&bytes)?;
}
}
return Ok(());
}
let transactions = spell_prover.prove_spell_tx(prove_request).await?;
match chain {
Chain::Bitcoin => {
let hex_txs: Vec<Tx> = transactions;
println!("{}", serde_json::to_string(&hex_txs)?);
}
Chain::Cardano => {
let Some(tx) = transactions.into_iter().next() else {
unreachable!()
};
let tx_draft = json!({
"type": "Witnessed Tx ConwayEra",
"description": "Ledger Cddl Format",
"cborHex": tx.hex(),
});
println!("{}", tx_draft);
}
}
Ok(())
}
}
impl Check for SpellCli {
#[tracing::instrument(level = "debug", skip(self, spell, app_bins))]
fn check(
&self,
SpellCheckParams {
spell,
private_inputs,
beamed_from,
app_bins,
prev_txs,
chain,
mock,
}: SpellCheckParams,
) -> Result<()> {
let mut norm_spell: NormalizedSpell = serde_yaml::from_slice(&std::fs::read(spell)?)?;
let app_private_inputs = private_inputs
.map(|p| read_private_inputs(&p))
.transpose()?
.unwrap_or_default();
let tx_ins_beamed_source_utxos = beamed_from
.map(|s| serde_yaml::from_str(&s))
.transpose()?
.unwrap_or_default();
let prev_txs = prev_txs.unwrap_or_else(|| vec![]);
let prev_txs = from_strings(&prev_txs)?;
adjust_coin_contents(&mut norm_spell, chain)?;
ensure_all_prev_txs_are_present(
&norm_spell,
&tx_ins_beamed_source_utxos,
&by_txid(&prev_txs),
)?;
let binaries = cli::app::binaries_by_vk(&self.app_runner, app_bins)?;
let prev_spells = charms_client::prev_spells(&prev_txs, SPELL_VK, &norm_spell)?;
let charms_tx = charms_client::to_tx(
&norm_spell,
&prev_spells,
&tx_ins_beamed_source_utxos,
&prev_txs,
);
ensure_exact_app_binaries(&norm_spell, &app_private_inputs, &charms_tx, &binaries)?;
let app_input = match binaries.is_empty() {
true => None,
false => Some(charms_data::AppInput {
app_binaries: binaries.clone(),
app_private_inputs: app_private_inputs.clone(),
}),
};
ensure!(
charms_client::is_correct(
&norm_spell,
&prev_txs,
app_input,
SPELL_VK,
&tx_ins_beamed_source_utxos,
)?,
"spell verification failed"
);
let cycles_spent = self.app_runner.run_all(
&binaries,
&charms_tx,
&norm_spell.app_public_inputs,
&app_private_inputs,
)?;
eprintln!("cycles spent: {:?}", cycles_spent);
Ok(())
}
}