Support tuples in blueprint generation.
This commit is contained in:
parent
d2cc44e5f4
commit
0bd9d045b0
|
@ -35,8 +35,8 @@ pub enum Schema {
|
||||||
Integer,
|
Integer,
|
||||||
Bytes,
|
Bytes,
|
||||||
String,
|
String,
|
||||||
Pair(Box<Data>, Box<Data>),
|
Pair(Data, Data),
|
||||||
List(Box<Data>),
|
List(Vec<Data>),
|
||||||
Data(Option<Data>),
|
Data(Option<Data>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,16 +84,13 @@ impl Annotated<Schema> {
|
||||||
description: Some("Any Plutus data.".to_string()),
|
description: Some("Any Plutus data.".to_string()),
|
||||||
annotated: Schema::Data(None),
|
annotated: Schema::Data(None),
|
||||||
}),
|
}),
|
||||||
"ByteArray" => Ok(Annotated {
|
|
||||||
title: None,
|
"ByteArray" => Ok(Schema::Data(Some(Data::Bytes)).into()),
|
||||||
description: None,
|
|
||||||
annotated: Schema::Data(Some(Data::Bytes)),
|
"Int" => Ok(Schema::Data(Some(Data::Integer)).into()),
|
||||||
}),
|
|
||||||
"Int" => Ok(Annotated {
|
"String" => Ok(Schema::String.into()),
|
||||||
title: None,
|
|
||||||
description: None,
|
|
||||||
annotated: Schema::Data(Some(Data::Integer)),
|
|
||||||
}),
|
|
||||||
// TODO: Check whether this matches with the UPLC code generation as there are two
|
// 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
|
// options here since there's technically speaking a `unit` constant constructor in
|
||||||
// the UPLC primitives.
|
// the UPLC primitives.
|
||||||
|
@ -109,6 +106,7 @@ impl Annotated<Schema> {
|
||||||
},
|
},
|
||||||
}]))),
|
}]))),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// TODO: Also check here whether this matches with the UPLC code generation.
|
// TODO: Also check here whether this matches with the UPLC code generation.
|
||||||
"Bool" => Ok(Annotated {
|
"Bool" => Ok(Annotated {
|
||||||
title: Some("Bool".to_string()),
|
title: Some("Bool".to_string()),
|
||||||
|
@ -132,6 +130,7 @@ impl Annotated<Schema> {
|
||||||
},
|
},
|
||||||
]))),
|
]))),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
"Option" => {
|
"Option" => {
|
||||||
let generic = Annotated::from_type(modules, args.get(0).unwrap())
|
let generic = Annotated::from_type(modules, args.get(0).unwrap())
|
||||||
.and_then(|s| s.into_data(type_info))?;
|
.and_then(|s| s.into_data(type_info))?;
|
||||||
|
@ -158,15 +157,13 @@ impl Annotated<Schema> {
|
||||||
]))),
|
]))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
"List" => {
|
"List" => {
|
||||||
let generic = Annotated::from_type(modules, args.get(0).unwrap())
|
let generic = Annotated::from_type(modules, args.get(0).unwrap())
|
||||||
.and_then(|s| s.into_data(type_info))?;
|
.and_then(|s| s.into_data(type_info))?;
|
||||||
Ok(Annotated {
|
Ok(Schema::Data(Some(Data::List(Box::new(generic.annotated)))).into())
|
||||||
title: None,
|
|
||||||
description: None,
|
|
||||||
annotated: Schema::Data(Some(Data::List(Box::new(generic.annotated)))),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => Err(Error::UnsupportedType {
|
_ => Err(Error::UnsupportedType {
|
||||||
type_info: type_info.clone(),
|
type_info: type_info.clone(),
|
||||||
}),
|
}),
|
||||||
|
@ -193,7 +190,27 @@ impl Annotated<Schema> {
|
||||||
type_info: type_info.clone(),
|
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::Fn { .. } => Err(Error::UnsupportedType {
|
||||||
type_info: type_info.clone(),
|
type_info: type_info.clone(),
|
||||||
}),
|
}),
|
||||||
|
@ -211,8 +228,8 @@ impl Annotated<Schema> {
|
||||||
description,
|
description,
|
||||||
annotated: data,
|
annotated: data,
|
||||||
}),
|
}),
|
||||||
_ => Err(Error::UnsupportedType {
|
_ => Err(Error::ExpectedData {
|
||||||
type_info: type_info.to_owned(),
|
got: type_info.to_owned(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,7 +260,7 @@ impl Data {
|
||||||
|
|
||||||
let variant = Annotated {
|
let variant = Annotated {
|
||||||
title: Some(constructor.name.clone()),
|
title: Some(constructor.name.clone()),
|
||||||
description: constructor.doc.clone(),
|
description: constructor.doc.clone().map(|s| s.trim().to_string()),
|
||||||
annotated: Constructor { index, fields },
|
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()
|
, type_signature = pretty::Printer::new().print(type_info).to_pretty_string(70).bright_blue()
|
||||||
))]
|
))]
|
||||||
UnsupportedType { type_info: Type },
|
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>(
|
fn find_definition<'a>(
|
||||||
|
|
|
@ -187,12 +187,15 @@ mod test {
|
||||||
|
|
||||||
fn parse(&self, source_code: &str) -> ParsedModule {
|
fn parse(&self, source_code: &str) -> ParsedModule {
|
||||||
let kind = ModuleKind::Validator;
|
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 {
|
let mut module = ParsedModule {
|
||||||
kind,
|
kind,
|
||||||
ast,
|
ast,
|
||||||
code: source_code.to_string(),
|
code: source_code.to_string(),
|
||||||
name: "test".to_owned(),
|
name,
|
||||||
path: PathBuf::new(),
|
path: PathBuf::new(),
|
||||||
extra,
|
extra,
|
||||||
package: self.package.to_string(),
|
package: self.package.to_string(),
|
||||||
|
@ -201,7 +204,7 @@ mod test {
|
||||||
module
|
module
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(&self, module: ParsedModule) -> CheckedModule {
|
fn check(&mut self, module: ParsedModule) -> CheckedModule {
|
||||||
let mut warnings = vec![];
|
let mut warnings = vec![];
|
||||||
|
|
||||||
let ast = module
|
let ast = module
|
||||||
|
@ -213,7 +216,10 @@ mod test {
|
||||||
&self.module_types,
|
&self.module_types,
|
||||||
&mut warnings,
|
&mut warnings,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.expect("Failed to type-check module");
|
||||||
|
|
||||||
|
self.module_types
|
||||||
|
.insert(module.name.clone(), ast.type_info.clone());
|
||||||
|
|
||||||
CheckedModule {
|
CheckedModule {
|
||||||
kind: module.kind,
|
kind: module.kind,
|
||||||
|
@ -228,7 +234,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_validator(source_code: &str, json: serde_json::Value) {
|
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 modules = CheckedModules::singleton(project.check(project.parse(source_code)));
|
||||||
let mut generator = modules.new_generator(
|
let mut generator = modules.new_generator(
|
||||||
|
@ -242,35 +248,209 @@ mod test {
|
||||||
.next()
|
.next()
|
||||||
.expect("source code did no yield any validator");
|
.expect("source code did no yield any validator");
|
||||||
|
|
||||||
let validator =
|
let validator = Validator::from_checked_module(&modules, &mut generator, validator, def)
|
||||||
Validator::from_checked_module(&modules, &mut generator, validator, def).unwrap();
|
.expect("Failed to create validator blueprint");
|
||||||
|
|
||||||
println!("{}", validator);
|
println!("{}", validator);
|
||||||
assert_json_eq!(serde_json::to_value(&validator).unwrap(), json);
|
assert_json_eq!(serde_json::to_value(&validator).unwrap(), json);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validator_1() {
|
fn validator_mint_basic() {
|
||||||
assert_validator(
|
assert_validator(
|
||||||
r#"
|
r#"
|
||||||
fn spend(datum: Data, redeemer: Data, ctx: Data) {
|
fn mint(redeemer: Data, ctx: Data) {
|
||||||
True
|
True
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
json!({
|
json!({
|
||||||
"title": "test",
|
"title": "test_module",
|
||||||
"purpose": "spend",
|
"purpose": "mint",
|
||||||
"hash": "cf2cd3bed32615bfecbd280618c1c1bec2198fc0f72b04f323a8a0d2",
|
"hash": "da4a98cee05a17be402b07c414d59bf894c9ebd0487186417121de8f",
|
||||||
"datum": {
|
|
||||||
"title": "Data",
|
|
||||||
"description": "Any Plutus data."
|
|
||||||
},
|
|
||||||
"redeemer": {
|
"redeemer": {
|
||||||
"title": "Data",
|
"title": "Data",
|
||||||
"description": "Any Plutus 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"
|
"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"
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue