Allow annotating Data for blueprint

This commit allows Data to be optionally annotated with a
  phantom-type. This doesn't change anything in codegen but we can now
  leverage this information to generate better blueprint schemas.
This commit is contained in:
KtorZ
2024-02-07 16:01:35 +01:00
committed by Kasey
parent 20ce19dfb1
commit 3c8460e6af
8 changed files with 174 additions and 51 deletions

View File

@@ -0,0 +1,46 @@
---
source: crates/aiken-project/src/blueprint/validator.rs
description: "Code:\n\npub type Foo {\n foo: Int\n}\n\nvalidator {\n fn annotated_data(datum: Data<Foo>, redeemer: Data, ctx: Void) {\n True\n }\n}\n"
---
{
"title": "test_module.annotated_data",
"datum": {
"title": "datum",
"schema": {
"$ref": "#/definitions/test_module~1Foo"
}
},
"redeemer": {
"title": "redeemer",
"schema": {
"$ref": "#/definitions/Data"
}
},
"compiledCode": "5833010000323222253330044a22930a99802a491856616c696461746f722072657475726e65642066616c736500136565734ae701",
"hash": "52a21f2b4f282074cb6c5aefef20d18c25f3657ca348c73875810c37",
"definitions": {
"Data": {
"title": "Data",
"description": "Any Plutus data."
},
"Int": {
"dataType": "integer"
},
"test_module/Foo": {
"title": "Foo",
"anyOf": [
{
"title": "Foo",
"dataType": "constructor",
"index": 0,
"fields": [
{
"title": "foo",
"$ref": "#/definitions/Int"
}
]
}
]
}
}
}

View File

@@ -5,14 +5,15 @@ use super::{
schema::{Annotated, Schema},
};
use crate::module::{CheckedModule, CheckedModules};
use std::rc::Rc;
use aiken_lang::{
ast::{TypedArg, TypedFunction, TypedValidator},
ast::{Annotation, TypedArg, TypedFunction, TypedValidator},
gen_uplc::CodeGenerator,
tipo::Type,
};
use miette::NamedSource;
use serde;
use std::borrow::Borrow;
use std::rc::Rc;
use uplc::{
ast::{Constant, DeBruijn, Program, Term},
PlutusData,
@@ -96,19 +97,23 @@ impl Validator {
let parameters = params
.iter()
.map(|param| {
Annotated::from_type(modules.into(), &param.tipo, &mut definitions)
.map(|schema| Parameter {
title: Some(param.arg_name.get_label()),
schema,
})
.map_err(|error| Error::Schema {
error,
location: param.location,
source_code: NamedSource::new(
module.input_path.display().to_string(),
module.code.clone(),
),
})
Annotated::from_type(
modules.into(),
tipo_or_annotation(module, param),
&mut definitions,
)
.map(|schema| Parameter {
title: Some(param.arg_name.get_label()),
schema,
})
.map_err(|error| Error::Schema {
error,
location: param.location,
source_code: NamedSource::new(
module.input_path.display().to_string(),
module.code.clone(),
),
})
})
.collect::<Result<_, _>>()?;
@@ -118,48 +123,78 @@ impl Validator {
parameters,
datum: datum
.map(|datum| {
Annotated::from_type(modules.into(), &datum.tipo, &mut definitions).map_err(
|error| Error::Schema {
error,
location: datum.location,
source_code: NamedSource::new(
module.input_path.display().to_string(),
module.code.clone(),
),
},
Annotated::from_type(
modules.into(),
tipo_or_annotation(module, datum),
&mut definitions,
)
.map_err(|error| Error::Schema {
error,
location: datum.location,
source_code: NamedSource::new(
module.input_path.display().to_string(),
module.code.clone(),
),
})
})
.transpose()?
.map(|schema| Parameter {
title: datum.map(|datum| datum.arg_name.get_label()),
schema,
}),
redeemer: Annotated::from_type(modules.into(), &redeemer.tipo, &mut definitions)
.map_err(|error| Error::Schema {
error,
location: redeemer.location,
source_code: NamedSource::new(
module.input_path.display().to_string(),
module.code.clone(),
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(),
),
})
.map(|schema| Parameter {
title: Some(redeemer.arg_name.get_label()),
schema: match datum {
Some(..) if is_multi_validator => Annotated::as_wrapped_redeemer(
&mut definitions,
schema,
redeemer.tipo.clone(),
),
})
.map(|schema| Parameter {
title: Some(redeemer.arg_name.get_label()),
schema: match datum {
Some(..) if is_multi_validator => Annotated::as_wrapped_redeemer(
&mut definitions,
schema,
redeemer.tipo.clone(),
),
_ => schema,
},
})?,
_ => schema,
},
})?,
program: program.clone(),
definitions,
})
}
}
fn tipo_or_annotation<'a>(module: &'a CheckedModule, arg: &'a TypedArg) -> &'a Type {
match *arg.tipo.borrow() {
Type::App {
module: ref module_name,
name: ref type_name,
..
} if module_name.is_empty() && &type_name[..] == "Data" => match arg.annotation {
Some(Annotation::Constructor { ref arguments, .. }) if !arguments.is_empty() => module
.ast
.type_info
.annotations
.get(
arguments
.first()
.expect("guard ensures at least one element"),
)
.unwrap_or(&arg.tipo),
_ => &arg.tipo,
},
_ => &arg.tipo,
}
}
impl Validator {
pub fn apply(
self,
@@ -543,6 +578,23 @@ mod tests {
);
}
#[test]
fn annotated_data() {
assert_validator!(
r#"
pub type Foo {
foo: Int
}
validator {
fn annotated_data(datum: Data<Foo>, redeemer: Data, ctx: Void) {
True
}
}
"#
);
}
#[test]
fn validate_arguments_integer() {
let definitions = fixture_definitions();