From 30841fe000ce50235e40e735895fc35f5d0c505f Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sun, 3 Mar 2024 19:24:35 +0100 Subject: [PATCH] Rework generate_raw to avoid need to intern in prop tests Also, this commit makes `apply_term` automatically re-intern the program since it isn't safe to apply any term onto a UPLC program. In particular, terms that introduce new let-bindings (via lambdas) will mess with the already generated DeBruijn indices. The problem doesn't occur for pure constant terms like Data. So we still have a safe and fast version 'apply_data' when needed. --- crates/aiken-lang/src/gen_uplc.rs | 17 +++++- .../aiken-project/src/blueprint/parameter.rs | 22 ++++--- .../aiken-project/src/blueprint/validator.rs | 32 +++++----- crates/aiken-project/src/lib.rs | 10 ++-- crates/aiken-project/src/test_framework.rs | 59 ++++++++----------- crates/aiken-project/src/tests/gen_uplc.rs | 2 +- crates/aiken/src/cmd/blueprint/apply.rs | 41 ++++++------- crates/aiken/src/cmd/uplc/eval.rs | 24 ++++---- crates/uplc/src/ast.rs | 40 ++++++++----- 9 files changed, 127 insertions(+), 120 deletions(-) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index af367df3..44b7f16f 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -184,8 +184,13 @@ impl<'a> CodeGenerator<'a> { self.finalize(term) } - pub fn generate_raw(&mut self, test_body: &TypedExpr, module_name: &str) -> Program { - let mut air_tree = self.build(test_body, module_name, &[]); + pub fn generate_raw( + &mut self, + body: &TypedExpr, + args: &[TypedArg], + module_name: &str, + ) -> Program { + let mut air_tree = self.build(body, module_name, &[]); air_tree = AirTree::no_op(air_tree); @@ -194,7 +199,13 @@ impl<'a> CodeGenerator<'a> { // optimizations on air tree let full_vec = full_tree.to_vec(); - let term = self.uplc_code_gen(full_vec); + let mut term = self.uplc_code_gen(full_vec); + + term = if args.is_empty() { + term + } else { + cast_validator_args(term, args) + }; self.finalize(term) } diff --git a/crates/aiken-project/src/blueprint/parameter.rs b/crates/aiken-project/src/blueprint/parameter.rs index ef0633e6..9dfae48b 100644 --- a/crates/aiken-project/src/blueprint/parameter.rs +++ b/crates/aiken-project/src/blueprint/parameter.rs @@ -5,7 +5,7 @@ use super::{ }; use std::{iter, ops::Deref}; use uplc::{ - ast::{Constant, Data as UplcData, DeBruijn, Term}, + ast::{Constant, Data as UplcData}, PlutusData, }; @@ -30,7 +30,7 @@ impl Parameter { pub fn validate( &self, definitions: &Definitions>, - term: &Term, + constant: &Constant, ) -> Result<(), Error> { let schema = &definitions .lookup(&self.schema) @@ -42,11 +42,7 @@ impl Parameter { })? .annotated; - if let Term::Constant(constant) = term { - validate_schema(schema, definitions, constant) - } else { - Err(Error::NonConstantParameter) - } + validate_schema(schema, definitions, constant) } } @@ -352,11 +348,13 @@ fn expect_data_constr(term: &Constant, index: usize) -> Result, Er Err(mismatch( term, - Schema::Data(Data::AnyOf(vec![Constructor { - index, - fields: vec![], - } - .into()])), + Schema::Data(Data::AnyOf(vec![ + Constructor { + index, + fields: vec![], + } + .into(), + ])), )) } diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 71be9545..1a013a4e 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -12,9 +12,9 @@ use aiken_lang::{ }; use miette::NamedSource; use serde; -use std::{borrow::Borrow, rc::Rc}; +use std::borrow::Borrow; use uplc::{ - ast::{Constant, DeBruijn, Program, Term}, + ast::{Constant, DeBruijn, Program}, PlutusData, }; @@ -198,14 +198,14 @@ impl Validator { pub fn apply( self, definitions: &Definitions>, - arg: &Term, + arg: &PlutusData, ) -> Result { match self.parameters.split_first() { None => Err(Error::NoParametersToApply), Some((head, tail)) => { - head.validate(definitions, arg)?; + head.validate(definitions, &Constant::Data(arg.clone()))?; Ok(Self { - program: self.program.apply_term(arg), + program: self.program.apply_data(arg.clone()), parameters: tail.to_vec(), ..self }) @@ -217,7 +217,7 @@ impl Validator { &self, definitions: &Definitions>, ask: F, - ) -> Result, Error> + ) -> Result where F: Fn(&Annotated, &Definitions>) -> Result, { @@ -241,7 +241,7 @@ impl Validator { let data = ask(&schema, definitions)?; - Ok(Term::Constant(Rc::new(Constant::Data(data.clone())))) + Ok(data.clone()) } } } @@ -619,7 +619,7 @@ mod tests { fn validate_arguments_integer() { let definitions = fixture_definitions(); - let term = Term::data(uplc_ast::Data::integer(42.into())); + let term = Constant::Data(uplc_ast::Data::integer(42.into())); let param = Parameter { title: None, @@ -633,7 +633,7 @@ mod tests { fn validate_arguments_bytestring() { let definitions = fixture_definitions(); - let term = Term::data(uplc_ast::Data::bytestring(vec![102, 111, 111])); + let term = Constant::Data(uplc_ast::Data::bytestring(vec![102, 111, 111])); let param = Parameter { title: None, @@ -662,7 +662,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::list(vec![ + let term = Constant::Data(uplc_ast::Data::list(vec![ uplc_ast::Data::integer(42.into()), uplc_ast::Data::integer(14.into()), ])); @@ -691,7 +691,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::list(vec![uplc_ast::Data::bytestring( + let term = Constant::Data(uplc_ast::Data::list(vec![uplc_ast::Data::bytestring( vec![102, 111, 111], )])); @@ -723,7 +723,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::list(vec![ + let term = Constant::Data(uplc_ast::Data::list(vec![ uplc_ast::Data::integer(42.into()), uplc_ast::Data::bytestring(vec![102, 111, 111]), ])); @@ -754,7 +754,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::map(vec![( + let term = Constant::Data(uplc_ast::Data::map(vec![( uplc_ast::Data::bytestring(vec![102, 111, 111]), uplc_ast::Data::integer(42.into()), )])); @@ -770,7 +770,7 @@ mod tests { let definitions = fixture_definitions(); - let term = Term::data(uplc_ast::Data::constr(1, vec![])); + let term = Constant::Data(uplc_ast::Data::constr(1, vec![])); let param: Parameter = schema.into(); @@ -807,7 +807,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::constr( + let term = Constant::Data(uplc_ast::Data::constr( 0, vec![uplc_ast::Data::constr(0, vec![])], )); @@ -863,7 +863,7 @@ mod tests { .into(), ); - let term = Term::data(uplc_ast::Data::constr( + let term = Constant::Data(uplc_ast::Data::constr( 1, vec![ uplc_ast::Data::integer(14.into()), diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index 70d51a0f..f413ccc2 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -59,7 +59,7 @@ use std::{ use telemetry::EventListener; use test_framework::{Test, TestResult}; use uplc::{ - ast::{DeBruijn, Name, Program, Term}, + ast::{Name, Program}, PlutusData, }; @@ -445,7 +445,7 @@ where &self, title: Option<&String>, ask: F, - ) -> Result, Error> + ) -> Result where F: Fn( &Annotated, @@ -462,19 +462,19 @@ where |known_validators| Error::MoreThanOneValidatorFound { known_validators }; let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators }; - let term = blueprint.with_validator(title, when_too_many, when_missing, |validator| { + let data = blueprint.with_validator(title, when_too_many, when_missing, |validator| { validator .ask_next_parameter(&blueprint.definitions, &ask) .map_err(|e| e.into()) })?; - Ok(term) + Ok(data) } pub fn apply_parameter( &self, title: Option<&String>, - param: &Term, + param: &PlutusData, ) -> Result { // Read blueprint let blueprint = File::open(self.blueprint_path()) diff --git a/crates/aiken-project/src/test_framework.rs b/crates/aiken-project/src/test_framework.rs index 34e2eb83..e4d5a658 100644 --- a/crates/aiken-project/src/test_framework.rs +++ b/crates/aiken-project/src/test_framework.rs @@ -1,11 +1,8 @@ use crate::pretty; use aiken_lang::{ - ast::{BinOp, DataTypeKey, Span, TypedDataType, TypedTest}, - expr::{TypedExpr, UntypedExpr}, - gen_uplc::{ - builder::{convert_data_to_type, convert_opaque_type}, - CodeGenerator, - }, + ast::{Arg, BinOp, DataTypeKey, TypedDataType, TypedTest}, + expr::UntypedExpr, + gen_uplc::{builder::convert_opaque_type, CodeGenerator}, tipo::Type, }; use indexmap::IndexMap; @@ -22,7 +19,6 @@ use std::{ use uplc::{ ast::{Constant, Data, Name, NamedDeBruijn, Program, Term}, machine::{cost_model::ExBudget, eval_result::EvalResult}, - parser::interner::Interner, }; /// ----- Test ----------------------------------------------------------------- @@ -94,20 +90,20 @@ impl Test { input_path: PathBuf, ) -> Test { if test.arguments.is_empty() { - let program = generator.generate_raw(&test.body, &module_name); + let program = generator.generate_raw(&test.body, &[], &module_name); // TODO: Check whether we really need to clone the _entire_ generator, or whether we // can mostly copy the generator and only clone parts that matters. let assertion = test.test_hint().map(|(bin_op, left_src, right_src)| { let left = generator .clone() - .generate_raw(&left_src, &module_name) + .generate_raw(&left_src, &[], &module_name) .try_into() .unwrap(); let right = generator .clone() - .generate_raw(&right_src, &module_name) + .generate_raw(&right_src, &[], &module_name) .try_into() .unwrap(); @@ -134,23 +130,21 @@ impl Test { let type_info = parameter.tipo.clone(); - // TODO: Possibly refactor 'generate_raw' to accept arguments and do this wrapping - // itself. - let body = TypedExpr::Fn { - location: Span::empty(), - tipo: Rc::new(Type::Fn { - args: vec![type_info.clone()], - ret: test.body.tipo(), - }), - is_capture: false, - args: vec![parameter.into()], - body: Box::new(test.body), - return_annotation: None, - }; + let stripped_type_info = convert_opaque_type(&type_info, generator.data_types(), true); - let program = generator.clone().generate_raw(&body, &module_name); + let program = generator.clone().generate_raw( + &test.body, + &[Arg { + tipo: stripped_type_info.clone(), + ..parameter.clone().into() + }], + &module_name, + ); - let fuzzer = generator.clone().generate_raw(&via, &module_name); + // NOTE: We need not to pass any parameter to the fuzzer here because the fuzzer + // argument is a Data constructor which needs not any conversion. So we can just safely + // apply onto it later. + let fuzzer = generator.clone().generate_raw(&via, &[], &module_name); Self::property_test( input_path, @@ -160,11 +154,7 @@ impl Test { program, Fuzzer { program: fuzzer, - stripped_type_info: convert_opaque_type( - &type_info, - generator.data_types(), - true, - ), + stripped_type_info, type_info, }, ) @@ -277,6 +267,8 @@ impl PropertyTest { } = next_prng { if result.failed(self.can_error) { + println!("{:#?}", result.result()); + let mut counterexample = Counterexample { value, choices: next_prng.choices(), @@ -297,12 +289,7 @@ impl PropertyTest { } pub fn eval(&self, value: &PlutusData) -> EvalResult { - let term: Term = - convert_data_to_type(Term::data(value.clone()), &self.fuzzer.stripped_type_info); - - let mut program = self.program.apply_term(&term); - - Interner::new().program(&mut program); + let program = self.program.apply_data(value.clone()); Program::::try_from(program) .unwrap() diff --git a/crates/aiken-project/src/tests/gen_uplc.rs b/crates/aiken-project/src/tests/gen_uplc.rs index fe2b22c1..cc51c24f 100644 --- a/crates/aiken-project/src/tests/gen_uplc.rs +++ b/crates/aiken-project/src/tests/gen_uplc.rs @@ -49,7 +49,7 @@ fn assert_uplc(source_code: &str, expected: Term, should_fail: bool) { match &script.2 { TestType::Func(Function { body: func, .. }) => { - let program = generator.generate_raw(func, &script.1); + let program = generator.generate_raw(func, &[], &script.1); let debruijn_program: Program = program.try_into().unwrap(); diff --git a/crates/aiken/src/cmd/blueprint/apply.rs b/crates/aiken/src/cmd/blueprint/apply.rs index 63b9956a..a25c3073 100644 --- a/crates/aiken/src/cmd/blueprint/apply.rs +++ b/crates/aiken/src/cmd/blueprint/apply.rs @@ -13,9 +13,8 @@ use num_bigint::BigInt; use ordinal::Ordinal; use owo_colors::{OwoColorize, Stream::Stderr}; use pallas::ledger::primitives::alonzo::PlutusData; -use std::str::FromStr; -use std::{fs, path::PathBuf, process, rc::Rc}; -use uplc::ast::{Constant, Data as UplcData, DeBruijn, Term}; +use std::{fs, path::PathBuf, process, str::FromStr}; +use uplc::ast::Data as UplcData; /// Apply a parameter to a parameterized validator. #[derive(clap::Args)] @@ -68,7 +67,7 @@ pub fn exec( .if_supports_color(Stderr, |s| s.bold()), ); - let term: Term = match ¶meter { + let data: PlutusData = match ¶meter { Some(param) => { eprintln!( "{} inputs", @@ -90,7 +89,7 @@ pub fn exec( process::exit(1) }); - let data = uplc::plutus_data(&bytes) + uplc::plutus_data(&bytes) .map_err::(|e| { blueprint::error::Error::MalformedParameter { hint: format!("Invalid Plutus data; malformed CBOR encoding: {e}"), @@ -101,9 +100,7 @@ pub fn exec( println!(); e.report(); process::exit(1) - }); - - Term::Constant(Rc::new(Constant::Data(data))) + }) } None => p.construct_parameter_incrementally(title, ask_schema)?, @@ -114,16 +111,13 @@ pub fn exec( " Applying" .if_supports_color(Stderr, |s| s.purple()) .if_supports_color(Stderr, |s| s.bold()), - match TryInto::::try_into(term.clone()) { - Ok(data) => { - let padding = "\n "; - multiline(48, UplcData::to_hex(data)).join(padding) - } - Err(_) => term.to_pretty(), + { + let padding = "\n "; + multiline(48, UplcData::to_hex(data.clone())).join(padding) } ); - let blueprint = p.apply_parameter(title, &term)?; + let blueprint = p.apply_parameter(title, &data)?; let json = serde_json::to_string_pretty(&blueprint).unwrap(); @@ -179,16 +173,21 @@ fn ask_schema( } Schema::Data(Data::List(Items::Many(ref decls))) => { - eprintln!(" {}", asking(schema, "Found", &format!("a {}-tuple", decls.len()))); + eprintln!( + " {}", + asking(schema, "Found", &format!("a {}-tuple", decls.len())) + ); let mut elems = vec![]; for (ix, decl) in decls.iter().enumerate() { eprintln!( " {} Tuple's {}{} element", - "Asking".if_supports_color(Stderr, |s| s.purple()).if_supports_color(Stderr, |s| s.bold()), - ix+1, - Ordinal::(ix+1).suffix() + "Asking" + .if_supports_color(Stderr, |s| s.purple()) + .if_supports_color(Stderr, |s| s.bold()), + ix + 1, + Ordinal::(ix + 1).suffix() ); let inner_schema = lookup_declaration(&decl.clone().into(), definitions); elems.push(ask_schema(&inner_schema, definitions)?); @@ -252,7 +251,9 @@ fn ask_schema( Ok(UplcData::constr(ix.try_into().unwrap(), fields)) } - _ => unimplemented!("Hey! You've found a case that we haven't implemented yet. Yes, we've been a bit lazy on that one... If that use-case is important to you, please let us know on Discord or on Github."), + _ => unimplemented!( + "Hey! You've found a case that we haven't implemented yet. Yes, we've been a bit lazy on that one... If that use-case is important to you, please let us know on Discord or on Github." + ), } } diff --git a/crates/aiken/src/cmd/uplc/eval.rs b/crates/aiken/src/cmd/uplc/eval.rs index 7453e472..b1ba5935 100644 --- a/crates/aiken/src/cmd/uplc/eval.rs +++ b/crates/aiken/src/cmd/uplc/eval.rs @@ -30,39 +30,41 @@ pub fn exec( cbor, }: Args, ) -> miette::Result<()> { - let mut program = if cbor { + let mut program: Program = if cbor { let cbor_hex = std::fs::read_to_string(&script).into_diagnostic()?; let raw_cbor = hex::decode(cbor_hex.trim()).into_diagnostic()?; - let prog = Program::::from_cbor(&raw_cbor, &mut Vec::new()) + let program = Program::::from_cbor(&raw_cbor, &mut Vec::new()) .into_diagnostic()?; - prog.into() + let program: Program = program.into(); + + Program::::try_from(program).into_diagnostic()? } else if flat { let bytes = std::fs::read(&script).into_diagnostic()?; - let prog = Program::::from_flat(&bytes).into_diagnostic()?; + let program = Program::::from_flat(&bytes).into_diagnostic()?; - prog.into() + let program: Program = program.into(); + + Program::::try_from(program).into_diagnostic()? } else { let code = std::fs::read_to_string(&script).into_diagnostic()?; - let prog = parser::program(&code).into_diagnostic()?; - - Program::::try_from(prog).into_diagnostic()? + parser::program(&code).into_diagnostic()? }; for arg in args { let term = parser::term(&arg).into_diagnostic()?; - let term = Term::::try_from(term).into_diagnostic()?; - - program = program.apply_term(&term); + program = program.apply_term(&term) } let budget = ExBudget::default(); + let program = Program::::try_from(program).into_diagnostic()?; + let mut eval_result = program.eval(budget); let cost = eval_result.cost(); diff --git a/crates/uplc/src/ast.rs b/crates/uplc/src/ast.rs index 880890e0..6cc92bf8 100644 --- a/crates/uplc/src/ast.rs +++ b/crates/uplc/src/ast.rs @@ -7,6 +7,7 @@ use crate::{ eval_result::EvalResult, Machine, }, + parser::interner::Interner, }; use num_bigint::BigInt; use num_traits::ToPrimitive; @@ -18,7 +19,6 @@ use pallas::ledger::{ }, traverse::ComputeHash, }; - use serde::{ self, de::{self, Deserialize, Deserializer, MapAccess, Visitor}, @@ -58,21 +58,8 @@ where } } - /// We use this to apply the validator to Datum, - /// then redeemer, then ScriptContext. If datum is - /// even necessary (i.e. minting policy). - pub fn apply_term(&self, term: &Term) -> Self { - let applied_term = Term::Apply { - function: Rc::new(self.term.clone()), - argument: Rc::new(term.clone()), - }; - - Program { - version: self.version, - term: applied_term, - } - } - + /// A convenient and faster version that `apply_term` since the program doesn't need to be + /// re-interned (constant Data do not introduce new bindings). pub fn apply_data(&self, plutus_data: PlutusData) -> Self { let applied_term = Term::Apply { function: Rc::new(self.term.clone()), @@ -86,6 +73,27 @@ where } } +impl Program { + /// We use this to apply the validator to Datum, + /// then redeemer, then ScriptContext. If datum is + /// even necessary (i.e. minting policy). + pub fn apply_term(&self, term: &Term) -> Self { + let applied_term = Term::Apply { + function: Rc::new(self.term.clone()), + argument: Rc::new(term.clone()), + }; + + let mut program = Program { + version: self.version, + term: applied_term, + }; + + Interner::new().program(&mut program); + + program + } +} + impl<'a, T> Display for Program where T: Binder<'a>,