use std::collections::BTreeMap;
use std::path::PathBuf;
use clap::Parser;
use miden_client::keystore::Keystore;
use miden_client::vm::AdviceInputs;
use miden_client::{Client, Felt, Word};
use serde::{Deserialize, Deserializer, Serialize, de};
use crate::errors::CliError;
use crate::utils::get_input_acc_id_by_prefix_or_default;
#[derive(Debug, Clone, Parser)]
#[command(about = "Execute the specified program against the specified account")]
pub struct ExecCmd {
#[arg(short = 'a', long = "account")]
account_id: Option<String>,
#[arg(long, short)]
script_path: String,
#[rustfmt::skip]
#[allow(clippy::doc_link_with_quotes)]
#[arg(long, short)]
#[clap(verbatim_doc_comment)]
inputs_path: Option<String>,
#[arg(long, default_value_t = false)]
hex_words: bool,
}
impl ExecCmd {
pub async fn execute<AUTH: Keystore + Sync + 'static>(
&self,
mut client: Client<AUTH>,
) -> Result<(), CliError> {
let script_path = PathBuf::from(&self.script_path);
if !script_path.exists() {
return Err(CliError::Exec(
"error with the program file".to_string().into(),
format!("the program file at path {} does not exist", self.script_path),
));
}
let program = std::fs::read_to_string(script_path)?;
let account_id =
get_input_acc_id_by_prefix_or_default(&client, self.account_id.clone()).await?;
let inputs = match &self.inputs_path {
Some(input_file) => {
let input_file = PathBuf::from(input_file);
if !input_file.exists() {
return Err(CliError::Exec(
"error with the input file".to_string().into(),
format!("the input file at path {} does not exist", input_file.display()),
));
}
let input_data = std::fs::read_to_string(input_file)?;
deserialize_tx_inputs(&input_data)?
},
None => vec![],
};
let advice_inputs = AdviceInputs::default().with_map(inputs);
let tx_script = client.code_builder().compile_tx_script(&program)?;
let result = client
.execute_program(account_id, tx_script, advice_inputs, BTreeMap::new())
.await;
match result {
Ok(output_stack) => {
println!("Program executed successfully");
println!("Output stack:");
self.print_stack(output_stack);
Ok(())
},
Err(err) => Err(CliError::Exec(err.into(), "error executing the program".to_string())),
}
}
fn print_stack(&self, stack: [Felt; 16]) {
if self.hex_words {
for i in 0..4 {
let word_idx = i * 4;
let word_idx_end = word_idx + 3;
if word_idx == 12 {
print!("└── {word_idx:2} - {word_idx_end:2}: ");
} else {
print!("├── {word_idx:2} - {word_idx_end:2}: ");
}
let word: [Felt; 4] =
stack[word_idx..=word_idx_end].try_into().expect("Length should be 4");
println!("{:?} ({})", word, Word::from(word).to_hex());
}
} else {
for (i, value) in stack.iter().enumerate() {
if i == 15 {
println!("└── {i:2}: {value}");
} else {
println!("├── {i:2}: {value}");
}
}
}
}
}
#[derive(Serialize, Deserialize)]
struct CliTxInput {
key: String,
#[serde(deserialize_with = "string_to_u64")]
values: Vec<u64>,
}
#[derive(Serialize, Deserialize)]
struct CliTxInputs {
inputs: Vec<CliTxInput>,
}
impl IntoIterator for CliTxInputs {
type Item = CliTxInput;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.inputs.into_iter()
}
}
fn string_to_u64<'de, D>(deserializer: D) -> Result<Vec<u64>, D::Error>
where
D: Deserializer<'de>,
{
Vec::<String>::deserialize(deserializer)?
.into_iter()
.map(|a| a.parse::<u64>())
.collect::<Result<Vec<u64>, _>>()
.map_err(|_| {
de::Error::custom(
"invalid type: expected u64 in between parentheses. For example: values = [\"13\", \"9\"]",
)
})
}
fn deserialize_tx_inputs(serialized: &str) -> Result<Vec<(Word, Vec<Felt>)>, CliError> {
let cli_inputs: CliTxInputs = toml::from_str(serialized).map_err(|err| {
CliError::Exec(
"error deserializing transaction inputs".into(),
format!("failed to parse input data: {err}"),
)
})?;
cli_inputs
.into_iter()
.map(|input| {
let word = Word::try_from(input.key).map_err(|err| err.to_string())?;
let felts = input.values.into_iter().map(Felt::new).collect();
Ok((word, felts))
})
.collect::<Result<Vec<_>, _>>()
.map_err(|err| CliError::Exec("error deserializing transaction inputs".into(), err))
}