diff --git a/Cargo.lock b/Cargo.lock index 38d31c98..897ed1c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,14 +61,8 @@ dependencies = [ "pallas-crypto", "pallas-primitives", "pallas-traverse", - "petgraph", - "regex", - "serde", - "serde_json", - "thiserror", - "toml", + "project", "uplc", - "walkdir", ] [[package]] @@ -808,6 +802,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "project" +version = "0.0.21" +dependencies = [ + "aiken-lang", + "miette", + "petgraph", + "regex", + "serde", + "serde_json", + "thiserror", + "toml", + "walkdir", +] + [[package]] name = "proptest" version = "1.0.0" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 19e34dc7..b566e94b 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -12,19 +12,14 @@ authors = ["Lucas Rosa ", "Kasey White "] anyhow = "1.0.57" clap = { version = "3.1.14", features = ["derive"] } hex = "0.4.3" +ignore = "0.4.18" +miette = { version = "5.3.0", features = ["fancy"] } pallas-addresses = "0.14.0-alpha.3" pallas-codec = "0.14.0-alpha.3" pallas-crypto = "0.14.0-alpha.3" pallas-primitives = "0.14.0-alpha.3" pallas-traverse = "0.14.0-alpha.3" -serde = { version = "1.0.144", features = ["derive"] } -serde_json = "1.0.85" -uplc = { path = '../uplc', version = "0.0.21" } + aiken-lang = { path = "../lang", version = "0.0.20" } -toml = "0.5.9" -walkdir = "2.3.2" -ignore = "0.4.18" -regex = "1.6.0" -miette = { version = "5.3.0", features = ["fancy"] } -thiserror = "1.0.37" -petgraph = "0.6.2" +project = { path = '../project', version = "0.0.21" } +uplc = { path = '../uplc', version = "0.0.21" } 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..b16a0140 --- /dev/null +++ b/crates/cli/src/cmd/build.rs @@ -0,0 +1,13 @@ +use std::path::PathBuf; + +#[derive(clap::Args)] +/// Build an Aiken project +pub struct Args { + /// Path to project + #[clap(short, long)] + directory: Option, +} + +pub fn exec(Args { directory }: Args) -> miette::Result<()> { + crate::with_project(directory, |p| p.build()) +} diff --git a/crates/cli/src/cmd/check.rs b/crates/cli/src/cmd/check.rs new file mode 100644 index 00000000..9f99e617 --- /dev/null +++ b/crates/cli/src/cmd/check.rs @@ -0,0 +1,13 @@ +use std::path::PathBuf; + +#[derive(clap::Args)] +/// Type-check an Aiken project +pub struct Args { + /// Path to project + #[clap(short, long)] + directory: Option, +} + +pub fn exec(Args { directory }: Args) -> miette::Result<()> { + crate::with_project(directory, |p| p.check()) +} 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..ab4ce26b --- /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()?; + } + + 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..ae03fece --- /dev/null +++ b/crates/cli/src/cmd/tx/mod.rs @@ -0,0 +1,16 @@ +pub mod simulate; + +use clap::Subcommand; + +/// Commands for working with transactions +#[derive(Subcommand)] +#[clap(setting(clap::AppSettings::DeriveDisplayOrder))] +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..8f7c4492 --- /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); + } + } + } + + 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..0f22cfee --- /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")) + } + + 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..e7efe84c --- /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()?; + } + } + + 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..c5bf026a --- /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()?; + } + + 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..3aaa8eb7 --- /dev/null +++ b/crates/cli/src/cmd/uplc/mod.rs @@ -0,0 +1,25 @@ +mod eval; +mod flat; +mod fmt; +mod unflat; + +use clap::Subcommand; + +/// Commands for working with untyped Plutus-core +#[derive(Subcommand)] +#[clap(setting(clap::AppSettings::DeriveDisplayOrder))] +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..b45c4721 --- /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()?; + } + Ok(()) +} diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index b2a46373..84e28b39 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,7 +1,41 @@ -pub mod config; -pub mod error; -pub mod module; -pub mod project; +pub mod cmd; -pub use aiken_lang; -pub use uplc; +use miette::IntoDiagnostic; +use project::{config::Config, Project}; +use std::env; +use std::path::PathBuf; + +pub fn with_project(directory: Option, mut action: A) -> miette::Result<()> +where + A: FnMut(&mut Project) -> Result<(), project::error::Error>, +{ + 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 = action(&mut project); + + 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)"); + Ok(()) +} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 6faa2f2b..70d150e7 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,355 +1,36 @@ -use std::{env, fmt::Write as _, fs}; +use aiken::cmd::{build, check, new, tx, uplc}; +use clap::Parser; -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}, - }, -}; +/// Aiken: a smart-contract language and toolchain for Cardano +#[derive(Parser)] +#[clap(version, about, long_about = None)] +#[clap(propagate_version = true)] +#[clap(setting(clap::AppSettings::DeriveDisplayOrder))] +pub enum Cmd { + New(new::Args), + Build(build::Args), + Check(check::Args), -use aiken::{config::Config, project::Project}; + #[clap(subcommand)] + Tx(tx::Cmd), -mod args; + #[clap(subcommand)] + Uplc(uplc::Cmd), +} -use args::{Args, TxCommand, UplcCommand}; +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::New(args) => new::exec(args), + Cmd::Build(args) => build::exec(args), + Cmd::Check(args) => check::exec(args), + Cmd::Tx(sub_cmd) => tx::exec(sub_cmd), + Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd), } - - Ok(()) } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml new file mode 100644 index 00000000..2d37e918 --- /dev/null +++ b/crates/project/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "project" +description = "Aiken project utilities" +version = "0.0.21" +edition = "2021" +repository = "https://github.com/txpipe/aiken/crates/project" +homepage = "https://github.com/txpipe/aiken" +license = "Apache-2.0" +authors = ["Lucas Rosa ", "Kasey White "] + +[dependencies] +aiken-lang = { path = "../lang", version = "0.0.20" } +miette = { version = "5.3.0", features = ["fancy"] } +petgraph = "0.6.2" +regex = "1.6.0" +serde = { version = "1.0.144", features = ["derive"] } +serde_json = "1.0.85" +thiserror = "1.0.37" +toml = "0.5.9" +walkdir = "2.3.2" diff --git a/crates/project/README.md b/crates/project/README.md new file mode 100644 index 00000000..7328ab4c --- /dev/null +++ b/crates/project/README.md @@ -0,0 +1,3 @@ +# Project + +This crate encapsulates the code used to manage Aiken projects. See [crates/cli](../cli) for usage. diff --git a/crates/cli/src/config.rs b/crates/project/src/config.rs similarity index 100% rename from crates/cli/src/config.rs rename to crates/project/src/config.rs diff --git a/crates/cli/src/error.rs b/crates/project/src/error.rs similarity index 100% rename from crates/cli/src/error.rs rename to crates/project/src/error.rs diff --git a/crates/cli/src/project.rs b/crates/project/src/lib.rs similarity index 99% rename from crates/cli/src/project.rs rename to crates/project/src/lib.rs index bc5ab999..c7da293f 100644 --- a/crates/cli/src/project.rs +++ b/crates/project/src/lib.rs @@ -4,6 +4,10 @@ use std::{ path::{Path, PathBuf}, }; +pub mod config; +pub mod error; +pub mod module; + use aiken_lang::{ast::ModuleKind, builtins, tipo::TypeInfo, IdGenerator}; use crate::{ diff --git a/crates/cli/src/module.rs b/crates/project/src/module.rs similarity index 100% rename from crates/cli/src/module.rs rename to crates/project/src/module.rs