From 9c608ad9f1abd864aada2cd8290479ce91292f1a Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 28 Oct 2022 15:14:14 +0200 Subject: [PATCH] Refactor cli's crate; split code into a hierarchy of modules. This follows a simple convention: - `main.rs` contains as little as possible and delegates both data-types definitions and command executions to sub-modules. - modules are named after their respective commands. For sub-commands, - Each command module can be in one of two forms: - Either it is a leaf command, and it then contains an `Args` struct that defines the command arguments; and a function `exec` when outlines the execution logic. - Or, it is a group command with multiple sub-commands. In which case the module defines a `Cmd` struct encapsulating all sub-commands; and also an `exec` function which simply dispatches the logic to sub-functions. --- This commit also removes the `dev` command which is currently unused. The rationale being: if it's not there, it's not there. --- crates/cli/src/args.rs | 129 ----------- crates/cli/src/cmd/build.rs | 44 ++++ crates/cli/src/cmd/check.rs | 46 ++++ crates/cli/src/cmd/mod.rs | 5 + crates/cli/src/cmd/new.rs | 20 ++ crates/cli/src/cmd/tx/mod.rs | 15 ++ crates/cli/src/cmd/tx/simulate.rs | 125 ++++++++++ crates/cli/src/cmd/uplc/eval.rs | 75 ++++++ crates/cli/src/cmd/uplc/flat.rs | 83 +++++++ crates/cli/src/cmd/uplc/fmt.rs | 30 +++ crates/cli/src/cmd/uplc/mod.rs | 24 ++ crates/cli/src/cmd/uplc/unflat.rs | 61 +++++ crates/cli/src/lib.rs | 2 - crates/cli/src/main.rs | 374 +++--------------------------- 14 files changed, 556 insertions(+), 477 deletions(-) delete mode 100644 crates/cli/src/args.rs create mode 100644 crates/cli/src/cmd/build.rs create mode 100644 crates/cli/src/cmd/check.rs create mode 100644 crates/cli/src/cmd/mod.rs create mode 100644 crates/cli/src/cmd/new.rs create mode 100644 crates/cli/src/cmd/tx/mod.rs create mode 100644 crates/cli/src/cmd/tx/simulate.rs create mode 100644 crates/cli/src/cmd/uplc/eval.rs create mode 100644 crates/cli/src/cmd/uplc/flat.rs create mode 100644 crates/cli/src/cmd/uplc/fmt.rs create mode 100644 crates/cli/src/cmd/uplc/mod.rs create mode 100644 crates/cli/src/cmd/uplc/unflat.rs delete mode 100644 crates/cli/src/lib.rs diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs deleted file mode 100644 index c6bf8bb0..00000000 --- a/crates/cli/src/args.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::path::PathBuf; - -use clap::{Parser, Subcommand}; - -/// Cardano smart contract toolchain -#[derive(Parser)] -#[clap(version, about, long_about = None)] -#[clap(propagate_version = true)] -pub enum Args { - /// Build an aiken project - Build { - /// Path to project - #[clap(short, long)] - directory: Option, - }, - /// Typecheck a project project - Check { - /// Path to project - #[clap(short, long)] - directory: Option, - }, - /// Start a development server - Dev, - /// Create a new aiken project - New { - /// Project name - name: PathBuf, - }, - /// A subcommand for working with transactions - #[clap(subcommand)] - Tx(TxCommand), - /// A subcommand for working with Untyped Plutus Core - #[clap(subcommand)] - Uplc(UplcCommand), -} - -/// Commands for working with transactions -#[derive(Subcommand)] -pub enum TxCommand { - /// Simulate a transaction by evaluating it's script - Simulate { - /// A file containing cbor hex for a transaction - input: PathBuf, - - /// Toggle whether input is raw cbor or a hex string - #[clap(short, long)] - cbor: bool, - - /// A file containing cbor hex for the raw inputs - raw_inputs: PathBuf, - - /// A file containing cbor hex for the raw outputs - raw_outputs: PathBuf, - - /// Time between each slot - #[clap(short, long, default_value_t = 1000)] - slot_length: u32, - - /// Time of shelley hardfork - #[clap(long, default_value_t = 1596059091000)] - zero_time: u64, - - /// Slot number at the start of the shelley hardfork - #[clap(long, default_value_t = 4492800)] - zero_slot: u64, - }, -} - -/// Commands for working with Untyped Plutus Core -#[derive(Subcommand)] -pub enum UplcCommand { - /// Evaluate an Untyped Plutus Core program - Eval { - script: PathBuf, - - #[clap(short, long)] - flat: bool, - - /// Arguments to pass to the uplc program - args: Vec, - }, - /// Encode textual Untyped Plutus Core to flat bytes - Flat { - /// Textual Untyped Plutus Core file - input: PathBuf, - - /// Output file name - #[clap(short, long)] - out: Option, - - /// Print output instead of saving to file - #[clap(short, long)] - print: bool, - - #[clap(short, long)] - cbor_hex: bool, - }, - /// Format an Untyped Plutus Core program - Fmt { - /// Textual Untyped Plutus Core file - input: PathBuf, - - /// Print output instead of saving to file - #[clap(short, long)] - print: bool, - }, - /// Decode flat bytes to textual Untyped Plutus Core - Unflat { - /// Flat encoded Untyped Plutus Core file - input: PathBuf, - - /// Output file name - #[clap(short, long)] - out: Option, - - /// Print output instead of saving to file - #[clap(short, long)] - print: bool, - - #[clap(short, long)] - cbor_hex: bool, - }, -} - -impl Default for Args { - fn default() -> Self { - Self::parse() - } -} diff --git a/crates/cli/src/cmd/build.rs b/crates/cli/src/cmd/build.rs new file mode 100644 index 00000000..51e974ea --- /dev/null +++ b/crates/cli/src/cmd/build.rs @@ -0,0 +1,44 @@ +use miette::IntoDiagnostic; +use project::{config::Config, Project}; +use std::env; +use std::path::PathBuf; + +#[derive(clap::Args)] +/// Build an Aiken project at the given working directory. +pub struct Args { + /// Path to project + #[clap(short, long)] + directory: Option, +} + +pub fn exec(Args { directory }: Args) -> miette::Result<()> { + let project_path = if let Some(d) = directory { + d + } else { + env::current_dir().into_diagnostic()? + }; + + let config = Config::load(project_path.clone()).into_diagnostic()?; + + let mut project = Project::new(config, project_path); + + let build_result = project.build(); + + let warning_count = project.warnings.len(); + + for warning in project.warnings { + warning.report() + } + + if let Err(err) = build_result { + err.report(); + + miette::bail!( + "failed: {} error(s), {warning_count} warning(s)", + err.total(), + ); + }; + + println!("finished with {warning_count} warning(s)"); + return Ok(()); +} diff --git a/crates/cli/src/cmd/check.rs b/crates/cli/src/cmd/check.rs new file mode 100644 index 00000000..0b20e975 --- /dev/null +++ b/crates/cli/src/cmd/check.rs @@ -0,0 +1,46 @@ +use miette::IntoDiagnostic; +use project::{config::Config, Project}; +use std::env; +use std::path::PathBuf; + +// TODO: Refactor this to remove logic duplication with the 'build command' + +#[derive(clap::Args)] +/// Typecheck a project project +pub struct Args { + /// Path to project + #[clap(short, long)] + directory: Option, +} + +pub fn exec(Args { directory }: Args) -> miette::Result<()> { + let project_path = if let Some(d) = directory { + d + } else { + env::current_dir().into_diagnostic()? + }; + + let config = Config::load(project_path.clone()).into_diagnostic()?; + + let mut project = Project::new(config, project_path); + + let build_result = project.check(); + + let warning_count = project.warnings.len(); + + for warning in project.warnings { + warning.report() + } + + if let Err(err) = build_result { + err.report(); + + miette::bail!( + "failed: {} error(s), {warning_count} warning(s)", + err.total(), + ); + }; + + println!("finished with {warning_count} warning(s)"); + return Ok(()); +} diff --git a/crates/cli/src/cmd/mod.rs b/crates/cli/src/cmd/mod.rs new file mode 100644 index 00000000..f7fa2e10 --- /dev/null +++ b/crates/cli/src/cmd/mod.rs @@ -0,0 +1,5 @@ +pub mod build; +pub mod check; +pub mod new; +pub mod tx; +pub mod uplc; diff --git a/crates/cli/src/cmd/new.rs b/crates/cli/src/cmd/new.rs new file mode 100644 index 00000000..920aac1b --- /dev/null +++ b/crates/cli/src/cmd/new.rs @@ -0,0 +1,20 @@ +use miette::IntoDiagnostic; +use std::fs; +use std::path::PathBuf; + +#[derive(clap::Args)] +/// Create a new Aiken project +pub struct Args { + /// Project name + name: PathBuf, +} + +pub fn exec(Args { name }: Args) -> miette::Result<()> { + if !name.exists() { + fs::create_dir_all(name.join("lib")).into_diagnostic()?; + fs::create_dir_all(name.join("policies")).into_diagnostic()?; + fs::create_dir_all(name.join("scripts")).into_diagnostic()?; + } + + return Ok(()); +} diff --git a/crates/cli/src/cmd/tx/mod.rs b/crates/cli/src/cmd/tx/mod.rs new file mode 100644 index 00000000..e2d63a97 --- /dev/null +++ b/crates/cli/src/cmd/tx/mod.rs @@ -0,0 +1,15 @@ +pub mod simulate; + +use clap::Subcommand; + +/// Commands for working with transactions +#[derive(Subcommand)] +pub enum Cmd { + Simulate(simulate::Args), +} + +pub fn exec(cmd: Cmd) -> miette::Result<()> { + match cmd { + Cmd::Simulate(args) => simulate::exec(args), + } +} diff --git a/crates/cli/src/cmd/tx/simulate.rs b/crates/cli/src/cmd/tx/simulate.rs new file mode 100644 index 00000000..380a88e0 --- /dev/null +++ b/crates/cli/src/cmd/tx/simulate.rs @@ -0,0 +1,125 @@ +use miette::IntoDiagnostic; +use pallas_primitives::{ + babbage::{TransactionInput, TransactionOutput}, + Fragment, +}; +use pallas_traverse::{Era, MultiEraTx}; +use std::fs; +use std::path::PathBuf; +use uplc::{ + machine::cost_model::ExBudget, + tx::{ + self, + script_context::{ResolvedInput, SlotConfig}, + }, +}; + +#[derive(clap::Args)] +/// Simulate a transaction by evaluating it's script +pub struct Args { + /// A file containing cbor hex for a transaction + input: PathBuf, + + /// Toggle whether input is raw cbor or a hex string + #[clap(short, long)] + cbor: bool, + + /// A file containing cbor hex for the raw inputs + raw_inputs: PathBuf, + + /// A file containing cbor hex for the raw outputs + raw_outputs: PathBuf, + + /// Time between each slot + #[clap(short, long, default_value_t = 1000)] + slot_length: u32, + + /// Time of shelley hardfork + #[clap(long, default_value_t = 1596059091000)] + zero_time: u64, + + /// Slot number at the start of the shelley hardfork + #[clap(long, default_value_t = 4492800)] + zero_slot: u64, +} + +pub fn exec( + Args { + input, + cbor, + raw_inputs, + raw_outputs, + slot_length, + zero_time, + zero_slot, + }: Args, +) -> miette::Result<()> { + let (tx_bytes, inputs_bytes, outputs_bytes) = if cbor { + ( + fs::read(input).into_diagnostic()?, + fs::read(raw_inputs).into_diagnostic()?, + fs::read(raw_outputs).into_diagnostic()?, + ) + } else { + let cbor_hex = fs::read_to_string(input).into_diagnostic()?; + let inputs_hex = fs::read_to_string(raw_inputs).into_diagnostic()?; + let outputs_hex = fs::read_to_string(raw_outputs).into_diagnostic()?; + + ( + hex::decode(cbor_hex.trim()).into_diagnostic()?, + hex::decode(inputs_hex.trim()).into_diagnostic()?, + hex::decode(outputs_hex.trim()).into_diagnostic()?, + ) + }; + + let tx = MultiEraTx::decode(Era::Babbage, &tx_bytes) + .or_else(|_| MultiEraTx::decode(Era::Alonzo, &tx_bytes)) + .into_diagnostic()?; + + let inputs = Vec::::decode_fragment(&inputs_bytes).unwrap(); + let outputs = Vec::::decode_fragment(&outputs_bytes).unwrap(); + + let resolved_inputs: Vec = inputs + .iter() + .zip(outputs.iter()) + .map(|(input, output)| ResolvedInput { + input: input.clone(), + output: output.clone(), + }) + .collect(); + + println!("Simulating: {}", tx.hash()); + + if let Some(tx_babbage) = tx.as_babbage() { + let slot_config = SlotConfig { + zero_time, + zero_slot, + slot_length, + }; + + let result = + tx::eval_phase_two(tx_babbage, &resolved_inputs, None, None, &slot_config, true); + + match result { + Ok(redeemers) => { + println!("\nTotal Budget Used\n-----------------\n"); + + let total_budget_used = + redeemers + .iter() + .fold(ExBudget { mem: 0, cpu: 0 }, |accum, curr| ExBudget { + mem: accum.mem + curr.ex_units.mem as i64, + cpu: accum.cpu + curr.ex_units.steps as i64, + }); + + println!("mem: {}", total_budget_used.mem); + println!("cpu: {}", total_budget_used.cpu); + } + Err(err) => { + eprintln!("\nError\n-----\n\n{}\n", err); + } + } + } + + return Ok(()); +} diff --git a/crates/cli/src/cmd/uplc/eval.rs b/crates/cli/src/cmd/uplc/eval.rs new file mode 100644 index 00000000..d55cf2eb --- /dev/null +++ b/crates/cli/src/cmd/uplc/eval.rs @@ -0,0 +1,75 @@ +use miette::IntoDiagnostic; +use std::path::PathBuf; +use uplc::{ + ast::{FakeNamedDeBruijn, Name, NamedDeBruijn, Program, Term}, + machine::cost_model::ExBudget, + parser, +}; + +#[derive(clap::Args)] +/// Evaluate an Untyped Plutus Core program +pub struct Args { + script: PathBuf, + + #[clap(short, long)] + flat: bool, + + /// Arguments to pass to the uplc program + args: Vec, +} + +pub fn exec(Args { script, flat, args }: Args) -> miette::Result<()> { + let mut program = if flat { + let bytes = std::fs::read(&script).into_diagnostic()?; + + let prog = Program::::from_flat(&bytes).into_diagnostic()?; + + prog.into() + } else { + let code = std::fs::read_to_string(&script).into_diagnostic()?; + + let prog = parser::program(&code).into_diagnostic()?; + + Program::::try_from(prog).into_diagnostic()? + }; + + for arg in args { + let term: Term = parser::term(&arg) + .into_diagnostic()? + .try_into() + .into_diagnostic()?; + + program = program.apply_term(&term); + } + + let (term, cost, logs) = program.eval(); + + match term { + Ok(term) => { + let term: Term = term.try_into().into_diagnostic()?; + + println!("\nResult\n------\n\n{}\n", term.to_pretty()); + } + Err(err) => { + eprintln!("\nError\n-----\n\n{}\n", err); + } + } + + let budget = ExBudget::default(); + + println!( + "\nCosts\n-----\ncpu: {}\nmemory: {}", + budget.cpu - cost.cpu, + budget.mem - cost.mem + ); + println!( + "\nBudget\n------\ncpu: {}\nmemory: {}\n", + cost.cpu, cost.mem + ); + + if !logs.is_empty() { + println!("\nLogs\n----\n{}", logs.join("\n")) + } + + return Ok(()); +} diff --git a/crates/cli/src/cmd/uplc/flat.rs b/crates/cli/src/cmd/uplc/flat.rs new file mode 100644 index 00000000..2412994b --- /dev/null +++ b/crates/cli/src/cmd/uplc/flat.rs @@ -0,0 +1,83 @@ +use miette::IntoDiagnostic; +use std::{fmt::Write, fs, path::PathBuf}; +use uplc::{ + ast::{DeBruijn, Program}, + parser, +}; + +#[derive(clap::Args)] +/// Encode textual Untyped Plutus Core to flat bytes +pub struct Args { + /// Textual Untyped Plutus Core file + input: PathBuf, + + /// Output file name + #[clap(short, long)] + out: Option, + + /// Print output instead of saving to file + #[clap(short, long)] + print: bool, + + #[clap(short, long)] + cbor_hex: bool, +} + +pub fn exec( + Args { + input, + out, + print, + cbor_hex, + }: Args, +) -> miette::Result<()> { + let code = std::fs::read_to_string(&input).into_diagnostic()?; + + let program = parser::program(&code).into_diagnostic()?; + + let program = Program::::try_from(program).into_diagnostic()?; + + if !cbor_hex { + let bytes = program.to_flat().into_diagnostic()?; + + if print { + let mut output = String::new(); + + for (i, byte) in bytes.iter().enumerate() { + let _ = write!(output, "{:08b}", byte); + + if (i + 1) % 4 == 0 { + output.push('\n'); + } else { + output.push(' '); + } + } + + println!("{}", output); + } else { + let out_name = if let Some(out) = out { + out + } else { + format!("{}.flat", input.file_stem().unwrap().to_str().unwrap()) + }; + + fs::write(&out_name, &bytes).into_diagnostic()?; + } + } else { + let cbor = program.to_hex().into_diagnostic()?; + + if print { + println!("{}", &cbor); + } else { + let out_name = if let Some(out) = out { + out + } else { + format!("{}.cbor", input.file_stem().unwrap().to_str().unwrap()) + }; + + fs::write(&out_name, &cbor).into_diagnostic()?; + } + } + + return Ok(()); +} diff --git a/crates/cli/src/cmd/uplc/fmt.rs b/crates/cli/src/cmd/uplc/fmt.rs new file mode 100644 index 00000000..7391e49a --- /dev/null +++ b/crates/cli/src/cmd/uplc/fmt.rs @@ -0,0 +1,30 @@ +use miette::IntoDiagnostic; +use std::{fs, path::PathBuf}; +use uplc::parser; + +#[derive(clap::Args)] +/// Format an Untyped Plutus Core program +pub struct Args { + /// Textual Untyped Plutus Core file + input: PathBuf, + + /// Print output instead of saving to file + #[clap(short, long)] + print: bool, +} + +pub fn exec(Args { input, print }: Args) -> miette::Result<()> { + let code = std::fs::read_to_string(&input).into_diagnostic()?; + + let program = parser::program(&code).into_diagnostic()?; + + let pretty = program.to_pretty(); + + if print { + println!("{}", pretty); + } else { + fs::write(&input, pretty).into_diagnostic()?; + } + + return Ok(()); +} diff --git a/crates/cli/src/cmd/uplc/mod.rs b/crates/cli/src/cmd/uplc/mod.rs new file mode 100644 index 00000000..edb1c56b --- /dev/null +++ b/crates/cli/src/cmd/uplc/mod.rs @@ -0,0 +1,24 @@ +mod eval; +mod flat; +mod fmt; +mod unflat; + +use clap::Subcommand; + +/// Commands for working with untyped Plutus-core +#[derive(Subcommand)] +pub enum Cmd { + Fmt(fmt::Args), + Eval(eval::Args), + Flat(flat::Args), + Unflat(unflat::Args), +} + +pub fn exec(cmd: Cmd) -> miette::Result<()> { + match cmd { + Cmd::Fmt(args) => fmt::exec(args), + Cmd::Eval(args) => eval::exec(args), + Cmd::Flat(args) => flat::exec(args), + Cmd::Unflat(args) => unflat::exec(args), + } +} diff --git a/crates/cli/src/cmd/uplc/unflat.rs b/crates/cli/src/cmd/uplc/unflat.rs new file mode 100644 index 00000000..3f9abb6b --- /dev/null +++ b/crates/cli/src/cmd/uplc/unflat.rs @@ -0,0 +1,61 @@ +use miette::IntoDiagnostic; +use std::{fs, path::PathBuf}; +use uplc::ast::{DeBruijn, Name, Program}; + +#[derive(clap::Args)] +/// Decode flat bytes to textual Untyped Plutus Core +pub struct Args { + /// Flat encoded Untyped Plutus Core file + input: PathBuf, + + /// Output file name + #[clap(short, long)] + out: Option, + + /// Print output instead of saving to file + #[clap(short, long)] + print: bool, + + #[clap(short, long)] + cbor_hex: bool, +} + +pub fn exec( + Args { + input, + out, + print, + cbor_hex, + }: Args, +) -> miette::Result<()> { + let program = if cbor_hex { + let cbor = std::fs::read_to_string(&input).into_diagnostic()?; + + let mut cbor_buffer = Vec::new(); + let mut flat_buffer = Vec::new(); + + Program::::from_hex(cbor.trim(), &mut cbor_buffer, &mut flat_buffer) + .into_diagnostic()? + } else { + let bytes = std::fs::read(&input).into_diagnostic()?; + + Program::::from_flat(&bytes).into_diagnostic()? + }; + + let program: Program = program.try_into().into_diagnostic()?; + + let pretty = program.to_pretty(); + + if print { + println!("{}", pretty); + } else { + let out_name = if let Some(out) = out { + out + } else { + format!("{}.uplc", input.file_stem().unwrap().to_str().unwrap()) + }; + + fs::write(&out_name, pretty).into_diagnostic()?; + } + return Ok(()); +} diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs deleted file mode 100644 index 362113c6..00000000 --- a/crates/cli/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub use aiken_lang; -pub use uplc; diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index dc28d59e..2e64ebf1 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,355 +1,37 @@ -use std::{env, fmt::Write as _, fs}; +mod cmd; -use miette::IntoDiagnostic; -use pallas_primitives::{ - babbage::{TransactionInput, TransactionOutput}, - Fragment, -}; -use pallas_traverse::{Era, MultiEraTx}; -use uplc::{ - ast::{DeBruijn, FakeNamedDeBruijn, Name, NamedDeBruijn, Program, Term}, - machine::cost_model::ExBudget, - parser, - tx::{ - self, - script_context::{ResolvedInput, SlotConfig}, - }, -}; +use clap::Parser; +use cmd::{build, check, new, tx, uplc}; -use project::{config::Config, Project}; +/// Aiken: a smart-contract language and toolchain for Cardano +#[derive(Parser)] +#[clap(version, about, long_about = None)] +#[clap(propagate_version = true)] +pub enum Cmd { + New(new::Args), + Build(build::Args), + Check(check::Args), -mod args; + #[clap(subcommand)] + Tx(tx::Cmd), -use args::{Args, TxCommand, UplcCommand}; + #[clap(subcommand)] + Uplc(uplc::Cmd), +} + +impl Default for Cmd { + fn default() -> Self { + Self::parse() + } +} fn main() -> miette::Result<()> { miette::set_panic_hook(); - - let args = Args::default(); - - match args { - Args::Build { directory } => { - let project_path = if let Some(d) = directory { - d - } else { - env::current_dir().into_diagnostic()? - }; - - let config = Config::load(project_path.clone()).into_diagnostic()?; - - let mut project = Project::new(config, project_path); - - let build_result = project.build(); - - let warning_count = project.warnings.len(); - - for warning in project.warnings { - warning.report() - } - - if let Err(err) = build_result { - err.report(); - - miette::bail!( - "failed: {} error(s), {warning_count} warning(s)", - err.total(), - ); - }; - - println!("finished with {warning_count} warning(s)") - } - - Args::Check { directory } => { - let project_path = if let Some(d) = directory { - d - } else { - env::current_dir().into_diagnostic()? - }; - - let config = Config::load(project_path.clone()).into_diagnostic()?; - - let mut project = Project::new(config, project_path); - - let build_result = project.check(); - - let warning_count = project.warnings.len(); - - for warning in project.warnings { - warning.report() - } - - if let Err(err) = build_result { - err.report(); - - miette::bail!( - "failed: {} error(s), {warning_count} warning(s)", - err.total(), - ); - }; - - println!("finished with {warning_count} warning(s)") - } - - Args::Dev => { - // launch a development server - // this should allow people to test - // their contracts over http - todo!() - } - - Args::New { name } => { - if !name.exists() { - fs::create_dir_all(name.join("lib")).into_diagnostic()?; - fs::create_dir_all(name.join("policies")).into_diagnostic()?; - fs::create_dir_all(name.join("scripts")).into_diagnostic()?; - } - } - - Args::Tx(tx_cmd) => match tx_cmd { - TxCommand::Simulate { - input, - cbor, - raw_inputs, - raw_outputs, - slot_length, - zero_time, - zero_slot, - } => { - let (tx_bytes, inputs_bytes, outputs_bytes) = if cbor { - ( - fs::read(input).into_diagnostic()?, - fs::read(raw_inputs).into_diagnostic()?, - fs::read(raw_outputs).into_diagnostic()?, - ) - } else { - let cbor_hex = fs::read_to_string(input).into_diagnostic()?; - let inputs_hex = fs::read_to_string(raw_inputs).into_diagnostic()?; - let outputs_hex = fs::read_to_string(raw_outputs).into_diagnostic()?; - - ( - hex::decode(cbor_hex.trim()).into_diagnostic()?, - hex::decode(inputs_hex.trim()).into_diagnostic()?, - hex::decode(outputs_hex.trim()).into_diagnostic()?, - ) - }; - - let tx = MultiEraTx::decode(Era::Babbage, &tx_bytes) - .or_else(|_| MultiEraTx::decode(Era::Alonzo, &tx_bytes)) - .into_diagnostic()?; - - let inputs = Vec::::decode_fragment(&inputs_bytes).unwrap(); - let outputs = Vec::::decode_fragment(&outputs_bytes).unwrap(); - - let resolved_inputs: Vec = inputs - .iter() - .zip(outputs.iter()) - .map(|(input, output)| ResolvedInput { - input: input.clone(), - output: output.clone(), - }) - .collect(); - - println!("Simulating: {}", tx.hash()); - - if let Some(tx_babbage) = tx.as_babbage() { - let slot_config = SlotConfig { - zero_time, - zero_slot, - slot_length, - }; - - let result = tx::eval_phase_two( - tx_babbage, - &resolved_inputs, - None, - None, - &slot_config, - true, - ); - - match result { - Ok(redeemers) => { - println!("\nTotal Budget Used\n-----------------\n"); - - let total_budget_used = redeemers.iter().fold( - ExBudget { mem: 0, cpu: 0 }, - |accum, curr| ExBudget { - mem: accum.mem + curr.ex_units.mem as i64, - cpu: accum.cpu + curr.ex_units.steps as i64, - }, - ); - - println!("mem: {}", total_budget_used.mem); - println!("cpu: {}", total_budget_used.cpu); - } - Err(err) => { - eprintln!("\nError\n-----\n\n{}\n", err); - } - } - } - } - }, - Args::Uplc(uplc_cmd) => match uplc_cmd { - UplcCommand::Flat { - input, - print, - out, - cbor_hex, - } => { - let code = std::fs::read_to_string(&input).into_diagnostic()?; - - let program = parser::program(&code).into_diagnostic()?; - - let program = Program::::try_from(program).into_diagnostic()?; - - if !cbor_hex { - let bytes = program.to_flat().into_diagnostic()?; - - if print { - let mut output = String::new(); - - for (i, byte) in bytes.iter().enumerate() { - let _ = write!(output, "{:08b}", byte); - - if (i + 1) % 4 == 0 { - output.push('\n'); - } else { - output.push(' '); - } - } - - println!("{}", output); - } else { - let out_name = if let Some(out) = out { - out - } else { - format!("{}.flat", input.file_stem().unwrap().to_str().unwrap()) - }; - - fs::write(&out_name, &bytes).into_diagnostic()?; - } - } else { - let cbor = program.to_hex().into_diagnostic()?; - - if print { - println!("{}", &cbor); - } else { - let out_name = if let Some(out) = out { - out - } else { - format!("{}.cbor", input.file_stem().unwrap().to_str().unwrap()) - }; - - fs::write(&out_name, &cbor).into_diagnostic()?; - } - } - } - - UplcCommand::Fmt { input, print } => { - let code = std::fs::read_to_string(&input).into_diagnostic()?; - - let program = parser::program(&code).into_diagnostic()?; - - let pretty = program.to_pretty(); - - if print { - println!("{}", pretty); - } else { - fs::write(&input, pretty).into_diagnostic()?; - } - } - UplcCommand::Unflat { - input, - print, - out, - cbor_hex, - } => { - let program = if cbor_hex { - let cbor = std::fs::read_to_string(&input).into_diagnostic()?; - - let mut cbor_buffer = Vec::new(); - let mut flat_buffer = Vec::new(); - - Program::::from_hex(cbor.trim(), &mut cbor_buffer, &mut flat_buffer) - .into_diagnostic()? - } else { - let bytes = std::fs::read(&input).into_diagnostic()?; - - Program::::from_flat(&bytes).into_diagnostic()? - }; - - let program: Program = program.try_into().into_diagnostic()?; - - let pretty = program.to_pretty(); - - if print { - println!("{}", pretty); - } else { - let out_name = if let Some(out) = out { - out - } else { - format!("{}.uplc", input.file_stem().unwrap().to_str().unwrap()) - }; - - fs::write(&out_name, pretty).into_diagnostic()?; - } - } - - UplcCommand::Eval { script, flat, args } => { - let mut program = if flat { - let bytes = std::fs::read(&script).into_diagnostic()?; - - let prog = Program::::from_flat(&bytes).into_diagnostic()?; - - prog.into() - } else { - let code = std::fs::read_to_string(&script).into_diagnostic()?; - - let prog = parser::program(&code).into_diagnostic()?; - - Program::::try_from(prog).into_diagnostic()? - }; - - for arg in args { - let term: Term = parser::term(&arg) - .into_diagnostic()? - .try_into() - .into_diagnostic()?; - - program = program.apply_term(&term); - } - - let (term, cost, logs) = program.eval(); - - match term { - Ok(term) => { - let term: Term = term.try_into().into_diagnostic()?; - - println!("\nResult\n------\n\n{}\n", term.to_pretty()); - } - Err(err) => { - eprintln!("\nError\n-----\n\n{}\n", err); - } - } - - let budget = ExBudget::default(); - - println!( - "\nCosts\n-----\ncpu: {}\nmemory: {}", - budget.cpu - cost.cpu, - budget.mem - cost.mem - ); - println!( - "\nBudget\n------\ncpu: {}\nmemory: {}\n", - cost.cpu, cost.mem - ); - - if !logs.is_empty() { - println!("\nLogs\n----\n{}", logs.join("\n")) - } - } - }, + match Cmd::default() { + Cmd::Build(args) => build::exec(args), + Cmd::Check(args) => check::exec(args), + Cmd::New(args) => new::exec(args), + Cmd::Tx(sub_cmd) => tx::exec(sub_cmd), + Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd), } - - Ok(()) }