diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index 6a370c75..5e23c808 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -35,8 +35,8 @@ pub enum Schema { Integer, Bytes, String, - Pair(Box, Box), - List(Box), + Pair(Data, Data), + List(Vec), Data(Option), } @@ -84,16 +84,13 @@ impl Annotated { 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 { }, }]))), }), + // 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 { }, ]))), }), + "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 { ]))), }) } + "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 { 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, _> = 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 { 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>( diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 6fc95ba8..82af2aad 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -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, + utxoHash: Hash, + } + + /// A Hash digest for a given algorithm. + type Hash = 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" + }), + ) + } }