Support tuples in blueprint generation.

This commit is contained in:
KtorZ 2023-01-28 17:34:43 +01:00
parent d2cc44e5f4
commit 0bd9d045b0
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
2 changed files with 236 additions and 37 deletions

View File

@ -35,8 +35,8 @@ pub enum Schema {
Integer,
Bytes,
String,
Pair(Box<Data>, Box<Data>),
List(Box<Data>),
Pair(Data, Data),
List(Vec<Data>),
Data(Option<Data>),
}
@ -84,16 +84,13 @@ impl Annotated<Schema> {
description: Some("Any Plutus data.".to_string()),
annotated: Schema::Data(None),
}),
"ByteArray" => Ok(Annotated {
title: None,
description: None,
annotated: Schema::Data(Some(Data::Bytes)),
}),
"Int" => Ok(Annotated {
title: None,
description: None,
annotated: Schema::Data(Some(Data::Integer)),
}),
"ByteArray" => Ok(Schema::Data(Some(Data::Bytes)).into()),
"Int" => Ok(Schema::Data(Some(Data::Integer)).into()),
"String" => Ok(Schema::String.into()),
// TODO: Check whether this matches with the UPLC code generation as there are two
// options here since there's technically speaking a `unit` constant constructor in
// the UPLC primitives.
@ -109,6 +106,7 @@ impl Annotated<Schema> {
},
}]))),
}),
// TODO: Also check here whether this matches with the UPLC code generation.
"Bool" => Ok(Annotated {
title: Some("Bool".to_string()),
@ -132,6 +130,7 @@ impl Annotated<Schema> {
},
]))),
}),
"Option" => {
let generic = Annotated::from_type(modules, args.get(0).unwrap())
.and_then(|s| s.into_data(type_info))?;
@ -158,15 +157,13 @@ impl Annotated<Schema> {
]))),
})
}
"List" => {
let generic = Annotated::from_type(modules, args.get(0).unwrap())
.and_then(|s| s.into_data(type_info))?;
Ok(Annotated {
title: None,
description: None,
annotated: Schema::Data(Some(Data::List(Box::new(generic.annotated)))),
})
Ok(Schema::Data(Some(Data::List(Box::new(generic.annotated)))).into())
}
_ => Err(Error::UnsupportedType {
type_info: type_info.clone(),
}),
@ -193,7 +190,27 @@ impl Annotated<Schema> {
type_info: type_info.clone(),
}),
},
Type::Tuple { .. } => todo!(),
Type::Tuple { elems } => match &elems[..] {
[left, right] => {
let left = Annotated::from_type(modules, left)?.into_data(left)?;
let right = Annotated::from_type(modules, right)?.into_data(right)?;
Ok(Schema::Pair(left.annotated, right.annotated).into())
}
_ => {
let elems: Result<Vec<Data>, _> = elems
.iter()
.map(|e| {
Annotated::from_type(modules, e)
.and_then(|s| s.into_data(e).map(|s| s.annotated))
})
.collect();
Ok(Annotated {
title: Some("Tuple".to_owned()),
description: None,
annotated: Schema::List(elems?),
})
}
},
Type::Fn { .. } => Err(Error::UnsupportedType {
type_info: type_info.clone(),
}),
@ -211,8 +228,8 @@ impl Annotated<Schema> {
description,
annotated: data,
}),
_ => Err(Error::UnsupportedType {
type_info: type_info.to_owned(),
_ => Err(Error::ExpectedData {
got: type_info.to_owned(),
}),
}
}
@ -243,7 +260,7 @@ impl Data {
let variant = Annotated {
title: Some(constructor.name.clone()),
description: constructor.doc.clone(),
description: constructor.doc.clone().map(|s| s.trim().to_string()),
annotated: Constructor { index, fields },
};
@ -370,6 +387,8 @@ pub enum Error {
, type_signature = pretty::Printer::new().print(type_info).to_pretty_string(70).bright_blue()
))]
UnsupportedType { type_info: Type },
#[error("I had the misfortune to find an invalid type in an interface boundary.")]
ExpectedData { got: Type },
}
fn find_definition<'a>(

View File

