Merge pull request #86 from txpipe/54-organize-cli-commands
Organize CLI commands
This commit is contained in:
commit
aae6e2f360
|
@ -61,14 +61,8 @@ dependencies = [
|
||||||
"pallas-crypto",
|
"pallas-crypto",
|
||||||
"pallas-primitives",
|
"pallas-primitives",
|
||||||
"pallas-traverse",
|
"pallas-traverse",
|
||||||
"petgraph",
|
"project",
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror",
|
|
||||||
"toml",
|
|
||||||
"uplc",
|
"uplc",
|
||||||
"walkdir",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -808,6 +802,21 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "project"
|
||||||
|
version = "0.0.21"
|
||||||
|
dependencies = [
|
||||||
|
"aiken-lang",
|
||||||
|
"miette",
|
||||||
|
"petgraph",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"toml",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proptest"
|
name = "proptest"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|
|
@ -12,19 +12,14 @@ authors = ["Lucas Rosa <x@rvcas.dev>", "Kasey White <kwhitemsg@gmail.com>"]
|
||||||
anyhow = "1.0.57"
|
anyhow = "1.0.57"
|
||||||
clap = { version = "3.1.14", features = ["derive"] }
|
clap = { version = "3.1.14", features = ["derive"] }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
ignore = "0.4.18"
|
||||||
|
miette = { version = "5.3.0", features = ["fancy"] }
|
||||||
pallas-addresses = "0.14.0-alpha.3"
|
pallas-addresses = "0.14.0-alpha.3"
|
||||||
pallas-codec = "0.14.0-alpha.3"
|
pallas-codec = "0.14.0-alpha.3"
|
||||||
pallas-crypto = "0.14.0-alpha.3"
|
pallas-crypto = "0.14.0-alpha.3"
|
||||||
pallas-primitives = "0.14.0-alpha.3"
|
pallas-primitives = "0.14.0-alpha.3"
|
||||||
pallas-traverse = "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" }
|
aiken-lang = { path = "../lang", version = "0.0.20" }
|
||||||
toml = "0.5.9"
|
project = { path = '../project', version = "0.0.21" }
|
||||||
walkdir = "2.3.2"
|
uplc = { path = '../uplc', version = "0.0.21" }
|
||||||
ignore = "0.4.18"
|
|
||||||
regex = "1.6.0"
|
|
||||||
miette = { version = "5.3.0", features = ["fancy"] }
|
|
||||||
thiserror = "1.0.37"
|
|
||||||
petgraph = "0.6.2"
|
|
||||||
|
|
|
@ -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<PathBuf>,
|
|
||||||
},
|
|
||||||
/// Typecheck a project project
|
|
||||||
Check {
|
|
||||||
/// Path to project
|
|
||||||
#[clap(short, long)]
|
|
||||||
directory: Option<PathBuf>,
|
|
||||||
},
|
|
||||||
/// 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<String>,
|
|
||||||
},
|
|
||||||
/// Encode textual Untyped Plutus Core to flat bytes
|
|
||||||
Flat {
|
|
||||||
/// Textual Untyped Plutus Core file
|
|
||||||
input: PathBuf,
|
|
||||||
|
|
||||||
/// Output file name
|
|
||||||
#[clap(short, long)]
|
|
||||||
out: Option<String>,
|
|
||||||
|
|
||||||
/// 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<String>,
|
|
||||||
|
|
||||||
/// 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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec(Args { directory }: Args) -> miette::Result<()> {
|
||||||
|
crate::with_project(directory, |p| p.build())
|
||||||
|
}
|
|
@ -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<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec(Args { directory }: Args) -> miette::Result<()> {
|
||||||
|
crate::with_project(directory, |p| p.check())
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod build;
|
||||||
|
pub mod check;
|
||||||
|
pub mod new;
|
||||||
|
pub mod tx;
|
||||||
|
pub mod uplc;
|
|
@ -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(())
|
||||||
|
}
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
|
@ -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::<TransactionInput>::decode_fragment(&inputs_bytes).unwrap();
|
||||||
|
let outputs = Vec::<TransactionOutput>::decode_fragment(&outputs_bytes).unwrap();
|
||||||
|
|
||||||
|
let resolved_inputs: Vec<ResolvedInput> = 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(())
|
||||||
|
}
|
|
@ -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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<FakeNamedDeBruijn>::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::<NamedDeBruijn>::try_from(prog).into_diagnostic()?
|
||||||
|
};
|
||||||
|
|
||||||
|
for arg in args {
|
||||||
|
let term: Term<NamedDeBruijn> = 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<Name> = 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(())
|
||||||
|
}
|
|
@ -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<String>,
|
||||||
|
|
||||||
|
/// 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::<DeBruijn>::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(())
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String>,
|
||||||
|
|
||||||
|
/// 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::<DeBruijn>::from_hex(cbor.trim(), &mut cbor_buffer, &mut flat_buffer)
|
||||||
|
.into_diagnostic()?
|
||||||
|
} else {
|
||||||
|
let bytes = std::fs::read(&input).into_diagnostic()?;
|
||||||
|
|
||||||
|
Program::<DeBruijn>::from_flat(&bytes).into_diagnostic()?
|
||||||
|
};
|
||||||
|
|
||||||
|
let program: Program<Name> = 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(())
|
||||||
|
}
|
|
@ -1,7 +1,41 @@
|
||||||
pub mod config;
|
pub mod cmd;
|
||||||
pub mod error;
|
|
||||||
pub mod module;
|
|
||||||
pub mod project;
|
|
||||||
|
|
||||||
pub use aiken_lang;
|
use miette::IntoDiagnostic;
|
||||||
pub use uplc;
|
use project::{config::Config, Project};
|
||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub fn with_project<A>(directory: Option<PathBuf>, 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(())
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
/// Aiken: a smart-contract language and toolchain for Cardano
|
||||||
use pallas_primitives::{
|
#[derive(Parser)]
|
||||||
babbage::{TransactionInput, TransactionOutput},
|
#[clap(version, about, long_about = None)]
|
||||||
Fragment,
|
#[clap(propagate_version = true)]
|
||||||
};
|
#[clap(setting(clap::AppSettings::DeriveDisplayOrder))]
|
||||||
use pallas_traverse::{Era, MultiEraTx};
|
pub enum Cmd {
|
||||||
use uplc::{
|
New(new::Args),
|
||||||
ast::{DeBruijn, FakeNamedDeBruijn, Name, NamedDeBruijn, Program, Term},
|
Build(build::Args),
|
||||||
machine::cost_model::ExBudget,
|
Check(check::Args),
|
||||||
parser,
|
|
||||||
tx::{
|
|
||||||
self,
|
|
||||||
script_context::{ResolvedInput, SlotConfig},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
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<()> {
|
fn main() -> miette::Result<()> {
|
||||||
miette::set_panic_hook();
|
miette::set_panic_hook();
|
||||||
|
match Cmd::default() {
|
||||||
let args = Args::default();
|
Cmd::New(args) => new::exec(args),
|
||||||
|
Cmd::Build(args) => build::exec(args),
|
||||||
match args {
|
Cmd::Check(args) => check::exec(args),
|
||||||
Args::Build { directory } => {
|
Cmd::Tx(sub_cmd) => tx::exec(sub_cmd),
|
||||||
let project_path = if let Some(d) = directory {
|
Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd),
|
||||||
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::<TransactionInput>::decode_fragment(&inputs_bytes).unwrap();
|
|
||||||
let outputs = Vec::<TransactionOutput>::decode_fragment(&outputs_bytes).unwrap();
|
|
||||||
|
|
||||||
let resolved_inputs: Vec<ResolvedInput> = 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::<DeBruijn>::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::<DeBruijn>::from_hex(cbor.trim(), &mut cbor_buffer, &mut flat_buffer)
|
|
||||||
.into_diagnostic()?
|
|
||||||
} else {
|
|
||||||
let bytes = std::fs::read(&input).into_diagnostic()?;
|
|
||||||
|
|
||||||
Program::<DeBruijn>::from_flat(&bytes).into_diagnostic()?
|
|
||||||
};
|
|
||||||
|
|
||||||
let program: Program<Name> = 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::<FakeNamedDeBruijn>::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::<NamedDeBruijn>::try_from(prog).into_diagnostic()?
|
|
||||||
};
|
|
||||||
|
|
||||||
for arg in args {
|
|
||||||
let term: Term<NamedDeBruijn> = 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<Name> = 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(())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <x@rvcas.dev>", "Kasey White <kwhitemsg@gmail.com>"]
|
||||||
|
|
||||||
|
[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"
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Project
|
||||||
|
|
||||||
|
This crate encapsulates the code used to manage Aiken projects. See [crates/cli](../cli) for usage.
|
|
@ -4,6 +4,10 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod error;
|
||||||
|
pub mod module;
|
||||||
|
|
||||||
use aiken_lang::{ast::ModuleKind, builtins, tipo::TypeInfo, IdGenerator};
|
use aiken_lang::{ast::ModuleKind, builtins, tipo::TypeInfo, IdGenerator};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
Loading…
Reference in New Issue