From 55ecc199d185aad87615a8bff83706edd23aed39 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sat, 4 Feb 2023 09:31:39 +0100 Subject: [PATCH 1/8] Add 'parameters' to blueprints for parameterized validators. Without that, we have no way to distinguish between fully applied validators and those that still require some hard-coded parameters. Next steps is to make it easier to apply parameters to those, as well as forbid the creation of addresses of validators that aren't fully qualified. --- .../aiken-project/src/blueprint/validator.rs | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 6de76d15..faf7b229 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -21,6 +21,8 @@ pub struct Validator { #[serde(skip_serializing_if = "Option::is_none")] pub datum: Option>, pub redeemer: Annotated, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub parameters: Vec>, #[serde(flatten)] pub program: Program, } @@ -58,12 +60,39 @@ impl Validator { assert_min_arity(validator, def, purpose.min_arity())?; let mut args = def.arguments.iter().rev(); - let (_, redeemer, datum) = (args.next(), args.next().unwrap(), args.next()); + let (_, redeemer) = (args.next(), args.next().unwrap()); + let datum = if purpose.min_arity() > 2 { + args.next() + } else { + None + }; Ok(Validator { title: validator.name.clone(), description: None, purpose, + parameters: args + .rev() + .map(|param| { + let annotation = + Annotated::from_type(modules.into(), ¶m.tipo, &HashMap::new()).map_err( + |error| Error::Schema { + error, + location: param.location, + source_code: NamedSource::new( + validator.input_path.display().to_string(), + validator.code.clone(), + ), + }, + ); + annotation.map(|mut annotation| { + annotation.title = annotation + .title + .or_else(|| Some(param.arg_name.get_label())); + annotation + }) + }) + .collect::>()?, datum: datum .map(|datum| { Annotated::from_type(modules.into(), &datum.tipo, &HashMap::new()).map_err( @@ -272,6 +301,32 @@ mod test { ); } + #[test] + fn validator_mint_parameterized() { + assert_validator( + r#" + fn mint(utxo_ref: Int, redeemer: Data, ctx: Data) { + True + } + "#, + json!({ + "title": "test_module", + "purpose": "mint", + "hash": "455f24922a520c59499fdafad95e1272fab81a99452f6b9545f95337", + "parameters": [{ + "title": "utxo_ref", + "dataType": "integer" + + }], + "redeemer": { + "title": "Data", + "description": "Any Plutus data." + }, + "compiledCode": "4d01000022253335734944526161" + }), + ); + } + #[test] fn validator_spend() { assert_validator( From 9c71aab3dbedccb91df7b27d53bf0a66dbb7fed6 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sat, 4 Feb 2023 10:21:45 +0100 Subject: [PATCH 2/8] Factor out reusable parts regarding blueprints from lib/project So we can re-apply the same logic for applying arguments. --- crates/aiken-project/src/blueprint/mod.rs | 60 ++++++++++++++++++++++- crates/aiken-project/src/lib.rs | 47 +++++------------- 2 files changed, 70 insertions(+), 37 deletions(-) diff --git a/crates/aiken-project/src/blueprint/mod.rs b/crates/aiken-project/src/blueprint/mod.rs index 0f23a14d..6e9c2343 100644 --- a/crates/aiken-project/src/blueprint/mod.rs +++ b/crates/aiken-project/src/blueprint/mod.rs @@ -7,12 +7,12 @@ use aiken_lang::uplc::CodeGenerator; use error::Error; use schema::Schema; use std::fmt::{self, Debug, Display}; -use validator::Validator; +use validator::{Purpose, Validator}; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub struct Blueprint { pub preamble: Preamble, - pub validators: Vec>, + pub validators: Vec>, } #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] @@ -25,6 +25,12 @@ pub struct Preamble { pub license: Option, } +#[derive(Debug, PartialEq, Clone)] +pub enum LookupResult<'a, T> { + One(&'a T), + Many, +} + impl Blueprint { pub fn new( config: &Config, @@ -47,6 +53,56 @@ impl Blueprint { } } +impl Blueprint { + pub fn lookup( + &self, + title: Option<&String>, + purpose: Option<&Purpose>, + ) -> Option>> { + let mut validator = None; + for v in self.validators.iter() { + let match_title = Some(&v.title) == title.or(Some(&v.title)); + let match_purpose = Some(&v.purpose) == purpose.or(Some(&v.purpose)); + if match_title && match_purpose { + validator = Some(if validator.is_none() { + LookupResult::One(v) + } else { + LookupResult::Many + }) + } + } + validator + } + + pub fn with_validator( + &self, + title: Option<&String>, + purpose: Option<&Purpose>, + when_missing: fn(Vec<(String, Purpose)>) -> E, + when_too_many: fn(Vec<(String, Purpose)>) -> E, + action: F, + ) -> Result + where + F: Fn(&Validator) -> Result, + { + match self.lookup(title, purpose) { + Some(LookupResult::One(validator)) => action(validator), + Some(LookupResult::Many) => Err(when_too_many( + self.validators + .iter() + .map(|v| (v.title.clone(), v.purpose.clone())) + .collect(), + )), + None => Err(when_missing( + self.validators + .iter() + .map(|v| (v.title.clone(), v.purpose.clone())) + .collect(), + )), + } + } +} + impl Display for Blueprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = serde_json::to_string_pretty(self).map_err(|_| fmt::Error)?; diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index ae14e46f..4f5e55ad 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -12,7 +12,7 @@ pub mod pretty; pub mod script; pub mod telemetry; -use crate::blueprint::{schema::Schema, validator, Blueprint}; +use crate::blueprint::{schema::Schema, validator, Blueprint, LookupResult}; use aiken_lang::{ ast::{Definition, Function, ModuleKind, TypedDataType, TypedFunction}, builder::{DataTypeKey, FunctionAccessKey}, @@ -283,8 +283,8 @@ where pub fn address( &self, - with_title: Option<&String>, - with_purpose: Option<&validator::Purpose>, + title: Option<&String>, + purpose: Option<&validator::Purpose>, stake_address: Option<&String>, ) -> Result { // Parse stake address @@ -312,38 +312,15 @@ where let blueprint: Blueprint = serde_json::from_reader(BufReader::new(blueprint))?; - // Find validator's program - let mut program = None; - for v in blueprint.validators.iter() { - if Some(&v.title) == with_title.or(Some(&v.title)) - && Some(&v.purpose) == with_purpose.or(Some(&v.purpose)) - { - program = Some(if program.is_none() { - Ok(v.program.clone()) - } else { - Err(Error::MoreThanOneValidatorFound { - known_validators: blueprint - .validators - .iter() - .map(|v| (v.title.clone(), v.purpose.clone())) - .collect(), - }) - }) - } - } - - // Print the address - match program { - Some(Ok(program)) => Ok(program.address(Network::Testnet, delegation_part)), - Some(Err(e)) => Err(e), - None => Err(Error::NoValidatorNotFound { - known_validators: blueprint - .validators - .iter() - .map(|v| (v.title.clone(), v.purpose.clone())) - .collect(), - }), - } + // Calculate the address + let when_too_many = + |known_validators| Error::MoreThanOneValidatorFound { known_validators }; + let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators }; + blueprint.with_validator(title, purpose, when_too_many, when_missing, |validator| { + Ok(validator + .program + .address(Network::Testnet, delegation_part.to_owned())) + }) } fn compile_deps(&mut self) -> Result<(), Error> { From 12f476800868dc0587073b77e25fc46178d48cd5 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sat, 4 Feb 2023 10:22:23 +0100 Subject: [PATCH 3/8] Write method to apply a UPLC term to an existing 'Validator' --- crates/aiken-project/src/blueprint/error.rs | 6 ++++++ crates/aiken-project/src/blueprint/validator.rs | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index f9acdb29..a7f93555 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -27,6 +27,7 @@ pub enum Error { source_code: NamedSource, return_type: Arc, }, + #[error("A {} validator requires at least {} arguments.", name.purple().bold(), at_least.to_string().purple().bold())] #[diagnostic(code("aiken::blueprint::invalid::arity"))] WrongValidatorArity { @@ -37,6 +38,7 @@ pub enum Error { #[source_code] source_code: NamedSource, }, + #[error("{}", error)] #[diagnostic(help("{}", error.help()))] #[diagnostic(code("aiken::blueprint::interface"))] @@ -52,6 +54,10 @@ pub enum Error { #[diagnostic(code("aiken::blueprint::missing"))] #[diagnostic(help("Did you forget to {build} the project?", build = "build".purple().bold()))] InvalidOrMissingFile, + + #[error("I didn't find any parameters to apply in the given validator.")] + #[diagnostic(code("aiken::blueprint::apply::no_parameters"))] + NoParametersToApply, } pub fn assert_return_bool(module: &CheckedModule, def: &TypedFunction) -> Result<(), Error> { diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index faf7b229..aef9a368 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -10,7 +10,7 @@ use std::{ collections::HashMap, fmt::{self, Display}, }; -use uplc::ast::{DeBruijn, Program}; +use uplc::ast::{DeBruijn, Program, Term}; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub struct Validator { @@ -122,6 +122,18 @@ impl Validator { .unwrap(), }) } + + pub fn apply(&mut self, arg: &Term) -> Result<(), Error> { + match self.parameters.split_first() { + None => Err(Error::NoParametersToApply), + Some((_, tail)) => { + // TODO: Ideally, we should control that the applied term matches its schema. + self.program = self.program.apply_term(arg); + self.parameters = tail.to_vec(); + Ok(()) + } + } + } } impl Purpose { From 592d3d7a1cc83fdd0942ef6acde499482edf5185 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sat, 4 Feb 2023 10:44:33 +0100 Subject: [PATCH 4/8] Define 'apply_parameter' method on 'Project' --- crates/aiken-project/src/blueprint/mod.rs | 9 +++-- .../aiken-project/src/blueprint/validator.rs | 5 +++ crates/aiken-project/src/lib.rs | 36 ++++++++++++++++--- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/crates/aiken-project/src/blueprint/mod.rs b/crates/aiken-project/src/blueprint/mod.rs index 6e9c2343..7652e7bd 100644 --- a/crates/aiken-project/src/blueprint/mod.rs +++ b/crates/aiken-project/src/blueprint/mod.rs @@ -53,7 +53,10 @@ impl Blueprint { } } -impl Blueprint { +impl Blueprint +where + T: Clone, +{ pub fn lookup( &self, title: Option<&String>, @@ -83,10 +86,10 @@ impl Blueprint { action: F, ) -> Result where - F: Fn(&Validator) -> Result, + F: Fn(Validator) -> Result, { match self.lookup(title, purpose) { - Some(LookupResult::One(validator)) => action(validator), + Some(LookupResult::One(validator)) => action(validator.to_owned()), Some(LookupResult::Many) => Err(when_too_many( self.validators .iter() diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index aef9a368..ea13ce18 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -122,7 +122,12 @@ impl Validator { .unwrap(), }) } +} +impl Validator +where + T: Clone, +{ pub fn apply(&mut self, arg: &Term) -> Result<(), Error> { match self.parameters.split_first() { None => Err(Error::NoParametersToApply), diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index 4f5e55ad..5f79b988 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -12,7 +12,7 @@ pub mod pretty; pub mod script; pub mod telemetry; -use crate::blueprint::{schema::Schema, validator, Blueprint, LookupResult}; +use crate::blueprint::{schema::Schema, validator, Blueprint}; use aiken_lang::{ ast::{Definition, Function, ModuleKind, TypedDataType, TypedFunction}, builder::{DataTypeKey, FunctionAccessKey}, @@ -37,7 +37,7 @@ use std::{ }; use telemetry::EventListener; use uplc::{ - ast::{Constant, Term}, + ast::{Constant, DeBruijn, Term}, machine::cost_model::ExBudget, }; @@ -306,9 +306,8 @@ where }; // Read blueprint - let filepath = self.blueprint_path(); - let blueprint = - File::open(filepath).map_err(|_| blueprint::error::Error::InvalidOrMissingFile)?; + let blueprint = File::open(self.blueprint_path()) + .map_err(|_| blueprint::error::Error::InvalidOrMissingFile)?; let blueprint: Blueprint = serde_json::from_reader(BufReader::new(blueprint))?; @@ -323,6 +322,33 @@ where }) } + pub fn apply_parameter( + &self, + title: Option<&String>, + purpose: Option<&validator::Purpose>, + param: &Term, + ) -> Result, Error> { + // Read blueprint + let blueprint = File::open(self.blueprint_path()) + .map_err(|_| blueprint::error::Error::InvalidOrMissingFile)?; + let blueprint: Blueprint = + serde_json::from_reader(BufReader::new(blueprint))?; + + // Apply parameters + let when_too_many = + |known_validators| Error::MoreThanOneValidatorFound { known_validators }; + let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators }; + blueprint.with_validator( + title, + purpose, + when_too_many, + when_missing, + |mut validator| validator.apply(param).map_err(|e| e.into()), + )?; + + Ok(blueprint) + } + fn compile_deps(&mut self) -> Result<(), Error> { let manifest = deps::download( &self.event_listener, From ea269b14a288edc9df10f8eceedfff9e430ffc2b Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sat, 4 Feb 2023 11:38:09 +0100 Subject: [PATCH 5/8] Fix deserialization issue when 'parameters' is missing. Deserialize to an empty vector. --- crates/aiken-project/src/blueprint/mod.rs | 4 ++-- crates/aiken-project/src/blueprint/schema.rs | 6 ++++++ crates/aiken-project/src/blueprint/validator.rs | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/aiken-project/src/blueprint/mod.rs b/crates/aiken-project/src/blueprint/mod.rs index 7652e7bd..a420d6a1 100644 --- a/crates/aiken-project/src/blueprint/mod.rs +++ b/crates/aiken-project/src/blueprint/mod.rs @@ -10,7 +10,7 @@ use std::fmt::{self, Debug, Display}; use validator::{Purpose, Validator}; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] -pub struct Blueprint { +pub struct Blueprint { pub preamble: Preamble, pub validators: Vec>, } @@ -55,7 +55,7 @@ impl Blueprint { impl Blueprint where - T: Clone, + T: Clone + Default, { pub fn lookup( &self, diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index f598199b..df96b9a5 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -302,6 +302,12 @@ impl Data { } } +impl Default for Schema { + fn default() -> Self { + Schema::Unit + } +} + impl Display for Schema { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = serde_json::to_string_pretty(self).map_err(|_| fmt::Error)?; diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index ea13ce18..84e86645 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -22,6 +22,7 @@ pub struct Validator { pub datum: Option>, pub redeemer: Annotated, #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] pub parameters: Vec>, #[serde(flatten)] pub program: Program, From 9b8ff590b8453f283d177b389a2f9a52fb84999f Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sat, 4 Feb 2023 11:39:55 +0100 Subject: [PATCH 6/8] Implement 'blueprint apply' command. This is still a bit clunky as the interface is expecting parameters in UPLC form and we don't do any kind of verification. So it is easy to shoot oneself in the foot at the moment (for example, to apply an integer into something that should have received a data). To be improved later. --- .../aiken-project/src/blueprint/validator.rs | 10 ++-- crates/aiken-project/src/lib.rs | 28 ++++++--- .../aiken/src/cmd/{ => blueprint}/address.rs | 0 crates/aiken/src/cmd/blueprint/apply.rs | 60 +++++++++++++++++++ crates/aiken/src/cmd/blueprint/mod.rs | 19 ++++++ crates/aiken/src/cmd/mod.rs | 2 +- crates/aiken/src/main.rs | 7 ++- 7 files changed, 112 insertions(+), 14 deletions(-) rename crates/aiken/src/cmd/{ => blueprint}/address.rs (100%) create mode 100644 crates/aiken/src/cmd/blueprint/apply.rs create mode 100644 crates/aiken/src/cmd/blueprint/mod.rs diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 84e86645..18f230f9 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -129,14 +129,16 @@ impl Validator where T: Clone, { - pub fn apply(&mut self, arg: &Term) -> Result<(), Error> { + pub fn apply(self, arg: &Term) -> Result { match self.parameters.split_first() { None => Err(Error::NoParametersToApply), Some((_, tail)) => { // TODO: Ideally, we should control that the applied term matches its schema. - self.program = self.program.apply_term(arg); - self.parameters = tail.to_vec(); - Ok(()) + Ok(Self { + program: self.program.apply_term(arg), + parameters: tail.to_vec(), + ..self + }) } } } diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index 5f79b988..d54c2ff4 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -331,20 +331,32 @@ where // Read blueprint let blueprint = File::open(self.blueprint_path()) .map_err(|_| blueprint::error::Error::InvalidOrMissingFile)?; - let blueprint: Blueprint = + let mut blueprint: Blueprint = serde_json::from_reader(BufReader::new(blueprint))?; // Apply parameters let when_too_many = |known_validators| Error::MoreThanOneValidatorFound { known_validators }; let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators }; - blueprint.with_validator( - title, - purpose, - when_too_many, - when_missing, - |mut validator| validator.apply(param).map_err(|e| e.into()), - )?; + let applied_validator = + blueprint.with_validator(title, purpose, when_too_many, when_missing, |validator| { + validator.apply(param).map_err(|e| e.into()) + })?; + + // Overwrite validator + blueprint.validators = blueprint + .validators + .into_iter() + .map(|validator| { + let same_title = validator.title == applied_validator.title; + let same_purpose = validator.purpose == applied_validator.purpose; + if same_title && same_purpose { + applied_validator.to_owned() + } else { + validator + } + }) + .collect(); Ok(blueprint) } diff --git a/crates/aiken/src/cmd/address.rs b/crates/aiken/src/cmd/blueprint/address.rs similarity index 100% rename from crates/aiken/src/cmd/address.rs rename to crates/aiken/src/cmd/blueprint/address.rs diff --git a/crates/aiken/src/cmd/blueprint/apply.rs b/crates/aiken/src/cmd/blueprint/apply.rs new file mode 100644 index 00000000..6f290146 --- /dev/null +++ b/crates/aiken/src/cmd/blueprint/apply.rs @@ -0,0 +1,60 @@ +use crate::with_project; +use aiken_lang::VALIDATOR_NAMES; +use aiken_project::error::Error; +use miette::IntoDiagnostic; +use std::{fs, path::PathBuf}; +use uplc::{ + ast::{DeBruijn, Term}, + parser, +}; + +#[derive(clap::Args)] +#[clap(setting(clap::AppSettings::DeriveDisplayOrder))] +/// Apply a parameter to a parameterized validator. +pub struct Args { + /// Path to project + directory: Option, + + /// Name of the validator's module within the project. Optional if there's only one validator. + #[clap(short, long)] + validator: Option, + + /// Purpose of the validator within the module. Optional if there's only one validator. + #[clap(short, long, possible_values=&VALIDATOR_NAMES)] + purpose: Option, + + /// The parameter, using high-level UPLC-syntax + #[clap(long)] + parameter: String, +} + +pub fn exec( + Args { + directory, + validator, + purpose, + parameter, + }: Args, +) -> miette::Result<()> { + let term: Term = parser::term(¶meter) + .into_diagnostic()? + .try_into() + .into_diagnostic()?; + + with_project(directory, |p| { + let blueprint = p.apply_parameter( + validator.as_ref(), + purpose + .as_ref() + .map(|p| p.clone().try_into().unwrap()) + .as_ref(), + &term, + )?; + + let json = serde_json::to_string_pretty(&blueprint).unwrap(); + fs::write(p.blueprint_path(), json).map_err(|error| Error::FileIo { + error, + path: p.blueprint_path(), + }) + }) +} diff --git a/crates/aiken/src/cmd/blueprint/mod.rs b/crates/aiken/src/cmd/blueprint/mod.rs new file mode 100644 index 00000000..0dde4d8a --- /dev/null +++ b/crates/aiken/src/cmd/blueprint/mod.rs @@ -0,0 +1,19 @@ +pub mod address; +pub mod apply; + +use clap::Subcommand; + +/// Commands for working with Plutus blueprints +#[derive(Subcommand)] +#[clap(setting(clap::AppSettings::DeriveDisplayOrder))] +pub enum Cmd { + Address(address::Args), + Apply(apply::Args), +} + +pub fn exec(cmd: Cmd) -> miette::Result<()> { + match cmd { + Cmd::Address(args) => address::exec(args), + Cmd::Apply(args) => apply::exec(args), + } +} diff --git a/crates/aiken/src/cmd/mod.rs b/crates/aiken/src/cmd/mod.rs index 1f69dae9..bfef865e 100644 --- a/crates/aiken/src/cmd/mod.rs +++ b/crates/aiken/src/cmd/mod.rs @@ -1,4 +1,4 @@ -pub mod address; +pub mod blueprint; pub mod build; pub mod check; pub mod docs; diff --git a/crates/aiken/src/main.rs b/crates/aiken/src/main.rs index 597f134a..f3bc0fc4 100644 --- a/crates/aiken/src/main.rs +++ b/crates/aiken/src/main.rs @@ -1,5 +1,6 @@ use aiken::cmd::{ - address, build, check, docs, fmt, lsp, new, + blueprint::{self, address}, + build, check, docs, fmt, lsp, new, packages::{self, add}, tx, uplc, }; @@ -19,6 +20,9 @@ pub enum Cmd { Docs(docs::Args), Add(add::Args), + #[clap(subcommand)] + Blueprint(blueprint::Cmd), + #[clap(subcommand)] Packages(packages::Cmd), @@ -48,6 +52,7 @@ fn main() -> miette::Result<()> { Cmd::Check(args) => check::exec(args), Cmd::Docs(args) => docs::exec(args), Cmd::Add(args) => add::exec(args), + Cmd::Blueprint(args) => blueprint::exec(args), Cmd::Packages(args) => packages::exec(args), Cmd::Lsp(args) => lsp::exec(args), Cmd::Tx(sub_cmd) => tx::exec(sub_cmd), From b04fb5962acc2a06b45904aa4e97bedfe62955a1 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sat, 4 Feb 2023 11:49:56 +0100 Subject: [PATCH 7/8] Do not compute addresses of parameterized validators And leave a proper notice. --- crates/aiken-project/src/blueprint/error.rs | 7 ++++++- crates/aiken-project/src/lib.rs | 11 ++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index a7f93555..0fceaab4 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -44,7 +44,7 @@ pub enum Error { #[diagnostic(code("aiken::blueprint::interface"))] Schema { error: schema::Error, - #[label("invalid contract's boundary")] + #[label("invalid validator's boundary")] location: Span, #[source_code] source_code: NamedSource, @@ -58,6 +58,11 @@ pub enum Error { #[error("I didn't find any parameters to apply in the given validator.")] #[diagnostic(code("aiken::blueprint::apply::no_parameters"))] NoParametersToApply, + + #[error("I couldn't compute the address of the given validator because it's parameterized by {} parameter(s)!", format!("{n}").purple())] + #[diagnostic(code("aiken::blueprint::address::parameterized"))] + #[diagnostic(help("I can only compute addresses of validators that are fully applied. For example, a {keyword_spend} validator must have exactly 3 arguments: a datum, a redeemer and a context. If it has more, they need to be provided beforehand and applied directly in the validator. Applying parameters change the validator's compiled code, and thus the address.\n\nThis is why I need you to apply parmeters first.", keyword_spend = "spend".purple()))] + ParameterizedValidator { n: usize }, } pub fn assert_return_bool(module: &CheckedModule, def: &TypedFunction) -> Result<(), Error> { diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index d54c2ff4..da0923ef 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -316,9 +316,14 @@ where |known_validators| Error::MoreThanOneValidatorFound { known_validators }; let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators }; blueprint.with_validator(title, purpose, when_too_many, when_missing, |validator| { - Ok(validator - .program - .address(Network::Testnet, delegation_part.to_owned())) + let n = validator.parameters.len(); + if n > 0 { + Err(blueprint::error::Error::ParameterizedValidator { n }.into()) + } else { + Ok(validator + .program + .address(Network::Testnet, delegation_part.to_owned())) + } }) } From 4aa92a6d5e73de55443d1be6b35605697db0b70b Mon Sep 17 00:00:00 2001 From: KtorZ Date: Tue, 7 Feb 2023 11:42:42 +0100 Subject: [PATCH 8/8] Use positional argument for blueprint 'apply' command --- crates/aiken/src/cmd/blueprint/apply.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/aiken/src/cmd/blueprint/apply.rs b/crates/aiken/src/cmd/blueprint/apply.rs index 6f290146..9fb70586 100644 --- a/crates/aiken/src/cmd/blueprint/apply.rs +++ b/crates/aiken/src/cmd/blueprint/apply.rs @@ -24,7 +24,6 @@ pub struct Args { purpose: Option, /// The parameter, using high-level UPLC-syntax - #[clap(long)] parameter: String, }