@ -187,12 +187,15 @@ mod test {
fn parse(&self, source_code: &str) -> ParsedModule {
let kind = ModuleKind::Validator;
let (ast, extra) = parser::module(source_code, kind).unwrap();
let name = "test_module".to_owned();
let (mut ast, extra) =
parser::module(source_code, kind).expect("Failed to parse module");
ast.name = name.clone();
let mut module = ParsedModule {
kind,
ast,
code: source_code.to_string(),
name: "test".to_owned(),
name,
path: PathBuf::new(),
extra,
package: self.package.to_string(),
@ -201,7 +204,7 @@ mod test {
module
}
fn check(&self, module: ParsedModule) -> CheckedModule {
fn check(&mut self, module: ParsedModule) -> CheckedModule {
let mut warnings = vec![];
let ast = module
@ -213,7 +216,10 @@ mod test {
&self.module_types,
&mut warnings,
)
.unwrap();
.expect("Failed to type-check module");
self.module_types
.insert(module.name.clone(), ast.type_info.clone());
CheckedModule {
kind: module.kind,
@ -228,7 +234,7 @@ mod test {
}
fn assert_validator(source_code: &str, json: serde_json::Value) {
let project = TestProject::new();
let mut project = TestProject::new();
let modules = CheckedModules::singleton(project.check(project.parse(source_code)));
let mut generator = modules.new_generator(
@ -242,35 +248,209 @@ mod test {
.next()
.expect("source code did no yield any validator");
let validator =
Validator::from_checked_module(&modules, &mut generator, validator, def).unwrap();
let validator = Validator::from_checked_module(&modules, &mut generator, validator, def)
.expect("Failed to create validator blueprint");
println!("{}", validator);
assert_json_eq!(serde_json::to_value(&validator).unwrap(), json);
}
#[test]
fn validator_1() {
fn validator_mint_basic() {
assert_validator(
r#"
fn spend(datum: Data, redeemer: Data, ctx: Data) {
fn mint(redeemer: Data, ctx: Data) {
True
}
"#,
json!({
"title": "test",
"purpose": "spend",
"hash": "cf2cd3bed32615bfecbd280618c1c1bec2198fc0f72b04f323a8a0d2",
"datum": {
"title": "Data",
"description": "Any Plutus data."
},
"title": "test_module",
"purpose": "mint",
"hash": "da4a98cee05a17be402b07c414d59bf894c9ebd0487186417121de8f",
"redeemer": {
"title": "Data",
"description": "Any Plutus data."
},
"compiledCode": "581d010000210872656465656d657200210363747800533357349445261601"
}),
);
}
#[test]
fn validator_spend() {
assert_validator(
r#"
/// On-chain state
type State {
/// The contestation period as a number of seconds
contestationPeriod: ContestationPeriod,
/// List of public key hashes of all participants
parties: List<Party>,
utxoHash: Hash<Blake2b_256>,
}
/// A Hash digest for a given algorithm.
type Hash<alg> = ByteArray
type Blake2b_256 { Blake2b_256 }
/// Whatever
type ContestationPeriod {
/// A positive, non-zero number of seconds.
ContestationPeriod(Int)
}
type Party =
ByteArray
type Input {
CollectCom
Close
/// Abort a transaction
Abort
}
fn spend(datum: State, redeemer: Input, ctx: Data) {
True
}
"#,
json!({
"title": "test_module",
"purpose": "spend",
"hash": "cf2cd3bed32615bfecbd280618c1c1bec2198fc0f72b04f323a8a0d2",
"datum": {
"title": "State",
"description": "On-chain state",
"anyOf": [
{
"title": "State",
"dataType": "constructor",
"index": 0,
"fields": [
{
"title": "contestationPeriod",
"description": "The contestation period as a number of seconds",
"anyOf": [
{
"title": "ContestationPeriod",
"description": "A positive, non-zero number of seconds.",
"dataType": "constructor",
"index": 0,
"fields": [
{
"dataType": "integer"
}
]
}
]
},
{
"title": "parties",
"description": "List of public key hashes of all participants",
"dataType": "list",
"items": {
"dataType": "bytes"
}
},
{
"title": "utxoHash",
"dataType": "bytes"
}
]
}
]
},
"redeemer": {
"title": "Input",
"anyOf": [
{
"title": "CollectCom",
"dataType": "constructor",
"index": 0,
"fields": []
},
{
"title": "Close",
"dataType": "constructor",
"index": 1,
"fields": []
},
{
"title": "Abort",
"description": "Abort a transaction",
"dataType": "constructor",
"index": 2,
"fields": []
}
]
},
"compiledCode": "58250100002105646174756d00210872656465656d657200210363747800533357349445261601"
}),
);
}
#[test]
fn validator_spend_2tuple() {
assert_validator(
r#"
fn spend(datum: (Int, ByteArray), redeemer: String, ctx: Void) {
True
}
"#,
json!({
"title": "test_module",
"purpose": "spend",
"hash": "12065ad2edb75b9e497e50c4f8130b90c9108f8ae0991abc5442e074",
"datum": {
"dataType": "#pair",
"left": {
"dataType": "integer"
},
"right": {
"dataType": "bytes"
}
},
"redeemer": {
"dataType": "#string"
},
"compiledCode": "589f0100002105646174756d00320105646174756d00210872656465656d65720032010872656465656d657200210363747800533357349445261637326eb8010872656465656d6572000132010b5f5f6c6973745f64617461003201065f5f7461696c00337606ae84010b5f5f6c6973745f646174610002357421065f5f7461696c00013574410b5f5f6c6973745f64617461000137580105646174756d000101"
}),
)
}
#[test]
fn validator_spend_tuples() {
assert_validator(
r#"
fn spend(datum: (Int, Int, Int), redeemer: Data, ctx: Void) {
True
}
"#,
json!({
"title": "test_module",
"purpose": "spend",
"hash": "5c470f297728051a920bd9e70e14197c8fb0eaf4413e419827b0ec38",
"datum": {
"title": "Tuple",
"dataType": "#list",
"elements": [
{
"dataType": "integer"
},
{
"dataType": "integer"
},
{
"dataType": "integer"
}
]
},
"redeemer": {
"title": "Data",
"description": "Any Plutus data."
},
"compiledCode": "58390100002105646174756d00320105646174756d00210872656465656d657200210363747800533357349445261637580105646174756d000101"
}),
)
}
}