chore: make folder names match crate name
This commit is contained in:
30
crates/aiken/Cargo.toml
Normal file
30
crates/aiken/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "aiken"
|
||||
description = "Cardano smart contract language and toolchain"
|
||||
version = "0.0.26"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/aiken-lang/aiken"
|
||||
homepage = "https://github.com/aiken-lang/aiken"
|
||||
license = "Apache-2.0"
|
||||
authors = ["Lucas Rosa <x@rvcas.dev>", "Kasey White <kwhitemsg@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.57"
|
||||
clap = { version = "3.1.14", features = ["derive"] }
|
||||
hex = "0.4.3"
|
||||
ignore = "0.4.18"
|
||||
indoc = "1.0"
|
||||
miette = { version = "5.3.0", features = ["fancy"] }
|
||||
owo-colors = "3.5.0"
|
||||
pallas-addresses = "0.14.0"
|
||||
pallas-codec = "0.14.0"
|
||||
pallas-crypto = "0.14.0"
|
||||
pallas-primitives = "0.14.0"
|
||||
pallas-traverse = "0.14.0"
|
||||
regex = "1.5.4"
|
||||
thiserror = "1.0.31"
|
||||
|
||||
aiken-lang = { path = "../aiken-lang", version = "0.0.26" }
|
||||
aiken-lsp = { path = "../aiken-lsp", version = "0.0.26" }
|
||||
aiken-project = { path = '../aiken-project', version = "0.0.26" }
|
||||
uplc = { path = '../uplc', version = "0.0.25" }
|
||||
8
crates/aiken/README.md
Normal file
8
crates/aiken/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# CLI
|
||||
|
||||
This is the crate that contains the aiken command line application
|
||||
which bundles together all the other crates in this project.
|
||||
|
||||
## Install
|
||||
|
||||
`cargo install aiken`
|
||||
17
crates/aiken/src/cmd/build.rs
Normal file
17
crates/aiken/src/cmd/build.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
/// Build an Aiken project
|
||||
pub struct Args {
|
||||
/// Path to project
|
||||
#[clap(short, long)]
|
||||
directory: Option<PathBuf>,
|
||||
|
||||
/// Also dump textual uplc
|
||||
#[clap(short, long)]
|
||||
uplc: bool,
|
||||
}
|
||||
|
||||
pub fn exec(Args { directory, uplc }: Args) -> miette::Result<()> {
|
||||
crate::with_project(directory, |p| p.build(uplc))
|
||||
}
|
||||
34
crates/aiken/src/cmd/check.rs
Normal file
34
crates/aiken/src/cmd/check.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
/// Type-check an Aiken project
|
||||
pub struct Args {
|
||||
/// Path to project
|
||||
#[clap(short, long)]
|
||||
directory: Option<PathBuf>,
|
||||
|
||||
/// Skip tests; run only the type-checker
|
||||
#[clap(short, long)]
|
||||
skip_tests: bool,
|
||||
|
||||
/// When enabled, also pretty-print test UPLC on failure
|
||||
#[clap(long)]
|
||||
debug: bool,
|
||||
|
||||
/// Only run tests if their path + name match the given string
|
||||
#[clap(short, long)]
|
||||
match_tests: Option<String>,
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
Args {
|
||||
directory,
|
||||
skip_tests,
|
||||
debug,
|
||||
match_tests,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
crate::with_project(directory, |p| {
|
||||
p.check(skip_tests, match_tests.clone(), debug)
|
||||
})
|
||||
}
|
||||
22
crates/aiken/src/cmd/docs.rs
Normal file
22
crates/aiken/src/cmd/docs.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
/// Build an Aiken project
|
||||
pub struct Args {
|
||||
/// Path to project
|
||||
#[clap(short, long)]
|
||||
directory: Option<PathBuf>,
|
||||
|
||||
/// Output directory for the documentation
|
||||
#[clap(short = 'o', long)]
|
||||
destination: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
Args {
|
||||
directory,
|
||||
destination,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
crate::with_project(directory, |p| p.docs(destination.clone()))
|
||||
}
|
||||
37
crates/aiken/src/cmd/error.rs
Normal file
37
crates/aiken/src/cmd/error.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::fmt;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("'{}' is not a valid project name: {}", name, reason.to_string())]
|
||||
InvalidProjectName {
|
||||
name: String,
|
||||
reason: InvalidProjectNameReason,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum InvalidProjectNameReason {
|
||||
AikenPrefix,
|
||||
AikenReservedModule,
|
||||
Format,
|
||||
}
|
||||
|
||||
impl fmt::Display for InvalidProjectNameReason {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
InvalidProjectNameReason::AikenPrefix => write!(f, "it's a reserved word in Aiken."),
|
||||
InvalidProjectNameReason::AikenReservedModule => {
|
||||
write!(f, "it's a reserved module name in Aiken.")
|
||||
}
|
||||
InvalidProjectNameReason::Format => write!(
|
||||
f,
|
||||
"it is malformed.\n\nProjects must be named as:\n\n\t\
|
||||
{{repository}}/{{project}}\n\nEach part must start with a lowercase letter \
|
||||
and may only contain lowercase letters, numbers, hyphens or underscores.\
|
||||
\nFor example,\n\n\taiken-lang/stdlib"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
31
crates/aiken/src/cmd/fmt.rs
Normal file
31
crates/aiken/src/cmd/fmt.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
#[derive(clap::Args)]
|
||||
/// Format an Aiken project
|
||||
pub struct Args {
|
||||
/// Files to format
|
||||
#[clap(default_value = ".")]
|
||||
files: Vec<String>,
|
||||
|
||||
/// Read source from STDIN
|
||||
#[clap(long)]
|
||||
stdin: bool,
|
||||
|
||||
/// Check if inputs are formatted without changing them
|
||||
#[clap(long)]
|
||||
check: bool,
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
Args {
|
||||
check,
|
||||
stdin,
|
||||
files,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
if let Err(err) = aiken_project::format::run(stdin, check, files) {
|
||||
err.report();
|
||||
|
||||
miette::bail!("failed: {} error(s)", err.len());
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
13
crates/aiken/src/cmd/lsp.rs
Normal file
13
crates/aiken/src/cmd/lsp.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use miette::IntoDiagnostic;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
/// Start the Aiken language server
|
||||
pub struct Args {
|
||||
/// Run on stdio
|
||||
#[clap(long)]
|
||||
stdio: bool,
|
||||
}
|
||||
|
||||
pub fn exec(_args: Args) -> miette::Result<()> {
|
||||
aiken_lsp::start().into_diagnostic()
|
||||
}
|
||||
9
crates/aiken/src/cmd/mod.rs
Normal file
9
crates/aiken/src/cmd/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod build;
|
||||
pub mod check;
|
||||
pub mod docs;
|
||||
pub mod error;
|
||||
pub mod fmt;
|
||||
pub mod lsp;
|
||||
pub mod new;
|
||||
pub mod tx;
|
||||
pub mod uplc;
|
||||
175
crates/aiken/src/cmd/new.rs
Normal file
175
crates/aiken/src/cmd/new.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
use indoc::formatdoc;
|
||||
use miette::IntoDiagnostic;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::error::{Error, InvalidProjectNameReason};
|
||||
|
||||
#[derive(clap::Args)]
|
||||
/// Create a new Aiken project
|
||||
pub struct Args {
|
||||
/// Project name
|
||||
name: PathBuf,
|
||||
}
|
||||
|
||||
pub struct Creator {
|
||||
root: PathBuf,
|
||||
lib: PathBuf,
|
||||
validators: PathBuf,
|
||||
project_lib: PathBuf,
|
||||
project_name: String,
|
||||
}
|
||||
|
||||
impl Creator {
|
||||
fn new(args: Args, project_name: String) -> Self {
|
||||
let root = PathBuf::from(args.name.file_name().unwrap());
|
||||
let lib = root.join("lib");
|
||||
let validators = root.join("validators");
|
||||
let project_name = project_name;
|
||||
let project_lib = lib.join(&project_name);
|
||||
Self {
|
||||
root,
|
||||
lib,
|
||||
validators,
|
||||
project_lib,
|
||||
project_name,
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&self) -> miette::Result<()> {
|
||||
fs::create_dir_all(&self.lib).into_diagnostic()?;
|
||||
fs::create_dir_all(&self.project_lib).into_diagnostic()?;
|
||||
fs::create_dir_all(&self.validators).into_diagnostic()?;
|
||||
self.aiken_toml()?;
|
||||
self.readme()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn readme(&self) -> miette::Result<()> {
|
||||
write(
|
||||
self.root.join("README.md"),
|
||||
&format! {
|
||||
r#"# {name}
|
||||
|
||||
Write validators in the `validators` folder, and supporting functions in the `lib` folder using `.ak` as a file extension.
|
||||
|
||||
For example, as `validators/always_true.ak`
|
||||
|
||||
```gleam
|
||||
pub fn spend(_datum: Data, _redeemer: Data, _context: Data) -> Bool {{
|
||||
True
|
||||
}}
|
||||
```
|
||||
|
||||
Validators are named after their purpose, so one of:
|
||||
|
||||
- `script`
|
||||
- `mint`
|
||||
- `withdraw`
|
||||
- `certify`
|
||||
|
||||
## Building
|
||||
|
||||
```console
|
||||
aiken build
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
You can write tests in any module using the `test` keyword. For example:
|
||||
|
||||
```gleam
|
||||
test foo() {{
|
||||
1 + 1 == 2
|
||||
}}
|
||||
```
|
||||
|
||||
To run all tests, simply do:
|
||||
|
||||
```console
|
||||
aiken check
|
||||
```
|
||||
|
||||
To run only tests matching the string `foo`, do:
|
||||
|
||||
```console
|
||||
aiken check -m foo
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
If you're writing a library, you might want to generate an HTML documentation for it.
|
||||
|
||||
Use:
|
||||
|
||||
```
|
||||
aiken docs
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
Find more on the [Aiken's user manual](https://aiken-lang.org).
|
||||
"#,
|
||||
name = self.project_name
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn aiken_toml(&self) -> miette::Result<()> {
|
||||
write(
|
||||
self.root.join("aiken.toml"),
|
||||
&formatdoc! {
|
||||
r#"name = "{name}"
|
||||
version = "0.0.0"
|
||||
licences = ["Apache-2.0"]
|
||||
description = "Aiken contracts for project '{name}'"
|
||||
dependencies = []
|
||||
"#,
|
||||
name = self.project_name,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn write(path: PathBuf, contents: &str) -> miette::Result<()> {
|
||||
let mut f = fs::File::create(path).into_diagnostic()?;
|
||||
f.write_all(contents.as_bytes()).into_diagnostic()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_name(name: &str) -> Result<(), Error> {
|
||||
if name.starts_with("aiken_") {
|
||||
Err(Error::InvalidProjectName {
|
||||
name: name.to_string(),
|
||||
reason: InvalidProjectNameReason::AikenPrefix,
|
||||
})
|
||||
} else if name == "aiken" {
|
||||
Err(Error::InvalidProjectName {
|
||||
name: name.to_string(),
|
||||
reason: InvalidProjectNameReason::AikenReservedModule,
|
||||
})
|
||||
} else if !regex::Regex::new("^[a-z0-9_-]+/[a-z0-9_-]+$")
|
||||
.expect("regex could not be compiled")
|
||||
.is_match(name)
|
||||
{
|
||||
Err(Error::InvalidProjectName {
|
||||
name: name.to_string(),
|
||||
reason: InvalidProjectNameReason::Format,
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec(args: Args) -> miette::Result<()> {
|
||||
if !args.name.exists() {
|
||||
let project_name = args.name.clone().into_os_string().into_string().unwrap();
|
||||
|
||||
validate_name(&project_name).into_diagnostic()?;
|
||||
|
||||
Creator::new(args, project_name).run()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
16
crates/aiken/src/cmd/tx/mod.rs
Normal file
16
crates/aiken/src/cmd/tx/mod.rs
Normal file
@@ -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),
|
||||
}
|
||||
}
|
||||
125
crates/aiken/src/cmd/tx/simulate.rs
Normal file
125
crates/aiken/src/cmd/tx/simulate.rs
Normal file
@@ -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(())
|
||||
}
|
||||
94
crates/aiken/src/cmd/uplc/eval.rs
Normal file
94
crates/aiken/src/cmd/uplc/eval.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
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,
|
||||
|
||||
#[clap(short, long)]
|
||||
cbor: bool,
|
||||
|
||||
/// Arguments to pass to the uplc program
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
Args {
|
||||
script,
|
||||
flat,
|
||||
args,
|
||||
cbor,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
let mut program = if cbor {
|
||||
let cbor_hex = std::fs::read_to_string(&script).into_diagnostic()?;
|
||||
|
||||
let raw_cbor = hex::decode(cbor_hex).into_diagnostic()?;
|
||||
|
||||
let prog = Program::<FakeNamedDeBruijn>::from_cbor(&raw_cbor, &mut Vec::new())
|
||||
.into_diagnostic()?;
|
||||
|
||||
prog.into()
|
||||
} else 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 budget = ExBudget::default();
|
||||
|
||||
let (term, cost, logs) = program.eval(budget);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
83
crates/aiken/src/cmd/uplc/flat.rs
Normal file
83
crates/aiken/src/cmd/uplc/flat.rs
Normal file
@@ -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(())
|
||||
}
|
||||
30
crates/aiken/src/cmd/uplc/fmt.rs
Normal file
30
crates/aiken/src/cmd/uplc/fmt.rs
Normal file
@@ -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(())
|
||||
}
|
||||
25
crates/aiken/src/cmd/uplc/mod.rs
Normal file
25
crates/aiken/src/cmd/uplc/mod.rs
Normal file
@@ -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),
|
||||
}
|
||||
}
|
||||
61
crates/aiken/src/cmd/uplc/unflat.rs
Normal file
61
crates/aiken/src/cmd/uplc/unflat.rs
Normal file
@@ -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(())
|
||||
}
|
||||
291
crates/aiken/src/lib.rs
Normal file
291
crates/aiken/src/lib.rs
Normal file
@@ -0,0 +1,291 @@
|
||||
use aiken_project::{pretty, script::EvalInfo, telemetry, Project};
|
||||
use miette::IntoDiagnostic;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{collections::BTreeMap, env, path::PathBuf, process};
|
||||
use uplc::machine::cost_model::ExBudget;
|
||||
|
||||
pub mod cmd;
|
||||
|
||||
pub fn with_project<A>(directory: Option<PathBuf>, mut action: A) -> miette::Result<()>
|
||||
where
|
||||
A: FnMut(&mut Project<Terminal>) -> Result<(), aiken_project::error::Error>,
|
||||
{
|
||||
let project_path = if let Some(d) = directory {
|
||||
d
|
||||
} else {
|
||||
env::current_dir().into_diagnostic()?
|
||||
};
|
||||
|
||||
let mut project = match Project::new(project_path, Terminal::default()) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
e.report();
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
println!("\n{}", "Summary".purple().bold());
|
||||
println!(
|
||||
" {} error(s), {}",
|
||||
err.len(),
|
||||
format!("{warning_count} warning(s)").yellow(),
|
||||
);
|
||||
process::exit(1);
|
||||
} else {
|
||||
println!("\n{}", "Summary".purple().bold());
|
||||
println!(
|
||||
" 0 error, {}",
|
||||
format!("{warning_count} warning(s)").yellow(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct Terminal;
|
||||
|
||||
impl telemetry::EventListener for Terminal {
|
||||
fn handle_event(&self, event: telemetry::Event) {
|
||||
match event {
|
||||
telemetry::Event::StartingCompilation {
|
||||
name,
|
||||
version,
|
||||
root,
|
||||
} => {
|
||||
println!(
|
||||
"{} {} {} ({})",
|
||||
" Compiling".bold().purple(),
|
||||
name.bold(),
|
||||
version,
|
||||
root.display().bright_blue()
|
||||
);
|
||||
}
|
||||
telemetry::Event::BuildingDocumentation {
|
||||
name,
|
||||
version,
|
||||
root,
|
||||
} => {
|
||||
println!(
|
||||
"{} {} {} ({})",
|
||||
" Generating documentation".bold().purple(),
|
||||
name.bold(),
|
||||
version,
|
||||
root.to_str().unwrap_or("").bright_blue()
|
||||
);
|
||||
}
|
||||
telemetry::Event::WaitingForBuildDirLock => {
|
||||
println!("{}", "Waiting for build directory lock ...".bold().purple());
|
||||
}
|
||||
telemetry::Event::GeneratingUPLC { output_path, name } => {
|
||||
println!(
|
||||
"{} {} in {}",
|
||||
" Generating".bold().purple(),
|
||||
name.bold(),
|
||||
output_path.display().bright_blue()
|
||||
);
|
||||
}
|
||||
telemetry::Event::GeneratingDocFiles { output_path } => {
|
||||
println!(
|
||||
"{} in {}",
|
||||
" Generating documentation files".bold().purple(),
|
||||
output_path.to_str().unwrap_or("").bright_blue()
|
||||
);
|
||||
}
|
||||
telemetry::Event::GeneratingUPLCFor { name, path } => {
|
||||
println!(
|
||||
"{} {}.{{{}}}",
|
||||
" Generating Untyped Plutus Core for".bold().purple(),
|
||||
path.to_str().unwrap_or("").blue(),
|
||||
name.bright_blue(),
|
||||
);
|
||||
}
|
||||
telemetry::Event::EvaluatingFunction { results } => {
|
||||
println!("{}\n", " Evaluating function ...".bold().purple());
|
||||
|
||||
let (max_mem, max_cpu) = find_max_execution_units(&results);
|
||||
|
||||
for eval_info in &results {
|
||||
println!(" {}", fmt_eval(eval_info, max_mem, max_cpu))
|
||||
}
|
||||
}
|
||||
telemetry::Event::RunningTests => {
|
||||
println!("{} {}\n", " Testing".bold().purple(), "...".bold());
|
||||
}
|
||||
telemetry::Event::FinishedTests { tests } => {
|
||||
let (max_mem, max_cpu) = find_max_execution_units(&tests);
|
||||
|
||||
for (module, infos) in &group_by_module(&tests) {
|
||||
let title = module.bold().blue().to_string();
|
||||
|
||||
let tests = infos
|
||||
.iter()
|
||||
.map(|eval_info| fmt_test(eval_info, max_mem, max_cpu, true))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
let summary = fmt_test_summary(infos, true);
|
||||
|
||||
println!(
|
||||
"{}\n",
|
||||
pretty::indent(
|
||||
&pretty::open_box(&title, &tests, &summary, |border| border
|
||||
.bright_black()
|
||||
.to_string()),
|
||||
4
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
telemetry::Event::DownloadingPackage { name } => {
|
||||
println!("{} {}", " Downloading".bold().purple(), name.bold())
|
||||
}
|
||||
telemetry::Event::PackagesDownloaded { start, count } => {
|
||||
let elapsed = format!("{:.2}s", start.elapsed().as_millis() as f32 / 1000.);
|
||||
|
||||
let msg = match count {
|
||||
1 => format!("1 package in {}", elapsed),
|
||||
_ => format!("{} packages in {}", count, elapsed),
|
||||
};
|
||||
|
||||
println!("{} {}", " Downloaded".bold().purple(), msg.bold())
|
||||
}
|
||||
telemetry::Event::ResolvingVersions => {
|
||||
println!("{}", " Resolving versions".bold().purple(),)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool) -> String {
|
||||
let EvalInfo {
|
||||
success,
|
||||
script,
|
||||
spent_budget,
|
||||
logs,
|
||||
..
|
||||
} = eval_info;
|
||||
|
||||
let ExBudget { mem, cpu } = spent_budget;
|
||||
let mem_pad = pretty::pad_left(mem.to_string(), max_mem, " ");
|
||||
let cpu_pad = pretty::pad_left(cpu.to_string(), max_cpu, " ");
|
||||
|
||||
let test = format!(
|
||||
"{status} [mem: {mem_unit}, cpu: {cpu_unit}] {module}",
|
||||
status = if *success {
|
||||
pretty::style_if(styled, "PASS".to_string(), |s| s.bold().green().to_string())
|
||||
} else {
|
||||
pretty::style_if(styled, "FAIL".to_string(), |s| s.bold().red().to_string())
|
||||
},
|
||||
mem_unit = pretty::style_if(styled, mem_pad, |s| s.bright_white().to_string()),
|
||||
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s.bright_white().to_string()),
|
||||
module = pretty::style_if(styled, script.name.clone(), |s| s.bright_blue().to_string()),
|
||||
);
|
||||
|
||||
let logs = if logs.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
logs.iter()
|
||||
.map(|line| {
|
||||
format!(
|
||||
"{arrow} {styled_line}",
|
||||
arrow = "↳".bright_yellow(),
|
||||
styled_line = line.bright_black()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
if logs.is_empty() {
|
||||
test
|
||||
} else {
|
||||
[test, logs].join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_test_summary(tests: &Vec<&EvalInfo>, styled: bool) -> String {
|
||||
let (n_passed, n_failed) = tests
|
||||
.iter()
|
||||
.fold((0, 0), |(n_passed, n_failed), test_info| {
|
||||
if test_info.success {
|
||||
(n_passed + 1, n_failed)
|
||||
} else {
|
||||
(n_passed, n_failed + 1)
|
||||
}
|
||||
});
|
||||
format!(
|
||||
"{} | {} | {}",
|
||||
pretty::style_if(styled, format!("{} tests", tests.len()), |s| s
|
||||
.bold()
|
||||
.to_string()),
|
||||
pretty::style_if(styled, format!("{} passed", n_passed), |s| s
|
||||
.bright_green()
|
||||
.bold()
|
||||
.to_string()),
|
||||
pretty::style_if(styled, format!("{} failed", n_failed), |s| s
|
||||
.bright_red()
|
||||
.bold()
|
||||
.to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize) -> String {
|
||||
let EvalInfo {
|
||||
output,
|
||||
script,
|
||||
spent_budget,
|
||||
..
|
||||
} = eval_info;
|
||||
|
||||
let ExBudget { mem, cpu } = spent_budget;
|
||||
|
||||
format!(
|
||||
" {}::{} [mem: {}, cpu: {}]\n │\n ╰─▶ {}",
|
||||
script.module.blue(),
|
||||
script.name.bright_blue(),
|
||||
pretty::pad_left(mem.to_string(), max_mem, " "),
|
||||
pretty::pad_left(cpu.to_string(), max_cpu, " "),
|
||||
output
|
||||
.as_ref()
|
||||
.map(|x| format!("{}", x))
|
||||
.unwrap_or_else(|| "Error.".to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
fn group_by_module(infos: &Vec<EvalInfo>) -> BTreeMap<String, Vec<&EvalInfo>> {
|
||||
let mut modules = BTreeMap::new();
|
||||
for eval_info in infos {
|
||||
let xs: &mut Vec<&EvalInfo> = modules.entry(eval_info.script.module.clone()).or_default();
|
||||
xs.push(eval_info);
|
||||
}
|
||||
modules
|
||||
}
|
||||
|
||||
fn find_max_execution_units(xs: &[EvalInfo]) -> (usize, usize) {
|
||||
let (max_mem, max_cpu) = xs.iter().fold(
|
||||
(0, 0),
|
||||
|(max_mem, max_cpu), EvalInfo { spent_budget, .. }| {
|
||||
if spent_budget.mem >= max_mem && spent_budget.cpu >= max_cpu {
|
||||
(spent_budget.mem, spent_budget.cpu)
|
||||
} else if spent_budget.mem > max_mem {
|
||||
(spent_budget.mem, max_cpu)
|
||||
} else if spent_budget.cpu > max_cpu {
|
||||
(max_mem, spent_budget.cpu)
|
||||
} else {
|
||||
(max_mem, max_cpu)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
(max_mem.to_string().len(), max_cpu.to_string().len())
|
||||
}
|
||||
44
crates/aiken/src/main.rs
Normal file
44
crates/aiken/src/main.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use aiken::cmd::{build, check, docs, fmt, lsp, new, tx, uplc};
|
||||
use clap::Parser;
|
||||
|
||||
/// 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),
|
||||
Fmt(fmt::Args),
|
||||
Build(build::Args),
|
||||
Check(check::Args),
|
||||
Docs(docs::Args),
|
||||
|
||||
#[clap(hide = true)]
|
||||
Lsp(lsp::Args),
|
||||
|
||||
#[clap(subcommand)]
|
||||
Tx(tx::Cmd),
|
||||
|
||||
#[clap(subcommand)]
|
||||
Uplc(uplc::Cmd),
|
||||
}
|
||||
|
||||
impl Default for Cmd {
|
||||
fn default() -> Self {
|
||||
Self::parse()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> miette::Result<()> {
|
||||
miette::set_panic_hook();
|
||||
match Cmd::default() {
|
||||
Cmd::New(args) => new::exec(args),
|
||||
Cmd::Fmt(args) => fmt::exec(args),
|
||||
Cmd::Build(args) => build::exec(args),
|
||||
Cmd::Docs(args) => docs::exec(args),
|
||||
Cmd::Check(args) => check::exec(args),
|
||||
Cmd::Lsp(args) => lsp::exec(args),
|
||||
Cmd::Tx(sub_cmd) => tx::exec(sub_cmd),
|
||||
Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user