From 4799af3242038c36318af88f5f66e3dc0dd430ca Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 7 Apr 2023 17:40:40 +0200 Subject: [PATCH] Rework 'blueprint apply' command and wrap up wiring up validation. The apply command now works only from a serialized CBOR data (instead of a UPLC syntax). So it is no longer possible to specify arbitrary cbor terms through the CLI. I believe it to be an acceptable limitation for now; especially given that Aiken will never generate blueprints with non-data terms at the interface boundary. --- crates/aiken-project/src/blueprint/error.rs | 11 ++- .../aiken-project/src/blueprint/validator.rs | 8 +- crates/aiken-project/src/lib.rs | 4 +- crates/aiken/src/cmd/blueprint/apply.rs | 98 +++++++++++++++---- 4 files changed, 95 insertions(+), 26 deletions(-) diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index 894ec7ed..9aac7739 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -59,15 +59,15 @@ pub enum Error { #[error("I couldn't find a definition corresponding to a reference.")] #[diagnostic(code("aiken::blueprint::apply::unknown::reference"))] #[diagnostic(help( - "While resolving a schema definition, I stumble upon an unknown reference:\n\n {reference}\n\nThis is unfortunate, but signals that either the reference is invalid or that the correspond schema definition is missing.", - reference = reference.as_json_pointer() + "While resolving a schema definition, I stumbled upon an unknown reference:\n\n→ {reference}\n\nThis is unfortunate, but signals that either the reference is invalid or that the corresponding schema definition is missing. Double-check the blueprint for that reference or definition.", + reference = reference.as_json_pointer().if_supports_color(Stdout, |s| s.red()) ))] UnresolvedSchemaReference { reference: Reference }, #[error("I caught a parameter application that seems off.")] #[diagnostic(code("aiken::blueprint::apply::mismatch"))] #[diagnostic(help( - "When applying parameters to a validator, I control that the shape of the parameter you give me matches what is specified in the blueprint. Unfortunately, schemas didn't match in this case.\n\nI am expecting something of the shape:\n\n{expected}Which I couldn't match against the following term:\n\n{term}\n\nNote that this may only represent part of a bigger whole.", + "When applying parameters to a validator, I control that the shape of the parameter you give me matches what is specified in the blueprint. Unfortunately, it didn't match in this case.\n\nI am looking at the following value:\n\n{term}\n\nbut failed to match it against the specified schema:\n\n{expected}\n\n\nNOTE: this may only represent part of a bigger whole as I am validating the parameter incrementally.", expected = serde_json::to_string_pretty(&schema).unwrap().if_supports_color(Stdout, |s| s.green()), term = { let mut buf = vec![]; @@ -92,6 +92,11 @@ pub enum Error { found = found.if_supports_color(Stdout, |s| s.red()), ))] TupleItemsMismatch { expected: usize, found: usize }, + + #[error("I failed to convert some input into a valid parameter")] + #[diagnostic(code("aiken::blueprint::parse::parameter"))] + #[diagnostic(help("{hint}"))] + MalformedParameter { hint: String }, } unsafe impl Send for Error {} diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 7d8d87d5..1b46ff91 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -154,11 +154,15 @@ impl Validator { } impl Validator { - pub fn apply(self, arg: &Term) -> Result { + pub fn apply( + self, + definitions: &Definitions>, + arg: &Term, + ) -> Result { match self.parameters.split_first() { None => Err(Error::NoParametersToApply), Some((head, tail)) => { - head.validate(&self.definitions, arg)?; + head.validate(definitions, arg)?; Ok(Self { program: self.program.apply_term(arg), parameters: tail.to_vec(), diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index 6c455270..28677b20 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -391,7 +391,9 @@ where let applied_validator = blueprint.with_validator(title, when_too_many, when_missing, |validator| { - validator.apply(param).map_err(|e| e.into()) + validator + .apply(&blueprint.definitions, param) + .map_err(|e| e.into()) })?; // Overwrite validator diff --git a/crates/aiken/src/cmd/blueprint/apply.rs b/crates/aiken/src/cmd/blueprint/apply.rs index d9d3ff2c..d98200f9 100644 --- a/crates/aiken/src/cmd/blueprint/apply.rs +++ b/crates/aiken/src/cmd/blueprint/apply.rs @@ -1,18 +1,22 @@ use crate::with_project; -use aiken_project::error::Error; -use miette::IntoDiagnostic; -use std::{fs, path::PathBuf}; -use uplc::{ - ast::{DeBruijn, Term}, - parser, -}; +use aiken_project::{blueprint, error::Error}; +use owo_colors::{OwoColorize, Stream::Stderr}; +use std::{fs, path::PathBuf, process, rc::Rc}; +use uplc::ast::{Constant, DeBruijn, Term}; /// Apply a parameter to a parameterized validator. #[derive(clap::Args)] pub struct Args { + /// The parameter, as a Plutus Data (CBOR, hex-encoded) + parameter: String, + /// Path to project directory: Option, + /// Output file. Optional, print on stdout when omitted. + #[clap(short, long)] + out: Option, + /// Name of the validator's module within the project. Optional if there's only one validator. #[clap(short, long)] module: Option, @@ -20,23 +24,58 @@ pub struct Args { /// Name of the validator within the module. Optional if there's only one validator. #[clap(short, long)] validator: Option, - - /// The parameter, using high-level UPLC-syntax - parameter: String, } pub fn exec( Args { + parameter, directory, + out, module, validator, - parameter, }: Args, ) -> miette::Result<()> { - let term: Term = parser::term(¶meter) - .into_diagnostic()? - .try_into() - .into_diagnostic()?; + eprintln!( + "{} inputs", + " Parsing" + .if_supports_color(Stderr, |s| s.purple()) + .if_supports_color(Stderr, |s| s.bold()), + ); + + let bytes = hex::decode(parameter) + .map_err::(|e| { + blueprint::error::Error::MalformedParameter { + hint: format!("Invalid hex-encoded string: {e}"), + } + .into() + }) + .unwrap_or_else(|e| { + println!(); + e.report(); + process::exit(1) + }); + + let data = uplc::plutus_data(&bytes) + .map_err::(|e| { + blueprint::error::Error::MalformedParameter { + hint: format!("Invalid Plutus data; malformed CBOR encoding: {e}"), + } + .into() + }) + .unwrap_or_else(|e| { + println!(); + e.report(); + process::exit(1) + }); + + let term: Term = Term::Constant(Rc::new(Constant::Data(data))); + + eprintln!( + "{} blueprint", + " Analyzing" + .if_supports_color(Stderr, |s| s.purple()) + .if_supports_color(Stderr, |s| s.bold()), + ); with_project(directory, |p| { let title = module.as_ref().map(|m| { @@ -51,16 +90,35 @@ pub fn exec( let title = title.as_ref().or(validator.as_ref()); + eprintln!( + "{} parameter", + " Applying" + .if_supports_color(Stderr, |s| s.purple()) + .if_supports_color(Stderr, |s| s.bold()), + ); + let blueprint = p.apply_parameter(title, &term)?; let json = serde_json::to_string_pretty(&blueprint).unwrap(); - fs::write(p.blueprint_path(), json).map_err(|error| { - Error::FileIo { + match out { + None => { + println!("\n{}\n", json); + Ok(()) + } + Some(ref path) => fs::write(path, json).map_err(|error| Error::FileIo { error, path: p.blueprint_path(), - } - .into() - }) + }), + }?; + + eprintln!( + "{}", + " Done" + .if_supports_color(Stderr, |s| s.purple()) + .if_supports_color(Stderr, |s| s.bold()), + ); + + Ok(()) }) }