From 442010d0567a928a9e427013b75dfa76935364e1 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sat, 24 Aug 2024 11:15:56 +0200 Subject: [PATCH] Fix generation of fallback validator This must only happen in case all other validator succeed; otherwise we might generate invalid validators. --- crates/aiken-lang/src/ast/well_known.rs | 2 + crates/aiken-lang/src/gen_uplc.rs | 6 +- ...lueprint__validator__tests__free_vars.snap | 4 +- .../aiken-project/src/blueprint/validator.rs | 147 ++++++++++-------- 4 files changed, 89 insertions(+), 70 deletions(-) diff --git a/crates/aiken-lang/src/ast/well_known.rs b/crates/aiken-lang/src/ast/well_known.rs index d97db55a..497b9d4c 100644 --- a/crates/aiken-lang/src/ast/well_known.rs +++ b/crates/aiken-lang/src/ast/well_known.rs @@ -49,6 +49,8 @@ pub const SCRIPT_PURPOSE_CONSTRUCTORS: &[&str] = &[ SCRIPT_PURPOSE_PROPOSE, ]; +pub const VALIDATOR_ELSE: &str = "else"; + // ---------------------------------------------------------------------------- // Types diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index e0c5c2e9..7ad7c590 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -1575,7 +1575,11 @@ impl<'a> CodeGenerator<'a> { otherwise: AirTree, depth: usize, ) -> AirTree { - assert!(tipo.get_generic().is_none()); + assert!( + tipo.get_generic().is_none(), + "left-hand side of expect is generic: {}", + tipo.to_pretty(0) + ); // Shouldn't be needed but still here just in case // this function is called from anywhere else besides assignment let tipo = &convert_opaque_type(tipo, &self.data_types, true); diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap index 708547f5..f0ee948d 100644 --- a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__free_vars.snap @@ -1,6 +1,6 @@ --- source: crates/aiken-project/src/blueprint/validator.rs -description: "Code:\n\nvalidator {\n fn generics(redeemer: a, ctx: Void) {\n True\n }\n}\n" +description: "Code:\n\nvalidator generics {\n mint(redeemer: a, policy_id: ByteArray, transaction: Data) {\n True\n }\n}\n" --- Schema { error: Error { @@ -16,7 +16,7 @@ Schema { }, ], }, - location: 26..37, + location: 28..39, source_code: NamedSource { name: "", source: "", diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 4fb070ac..461999a5 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -69,15 +69,21 @@ impl Validator { )); } - validators.push(Validator::create_validator_blueprint( - generator, - modules, - module, - def, - &def.fallback, - &mut program, - plutus_version, - )); + // NOTE: Only push the fallback if all other validators have been successfully + // generated. Otherwise, we may fall into scenarios where we cannot generate validators + // (e.g. due to the presence of generics in datum/redeemer), which won't be caught by + // the else branch since it lacks arguments. + if validators.iter().all(|v| v.is_ok()) { + validators.push(Validator::create_validator_blueprint( + generator, + modules, + module, + def, + &def.fallback, + &mut program, + plutus_version, + )); + } validators } @@ -92,10 +98,6 @@ impl Validator { program: &mut MemoProgram, plutus_version: &PlutusVersion, ) -> Result { - let mut args = func.arguments.iter().rev(); - - let (_, _, redeemer, datum) = (args.next(), args.next(), args.next(), args.next()); - let mut definitions = Definitions::new(); let parameters = def @@ -122,72 +124,83 @@ impl Validator { }) .collect::>()?; - let datum = datum - .map(|datum| { - match datum.tipo.as_ref() { - Type::App { module: module_name, name, args, .. } if module_name.is_empty() && name == well_known::OPTION => { - let Some(Annotation::Constructor { arguments, .. }) = datum.annotation.as_ref() else { - panic!("Datum isn't an option but should be; this should have been caught by the type-checker!"); - }; + let (datum, redeemer) = if func.name == well_known::VALIDATOR_ELSE { + (None, None) + } else { + let mut args = func.arguments.iter().rev(); - Annotated::from_type( - modules.into(), - tipo_or_annotation(module, &TypedArg { - arg_name: datum.arg_name.clone(), + let (_, _, redeemer, datum) = ( + args.next(), + args.next(), + args.next().expect("redeemer is always present"), + args.next(), + ); + + let datum = datum + .map(|datum| { + match datum.tipo.as_ref() { + Type::App { module: module_name, name, args, .. } if module_name.is_empty() && name == well_known::OPTION => { + let Some(Annotation::Constructor { arguments, .. }) = datum.annotation.as_ref() else { + panic!("Datum isn't an option but should be; this should have been caught by the type-checker!"); + }; + + Annotated::from_type( + modules.into(), + tipo_or_annotation(module, &TypedArg { + arg_name: datum.arg_name.clone(), + location: datum.location, + annotation: arguments.first().cloned(), + doc: datum.doc.clone(), + is_validator_param: datum.is_validator_param, + tipo: args.first().expect("Option always have a single type argument.").clone() + }), + &mut definitions, + ) + .map_err(|error| Error::Schema { + error, location: datum.location, - annotation: arguments.first().cloned(), - doc: datum.doc.clone(), - is_validator_param: datum.is_validator_param, - tipo: args.first().expect("Option always have a single type argument.").clone() - }), - &mut definitions, - ) - .map_err(|error| Error::Schema { - error, - location: datum.location, - source_code: NamedSource::new( - module.input_path.display().to_string(), - module.code.clone(), - ), - }) - }, - _ => panic!("Datum isn't an option but should be; this should have been caught by the type-checker!"), - } - }) - .transpose()? - .map(|schema| Parameter { - title: datum.map(|datum| datum.arg_name.get_label()), - schema, - }); - - let redeemer = redeemer - .map(|redeemer| { - Annotated::from_type( - modules.into(), - tipo_or_annotation(module, redeemer), - &mut definitions, - ) - .map_err(|error| Error::Schema { - error, - location: redeemer.location, - source_code: NamedSource::new( - module.input_path.display().to_string(), - module.code.clone(), - ), + source_code: NamedSource::new( + module.input_path.display().to_string(), + module.code.clone(), + ), + }) + }, + _ => panic!("Datum isn't an option but should be; this should have been caught by the type-checker!"), + } }) + .transpose()? + .map(|schema| Parameter { + title: datum.map(|datum| datum.arg_name.get_label()), + schema, + }); + + let redeemer = Annotated::from_type( + modules.into(), + tipo_or_annotation(module, redeemer), + &mut definitions, + ) + .map_err(|error| Error::Schema { + error, + location: redeemer.location, + source_code: NamedSource::new( + module.input_path.display().to_string(), + module.code.clone(), + ), }) - .transpose()? .map(|schema| Parameter { - title: redeemer.map(|redeemer| redeemer.arg_name.get_label()), + title: Some(redeemer.arg_name.get_label()), schema, - }); + })?; + + (datum, Some(redeemer)) + }; Ok(Validator { title: format!( "{}.{}_{}", &module.name, &def.name, - if func.name == "else" { + if func.name == well_known::VALIDATOR_ELSE { "__fallback" } else { &func.name