Refactor blueprint & handle annotated schemas
This also now introduce two levels of representable types (because it's needed at least for tuples): Plutus Data (a.k.a Data) and UPLC primitives / constants (a.k.a Schema). In practice, we don't want to specify blueprints that use direct UPLC primitives because there's little support for producing those in the ecosystem. So we should aim for producing only Data whenever we can. Yet we don't want to forbid it either in case people know what they're doing. Which means that we need to capture that difference well in the type modelling (in Rust and in the CIP-0057 specification). I've also simplified the error type for now, just to provide some degree of feedback while working on this. I'll refine it later with proper errors.
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
use super::schema::NamedSchema;
|
||||
use super::{
|
||||
error::{assert_min_arity, assert_return_bool, Error},
|
||||
schema::{Annotated, Schema},
|
||||
};
|
||||
use crate::module::{CheckedModule, CheckedModules};
|
||||
use aiken_lang::{ast::TypedFunction, uplc::CodeGenerator};
|
||||
use pallas::ledger::primitives::babbage as cardano;
|
||||
use pallas_traverse::ComputeHash;
|
||||
use serde::{
|
||||
@@ -13,19 +18,35 @@ pub struct Validator {
|
||||
pub title: String,
|
||||
pub purpose: Purpose,
|
||||
pub description: Option<String>,
|
||||
pub datum: Option<NamedSchema>,
|
||||
pub redeemer: NamedSchema,
|
||||
pub datum: Option<Annotated<Schema>>,
|
||||
pub redeemer: Annotated<Schema>,
|
||||
pub program: Program<NamedDeBruijn>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Purpose {
|
||||
Spend,
|
||||
Mint,
|
||||
Withdraw,
|
||||
Publish,
|
||||
}
|
||||
|
||||
impl Serialize for Validator {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let cbor = self.program.to_cbor().unwrap();
|
||||
|
||||
let source_code = hex::encode(&cbor);
|
||||
let mut s = serializer.serialize_struct("Validator", 5)?;
|
||||
|
||||
let hash = cardano::PlutusV2Script(cbor.into()).compute_hash();
|
||||
|
||||
let fields = 5
|
||||
+ self.description.as_ref().map(|_| 1).unwrap_or_default()
|
||||
+ self.datum.as_ref().map(|_| 1).unwrap_or_default();
|
||||
|
||||
let mut s = serializer.serialize_struct("Validator", fields)?;
|
||||
s.serialize_field("title", &self.title)?;
|
||||
s.serialize_field("purpose", &self.purpose)?;
|
||||
let hash = cardano::PlutusV2Script(cbor.into()).compute_hash();
|
||||
s.serialize_field("hash", &hash)?;
|
||||
if let Some { .. } = self.description {
|
||||
s.serialize_field("description", &self.description)?;
|
||||
@@ -39,13 +60,38 @@ impl Serialize for Validator {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Purpose {
|
||||
Spend,
|
||||
Mint,
|
||||
Withdraw,
|
||||
Publish,
|
||||
impl Validator {
|
||||
pub fn from_checked_module(
|
||||
modules: &CheckedModules,
|
||||
generator: &mut CodeGenerator,
|
||||
validator: &CheckedModule,
|
||||
def: &TypedFunction,
|
||||
) -> Result<Validator, Error> {
|
||||
let purpose: Purpose = def.name.clone().into();
|
||||
|
||||
assert_return_bool(validator, def)?;
|
||||
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());
|
||||
|
||||
Ok(Validator {
|
||||
title: validator.name.clone(),
|
||||
description: None,
|
||||
purpose,
|
||||
datum: datum
|
||||
.map(|datum| {
|
||||
Annotated::from_type(modules.into(), &datum.tipo).map_err(Error::Schema)
|
||||
})
|
||||
.transpose()?,
|
||||
redeemer: Annotated::from_type(modules.into(), &redeemer.tipo)
|
||||
.map_err(Error::Schema)?,
|
||||
program: generator
|
||||
.generate(&def.body, &def.arguments, true)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Purpose {
|
||||
@@ -57,18 +103,6 @@ impl Purpose {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Purpose {
|
||||
fn from(purpose: String) -> Purpose {
|
||||
match &purpose[..] {
|
||||
"spend" => Purpose::Spend,
|
||||
"mint" => Purpose::Mint,
|
||||
"withdraw" => Purpose::Withdraw,
|
||||
"publish" => Purpose::Publish,
|
||||
unexpected => panic!("Can't turn '{}' into any Purpose", unexpected),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Purpose {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
@@ -80,9 +114,21 @@ impl Display for Purpose {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Purpose {
|
||||
fn from(purpose: String) -> Purpose {
|
||||
match &purpose[..] {
|
||||
"spend" => Purpose::Spend,
|
||||
"mint" => Purpose::Mint,
|
||||
"withdraw" => Purpose::Withdraw,
|
||||
"publish" => Purpose::Publish,
|
||||
unexpected => panic!("Can't turn '{}' into any Purpose", unexpected),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::schema::{Constructor, Schema};
|
||||
use super::super::schema::{Constructor, Data, Schema};
|
||||
use super::*;
|
||||
use serde_json::{self, json};
|
||||
use uplc::parser;
|
||||
@@ -98,13 +144,14 @@ mod test {
|
||||
description: Some("Lorem ipsum".to_string()),
|
||||
purpose: Purpose::Spend,
|
||||
datum: None,
|
||||
redeemer: NamedSchema {
|
||||
title: "Bar".to_string(),
|
||||
redeemer: Annotated {
|
||||
title: Some("Bar".to_string()),
|
||||
description: None,
|
||||
schema: Schema::AnyOf(vec![Constructor {
|
||||
annotated: Schema::Data(Some(Data::AnyOf(vec![Constructor {
|
||||
index: 0,
|
||||
fields: vec![Schema::Bytes],
|
||||
}]),
|
||||
fields: vec![Data::Bytes.into()],
|
||||
}
|
||||
.into()]))),
|
||||
},
|
||||
program,
|
||||
};
|
||||
@@ -112,18 +159,20 @@ mod test {
|
||||
serde_json::to_value(&validator).unwrap(),
|
||||
json!({
|
||||
"title": "foo",
|
||||
"description": "Lorem ipsum",
|
||||
"purpose": "spend",
|
||||
"hash": "27dc8e44c17b4ae5f4b9286ab599fffe70e61b49dec61eaca1fc5898",
|
||||
"description": "Lorem ipsum",
|
||||
"redeemer": {
|
||||
"title": "Bar",
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": [{
|
||||
"dataType": "bytes"
|
||||
}]
|
||||
"anyOf": [{
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": [{
|
||||
"dataType": "bytes"
|
||||
}]
|
||||
}],
|
||||
},
|
||||
"compiledCode": "46010000481501",
|
||||
"hash": "27dc8e44c17b4ae5f4b9286ab599fffe70e61b49dec61eaca1fc5898"
|
||||
"compiledCode": "46010000481501"
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user