Merge pull request #335 from aiken-lang/blueprint-parameters
Blueprint parameters
This commit is contained in:
@@ -27,6 +27,7 @@ pub enum Error {
|
||||
source_code: NamedSource,
|
||||
return_type: Arc<Type>,
|
||||
},
|
||||
|
||||
#[error("A {} validator requires at least {} arguments.", name.purple().bold(), at_least.to_string().purple().bold())]
|
||||
#[diagnostic(code("aiken::blueprint::invalid::arity"))]
|
||||
WrongValidatorArity {
|
||||
@@ -37,12 +38,13 @@ pub enum Error {
|
||||
#[source_code]
|
||||
source_code: NamedSource,
|
||||
},
|
||||
|
||||
#[error("{}", error)]
|
||||
#[diagnostic(help("{}", error.help()))]
|
||||
#[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,
|
||||
@@ -52,6 +54,15 @@ 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,
|
||||
|
||||
#[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> {
|
||||
|
||||
@@ -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<T> {
|
||||
pub struct Blueprint<T: Default> {
|
||||
pub preamble: Preamble,
|
||||
pub validators: Vec<validator::Validator<T>>,
|
||||
pub validators: Vec<Validator<T>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
@@ -25,6 +25,12 @@ pub struct Preamble {
|
||||
pub license: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum LookupResult<'a, T> {
|
||||
One(&'a T),
|
||||
Many,
|
||||
}
|
||||
|
||||
impl Blueprint<Schema> {
|
||||
pub fn new(
|
||||
config: &Config,
|
||||
@@ -47,6 +53,59 @@ impl Blueprint<Schema> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Blueprint<T>
|
||||
where
|
||||
T: Clone + Default,
|
||||
{
|
||||
pub fn lookup(
|
||||
&self,
|
||||
title: Option<&String>,
|
||||
purpose: Option<&Purpose>,
|
||||
) -> Option<LookupResult<Validator<T>>> {
|
||||
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<F, A, E>(
|
||||
&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<A, E>
|
||||
where
|
||||
F: Fn(Validator<T>) -> Result<A, E>,
|
||||
{
|
||||
match self.lookup(title, purpose) {
|
||||
Some(LookupResult::One(validator)) => action(validator.to_owned()),
|
||||
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<Schema> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = serde_json::to_string_pretty(self).map_err(|_| fmt::Error)?;
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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<T> {
|
||||
@@ -21,6 +21,9 @@ pub struct Validator<T> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub datum: Option<Annotated<T>>,
|
||||
pub redeemer: Annotated<T>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
#[serde(default)]
|
||||
pub parameters: Vec<Annotated<T>>,
|
||||
#[serde(flatten)]
|
||||
pub program: Program<DeBruijn>,
|
||||
}
|
||||
@@ -58,12 +61,39 @@ impl Validator<Schema> {
|
||||
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::<Result<_, _>>()?,
|
||||
datum: datum
|
||||
.map(|datum| {
|
||||
Annotated::from_type(modules.into(), &datum.tipo, &HashMap::new()).map_err(
|
||||
@@ -95,6 +125,25 @@ impl Validator<Schema> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Validator<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
pub fn apply(self, arg: &Term<DeBruijn>) -> Result<Self, Error> {
|
||||
match self.parameters.split_first() {
|
||||
None => Err(Error::NoParametersToApply),
|
||||
Some((_, tail)) => {
|
||||
// TODO: Ideally, we should control that the applied term matches its schema.
|
||||
Ok(Self {
|
||||
program: self.program.apply_term(arg),
|
||||
parameters: tail.to_vec(),
|
||||
..self
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Purpose {
|
||||
pub fn min_arity(&self) -> u8 {
|
||||
match self {
|
||||
@@ -272,6 +321,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(
|
||||
|
||||
Reference in New Issue
Block a user