Merge pull request #316 from aiken-lang/cip-0057-blueprints
Blueprints (CIP-0057) as build artifacts
This commit is contained in:
commit
ecc5e13ccd
|
@ -66,6 +66,7 @@ dependencies = [
|
|||
"pallas-primitives",
|
||||
"pallas-traverse",
|
||||
"regex",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"uplc",
|
||||
]
|
||||
|
@ -112,6 +113,7 @@ version = "0.0.28"
|
|||
dependencies = [
|
||||
"aiken-lang",
|
||||
"askama",
|
||||
"assert-json-diff",
|
||||
"dirs",
|
||||
"fslock",
|
||||
"futures",
|
||||
|
@ -197,6 +199,16 @@ dependencies = [
|
|||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert-json-diff"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
|
|
|
@ -988,7 +988,7 @@ pub fn convert_constants_to_data(constants: Vec<UplcConstant>) -> Vec<UplcConsta
|
|||
new_constants
|
||||
}
|
||||
|
||||
pub fn wrap_validator_args(term: Term<Name>, arguments: Vec<TypedArg>) -> Term<Name> {
|
||||
pub fn wrap_validator_args(term: Term<Name>, arguments: &[TypedArg]) -> Term<Name> {
|
||||
let mut term = term;
|
||||
for arg in arguments.iter().rev() {
|
||||
if !matches!(arg.tipo.get_uplc_type(), UplcType::Data) {
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
use std::{cell::RefCell, collections::HashMap, sync::Arc};
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use uplc::builtins::DefaultFunction;
|
||||
|
||||
use crate::{
|
||||
ast::{Arg, ArgName, CallArg, Function, ModuleKind, Span, TypedFunction, UnOp},
|
||||
builder::FunctionAccessKey,
|
||||
ast::{Arg, ArgName, CallArg, Function, ModuleKind, Span, TypedDataType, TypedFunction, UnOp},
|
||||
builder::{DataTypeKey, FunctionAccessKey},
|
||||
expr::TypedExpr,
|
||||
tipo::{
|
||||
fields::FieldMap, Type, TypeConstructor, TypeInfo, TypeVar, ValueConstructor,
|
||||
|
@ -14,6 +8,10 @@ use crate::{
|
|||
},
|
||||
IdGenerator,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use std::{cell::RefCell, collections::HashMap, sync::Arc};
|
||||
use strum::IntoEnumIterator;
|
||||
use uplc::builtins::DefaultFunction;
|
||||
|
||||
pub const BYTE_ARRAY: &str = "ByteArray";
|
||||
pub const BOOL: &str = "Bool";
|
||||
|
@ -533,8 +531,8 @@ pub fn from_default_function(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn prelude_functions(id_gen: &IdGenerator) -> HashMap<FunctionAccessKey, TypedFunction> {
|
||||
let mut functions = HashMap::new();
|
||||
pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap<FunctionAccessKey, TypedFunction> {
|
||||
let mut functions = IndexMap::new();
|
||||
|
||||
// /// Negate the argument. Useful for map/fold and pipelines.
|
||||
// pub fn not(self: Bool) -> Bool {
|
||||
|
@ -800,6 +798,22 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> HashMap<FunctionAccessKey, Typ
|
|||
functions
|
||||
}
|
||||
|
||||
pub fn prelude_data_types(id_gen: &IdGenerator) -> IndexMap<DataTypeKey, TypedDataType> {
|
||||
let mut data_types = IndexMap::new();
|
||||
|
||||
// Option
|
||||
let option_data_type = TypedDataType::option(generic_var(id_gen.next()));
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
module_name: "".to_string(),
|
||||
defined_type: "Option".to_string(),
|
||||
},
|
||||
option_data_type,
|
||||
);
|
||||
|
||||
data_types
|
||||
}
|
||||
|
||||
pub fn int() -> Arc<Type> {
|
||||
Arc::new(Type::App {
|
||||
public: true,
|
||||
|
|
|
@ -466,7 +466,7 @@ Perhaps, try the following:
|
|||
#[diagnostic(code("unknown::module"))]
|
||||
#[diagnostic(help(
|
||||
"{}",
|
||||
suggest_neighbor(name, imported_modules.iter(), "Did you forget to import it?")
|
||||
suggest_neighbor(name, imported_modules.iter(), "Did you forget to add a package as dependency?")
|
||||
))]
|
||||
UnknownModule {
|
||||
#[label]
|
||||
|
|
|
@ -80,7 +80,6 @@ impl UntypedModule {
|
|||
|
||||
for def in consts.into_iter().chain(not_consts) {
|
||||
let definition = infer_definition(def, &name, &mut hydrators, &mut environment, kind)?;
|
||||
|
||||
definitions.push(definition);
|
||||
}
|
||||
|
||||
|
@ -339,6 +338,7 @@ fn infer_definition(
|
|||
label,
|
||||
annotation,
|
||||
location,
|
||||
doc,
|
||||
..
|
||||
},
|
||||
t,
|
||||
|
@ -348,7 +348,7 @@ fn infer_definition(
|
|||
annotation,
|
||||
location,
|
||||
tipo: t.clone(),
|
||||
doc: None,
|
||||
doc,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
@ -39,12 +39,12 @@ use crate::{
|
|||
IdGenerator,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CodeGenerator<'a> {
|
||||
defined_functions: IndexMap<FunctionAccessKey, ()>,
|
||||
functions: &'a IndexMap<FunctionAccessKey, &'a TypedFunction>,
|
||||
// type_aliases: &'a IndexMap<(String, String), &'a TypeAlias<Arc<tipo::Type>>>,
|
||||
data_types: &'a IndexMap<DataTypeKey, &'a TypedDataType>,
|
||||
module_types: &'a IndexMap<String, TypeInfo>,
|
||||
functions: IndexMap<FunctionAccessKey, &'a TypedFunction>,
|
||||
data_types: IndexMap<DataTypeKey, &'a TypedDataType>,
|
||||
module_types: IndexMap<&'a String, &'a TypeInfo>,
|
||||
id_gen: IdGenerator,
|
||||
needs_field_access: bool,
|
||||
used_data_assert_on_list: bool,
|
||||
|
@ -53,15 +53,13 @@ pub struct CodeGenerator<'a> {
|
|||
|
||||
impl<'a> CodeGenerator<'a> {
|
||||
pub fn new(
|
||||
functions: &'a IndexMap<FunctionAccessKey, &'a TypedFunction>,
|
||||
// type_aliases: &'a IndexMap<(String, String), &'a TypeAlias<Arc<tipo::Type>>>,
|
||||
data_types: &'a IndexMap<DataTypeKey, &'a TypedDataType>,
|
||||
module_types: &'a IndexMap<String, TypeInfo>,
|
||||
functions: IndexMap<FunctionAccessKey, &'a TypedFunction>,
|
||||
data_types: IndexMap<DataTypeKey, &'a TypedDataType>,
|
||||
module_types: IndexMap<&'a String, &'a TypeInfo>,
|
||||
) -> Self {
|
||||
CodeGenerator {
|
||||
defined_functions: IndexMap::new(),
|
||||
functions,
|
||||
// type_aliases,
|
||||
data_types,
|
||||
module_types,
|
||||
id_gen: IdGenerator::new(),
|
||||
|
@ -73,14 +71,14 @@ impl<'a> CodeGenerator<'a> {
|
|||
|
||||
pub fn generate(
|
||||
&mut self,
|
||||
body: TypedExpr,
|
||||
arguments: Vec<TypedArg>,
|
||||
body: &TypedExpr,
|
||||
arguments: &[TypedArg],
|
||||
wrap_as_validator: bool,
|
||||
) -> Program<Name> {
|
||||
let mut ir_stack = vec![];
|
||||
let scope = vec![self.id_gen.next()];
|
||||
|
||||
self.build_ir(&body, &mut ir_stack, scope);
|
||||
self.build_ir(body, &mut ir_stack, scope);
|
||||
|
||||
self.define_ir(&mut ir_stack);
|
||||
|
||||
|
@ -2865,7 +2863,7 @@ impl<'a> CodeGenerator<'a> {
|
|||
variant_name: String::new(),
|
||||
};
|
||||
|
||||
let function = self.functions.get(&non_variant_function_key).unwrap();
|
||||
let function = *self.functions.get(&non_variant_function_key).unwrap();
|
||||
|
||||
let mut func_ir = vec![];
|
||||
|
||||
|
@ -3355,7 +3353,7 @@ impl<'a> CodeGenerator<'a> {
|
|||
count,
|
||||
scope,
|
||||
} => {
|
||||
if check_replaceable_opaque_type(&tipo, self.data_types) {
|
||||
if check_replaceable_opaque_type(&tipo, &self.data_types) {
|
||||
indices_to_remove.push(index);
|
||||
} else {
|
||||
let mut replaced_type = tipo.clone();
|
||||
|
@ -3377,7 +3375,7 @@ impl<'a> CodeGenerator<'a> {
|
|||
let record = ir_stack[index + 1].clone();
|
||||
let record_type = record.tipo();
|
||||
if let Some(record_type) = record_type {
|
||||
if check_replaceable_opaque_type(&record_type, self.data_types) {
|
||||
if check_replaceable_opaque_type(&record_type, &self.data_types) {
|
||||
indices_to_remove.push(index);
|
||||
} else {
|
||||
let mut replaced_type = tipo.clone();
|
||||
|
@ -3408,7 +3406,7 @@ impl<'a> CodeGenerator<'a> {
|
|||
let record = ir_stack[index + 1].clone();
|
||||
let record_type = record.tipo();
|
||||
if let Some(record_type) = record_type {
|
||||
if check_replaceable_opaque_type(&record_type, self.data_types) {
|
||||
if check_replaceable_opaque_type(&record_type, &self.data_types) {
|
||||
ir_stack[index] = Air::Let {
|
||||
scope,
|
||||
name: indices[0].1.clone(),
|
||||
|
|
|
@ -11,6 +11,7 @@ authors = ["Lucas Rosa <x@rvcas.dev>", "Kasey White <kwhitemsg@gmail.com>"]
|
|||
[dependencies]
|
||||
aiken-lang = { path = "../aiken-lang", version = "0.0.28" }
|
||||
askama = "0.10.5"
|
||||
assert-json-diff = "2.0.2"
|
||||
dirs = "4.0.0"
|
||||
fslock = "0.2.1"
|
||||
futures = "0.3.25"
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
use super::schema;
|
||||
use crate::module::CheckedModule;
|
||||
use aiken_lang::{
|
||||
ast::{Span, TypedFunction},
|
||||
tipo::Type,
|
||||
};
|
||||
use miette::{Diagnostic, NamedSource};
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
#[derive(Debug, thiserror::Error, Diagnostic)]
|
||||
pub enum Error {
|
||||
#[error("A validator must return {}", "Bool".bright_blue().bold())]
|
||||
#[diagnostic(code("aiken::blueprint::invalid::return_type"))]
|
||||
#[diagnostic(help(r#"While analyzing the return type of your validator, I found it to be:
|
||||
|
||||
╰─▶ {signature}
|
||||
|
||||
...but I expected this to be a {type_Bool}. If I am inferring the wrong type, you may want to add a type annotation to the function."#
|
||||
, type_Bool = "Bool".bright_blue().bold()
|
||||
, signature = return_type.to_pretty(0).red()
|
||||
))]
|
||||
ValidatorMustReturnBool {
|
||||
#[label("invalid return type")]
|
||||
location: Span,
|
||||
#[source_code]
|
||||
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 {
|
||||
name: String,
|
||||
at_least: u8,
|
||||
#[label("not enough arguments")]
|
||||
location: Span,
|
||||
#[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")]
|
||||
location: Span,
|
||||
#[source_code]
|
||||
source_code: NamedSource,
|
||||
},
|
||||
|
||||
#[error("Invalid or missing project's blueprint file.")]
|
||||
#[diagnostic(code("aiken::blueprint::missing"))]
|
||||
#[diagnostic(help("Did you forget to {build} the project?", build = "build".purple().bold()))]
|
||||
InvalidOrMissingFile,
|
||||
}
|
||||
|
||||
pub fn assert_return_bool(module: &CheckedModule, def: &TypedFunction) -> Result<(), Error> {
|
||||
if !def.return_type.is_bool() {
|
||||
Err(Error::ValidatorMustReturnBool {
|
||||
return_type: def.return_type.clone(),
|
||||
location: def.location,
|
||||
source_code: NamedSource::new(
|
||||
module.input_path.display().to_string(),
|
||||
module.code.clone(),
|
||||
),
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_min_arity(
|
||||
module: &CheckedModule,
|
||||
def: &TypedFunction,
|
||||
at_least: u8,
|
||||
) -> Result<(), Error> {
|
||||
if def.arguments.len() < at_least as usize {
|
||||
Err(Error::WrongValidatorArity {
|
||||
name: def.name.clone(),
|
||||
at_least,
|
||||
location: def.location,
|
||||
source_code: NamedSource::new(
|
||||
module.input_path.display().to_string(),
|
||||
module.code.clone(),
|
||||
),
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
pub mod error;
|
||||
pub mod schema;
|
||||
pub mod validator;
|
||||
|
||||
use crate::{config::Config, module::CheckedModules};
|
||||
use aiken_lang::uplc::CodeGenerator;
|
||||
use error::Error;
|
||||
use schema::Schema;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use validator::Validator;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Blueprint<T> {
|
||||
pub preamble: Preamble,
|
||||
pub validators: Vec<validator::Validator<T>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Preamble {
|
||||
pub title: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
pub version: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub license: Option<String>,
|
||||
}
|
||||
|
||||
impl Blueprint<Schema> {
|
||||
pub fn new(
|
||||
config: &Config,
|
||||
modules: &CheckedModules,
|
||||
generator: &mut CodeGenerator,
|
||||
) -> Result<Self, Error> {
|
||||
let preamble = config.into();
|
||||
|
||||
let validators: Result<Vec<_>, Error> = modules
|
||||
.validators()
|
||||
.map(|(validator, def)| {
|
||||
Validator::from_checked_module(modules, generator, validator, def)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Blueprint {
|
||||
preamble,
|
||||
validators: validators?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
f.write_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Config> for Preamble {
|
||||
fn from(config: &Config) -> Self {
|
||||
Preamble {
|
||||
title: config.name.to_string(),
|
||||
description: if config.description.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(config.description.clone())
|
||||
},
|
||||
version: config.version.clone(),
|
||||
license: config.license.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use serde_json::{self, json};
|
||||
|
||||
#[test]
|
||||
fn serialize_no_description() {
|
||||
let blueprint: Blueprint<Schema> = Blueprint {
|
||||
preamble: Preamble {
|
||||
title: "Foo".to_string(),
|
||||
description: None,
|
||||
version: "1.0.0".to_string(),
|
||||
license: Some("Apache-2.0".to_string()),
|
||||
},
|
||||
validators: vec![],
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(&blueprint).unwrap(),
|
||||
json!({
|
||||
"preamble": {
|
||||
"title": "Foo",
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"validators": []
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_with_description() {
|
||||
let blueprint: Blueprint<Schema> = Blueprint {
|
||||
preamble: Preamble {
|
||||
title: "Foo".to_string(),
|
||||
description: Some("Lorem ipsum".to_string()),
|
||||
version: "1.0.0".to_string(),
|
||||
license: None,
|
||||
},
|
||||
validators: vec![],
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(&blueprint).unwrap(),
|
||||
json!({
|
||||
"preamble": {
|
||||
"title": "Foo",
|
||||
"description": "Lorem ipsum",
|
||||
"version": "1.0.0",
|
||||
},
|
||||
"validators": []
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,750 @@
|
|||
use crate::CheckedModule;
|
||||
use aiken_lang::{
|
||||
ast::{DataType, Definition, TypedDefinition},
|
||||
tipo::{pretty, Type, TypeVar},
|
||||
};
|
||||
use owo_colors::OwoColorize;
|
||||
use serde::{
|
||||
self,
|
||||
ser::{Serialize, SerializeStruct, Serializer},
|
||||
};
|
||||
use serde_json;
|
||||
use std::ops::Deref;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, Display},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Annotated<T> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub annotated: T,
|
||||
}
|
||||
|
||||
/// A schema for low-level UPLC primitives.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Schema {
|
||||
Unit,
|
||||
Boolean,
|
||||
Integer,
|
||||
Bytes,
|
||||
String,
|
||||
Pair(Data, Data),
|
||||
List(Vec<Data>),
|
||||
Data(Option<Data>),
|
||||
}
|
||||
|
||||
/// A schema for Plutus' Data.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Data {
|
||||
Integer,
|
||||
Bytes,
|
||||
List(Box<Data>),
|
||||
Map(Box<Data>, Box<Data>),
|
||||
AnyOf(Vec<Annotated<Constructor>>),
|
||||
}
|
||||
|
||||
/// Captures a single UPLC constructor with its
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Constructor {
|
||||
pub index: usize,
|
||||
pub fields: Vec<Annotated<Data>>,
|
||||
}
|
||||
|
||||
impl<T> From<T> for Annotated<T> {
|
||||
fn from(annotated: T) -> Self {
|
||||
Annotated {
|
||||
title: None,
|
||||
description: None,
|
||||
annotated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Annotated<Schema> {
|
||||
pub fn from_type(
|
||||
modules: &HashMap<String, CheckedModule>,
|
||||
type_info: &Type,
|
||||
type_parameters: &HashMap<u64, &Arc<Type>>,
|
||||
) -> Result<Self, Error> {
|
||||
match type_info {
|
||||
Type::App {
|
||||
module: module_name,
|
||||
name: type_name,
|
||||
args,
|
||||
..
|
||||
} if module_name.is_empty() => match &type_name[..] {
|
||||
"Data" => Ok(Annotated {
|
||||
title: Some("Data".to_string()),
|
||||
description: Some("Any Plutus data.".to_string()),
|
||||
annotated: Schema::Data(None),
|
||||
}),
|
||||
|
||||
"ByteArray" => Ok(Schema::Data(Some(Data::Bytes)).into()),
|
||||
|
||||
"Int" => Ok(Schema::Data(Some(Data::Integer)).into()),
|
||||
|
||||
"String" => Ok(Schema::String.into()),
|
||||
|
||||
"Void" => Ok(Annotated {
|
||||
title: Some("Unit".to_string()),
|
||||
description: Some("The nullary constructor.".to_string()),
|
||||
annotated: Schema::Data(Some(Data::AnyOf(vec![Annotated {
|
||||
title: None,
|
||||
description: None,
|
||||
annotated: Constructor {
|
||||
index: 0,
|
||||
fields: vec![],
|
||||
},
|
||||
}]))),
|
||||
}),
|
||||
|
||||
"Bool" => Ok(Annotated {
|
||||
title: Some("Bool".to_string()),
|
||||
description: None,
|
||||
annotated: Schema::Data(Some(Data::AnyOf(vec![
|
||||
Annotated {
|
||||
title: Some("False".to_string()),
|
||||
description: None,
|
||||
annotated: Constructor {
|
||||
index: 0,
|
||||
fields: vec![],
|
||||
},
|
||||
},
|
||||
Annotated {
|
||||
title: Some("True".to_string()),
|
||||
description: None,
|
||||
annotated: Constructor {
|
||||
index: 1,
|
||||
fields: vec![],
|
||||
},
|
||||
},
|
||||
]))),
|
||||
}),
|
||||
|
||||
"Option" => {
|
||||
let generic =
|
||||
Annotated::from_type(modules, args.get(0).unwrap(), type_parameters)
|
||||
.and_then(|s| s.into_data(type_info))?;
|
||||
Ok(Annotated {
|
||||
title: Some("Optional".to_string()),
|
||||
description: None,
|
||||
annotated: Schema::Data(Some(Data::AnyOf(vec![
|
||||
Annotated {
|
||||
title: Some("Some".to_string()),
|
||||
description: Some("An optional value.".to_string()),
|
||||
annotated: Constructor {
|
||||
index: 0,
|
||||
fields: vec![generic],
|
||||
},
|
||||
},
|
||||
Annotated {
|
||||
title: Some("None".to_string()),
|
||||
description: Some("Nothing.".to_string()),
|
||||
annotated: Constructor {
|
||||
index: 1,
|
||||
fields: vec![],
|
||||
},
|
||||
},
|
||||
]))),
|
||||
})
|
||||
}
|
||||
|
||||
"List" => {
|
||||
let generic =
|
||||
Annotated::from_type(modules, args.get(0).unwrap(), type_parameters)?;
|
||||
|
||||
// NOTE: Lists of 2-tuples are treated as Maps. This is an oddity we inherit
|
||||
// from the PlutusTx / LedgerApi Haskell codebase, which encodes some elements
|
||||
// as such. We don't have a concept of language maps in Aiken, so we simply
|
||||
// make all types abide by this convention.
|
||||
let data = match generic.annotated {
|
||||
Schema::Pair(left, right) => Data::Map(Box::new(left), Box::new(right)),
|
||||
_ => {
|
||||
let inner = generic.into_data(type_info)?.annotated;
|
||||
Data::List(Box::new(inner))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Schema::Data(Some(data)).into())
|
||||
}
|
||||
|
||||
_ => Err(Error::new(ErrorContext::UnsupportedType, type_info)),
|
||||
},
|
||||
Type::App {
|
||||
module: module_name,
|
||||
name: type_name,
|
||||
args,
|
||||
..
|
||||
} => {
|
||||
let module = modules.get(module_name).unwrap();
|
||||
let constructor = find_definition(type_name, &module.ast.definitions).unwrap();
|
||||
let type_parameters = collect_type_parameters(&constructor.typed_parameters, args);
|
||||
let annotated = Schema::Data(Some(
|
||||
Data::from_data_type(modules, constructor, &type_parameters)
|
||||
.map_err(|e| e.backtrack(type_info))?,
|
||||
));
|
||||
|
||||
Ok(Annotated {
|
||||
title: Some(constructor.name.clone()),
|
||||
description: constructor.doc.clone().map(|s| s.trim().to_string()),
|
||||
annotated,
|
||||
})
|
||||
}
|
||||
Type::Var { tipo } => match tipo.borrow().deref() {
|
||||
TypeVar::Link { tipo } => Annotated::from_type(modules, tipo, type_parameters),
|
||||
TypeVar::Generic { id } => {
|
||||
let tipo = type_parameters
|
||||
.get(id)
|
||||
.ok_or_else(|| Error::new(ErrorContext::FreeTypeVariable, type_info))?;
|
||||
Annotated::from_type(modules, tipo, type_parameters)
|
||||
}
|
||||
TypeVar::Unbound { .. } => {
|
||||
Err(Error::new(ErrorContext::UnboundTypeVariable, type_info))
|
||||
}
|
||||
},
|
||||
Type::Tuple { elems } => match &elems[..] {
|
||||
[left, right] => {
|
||||
let left = Annotated::from_type(modules, left, type_parameters)?
|
||||
.into_data(left)
|
||||
.map_err(|e| e.backtrack(type_info))?;
|
||||
let right = Annotated::from_type(modules, right, type_parameters)?
|
||||
.into_data(right)
|
||||
.map_err(|e| e.backtrack(type_info))?;
|
||||
Ok(Schema::Pair(left.annotated, right.annotated).into())
|
||||
}
|
||||
_ => {
|
||||
let elems = elems
|
||||
.iter()
|
||||
.map(|e| {
|
||||
Annotated::from_type(modules, e, type_parameters)
|
||||
.and_then(|s| s.into_data(e).map(|s| s.annotated))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| e.backtrack(type_info))?;
|
||||
Ok(Annotated {
|
||||
title: Some("Tuple".to_owned()),
|
||||
description: None,
|
||||
annotated: Schema::List(elems),
|
||||
})
|
||||
}
|
||||
},
|
||||
Type::Fn { .. } => Err(Error::new(ErrorContext::UnexpectedFunction, type_info)),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_data(self, type_info: &Type) -> Result<Annotated<Data>, Error> {
|
||||
match self {
|
||||
Annotated {
|
||||
title,
|
||||
description,
|
||||
annotated: Schema::Data(Some(data)),
|
||||
} => Ok(Annotated {
|
||||
title,
|
||||
description,
|
||||
annotated: data,
|
||||
}),
|
||||
_ => Err(Error::new(ErrorContext::ExpectedData, type_info)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub fn from_data_type(
|
||||
modules: &HashMap<String, CheckedModule>,
|
||||
data_type: &DataType<Arc<Type>>,
|
||||
type_parameters: &HashMap<u64, &Arc<Type>>,
|
||||
) -> Result<Self, Error> {
|
||||
let mut variants = vec![];
|
||||
|
||||
for (index, constructor) in data_type.constructors.iter().enumerate() {
|
||||
let mut fields = vec![];
|
||||
for field in constructor.arguments.iter() {
|
||||
let mut schema = Annotated::from_type(modules, &field.tipo, type_parameters)
|
||||
.and_then(|t| t.into_data(&field.tipo))?;
|
||||
|
||||
if field.label.is_some() {
|
||||
schema.title = field.label.clone();
|
||||
}
|
||||
|
||||
if field.doc.is_some() {
|
||||
schema.description = field.doc.clone().map(|s| s.trim().to_string());
|
||||
}
|
||||
|
||||
fields.push(schema);
|
||||
}
|
||||
|
||||
let variant = Annotated {
|
||||
title: Some(constructor.name.clone()),
|
||||
description: constructor.doc.clone().map(|s| s.trim().to_string()),
|
||||
annotated: Constructor { index, fields },
|
||||
};
|
||||
|
||||
variants.push(variant);
|
||||
}
|
||||
|
||||
// NOTE: Opaque data-types with a single variant and a single field are transparent, they
|
||||
// are erased completely at compilation time.
|
||||
if data_type.opaque {
|
||||
if let [variant] = &variants[..] {
|
||||
if let [field] = &variant.annotated.fields[..] {
|
||||
return Ok(field.annotated.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Data::AnyOf(variants))
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
f.write_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Schema {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
Schema::Unit => {
|
||||
let mut s = serializer.serialize_struct("Unit", 1)?;
|
||||
s.serialize_field("dataType", "#unit")?;
|
||||
s.end()
|
||||
}
|
||||
Schema::Boolean => {
|
||||
let mut s = serializer.serialize_struct("Integer", 1)?;
|
||||
s.serialize_field("dataType", "#integer")?;
|
||||
s.end()
|
||||
}
|
||||
Schema::Integer => {
|
||||
let mut s = serializer.serialize_struct("Integer", 1)?;
|
||||
s.serialize_field("dataType", "#integer")?;
|
||||
s.end()
|
||||
}
|
||||
Schema::Bytes => {
|
||||
let mut s = serializer.serialize_struct("Bytes", 1)?;
|
||||
s.serialize_field("dataType", "#bytes")?;
|
||||
s.end()
|
||||
}
|
||||
Schema::String => {
|
||||
let mut s = serializer.serialize_struct("String", 1)?;
|
||||
s.serialize_field("dataType", "#string")?;
|
||||
s.end()
|
||||
}
|
||||
Schema::Pair(left, right) => {
|
||||
let mut s = serializer.serialize_struct("Pair", 3)?;
|
||||
s.serialize_field("dataType", "#pair")?;
|
||||
s.serialize_field("left", &left)?;
|
||||
s.serialize_field("right", &right)?;
|
||||
s.end()
|
||||
}
|
||||
Schema::List(elements) => {
|
||||
let mut s = serializer.serialize_struct("List", 2)?;
|
||||
s.serialize_field("dataType", "#list")?;
|
||||
s.serialize_field("elements", &elements)?;
|
||||
s.end()
|
||||
}
|
||||
Schema::Data(None) => {
|
||||
let s = serializer.serialize_struct("Data", 0)?;
|
||||
s.end()
|
||||
}
|
||||
Schema::Data(Some(data)) => data.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Data {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
Data::Integer => {
|
||||
let mut s = serializer.serialize_struct("Integer", 1)?;
|
||||
s.serialize_field("dataType", "integer")?;
|
||||
s.end()
|
||||
}
|
||||
Data::Bytes => {
|
||||
let mut s = serializer.serialize_struct("Bytes", 1)?;
|
||||
s.serialize_field("dataType", "bytes")?;
|
||||
s.end()
|
||||
}
|
||||
Data::List(items) => {
|
||||
let mut s = serializer.serialize_struct("List", 2)?;
|
||||
s.serialize_field("dataType", "list")?;
|
||||
s.serialize_field("items", &items)?;
|
||||
s.end()
|
||||
}
|
||||
Data::Map(keys, values) => {
|
||||
let mut s = serializer.serialize_struct("Map", 3)?;
|
||||
s.serialize_field("dataType", "map")?;
|
||||
s.serialize_field("keys", &keys)?;
|
||||
s.serialize_field("values", &values)?;
|
||||
s.end()
|
||||
}
|
||||
Data::AnyOf(constructors) => {
|
||||
// TODO: Avoid 'anyOf' applicator when there's only one constructor
|
||||
//
|
||||
// match &constructors[..] {
|
||||
// [constructor] => constructor.serialize(serializer),
|
||||
// _ => {
|
||||
let mut s = serializer.serialize_struct("AnyOf", 1)?;
|
||||
s.serialize_field("anyOf", &constructors)?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Serialize for Constructor {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut s = serializer.serialize_struct("Constructor", 3)?;
|
||||
s.serialize_field("dataType", "constructor")?;
|
||||
s.serialize_field("index", &self.index)?;
|
||||
s.serialize_field("fields", &self.fields)?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, thiserror::Error)]
|
||||
#[error("{}", context)]
|
||||
pub struct Error {
|
||||
context: ErrorContext,
|
||||
breadcrumbs: Vec<Type>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, thiserror::Error)]
|
||||
pub enum ErrorContext {
|
||||
#[error("I failed at my own job and couldn't figure out how to generate a specification for a type.")]
|
||||
UnsupportedType,
|
||||
|
||||
#[error("I discovered a type hole where I would expect a concrete type.")]
|
||||
UnboundTypeVariable,
|
||||
|
||||
#[error("I caught a free variable in the contract's interface boundary.")]
|
||||
FreeTypeVariable,
|
||||
|
||||
#[error("I had the misfortune to find an invalid type in an interface boundary.")]
|
||||
ExpectedData,
|
||||
|
||||
#[error("I figured you tried to export a function in your contract's binary interface.")]
|
||||
UnexpectedFunction,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new(context: ErrorContext, type_info: &Type) -> Self {
|
||||
Error {
|
||||
context,
|
||||
breadcrumbs: vec![type_info.clone()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn backtrack(self, type_info: &Type) -> Self {
|
||||
let mut breadcrumbs = vec![type_info.clone()];
|
||||
breadcrumbs.extend(self.breadcrumbs);
|
||||
Error {
|
||||
context: self.context,
|
||||
breadcrumbs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn help(&self) -> String {
|
||||
match self.context {
|
||||
ErrorContext::UnsupportedType => format!(
|
||||
r#"I do not know how to generate a portable Plutus specification for the following type:
|
||||
|
||||
╰─▶ {signature}
|
||||
|
||||
This is likely a bug. I should know. May you be kind enough and report this on <https://github.com/aiken-lang/aiken>."#,
|
||||
signature = Error::fmt_breadcrumbs(&[self.breadcrumbs.last().unwrap().to_owned()]),
|
||||
),
|
||||
|
||||
ErrorContext::FreeTypeVariable => format!(
|
||||
r#"There can't be any free type variable at the contract's boundary (i.e. in types used as datum and/or redeemer). Indeed, the validator can only be invoked with (very) concrete types. Since there's no reflexion possible inside a validator, it simply isn't possible to have any remaining free type variable in any of the datum or redeemer.
|
||||
|
||||
I got there when trying to generate a blueprint specification of the following type:
|
||||
|
||||
╰─▶ {breadcrumbs}"#,
|
||||
breadcrumbs = Error::fmt_breadcrumbs(&self.breadcrumbs)
|
||||
),
|
||||
|
||||
ErrorContext::UnboundTypeVariable => format!(
|
||||
r#"There cannot be any unbound type variable at the contract's boundary (i.e. in types used as datum and/or redeemer). Indeed, in order to generate an outward-facing specification of the contract's interface, I need to know what concrete representations will the datum and/or the redeemer have.
|
||||
|
||||
If your contract doesn't need datum or redeemer, you can always give them the type {type_Void} to indicate this. It is very concrete and will help me progress forward."#,
|
||||
type_Void = "Void".bright_blue().bold()
|
||||
),
|
||||
|
||||
ErrorContext::ExpectedData => format!(
|
||||
r#"While figuring out the outward-facing specification for your contract, I found a type that cannot actually be represented as valid Untyped Plutus Core (the low-level language Cardano uses to execute smart-contracts. For example, it isn't possible to have a list or a tuple of {type_String} because the underlying execution engine doesn't allow it.
|
||||
|
||||
There are few restrictions like this one. In this instance, here's the types I followed and that led me to this problem:
|
||||
|
||||
╰─▶ {breadcrumbs}"#,
|
||||
type_String = "String".bright_blue().bold(),
|
||||
breadcrumbs = Error::fmt_breadcrumbs(&self.breadcrumbs)
|
||||
),
|
||||
|
||||
ErrorContext::UnexpectedFunction => format!(
|
||||
r#"I can't allow that. Functions aren't serializable as data on-chain and thus cannot be used within your datum and/or redeemer types.
|
||||
|
||||
Here's the types I followed and that led me to this problem:
|
||||
|
||||
╰─▶ {breadcrumbs}"#,
|
||||
breadcrumbs = Error::fmt_breadcrumbs(&self.breadcrumbs)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_breadcrumbs(breadcrumbs: &[Type]) -> String {
|
||||
breadcrumbs
|
||||
.iter()
|
||||
.map(|type_info| {
|
||||
pretty::Printer::new()
|
||||
.print(type_info)
|
||||
.to_pretty_string(70)
|
||||
.bright_blue()
|
||||
.bold()
|
||||
.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" → ")
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_type_parameters<'a>(
|
||||
generics: &'a [Arc<Type>],
|
||||
applications: &'a [Arc<Type>],
|
||||
) -> HashMap<u64, &'a Arc<Type>> {
|
||||
let mut type_parameters = HashMap::new();
|
||||
|
||||
for (index, generic) in generics.iter().enumerate() {
|
||||
match &**generic {
|
||||
Type::Var { tipo } => match *tipo.borrow() {
|
||||
TypeVar::Generic { id } => {
|
||||
type_parameters.insert(id, applications.get(index).unwrap());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
type_parameters
|
||||
}
|
||||
|
||||
fn find_definition<'a>(
|
||||
name: &str,
|
||||
definitions: &'a Vec<TypedDefinition>,
|
||||
) -> Option<&'a DataType<Arc<Type>>> {
|
||||
for def in definitions {
|
||||
match def {
|
||||
Definition::DataType(data_type) if name == data_type.name => return Some(data_type),
|
||||
Definition::Fn { .. }
|
||||
| Definition::DataType { .. }
|
||||
| Definition::TypeAlias { .. }
|
||||
| Definition::Use { .. }
|
||||
| Definition::ModuleConstant { .. }
|
||||
| Definition::Test { .. } => continue,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use serde_json::{self, json, Value};
|
||||
|
||||
pub fn assert_json(schema: &impl Serialize, expected: Value) {
|
||||
assert_eq!(serde_json::to_value(schema).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_data_integer() {
|
||||
let schema = Schema::Data(Some(Data::Integer));
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
"dataType": "integer"
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_data_bytes() {
|
||||
let schema = Schema::Data(Some(Data::Bytes));
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
"dataType": "bytes"
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_data_list_1() {
|
||||
let schema = Schema::Data(Some(Data::List(Box::new(Data::Integer))));
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
"dataType": "list",
|
||||
"items": {
|
||||
"dataType": "integer"
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_data_list_2() {
|
||||
let schema = Schema::Data(Some(Data::List(Box::new(Data::List(Box::new(
|
||||
Data::Integer,
|
||||
))))));
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
"dataType": "list",
|
||||
"items":
|
||||
{
|
||||
"dataType": "list",
|
||||
"items": { "dataType": "integer" }
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_data_map_1() {
|
||||
let schema = Schema::Data(Some(Data::Map(
|
||||
Box::new(Data::Integer),
|
||||
Box::new(Data::Bytes),
|
||||
)));
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
"dataType": "map",
|
||||
"keys": {
|
||||
"dataType": "integer"
|
||||
},
|
||||
"values": {
|
||||
"dataType": "bytes"
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_data_map_2() {
|
||||
let schema = Schema::Data(Some(Data::Map(
|
||||
Box::new(Data::Bytes),
|
||||
Box::new(Data::List(Box::new(Data::Integer))),
|
||||
)));
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
"dataType": "map",
|
||||
"keys": {
|
||||
"dataType": "bytes"
|
||||
},
|
||||
"values": {
|
||||
"dataType": "list",
|
||||
"items": { "dataType": "integer" }
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_data_constr_1() {
|
||||
let schema = Schema::Data(Some(Data::AnyOf(vec![Constructor {
|
||||
index: 0,
|
||||
fields: vec![],
|
||||
}
|
||||
.into()])));
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
"anyOf": [{
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": []
|
||||
}]
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_data_constr_2() {
|
||||
let schema = Schema::Data(Some(Data::AnyOf(vec![
|
||||
Constructor {
|
||||
index: 0,
|
||||
fields: vec![Data::Integer.into()],
|
||||
}
|
||||
.into(),
|
||||
Constructor {
|
||||
index: 1,
|
||||
fields: vec![Data::Bytes.into()],
|
||||
}
|
||||
.into(),
|
||||
])));
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
"anyOf": [
|
||||
{
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": [{ "dataType": "integer" }]
|
||||
},
|
||||
{
|
||||
"dataType": "constructor",
|
||||
"index": 1,
|
||||
"fields": [{ "dataType": "bytes" }]
|
||||
}
|
||||
]
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_empty_data() {
|
||||
let schema = Schema::Data(None);
|
||||
assert_json(&schema, json!({}))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_annotated_1() {
|
||||
let schema = Annotated {
|
||||
title: Some("foo".to_string()),
|
||||
description: None,
|
||||
annotated: Schema::Integer,
|
||||
};
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
"title": "foo",
|
||||
"dataType": "#integer"
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_annotated_2() {
|
||||
let schema = Annotated {
|
||||
title: Some("foo".to_string()),
|
||||
description: Some("Lorem Ipsum".to_string()),
|
||||
annotated: Schema::String,
|
||||
};
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
"title": "foo",
|
||||
"description": "Lorem Ipsum",
|
||||
"dataType": "#string"
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,607 @@
|
|||
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 miette::NamedSource;
|
||||
use serde;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, Display},
|
||||
};
|
||||
use uplc::ast::{DeBruijn, Program};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Validator<T> {
|
||||
pub title: String,
|
||||
pub purpose: Purpose,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub datum: Option<Annotated<T>>,
|
||||
pub redeemer: Annotated<T>,
|
||||
#[serde(flatten)]
|
||||
pub program: Program<DeBruijn>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Purpose {
|
||||
Spend,
|
||||
Mint,
|
||||
Withdraw,
|
||||
Publish,
|
||||
}
|
||||
|
||||
impl Display for Validator<Schema> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = serde_json::to_string_pretty(self).map_err(|_| fmt::Error)?;
|
||||
f.write_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator<Schema> {
|
||||
pub fn from_checked_module(
|
||||
modules: &CheckedModules,
|
||||
generator: &mut CodeGenerator,
|
||||
validator: &CheckedModule,
|
||||
def: &TypedFunction,
|
||||
) -> Result<Validator<Schema>, Error> {
|
||||
let purpose: Purpose = def
|
||||
.name
|
||||
.clone()
|
||||
.try_into()
|
||||
.expect("unexpected validator name");
|
||||
|
||||
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, &HashMap::new()).map_err(
|
||||
|error| Error::Schema {
|
||||
error,
|
||||
location: datum.location,
|
||||
source_code: NamedSource::new(
|
||||
validator.input_path.display().to_string(),
|
||||
validator.code.clone(),
|
||||
),
|
||||
},
|
||||
)
|
||||
})
|
||||
.transpose()?,
|
||||
redeemer: Annotated::from_type(modules.into(), &redeemer.tipo, &HashMap::new())
|
||||
.map_err(|error| Error::Schema {
|
||||
error,
|
||||
location: redeemer.location,
|
||||
source_code: NamedSource::new(
|
||||
validator.input_path.display().to_string(),
|
||||
validator.code.clone(),
|
||||
),
|
||||
})?,
|
||||
program: generator
|
||||
.generate(&def.body, &def.arguments, true)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Purpose {
|
||||
pub fn min_arity(&self) -> u8 {
|
||||
match self {
|
||||
Purpose::Spend => 3,
|
||||
Purpose::Mint | Purpose::Withdraw | Purpose::Publish => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Purpose {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
Purpose::Spend => "spend",
|
||||
Purpose::Mint => "mint",
|
||||
Purpose::Withdraw => "withdraw",
|
||||
Purpose::Publish => "publish",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Purpose {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(purpose: String) -> Result<Purpose, Self::Error> {
|
||||
match &purpose[..] {
|
||||
"spend" => Ok(Purpose::Spend),
|
||||
"mint" => Ok(Purpose::Mint),
|
||||
"withdraw" => Ok(Purpose::Withdraw),
|
||||
"publish" => Ok(Purpose::Publish),
|
||||
unexpected => Err(format!("Can't turn '{}' into any Purpose", unexpected)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{module::ParsedModule, PackageName};
|
||||
use aiken_lang::{
|
||||
self,
|
||||
ast::{ModuleKind, TypedDataType, TypedFunction},
|
||||
builder::{DataTypeKey, FunctionAccessKey},
|
||||
builtins, parser,
|
||||
tipo::TypeInfo,
|
||||
IdGenerator,
|
||||
};
|
||||
use assert_json_diff::assert_json_eq;
|
||||
use indexmap::IndexMap;
|
||||
use serde_json::{self, json};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
// TODO: Possible refactor this out of the module and have it used by `Project`. The idea would
|
||||
// be to make this struct below the actual project, and wrap it in another metadata struct
|
||||
// which contains all the config and I/O stuff regarding the project.
|
||||
struct TestProject {
|
||||
package: PackageName,
|
||||
id_gen: IdGenerator,
|
||||
module_types: HashMap<String, TypeInfo>,
|
||||
functions: IndexMap<FunctionAccessKey, TypedFunction>,
|
||||
data_types: IndexMap<DataTypeKey, TypedDataType>,
|
||||
}
|
||||
|
||||
impl TestProject {
|
||||
fn new() -> Self {
|
||||
let id_gen = IdGenerator::new();
|
||||
|
||||
let package = PackageName {
|
||||
owner: "test".to_owned(),
|
||||
repo: "project".to_owned(),
|
||||
};
|
||||
|
||||
let mut module_types = HashMap::new();
|
||||
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
|
||||
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
|
||||
|
||||
let functions = builtins::prelude_functions(&id_gen);
|
||||
let data_types = builtins::prelude_data_types(&id_gen);
|
||||
|
||||
TestProject {
|
||||
package,
|
||||
id_gen,
|
||||
module_types,
|
||||
functions,
|
||||
data_types,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(&self, source_code: &str) -> ParsedModule {
|
||||
let kind = ModuleKind::Validator;
|
||||
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,
|
||||
path: PathBuf::new(),
|
||||
extra,
|
||||
package: self.package.to_string(),
|
||||
};
|
||||
module.attach_doc_and_module_comments();
|
||||
module
|
||||
}
|
||||
|
||||
fn check(&mut self, module: ParsedModule) -> CheckedModule {
|
||||
let mut warnings = vec![];
|
||||
|
||||
let ast = module
|
||||
.ast
|
||||
.infer(
|
||||
&self.id_gen,
|
||||
module.kind,
|
||||
&self.package.to_string(),
|
||||
&self.module_types,
|
||||
&mut warnings,
|
||||
)
|
||||
.expect("Failed to type-check module");
|
||||
|
||||
self.module_types
|
||||
.insert(module.name.clone(), ast.type_info.clone());
|
||||
|
||||
CheckedModule {
|
||||
kind: module.kind,
|
||||
extra: module.extra,
|
||||
name: module.name,
|
||||
code: module.code,
|
||||
package: module.package,
|
||||
input_path: module.path,
|
||||
ast,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_validator(source_code: &str, json: serde_json::Value) {
|
||||
let mut project = TestProject::new();
|
||||
|
||||
let modules = CheckedModules::singleton(project.check(project.parse(source_code)));
|
||||
let mut generator = modules.new_generator(
|
||||
&project.functions,
|
||||
&project.data_types,
|
||||
&project.module_types,
|
||||
);
|
||||
|
||||
let (validator, def) = modules
|
||||
.validators()
|
||||
.next()
|
||||
.expect("source code did no yield any validator");
|
||||
|
||||
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_mint_basic() {
|
||||
assert_validator(
|
||||
r#"
|
||||
fn mint(redeemer: Data, ctx: Data) {
|
||||
True
|
||||
}
|
||||
"#,
|
||||
json!({
|
||||
"title": "test_module",
|
||||
"purpose": "mint",
|
||||
"hash": "f9fcaa5bfce8bde3b85e595b5235a184fe0fb79916d38273c74a23cf",
|
||||
"redeemer": {
|
||||
"title": "Data",
|
||||
"description": "Any Plutus data."
|
||||
},
|
||||
"compiledCode": "582e0100003232225333573494452616300100122253335573e004293099ab9b3001357420046660060066ae88008005"
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[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": "3b7ee6139deb59d892955ac3cad15d53e48dcb1643227256b29d2b6f",
|
||||
"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": "582f01000032322225333573494452616300100122253335573e004293099ab9b3001357420046660060066ae880080041"
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[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": "4a0c0768ff3e8c8f9da5fc2c499e592ae37f676a11dbc6e9de958116",
|
||||
"datum": {
|
||||
"dataType": "#pair",
|
||||
"left": {
|
||||
"dataType": "integer"
|
||||
},
|
||||
"right": {
|
||||
"dataType": "bytes"
|
||||
}
|
||||
},
|
||||
"redeemer": {
|
||||
"dataType": "#string"
|
||||
},
|
||||
"compiledCode": "584901000032322322322533357349445261637326eb8004c8c8cdd81aba1002357420026ae88004dd600098008009112999aab9f00214984cd5cd98009aba100233300300335744004003"
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[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": "5e7487927f32a4d6e8c3b462c8e0e0f685506621f5f2683807805d0e",
|
||||
"datum": {
|
||||
"title": "Tuple",
|
||||
"dataType": "#list",
|
||||
"elements": [
|
||||
{
|
||||
"dataType": "integer"
|
||||
},
|
||||
{
|
||||
"dataType": "integer"
|
||||
},
|
||||
{
|
||||
"dataType": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"redeemer": {
|
||||
"title": "Data",
|
||||
"description": "Any Plutus data."
|
||||
},
|
||||
"compiledCode": "5833010000323223222533357349445261637580026002002444a666aae7c008526133573660026ae84008ccc00c00cd5d10010009"
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validator_generics() {
|
||||
assert_validator(
|
||||
r#"
|
||||
type Either<left, right> {
|
||||
Left(left)
|
||||
Right(right)
|
||||
}
|
||||
|
||||
type Interval<a> {
|
||||
Finite(a)
|
||||
Infinite
|
||||
}
|
||||
|
||||
fn withdraw(redeemer: Either<ByteArray, Interval<Int>>, ctx: Void) {
|
||||
True
|
||||
}
|
||||
"#,
|
||||
json!(
|
||||
{
|
||||
"title": "test_module",
|
||||
"purpose": "withdraw",
|
||||
"hash": "f9fcaa5bfce8bde3b85e595b5235a184fe0fb79916d38273c74a23cf",
|
||||
"redeemer": {
|
||||
"title": "Either",
|
||||
"anyOf": [
|
||||
{
|
||||
"title": "Left",
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": [
|
||||
{
|
||||
"dataType": "bytes"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Right",
|
||||
"dataType": "constructor",
|
||||
"index": 1,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Interval",
|
||||
"anyOf": [
|
||||
{
|
||||
"title": "Finite",
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": [
|
||||
{
|
||||
"dataType": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Infinite",
|
||||
"dataType": "constructor",
|
||||
"index": 1,
|
||||
"fields": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"compiledCode": "582e0100003232225333573494452616300100122253335573e004293099ab9b3001357420046660060066ae88008005"
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validator_phantom_types() {
|
||||
assert_validator(
|
||||
r#"
|
||||
type Dict<key, value> {
|
||||
inner: List<(ByteArray, value)>
|
||||
}
|
||||
|
||||
type UUID { UUID }
|
||||
|
||||
fn mint(redeemer: Dict<UUID, Int>, ctx: Void) {
|
||||
True
|
||||
}
|
||||
"#,
|
||||
json!(
|
||||
{
|
||||
"title": "test_module",
|
||||
"purpose": "mint",
|
||||
"hash": "f9fcaa5bfce8bde3b85e595b5235a184fe0fb79916d38273c74a23cf",
|
||||
"redeemer": {
|
||||
"title": "Dict",
|
||||
"anyOf": [
|
||||
{
|
||||
"title": "Dict",
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": [
|
||||
{
|
||||
"title": "inner",
|
||||
"dataType": "map",
|
||||
"keys": {
|
||||
"dataType": "bytes"
|
||||
},
|
||||
"values": {
|
||||
"dataType": "integer"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"compiledCode": "582e0100003232225333573494452616300100122253335573e004293099ab9b3001357420046660060066ae88008005"
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validator_opaque_types() {
|
||||
assert_validator(
|
||||
r#"
|
||||
pub opaque type Dict<key, value> {
|
||||
inner: List<(ByteArray, value)>
|
||||
}
|
||||
|
||||
type UUID { UUID }
|
||||
|
||||
fn mint(redeemer: Dict<UUID, Int>, ctx: Void) {
|
||||
True
|
||||
}
|
||||
"#,
|
||||
json!(
|
||||
{
|
||||
"title": "test_module",
|
||||
"purpose": "mint",
|
||||
"hash": "f9fcaa5bfce8bde3b85e595b5235a184fe0fb79916d38273c74a23cf",
|
||||
"redeemer": {
|
||||
"title": "Dict",
|
||||
"dataType": "map",
|
||||
"keys": {
|
||||
"dataType": "bytes"
|
||||
},
|
||||
"values": {
|
||||
"dataType": "integer"
|
||||
}
|
||||
},
|
||||
"compiledCode": "582e0100003232225333573494452616300100122253335573e004293099ab9b3001357420046660060066ae88008005"
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,10 @@
|
|||
use crate::{deps::manifest::Package, package_name::PackageName, pretty, script::EvalHint};
|
||||
use crate::{
|
||||
blueprint::{error as blueprint, validator},
|
||||
deps::manifest::Package,
|
||||
package_name::PackageName,
|
||||
pretty,
|
||||
script::EvalHint,
|
||||
};
|
||||
use aiken_lang::{
|
||||
ast::{BinOp, Span},
|
||||
parser::error::ParseError,
|
||||
|
@ -7,6 +13,7 @@ use aiken_lang::{
|
|||
use miette::{
|
||||
Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, NamedSource, RgbColors, SourceCode,
|
||||
};
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
io,
|
||||
|
@ -31,6 +38,9 @@ pub enum Error {
|
|||
#[error("I found some files with incorrectly formatted source code.")]
|
||||
Format { problem_files: Vec<Unformatted> },
|
||||
|
||||
#[error(transparent)]
|
||||
Blueprint(#[from] blueprint::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
StandardIo(#[from] io::Error),
|
||||
|
||||
|
@ -43,6 +53,9 @@ pub enum Error {
|
|||
#[error(transparent)]
|
||||
JoinError(#[from] tokio::task::JoinError),
|
||||
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[error("{help}")]
|
||||
TomlLoading {
|
||||
path: PathBuf,
|
||||
|
@ -114,6 +127,21 @@ pub enum Error {
|
|||
package.name.repo
|
||||
)]
|
||||
UnknownPackageVersion { package: Package },
|
||||
|
||||
#[error("I couldn't parse the provided stake address.")]
|
||||
MalformedStakeAddress {
|
||||
error: Option<pallas::ledger::addresses::Error>,
|
||||
},
|
||||
|
||||
#[error("I didn't find any validator matching your criteria.")]
|
||||
NoValidatorNotFound {
|
||||
known_validators: Vec<(String, validator::Purpose)>,
|
||||
},
|
||||
|
||||
#[error("I found multiple suitable validators and I need you to tell me which one to pick.")]
|
||||
MoreThanOneValidatorFound {
|
||||
known_validators: Vec<(String, validator::Purpose)>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
@ -183,6 +211,7 @@ impl Error {
|
|||
Error::FileIo { .. } => None,
|
||||
Error::Format { .. } => None,
|
||||
Error::StandardIo(_) => None,
|
||||
Error::Blueprint(_) => None,
|
||||
Error::MissingManifest { path } => Some(path.to_path_buf()),
|
||||
Error::TomlLoading { path, .. } => Some(path.to_path_buf()),
|
||||
Error::ImportCycle { .. } => None,
|
||||
|
@ -196,6 +225,10 @@ impl Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,6 +238,7 @@ impl Error {
|
|||
Error::FileIo { .. } => None,
|
||||
Error::Format { .. } => None,
|
||||
Error::StandardIo(_) => None,
|
||||
Error::Blueprint(_) => None,
|
||||
Error::MissingManifest { .. } => None,
|
||||
Error::TomlLoading { src, .. } => Some(src.to_string()),
|
||||
Error::ImportCycle { .. } => None,
|
||||
|
@ -218,6 +252,10 @@ impl Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -250,6 +288,7 @@ impl Diagnostic for Error {
|
|||
match self {
|
||||
Error::DuplicateModule { .. } => Some(Box::new("aiken::module::duplicate")),
|
||||
Error::FileIo { .. } => None,
|
||||
Error::Blueprint(e) => e.code(),
|
||||
Error::ImportCycle { .. } => Some(Box::new("aiken::module::cyclical")),
|
||||
Error::List(_) => None,
|
||||
Error::Parse { .. } => Some(Box::new("aiken::parser")),
|
||||
|
@ -268,6 +307,10 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion { .. } => Some(Box::new("aiken::packages::resolve")),
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,7 +321,8 @@ impl Diagnostic for Error {
|
|||
first.display(),
|
||||
second.display()
|
||||
))),
|
||||
Error::FileIo { .. } => None,
|
||||
Error::FileIo { error, .. } => Some(Box::new(format!("{error}"))),
|
||||
Error::Blueprint(e) => e.help(),
|
||||
Error::ImportCycle { modules } => Some(Box::new(format!(
|
||||
"Try moving the shared code to a separate module that the others can depend on\n- {}",
|
||||
modules.join("\n- ")
|
||||
|
@ -324,6 +368,23 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion{..} => Some(Box::new("Perhaps, double-check the package repository and version?")),
|
||||
Error::Json(error) => Some(Box::new(format!("{error}"))),
|
||||
Error::MalformedStakeAddress { error } => Some(Box::new(format!("A stake address must be provided either as a base16-encoded string, or as a bech32-encoded string with the 'stake' or 'stake_test' prefix.{hint}", hint = match error {
|
||||
Some(error) => format!("\n\nHere's the error I encountered: {error}"),
|
||||
None => String::new(),
|
||||
}))),
|
||||
Error::NoValidatorNotFound { known_validators } => {
|
||||
Some(Box::new(format!(
|
||||
"Here's a list of all validators (and their purpose) I've found in your project. Please double-check this list against the options that you've provided:\n\n{}",
|
||||
known_validators.iter().map(|(name, purpose)| format!("→ {name} (purpose = {purpose})", name = name.purple().bold(), purpose = purpose.bright_blue())).collect::<Vec<String>>().join("\n")
|
||||
)))
|
||||
},
|
||||
Error::MoreThanOneValidatorFound { known_validators } => {
|
||||
Some(Box::new(format!(
|
||||
"Here's a list of all validators (and their purpose) I've found in your project. Select one of them using the appropriate options:\n\n{}",
|
||||
known_validators.iter().map(|(name, purpose)| format!("→ {name} (purpose = {purpose})", name = name.purple().bold(), purpose = purpose.bright_blue())).collect::<Vec<String>>().join("\n")
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,6 +393,7 @@ impl Diagnostic for Error {
|
|||
Error::DuplicateModule { .. } => None,
|
||||
Error::FileIo { .. } => None,
|
||||
Error::ImportCycle { .. } => None,
|
||||
Error::Blueprint(e) => e.labels(),
|
||||
Error::List(_) => None,
|
||||
Error::Parse { error, .. } => error.labels(),
|
||||
Error::MissingManifest { .. } => None,
|
||||
|
@ -358,6 +420,10 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -366,6 +432,7 @@ impl Diagnostic for Error {
|
|||
Error::DuplicateModule { .. } => None,
|
||||
Error::FileIo { .. } => None,
|
||||
Error::ImportCycle { .. } => None,
|
||||
Error::Blueprint(e) => e.source_code(),
|
||||
Error::List(_) => None,
|
||||
Error::Parse { named, .. } => Some(named),
|
||||
Error::Type { named, .. } => Some(named),
|
||||
|
@ -380,6 +447,10 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,6 +459,7 @@ impl Diagnostic for Error {
|
|||
Error::DuplicateModule { .. } => None,
|
||||
Error::FileIo { .. } => None,
|
||||
Error::ImportCycle { .. } => None,
|
||||
Error::Blueprint(e) => e.url(),
|
||||
Error::List { .. } => None,
|
||||
Error::Parse { .. } => None,
|
||||
Error::Type { error, .. } => error.url(),
|
||||
|
@ -402,6 +474,10 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract { .. } => None,
|
||||
Error::JoinError { .. } => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -409,6 +485,7 @@ impl Diagnostic for Error {
|
|||
match self {
|
||||
Error::DuplicateModule { .. } => None,
|
||||
Error::FileIo { .. } => None,
|
||||
Error::Blueprint(e) => e.related(),
|
||||
Error::ImportCycle { .. } => None,
|
||||
Error::List { .. } => None,
|
||||
Error::Parse { .. } => None,
|
||||
|
@ -424,6 +501,10 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract { .. } => None,
|
||||
Error::JoinError { .. } => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod blueprint;
|
||||
pub mod config;
|
||||
pub mod deps;
|
||||
pub mod docs;
|
||||
|
@ -11,34 +12,32 @@ pub mod pretty;
|
|||
pub mod script;
|
||||
pub mod telemetry;
|
||||
|
||||
use crate::blueprint::{schema::Schema, validator, Blueprint};
|
||||
use aiken_lang::{
|
||||
ast::{Definition, Function, ModuleKind, TypedDataType, TypedDefinition, TypedFunction},
|
||||
ast::{Definition, Function, ModuleKind, TypedDataType, TypedFunction},
|
||||
builder::{DataTypeKey, FunctionAccessKey},
|
||||
builtins::{self, generic_var},
|
||||
builtins,
|
||||
tipo::TypeInfo,
|
||||
uplc::CodeGenerator,
|
||||
IdGenerator, MINT, PUBLISH, SPEND, VALIDATOR_NAMES, WITHDRAW,
|
||||
IdGenerator,
|
||||
};
|
||||
use deps::UseManifest;
|
||||
use indexmap::IndexMap;
|
||||
use miette::NamedSource;
|
||||
use options::{CodeGenMode, Options};
|
||||
use package_name::PackageName;
|
||||
use pallas::{
|
||||
codec::minicbor,
|
||||
ledger::{addresses::Address, primitives::babbage},
|
||||
use pallas::ledger::addresses::{
|
||||
Address, Network, ShelleyAddress, ShelleyDelegationPart, StakePayload,
|
||||
};
|
||||
use pallas_traverse::ComputeHash;
|
||||
use script::{EvalHint, EvalInfo, Script};
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
fs::{self, File},
|
||||
io::BufReader,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use telemetry::EventListener;
|
||||
use uplc::{
|
||||
ast::{Constant, DeBruijn, Program, Term},
|
||||
ast::{Constant, Term},
|
||||
machine::cost_model::ExBudget,
|
||||
};
|
||||
|
||||
|
@ -70,6 +69,8 @@ where
|
|||
sources: Vec<Source>,
|
||||
pub warnings: Vec<Warning>,
|
||||
event_listener: T,
|
||||
functions: IndexMap<FunctionAccessKey, TypedFunction>,
|
||||
data_types: IndexMap<DataTypeKey, TypedDataType>,
|
||||
}
|
||||
|
||||
impl<T> Project<T>
|
||||
|
@ -84,6 +85,10 @@ where
|
|||
module_types.insert("aiken".to_string(), builtins::prelude(&id_gen));
|
||||
module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen));
|
||||
|
||||
let functions = builtins::prelude_functions(&id_gen);
|
||||
|
||||
let data_types = builtins::prelude_data_types(&id_gen);
|
||||
|
||||
let config = Config::load(&root)?;
|
||||
|
||||
Ok(Project {
|
||||
|
@ -96,6 +101,8 @@ where
|
|||
sources: vec![],
|
||||
warnings: vec![],
|
||||
event_listener,
|
||||
functions,
|
||||
data_types,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -121,11 +128,7 @@ where
|
|||
|
||||
let destination = destination.unwrap_or_else(|| self.root.join("docs"));
|
||||
|
||||
let mut parsed_modules = self.parse_sources(self.config.name.clone())?;
|
||||
|
||||
for (_, module) in parsed_modules.iter_mut() {
|
||||
module.attach_doc_and_module_comments();
|
||||
}
|
||||
let parsed_modules = self.parse_sources(self.config.name.clone())?;
|
||||
|
||||
self.type_check(parsed_modules)?;
|
||||
|
||||
|
@ -170,6 +173,25 @@ where
|
|||
self.compile(options)
|
||||
}
|
||||
|
||||
pub fn dump_uplc(&self, blueprint: &Blueprint<Schema>) -> Result<(), Error> {
|
||||
let dir = self.root.join("artifacts");
|
||||
self.event_listener
|
||||
.handle_event(Event::DumpingUPLC { path: dir.clone() });
|
||||
fs::create_dir_all(&dir)?;
|
||||
for validator in &blueprint.validators {
|
||||
let path = dir
|
||||
.clone()
|
||||
.join(format!("{}::{}>.uplc", validator.title, validator.purpose));
|
||||
fs::write(&path, validator.program.to_pretty())
|
||||
.map_err(|error| Error::FileIo { error, path })?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn blueprint_path(&self) -> PathBuf {
|
||||
self.root.join("plutus.json")
|
||||
}
|
||||
|
||||
pub fn compile(&mut self, options: Options) -> Result<(), Error> {
|
||||
self.compile_deps()?;
|
||||
|
||||
|
@ -186,27 +208,42 @@ where
|
|||
|
||||
self.type_check(parsed_modules)?;
|
||||
|
||||
let validators = self.validate_validators()?;
|
||||
|
||||
match options.code_gen_mode {
|
||||
CodeGenMode::Build(uplc_dump) => {
|
||||
if validators.is_empty() {
|
||||
self.event_listener
|
||||
.handle_event(Event::GeneratingBlueprint {
|
||||
path: self.blueprint_path(),
|
||||
});
|
||||
|
||||
let mut generator = self.checked_modules.new_generator(
|
||||
&self.functions,
|
||||
&self.data_types,
|
||||
&self.module_types,
|
||||
);
|
||||
|
||||
let blueprint = Blueprint::new(&self.config, &self.checked_modules, &mut generator)
|
||||
.map_err(Error::Blueprint)?;
|
||||
|
||||
if blueprint.validators.is_empty() {
|
||||
self.warnings.push(Warning::NoValidators);
|
||||
}
|
||||
|
||||
let programs = self.code_gen(validators)?;
|
||||
if uplc_dump {
|
||||
self.dump_uplc(&blueprint)?;
|
||||
}
|
||||
|
||||
self.write_build_outputs(programs, uplc_dump)?;
|
||||
|
||||
Ok(())
|
||||
let json = serde_json::to_string_pretty(&blueprint).unwrap();
|
||||
fs::write(self.blueprint_path(), json).map_err(|error| Error::FileIo {
|
||||
error,
|
||||
path: self.blueprint_path(),
|
||||
})
|
||||
}
|
||||
CodeGenMode::Test {
|
||||
match_tests,
|
||||
verbose,
|
||||
exact_match,
|
||||
} => {
|
||||
let tests =
|
||||
self.collect_scripts(verbose, |def| matches!(def, Definition::Test(..)))?;
|
||||
let tests = self.collect_tests(verbose)?;
|
||||
|
||||
if !tests.is_empty() {
|
||||
self.event_listener.handle_event(Event::RunningTests);
|
||||
|
@ -244,6 +281,71 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub fn address(
|
||||
&self,
|
||||
with_title: Option<&String>,
|
||||
with_purpose: Option<&validator::Purpose>,
|
||||
stake_address: Option<&String>,
|
||||
) -> Result<ShelleyAddress, Error> {
|
||||
// Parse stake address
|
||||
let stake_address = stake_address
|
||||
.map(|s| {
|
||||
Address::from_hex(s)
|
||||
.or_else(|_| Address::from_bech32(s))
|
||||
.map_err(|error| Error::MalformedStakeAddress { error: Some(error) })
|
||||
.and_then(|addr| match addr {
|
||||
Address::Stake(addr) => Ok(addr),
|
||||
_ => Err(Error::MalformedStakeAddress { error: None }),
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
let delegation_part = match stake_address.map(|addr| addr.payload().to_owned()) {
|
||||
None => ShelleyDelegationPart::Null,
|
||||
Some(StakePayload::Stake(key)) => ShelleyDelegationPart::Key(key),
|
||||
Some(StakePayload::Script(script)) => ShelleyDelegationPart::Script(script),
|
||||
};
|
||||
|
||||
// Read blueprint
|
||||
let filepath = self.blueprint_path();
|
||||
let blueprint =
|
||||
File::open(filepath).map_err(|_| blueprint::error::Error::InvalidOrMissingFile)?;
|
||||
let blueprint: Blueprint<serde_json::Value> =
|
||||
serde_json::from_reader(BufReader::new(blueprint))?;
|
||||
|
||||
// Find validator's program
|
||||
let mut program = None;
|
||||
for v in blueprint.validators.iter() {
|
||||
if Some(&v.title) == with_title.or(Some(&v.title))
|
||||
&& Some(&v.purpose) == with_purpose.or(Some(&v.purpose))
|
||||
{
|
||||
program = Some(if program.is_none() {
|
||||
Ok(v.program.clone())
|
||||
} else {
|
||||
Err(Error::MoreThanOneValidatorFound {
|
||||
known_validators: blueprint
|
||||
.validators
|
||||
.iter()
|
||||
.map(|v| (v.title.clone(), v.purpose.clone()))
|
||||
.collect(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Print the address
|
||||
match program {
|
||||
Some(Ok(program)) => Ok(program.address(Network::Testnet, delegation_part)),
|
||||
Some(Err(e)) => Err(e),
|
||||
None => Err(Error::NoValidatorNotFound {
|
||||
known_validators: blueprint
|
||||
.validators
|
||||
.iter()
|
||||
.map(|v| (v.title.clone(), v.purpose.clone()))
|
||||
.collect(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_deps(&mut self) -> Result<(), Error> {
|
||||
let manifest = deps::download(
|
||||
&self.event_listener,
|
||||
|
@ -304,7 +406,7 @@ where
|
|||
// Store the name
|
||||
ast.name = name.clone();
|
||||
|
||||
let module = ParsedModule {
|
||||
let mut module = ParsedModule {
|
||||
kind,
|
||||
ast,
|
||||
code,
|
||||
|
@ -325,6 +427,8 @@ where
|
|||
});
|
||||
}
|
||||
|
||||
module.attach_doc_and_module_comments();
|
||||
|
||||
parsed_modules.insert(module.name.clone(), module);
|
||||
}
|
||||
Err(errs) => {
|
||||
|
@ -408,236 +512,20 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_validators(&self) -> Result<Vec<(PathBuf, String, TypedFunction)>, Error> {
|
||||
let mut errors = Vec::new();
|
||||
let mut validators = Vec::new();
|
||||
|
||||
for module in self.checked_modules.validators() {
|
||||
for def in module.ast.definitions() {
|
||||
if let Definition::Fn(func_def) = def {
|
||||
if VALIDATOR_NAMES.contains(&func_def.name.as_str()) {
|
||||
// validators must return a Bool
|
||||
if !func_def.return_type.is_bool() {
|
||||
errors.push(Error::ValidatorMustReturnBool {
|
||||
location: func_def.location,
|
||||
src: module.code.clone(),
|
||||
path: module.input_path.clone(),
|
||||
named: NamedSource::new(
|
||||
module.input_path.display().to_string(),
|
||||
module.code.clone(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
// depending on name, validate the minimum number of arguments
|
||||
// if too low, push a new error on to errors
|
||||
if [MINT, WITHDRAW, PUBLISH].contains(&func_def.name.as_str())
|
||||
&& func_def.arguments.len() < 2
|
||||
{
|
||||
errors.push(Error::WrongValidatorArity {
|
||||
location: func_def.location,
|
||||
src: module.code.clone(),
|
||||
path: module.input_path.clone(),
|
||||
named: NamedSource::new(
|
||||
module.input_path.display().to_string(),
|
||||
module.code.clone(),
|
||||
),
|
||||
name: func_def.name.clone(),
|
||||
at_least: 2,
|
||||
})
|
||||
}
|
||||
|
||||
if SPEND == func_def.name && func_def.arguments.len() < 3 {
|
||||
errors.push(Error::WrongValidatorArity {
|
||||
location: func_def.location,
|
||||
src: module.code.clone(),
|
||||
path: module.input_path.clone(),
|
||||
named: NamedSource::new(
|
||||
module.input_path.display().to_string(),
|
||||
module.code.clone(),
|
||||
),
|
||||
name: func_def.name.clone(),
|
||||
at_least: 3,
|
||||
})
|
||||
}
|
||||
|
||||
validators.push((
|
||||
module.input_path.clone(),
|
||||
module.name.clone(),
|
||||
func_def.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(validators)
|
||||
} else {
|
||||
Err(Error::List(errors))
|
||||
}
|
||||
}
|
||||
|
||||
fn code_gen(
|
||||
&mut self,
|
||||
validators: Vec<(PathBuf, String, TypedFunction)>,
|
||||
) -> Result<Vec<Script>, Error> {
|
||||
let mut programs = Vec::new();
|
||||
let mut functions = IndexMap::new();
|
||||
let mut type_aliases = IndexMap::new();
|
||||
let mut data_types = IndexMap::new();
|
||||
|
||||
let prelude_functions = builtins::prelude_functions(&self.id_gen);
|
||||
for (access_key, func) in prelude_functions.iter() {
|
||||
functions.insert(access_key.clone(), func);
|
||||
}
|
||||
|
||||
let option_data_type = TypedDataType::option(generic_var(self.id_gen.next()));
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
module_name: "".to_string(),
|
||||
defined_type: "Option".to_string(),
|
||||
},
|
||||
&option_data_type,
|
||||
);
|
||||
|
||||
for module in self.checked_modules.values() {
|
||||
for def in module.ast.definitions() {
|
||||
match def {
|
||||
Definition::Fn(func) => {
|
||||
functions.insert(
|
||||
FunctionAccessKey {
|
||||
module_name: module.name.clone(),
|
||||
function_name: func.name.clone(),
|
||||
variant_name: String::new(),
|
||||
},
|
||||
func,
|
||||
);
|
||||
}
|
||||
Definition::TypeAlias(ta) => {
|
||||
type_aliases.insert((module.name.clone(), ta.alias.clone()), ta);
|
||||
}
|
||||
Definition::DataType(dt) => {
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
module_name: module.name.clone(),
|
||||
defined_type: dt.name.clone(),
|
||||
},
|
||||
dt,
|
||||
);
|
||||
}
|
||||
|
||||
Definition::ModuleConstant(_) | Definition::Test(_) | Definition::Use(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (input_path, module_name, func_def) in validators {
|
||||
let Function {
|
||||
arguments,
|
||||
name,
|
||||
body,
|
||||
..
|
||||
} = func_def;
|
||||
|
||||
let mut modules_map = IndexMap::new();
|
||||
|
||||
modules_map.extend(self.module_types.clone());
|
||||
|
||||
let mut generator = CodeGenerator::new(
|
||||
&functions,
|
||||
// &type_aliases,
|
||||
&data_types,
|
||||
&modules_map,
|
||||
);
|
||||
|
||||
self.event_listener.handle_event(Event::GeneratingUPLC {
|
||||
output_path: self.output_path().join(&module_name).join(&name),
|
||||
name: format!("{}.{}", module_name, name),
|
||||
});
|
||||
|
||||
let program = generator.generate(body, arguments, true);
|
||||
|
||||
let script = Script::new(
|
||||
input_path,
|
||||
module_name,
|
||||
name,
|
||||
program.try_into().unwrap(),
|
||||
None,
|
||||
);
|
||||
|
||||
programs.push(script);
|
||||
}
|
||||
|
||||
Ok(programs)
|
||||
}
|
||||
|
||||
fn collect_scripts(
|
||||
&mut self,
|
||||
verbose: bool,
|
||||
should_collect: fn(&TypedDefinition) -> bool,
|
||||
) -> Result<Vec<Script>, Error> {
|
||||
let mut programs = Vec::new();
|
||||
let mut functions = IndexMap::new();
|
||||
let mut type_aliases = IndexMap::new();
|
||||
let mut data_types = IndexMap::new();
|
||||
|
||||
let prelude_functions = builtins::prelude_functions(&self.id_gen);
|
||||
for (access_key, func) in prelude_functions.iter() {
|
||||
functions.insert(access_key.clone(), func);
|
||||
}
|
||||
|
||||
let option_data_type = TypedDataType::option(generic_var(self.id_gen.next()));
|
||||
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
module_name: "".to_string(),
|
||||
defined_type: "Option".to_string(),
|
||||
},
|
||||
&option_data_type,
|
||||
);
|
||||
|
||||
fn collect_tests(&mut self, verbose: bool) -> Result<Vec<Script>, Error> {
|
||||
let mut scripts = Vec::new();
|
||||
|
||||
for module in self.checked_modules.values() {
|
||||
if module.package != self.config.name.to_string() {
|
||||
continue;
|
||||
}
|
||||
for def in module.ast.definitions() {
|
||||
match def {
|
||||
Definition::Fn(func) => {
|
||||
functions.insert(
|
||||
FunctionAccessKey {
|
||||
module_name: module.name.clone(),
|
||||
function_name: func.name.clone(),
|
||||
variant_name: String::new(),
|
||||
},
|
||||
func,
|
||||
);
|
||||
|
||||
if should_collect(def) && module.package == self.config.name.to_string() {
|
||||
scripts.push((module.input_path.clone(), module.name.clone(), func));
|
||||
}
|
||||
}
|
||||
Definition::Test(func) => {
|
||||
if should_collect(def) && module.package == self.config.name.to_string() {
|
||||
scripts.push((module.input_path.clone(), module.name.clone(), func));
|
||||
}
|
||||
}
|
||||
Definition::TypeAlias(ta) => {
|
||||
type_aliases.insert((module.name.clone(), ta.alias.clone()), ta);
|
||||
}
|
||||
Definition::DataType(dt) => {
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
module_name: module.name.clone(),
|
||||
defined_type: dt.name.clone(),
|
||||
},
|
||||
dt,
|
||||
);
|
||||
}
|
||||
Definition::Use(_) | Definition::ModuleConstant(_) => (),
|
||||
if let Definition::Test(func) = def {
|
||||
scripts.push((module.input_path.clone(), module.name.clone(), func))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut programs = Vec::new();
|
||||
for (input_path, module_name, func_def) in scripts {
|
||||
let Function {
|
||||
arguments,
|
||||
|
@ -653,26 +541,23 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
let mut modules_map = IndexMap::new();
|
||||
|
||||
modules_map.extend(self.module_types.clone());
|
||||
|
||||
let mut generator = CodeGenerator::new(
|
||||
&functions,
|
||||
// &type_aliases,
|
||||
&data_types,
|
||||
&modules_map,
|
||||
let mut generator = self.checked_modules.new_generator(
|
||||
&self.functions,
|
||||
&self.data_types,
|
||||
&self.module_types,
|
||||
);
|
||||
|
||||
let evaluation_hint = if let Some((bin_op, left_src, right_src)) = func_def.test_hint()
|
||||
{
|
||||
let left = CodeGenerator::new(&functions, &data_types, &modules_map)
|
||||
.generate(*left_src, vec![], false)
|
||||
let left = generator
|
||||
.clone()
|
||||
.generate(&left_src, &[], false)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let right = CodeGenerator::new(&functions, &data_types, &modules_map)
|
||||
.generate(*right_src, vec![], false)
|
||||
let right = generator
|
||||
.clone()
|
||||
.generate(&right_src, &[], false)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
|
@ -685,7 +570,7 @@ where
|
|||
None
|
||||
};
|
||||
|
||||
let program = generator.generate(body.clone(), arguments.clone(), false);
|
||||
let program = generator.generate(body, arguments, false);
|
||||
|
||||
let script = Script::new(
|
||||
input_path,
|
||||
|
@ -722,7 +607,7 @@ where
|
|||
.map(|match_test| {
|
||||
let mut match_split_dot = match_test.split('.');
|
||||
|
||||
let match_module = if match_test.contains('.') {
|
||||
let match_module = if match_test.contains('.') || match_test.contains('/') {
|
||||
match_split_dot.next().unwrap_or("")
|
||||
} else {
|
||||
""
|
||||
|
@ -746,14 +631,16 @@ where
|
|||
match_tests.iter().any(|(module, names)| {
|
||||
let matched_module = module == &"" || script.module.contains(module);
|
||||
|
||||
let matched_name = matches!(names, Some(names) if names
|
||||
.iter()
|
||||
.any(|name| if exact_match {
|
||||
let matched_name = match names {
|
||||
None => true,
|
||||
Some(names) => names.iter().any(|name| {
|
||||
if exact_match {
|
||||
name == &script.name
|
||||
} else {
|
||||
script.name.contains(name)
|
||||
}
|
||||
));
|
||||
}),
|
||||
};
|
||||
|
||||
matched_module && matched_name
|
||||
})
|
||||
|
@ -785,91 +672,6 @@ where
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn output_path(&self) -> PathBuf {
|
||||
self.root.join("assets")
|
||||
}
|
||||
|
||||
fn write_build_outputs(&self, programs: Vec<Script>, uplc_dump: bool) -> Result<(), Error> {
|
||||
for script in programs {
|
||||
let script_output_dir = self.output_path().join(script.module).join(script.name);
|
||||
|
||||
fs::create_dir_all(&script_output_dir)?;
|
||||
|
||||
// dump textual uplc
|
||||
if uplc_dump {
|
||||
let uplc_path = script_output_dir.join("raw.uplc");
|
||||
|
||||
fs::write(uplc_path, script.program.to_pretty())?;
|
||||
}
|
||||
|
||||
let program: Program<DeBruijn> = script.program.into();
|
||||
|
||||
let cbor = program.to_cbor().unwrap();
|
||||
|
||||
// Create file containing just the script cbor hex
|
||||
let script_path = script_output_dir.join("script.cbor");
|
||||
|
||||
let cbor_hex = hex::encode(&cbor);
|
||||
|
||||
fs::write(script_path, cbor_hex)?;
|
||||
|
||||
// Create the payment script JSON file
|
||||
let payment_script_path = script_output_dir.join("payment_script.json");
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
let mut encoder = minicbor::Encoder::new(&mut bytes);
|
||||
|
||||
encoder.bytes(&cbor).unwrap();
|
||||
|
||||
let prefixed_cbor_hex = hex::encode(&bytes);
|
||||
|
||||
let payment_script = json!({
|
||||
"type": "PlutusScriptV2",
|
||||
"description": "Generated by Aiken",
|
||||
"cborHex": prefixed_cbor_hex
|
||||
});
|
||||
|
||||
fs::write(
|
||||
payment_script_path,
|
||||
serde_json::to_string_pretty(&payment_script).unwrap(),
|
||||
)?;
|
||||
|
||||
// Create mainnet and testnet addresses
|
||||
let plutus_script = babbage::PlutusV2Script(cbor.into());
|
||||
|
||||
let hash = plutus_script.compute_hash();
|
||||
|
||||
// mainnet
|
||||
let mainnet_path = script_output_dir.join("mainnet.addr");
|
||||
let mut mainnet_bytes: Vec<u8> = vec![0b01110001];
|
||||
|
||||
mainnet_bytes.extend(hash.iter());
|
||||
|
||||
let mainnet_addr = Address::from_bytes(&mainnet_bytes)
|
||||
.unwrap()
|
||||
.to_bech32()
|
||||
.unwrap();
|
||||
|
||||
fs::write(mainnet_path, mainnet_addr)?;
|
||||
|
||||
// testnet
|
||||
let testnet_path = script_output_dir.join("testnet.addr");
|
||||
let mut testnet_bytes: Vec<u8> = vec![0b01110000];
|
||||
|
||||
testnet_bytes.extend(hash.iter());
|
||||
|
||||
let testnet_addr = Address::from_bytes(&testnet_bytes)
|
||||
.unwrap()
|
||||
.to_bech32()
|
||||
.unwrap();
|
||||
|
||||
fs::write(testnet_path, testnet_addr)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn aiken_files(&mut self, dir: &Path, kind: ModuleKind) -> Result<(), Error> {
|
||||
let paths = walkdir::WalkDir::new(dir)
|
||||
.follow_links(true)
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
use crate::error::Error;
|
||||
use aiken_lang::{
|
||||
ast::{
|
||||
DataType, Definition, ModuleKind, TypedDataType, TypedFunction, TypedModule, UntypedModule,
|
||||
},
|
||||
builder::{DataTypeKey, FunctionAccessKey},
|
||||
parser::extra::{comments_before, Comment, ModuleExtra},
|
||||
tipo::TypeInfo,
|
||||
uplc::CodeGenerator,
|
||||
VALIDATOR_NAMES,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use petgraph::{algo, graph::NodeIndex, Direction, Graph};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
ops::{Deref, DerefMut},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use aiken_lang::{
|
||||
ast::{DataType, Definition, ModuleKind, TypedModule, UntypedModule},
|
||||
parser::extra::{comments_before, Comment, ModuleExtra},
|
||||
};
|
||||
use petgraph::{algo, graph::NodeIndex, Direction, Graph};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParsedModule {
|
||||
pub path: PathBuf,
|
||||
|
@ -233,9 +238,31 @@ impl From<CheckedModules> for HashMap<String, CheckedModule> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a CheckedModules> for &'a HashMap<String, CheckedModule> {
|
||||
fn from(checked_modules: &'a CheckedModules) -> Self {
|
||||
&checked_modules.0
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedModules {
|
||||
pub fn validators(&self) -> impl Iterator<Item = &CheckedModule> {
|
||||
self.0.values().filter(|module| module.kind.is_validator())
|
||||
pub fn singleton(module: CheckedModule) -> Self {
|
||||
let mut modules = Self::default();
|
||||
modules.insert(module.name.clone(), module);
|
||||
modules
|
||||
}
|
||||
|
||||
pub fn validators(&self) -> impl Iterator<Item = (&CheckedModule, &TypedFunction)> {
|
||||
let mut items = vec![];
|
||||
for validator in self.0.values().filter(|module| module.kind.is_validator()) {
|
||||
for some_definition in validator.ast.definitions() {
|
||||
if let Definition::Fn(def) = some_definition {
|
||||
if VALIDATOR_NAMES.contains(&def.name.as_str()) {
|
||||
items.push((validator, def));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
items.into_iter()
|
||||
}
|
||||
|
||||
pub fn into_validators(self) -> impl Iterator<Item = CheckedModule> {
|
||||
|
@ -243,6 +270,59 @@ impl CheckedModules {
|
|||
.into_values()
|
||||
.filter(|module| module.kind.is_validator())
|
||||
}
|
||||
|
||||
pub fn new_generator<'a>(
|
||||
&'a self,
|
||||
builtin_functions: &'a IndexMap<FunctionAccessKey, TypedFunction>,
|
||||
builtin_data_types: &'a IndexMap<DataTypeKey, TypedDataType>,
|
||||
module_types: &'a HashMap<String, TypeInfo>,
|
||||
) -> CodeGenerator<'a> {
|
||||
let mut functions = IndexMap::new();
|
||||
for (k, v) in builtin_functions {
|
||||
functions.insert(k.clone(), v);
|
||||
}
|
||||
|
||||
let mut data_types = IndexMap::new();
|
||||
for (k, v) in builtin_data_types {
|
||||
data_types.insert(k.clone(), v);
|
||||
}
|
||||
|
||||
for module in self.values() {
|
||||
for def in module.ast.definitions() {
|
||||
match def {
|
||||
Definition::Fn(func) => {
|
||||
functions.insert(
|
||||
FunctionAccessKey {
|
||||
module_name: module.name.clone(),
|
||||
function_name: func.name.clone(),
|
||||
variant_name: String::new(),
|
||||
},
|
||||
func,
|
||||
);
|
||||
}
|
||||
Definition::DataType(dt) => {
|
||||
data_types.insert(
|
||||
DataTypeKey {
|
||||
module_name: module.name.clone(),
|
||||
defined_type: dt.name.clone(),
|
||||
},
|
||||
dt,
|
||||
);
|
||||
}
|
||||
|
||||
Definition::TypeAlias(_)
|
||||
| Definition::ModuleConstant(_)
|
||||
| Definition::Test(_)
|
||||
| Definition::Use(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut module_types_index = IndexMap::new();
|
||||
module_types_index.extend(module_types);
|
||||
|
||||
CodeGenerator::new(functions, data_types, module_types_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CheckedModules {
|
||||
|
|
|
@ -19,9 +19,11 @@ pub enum Event {
|
|||
GeneratingDocFiles {
|
||||
output_path: PathBuf,
|
||||
},
|
||||
GeneratingUPLC {
|
||||
output_path: PathBuf,
|
||||
name: String,
|
||||
GeneratingBlueprint {
|
||||
path: PathBuf,
|
||||
},
|
||||
DumpingUPLC {
|
||||
path: PathBuf,
|
||||
},
|
||||
GeneratingUPLCFor {
|
||||
name: String,
|
||||
|
|
|
@ -28,3 +28,4 @@ aiken-lang = { path = "../aiken-lang", version = "0.0.28" }
|
|||
aiken-lsp = { path = "../aiken-lsp", version = "0.0.28" }
|
||||
aiken-project = { path = '../aiken-project', version = "0.0.28" }
|
||||
uplc = { path = '../uplc', version = "0.0.28" }
|
||||
serde_json = "1.0.91"
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
use crate::with_project;
|
||||
use aiken_lang::VALIDATOR_NAMES;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
#[clap(setting(clap::AppSettings::DeriveDisplayOrder))]
|
||||
/// Compute a validator's address.
|
||||
pub struct Args {
|
||||
/// Path to project
|
||||
directory: Option<PathBuf>,
|
||||
|
||||
/// Name of the validator's module within the project. Optional if there's only one validator.
|
||||
#[clap(short, long)]
|
||||
validator: Option<String>,
|
||||
|
||||
/// Purpose of the validator within the module. Optional if there's only one validator.
|
||||
#[clap(short, long, possible_values=&VALIDATOR_NAMES)]
|
||||
purpose: Option<String>,
|
||||
|
||||
/// Stake address to attach, if any.
|
||||
#[clap(long)]
|
||||
delegated_to: Option<String>,
|
||||
|
||||
/// Force the project to be rebuilt, otherwise relies on existing artifacts (i.e. plutus.json).
|
||||
#[clap(long)]
|
||||
rebuild: bool,
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
Args {
|
||||
directory,
|
||||
validator,
|
||||
purpose,
|
||||
delegated_to,
|
||||
rebuild,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
with_project(directory, |p| {
|
||||
if rebuild {
|
||||
p.build(false)?;
|
||||
}
|
||||
let address = p.address(
|
||||
validator.as_ref(),
|
||||
purpose
|
||||
.as_ref()
|
||||
.map(|p| p.clone().try_into().unwrap())
|
||||
.as_ref(),
|
||||
delegated_to.as_ref(),
|
||||
)?;
|
||||
println!("{}", address.to_bech32().unwrap());
|
||||
Ok(())
|
||||
})
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod address;
|
||||
pub mod build;
|
||||
pub mod check;
|
||||
pub mod docs;
|
||||
|
|
|
@ -165,7 +165,7 @@ fn gitignore(root: &Path) -> miette::Result<()> {
|
|||
indoc! {
|
||||
r#"
|
||||
# Aiken compilation artifacts
|
||||
assets/
|
||||
artifacts/
|
||||
# Aiken's project working directory
|
||||
build/
|
||||
# Aiken's default documentation export
|
||||
|
|
|
@ -94,12 +94,20 @@ impl telemetry::EventListener for Terminal {
|
|||
telemetry::Event::WaitingForBuildDirLock => {
|
||||
println!("{}", "Waiting for build directory lock ...".bold().purple());
|
||||
}
|
||||
telemetry::Event::GeneratingUPLC { output_path, name } => {
|
||||
telemetry::Event::DumpingUPLC { path } => {
|
||||
println!(
|
||||
"{} {} in {}",
|
||||
"{} {} ({})",
|
||||
" Exporting".bold().purple(),
|
||||
"UPLC".bold(),
|
||||
path.display().bright_blue()
|
||||
);
|
||||
}
|
||||
telemetry::Event::GeneratingBlueprint { path } => {
|
||||
println!(
|
||||
"{} {} ({})",
|
||||
" Generating".bold().purple(),
|
||||
name.bold(),
|
||||
output_path.display().bright_blue()
|
||||
"project's blueprint".bold(),
|
||||
path.display().bright_blue()
|
||||
);
|
||||
}
|
||||
telemetry::Event::GeneratingDocFiles { output_path } => {
|
||||
|
@ -112,7 +120,7 @@ impl telemetry::EventListener for Terminal {
|
|||
telemetry::Event::GeneratingUPLCFor { name, path } => {
|
||||
println!(
|
||||
"{} {}.{{{}}}",
|
||||
" Generating Untyped Plutus Core for".bold().purple(),
|
||||
" Generating UPLC for".bold().purple(),
|
||||
path.to_str().unwrap_or("").blue(),
|
||||
name.bright_blue(),
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use aiken::cmd::{
|
||||
build, check, docs, fmt, lsp, new,
|
||||
address, build, check, docs, fmt, lsp, new,
|
||||
packages::{self, add},
|
||||
tx, uplc,
|
||||
};
|
||||
|
@ -14,6 +14,7 @@ pub enum Cmd {
|
|||
New(new::Args),
|
||||
Fmt(fmt::Args),
|
||||
Build(build::Args),
|
||||
Address(address::Args),
|
||||
Check(check::Args),
|
||||
Docs(docs::Args),
|
||||
Add(add::Args),
|
||||
|
@ -43,6 +44,7 @@ fn main() -> miette::Result<()> {
|
|||
Cmd::New(args) => new::exec(args),
|
||||
Cmd::Fmt(args) => fmt::exec(args),
|
||||
Cmd::Build(args) => build::exec(args),
|
||||
Cmd::Address(args) => address::exec(args),
|
||||
Cmd::Check(args) => check::exec(args),
|
||||
Cmd::Docs(args) => docs::exec(args),
|
||||
Cmd::Add(args) => add::exec(args),
|
||||
|
|
|
@ -1,6 +1,20 @@
|
|||
use std::{fmt::Display, rc::Rc};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use pallas_primitives::{alonzo::PlutusData, babbage::Language};
|
||||
use serde::{
|
||||
self,
|
||||
de::{self, Deserialize, Deserializer, MapAccess, Visitor},
|
||||
ser::{Serialize, SerializeStruct, Serializer},
|
||||
};
|
||||
|
||||
use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart};
|
||||
use pallas_primitives::{
|
||||
alonzo::PlutusData,
|
||||
babbage::{self as cardano, Language},
|
||||
};
|
||||
use pallas_traverse::ComputeHash;
|
||||
|
||||
use crate::{
|
||||
builtins::DefaultFunction,
|
||||
|
@ -79,6 +93,81 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Program<DeBruijn> {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let cbor = self.to_cbor().unwrap();
|
||||
let mut s = serializer.serialize_struct("Program<DeBruijn>", 2)?;
|
||||
s.serialize_field("compiledCode", &hex::encode(&cbor))?;
|
||||
s.serialize_field("hash", &cardano::PlutusV2Script(cbor.into()).compute_hash())?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deserialize<'a> for Program<DeBruijn> {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(field_identifier, rename_all = "camelCase")]
|
||||
enum Fields {
|
||||
CompiledCode,
|
||||
}
|
||||
|
||||
struct ProgramVisitor;
|
||||
|
||||
impl<'a> Visitor<'a> for ProgramVisitor {
|
||||
type Value = Program<DeBruijn>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("Program<Visitor>")
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Program<DeBruijn>, V::Error>
|
||||
where
|
||||
V: MapAccess<'a>,
|
||||
{
|
||||
let mut compiled_code: Option<String> = None;
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Fields::CompiledCode => {
|
||||
if compiled_code.is_some() {
|
||||
return Err(de::Error::duplicate_field("compiledCode"));
|
||||
}
|
||||
compiled_code = Some(map.next_value()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
let compiled_code =
|
||||
compiled_code.ok_or_else(|| de::Error::missing_field("compiledCode"))?;
|
||||
|
||||
let mut cbor_buffer = Vec::new();
|
||||
let mut flat_buffer = Vec::new();
|
||||
|
||||
Program::<DeBruijn>::from_hex(&compiled_code, &mut cbor_buffer, &mut flat_buffer)
|
||||
.map_err(|e| {
|
||||
de::Error::invalid_value(
|
||||
de::Unexpected::Other(&format!("{}", e)),
|
||||
&"a base16-encoded CBOR-serialized UPLC program",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const FIELDS: &[&str] = &["compiledCode"];
|
||||
deserializer.deserialize_struct("Program<DeBruijn>", FIELDS, ProgramVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Program<DeBruijn> {
|
||||
pub fn address(&self, network: Network, delegation: ShelleyDelegationPart) -> ShelleyAddress {
|
||||
let cbor = self.to_cbor().unwrap();
|
||||
let validator_hash = cardano::PlutusV2Script(cbor.into()).compute_hash();
|
||||
ShelleyAddress::new(
|
||||
network,
|
||||
ShelleyPaymentPart::Script(validator_hash),
|
||||
delegation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents a term in Untyped Plutus Core.
|
||||
/// We need a generic type for the different forms that a program may be in.
|
||||
/// Specifically, `Var` and `parameter_name` in `Lambda` can be a `Name`,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
addr1w8r2ln3c7meykuf6ejw0qu5qtdfxh4e4p68v5e3c0lwmrmsdujvef
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"type": "PlutusScriptV2",
|
||||
"description": "Generated by Aiken",
|
||||
"cborHex": "590173590170010000323222253335734646464646466002006464646464646464646600201291010500000000000022323232300600130060013300600100237566601c601e00490011199ab9a0014a09448c94ccd55cf8008a5114a00024464646600a00297adef6c60330050010020022232325333573466e1c005200210031323200137566ae84004c034008d55ce9baa001002223300300200130010012223253335573e002266e9520024bd700991919192999ab9a3371e00c002266e9520003357406e980092f5c0266601001000600c6eb8d55ce8019bab35573c0046ae88008d5d08008011800800911192999aab9f00114a026464a666ae68c01000852889998030030008021aba2002357420020046eb0cc004c008cc004c00800d20004801088c8ccc00400520000032223333573466e1c0100095d0919980200219b8000348008d5d100080091aab9e37540022930b18008009112999aab9f00214984cd5cd98009aba1002333003003357440040021"
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
590170010000323222253335734646464646466002006464646464646464646600201291010500000000000022323232300600130060013300600100237566601c601e00490011199ab9a0014a09448c94ccd55cf8008a5114a00024464646600a00297adef6c60330050010020022232325333573466e1c005200210031323200137566ae84004c034008d55ce9baa001002223300300200130010012223253335573e002266e9520024bd700991919192999ab9a3371e00c002266e9520003357406e980092f5c0266601001000600c6eb8d55ce8019bab35573c0046ae88008d5d08008011800800911192999aab9f00114a026464a666ae68c01000852889998030030008021aba2002357420020046eb0cc004c008cc004c00800d20004801088c8ccc00400520000032223333573466e1c0100095d0919980200219b8000348008d5d100080091aab9e37540022930b18008009112999aab9f00214984cd5cd98009aba1002333003003357440040021
|
|
@ -1 +0,0 @@
|
|||
addr_test1wrr2ln3c7meykuf6ejw0qu5qtdfxh4e4p68v5e3c0lwmrmsk5xskv
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"preamble": {
|
||||
"title": "aiken-lang/acceptance_test_036",
|
||||
"version": "0.0.0"
|
||||
},
|
||||
"validators": [
|
||||
{
|
||||
"title": "spend",
|
||||
"purpose": "spend",
|
||||
"datum": {
|
||||
"title": "Data",
|
||||
"description": "Any Plutus data."
|
||||
},
|
||||
"redeemer": {
|
||||
"title": "Data",
|
||||
"description": "Any Plutus data."
|
||||
},
|
||||
"compiledCode": "590170010000323222253335734646464646466002006464646464646464646600201291010500000000000022323232300600130060013300600100237566601c601e00490011199ab9a0014a09448c94ccd55cf8008a5114a00024464646600a00297adef6c60330050010020022232325333573466e1c005200210031323200137566ae84004c034008d55ce9baa001002223300300200130010012223253335573e002266e9520024bd700991919192999ab9a3371e00c002266e9520003357406e980092f5c0266601001000600c6eb8d55ce8019bab35573c0046ae88008d5d08008011800800911192999aab9f00114a026464a666ae68c01000852889998030030008021aba2002357420020046eb0cc004c008cc004c00800d20004801088c8ccc00400520000032223333573466e1c0100095d0919980200219b8000348008d5d100080091aab9e37540022930b18008009112999aab9f00214984cd5cd98009aba1002333003003357440040021",
|
||||
"hash": "c6afce38f6f24b713acc9cf072805b526bd7350e8eca66387fddb1ee"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -12,7 +12,7 @@ pub fn has_policy_id(self: Output, policy_id: PolicyId) -> Bool {
|
|||
|> not
|
||||
}
|
||||
|
||||
pub fn spend(_datum, _redeemer, ctx: ScriptContext) -> Bool {
|
||||
pub fn spend(_datum: Data, _redeemer: Data, ctx: ScriptContext) -> Bool {
|
||||
ctx.transaction.outputs
|
||||
|> list.any(has_policy_id(_, my_policy_id))
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
addr1wyahaesnnh44nkyjj4dv8jk3t4f7frwtzepjyujkk2wjkmczwqttr
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"type": "PlutusScriptV2",
|
||||
"description": "Generated by Aiken",
|
||||
"cborHex": "5831582f01000032322225333573494452616300100122253335573e004293099ab9b3001357420046660060066ae880080041"
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
582f01000032322225333573494452616300100122253335573e004293099ab9b3001357420046660060066ae880080041
|
|
@ -1 +0,0 @@
|
|||
addr_test1wqahaesnnh44nkyjj4dv8jk3t4f7frwtzepjyujkk2wjkmcex5hyx
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"preamble": {
|
||||
"title": "aiken-lang/acceptance_test_047",
|
||||
"version": "0.0.0"
|
||||
},
|
||||
"validators": [
|
||||
{
|
||||
"title": "foo",
|
||||
"purpose": "spend",
|
||||
"datum": {
|
||||
"title": "Unit",
|
||||
"description": "The nullary constructor.",
|
||||
"anyOf": [
|
||||
{
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"redeemer": {
|
||||
"title": "Unit",
|
||||
"description": "The nullary constructor.",
|
||||
"anyOf": [
|
||||
{
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"compiledCode": "582f01000032322225333573494452616300100122253335573e004293099ab9b3001357420046660060066ae880080041",
|
||||
"hash": "3b7ee6139deb59d892955ac3cad15d53e48dcb1643227256b29d2b6f"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
addr1w9d2hmr5s8j9cz5xgzjzn3srsfajcwjjzeruaj5mh3kpuqcmvmz3r
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"type": "PlutusScriptV2",
|
||||
"description": "Generated by Aiken",
|
||||
"cborHex": "5855585301000032322225333573466e1cc8c0052f7b6301010400010101002323232002375a6aae78008dd69aab9d0010014802052616300100122253335573e004293099ab9b3001357420046660060066ae88008005"
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
585301000032322225333573466e1cc8c0052f7b6301010400010101002323232002375a6aae78008dd69aab9d0010014802052616300100122253335573e004293099ab9b3001357420046660060066ae88008005
|
|
@ -1 +0,0 @@
|
|||
addr_test1wpd2hmr5s8j9cz5xgzjzn3srsfajcwjjzeruaj5mh3kpuqcqy077x
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"preamble": {
|
||||
"title": "aiken-lang/acceptance_test_048",
|
||||
"version": "0.0.0"
|
||||
},
|
||||
"validators": [
|
||||
{
|
||||
"title": "foo",
|
||||
"purpose": "spend",
|
||||
"datum": {
|
||||
"title": "Data",
|
||||
"description": "Any Plutus data."
|
||||
},
|
||||
"redeemer": {
|
||||
"title": "Data",
|
||||
"description": "Any Plutus data."
|
||||
},
|
||||
"compiledCode": "585301000032322225333573466e1cc8c0052f7b6301010400010101002323232002375a6aae78008dd69aab9d0010014802052616300100122253335573e004293099ab9b3001357420046660060066ae88008005",
|
||||
"hash": "5aabec7481e45c0a8640a429c603827b2c3a521647ceca9bbc6c1e03"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -4,6 +4,6 @@ fn when_tuple(a: (Int, Int)) -> Int {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn spend(a, b, c) -> Bool {
|
||||
pub fn spend(a: Data, b: Data, c) -> Bool {
|
||||
when_tuple((4, 1)) == 4
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
addr1w9z47fyj9ffqck2fnld04k27zfe04wq6n9zj76u4ghu4xdcd0futm
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"type": "PlutusScriptV2",
|
||||
"description": "Generated by Aiken",
|
||||
"cborHex": "4e4d01000022253335734944526161"
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
4d01000022253335734944526161
|
|
@ -1 +0,0 @@
|
|||
addr_test1wpz47fyj9ffqck2fnld04k27zfe04wq6n9zj76u4ghu4xdck8aqy7
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"type": "PlutusScriptV2",
|
||||
"description": "Generated by Aiken",
|
||||
"cborHex": "58bb58b90100002225333573464646464a666ae6800840045281919198009bac330043005330043005006480012010375c66008600a01090001800800911192999aab9f00114a026464a666ae68cdc78010020a511333006006001004357440046eb8d5d080080119b97323001375c66004600600a900011b9900149010d48656c6c6f2c20576f726c64210022323330010014800000c888cccd5cd19b870040025742466600800866e0000d20023574400200246aae78dd50008a4c2d"
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
58b90100002225333573464646464a666ae6800840045281919198009bac330043005330043005006480012010375c66008600a01090001800800911192999aab9f00114a026464a666ae68cdc78010020a511333006006001004357440046eb8d5d080080119b97323001375c66004600600a900011b9900149010d48656c6c6f2c20576f726c64210022323330010014800000c888cccd5cd19b870040025742466600800866e0000d20023574400200246aae78dd50008a4c2d
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"preamble": {
|
||||
"title": "aiken-lang/hello_world",
|
||||
"description": "Aiken contracts for project 'aiken-lang/hello_world'",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"validators": [
|
||||
{
|
||||
"title": "hello_world",
|
||||
"purpose": "spend",
|
||||
"datum": {
|
||||
"title": "Datum",
|
||||
"anyOf": [
|
||||
{
|
||||
"title": "Datum",
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": [
|
||||
{
|
||||
"title": "owner",
|
||||
"dataType": "bytes"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"redeemer": {
|
||||
"title": "Redeemer",
|
||||
"anyOf": [
|
||||
{
|
||||
"title": "Redeemer",
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": [
|
||||
{
|
||||
"title": "msg",
|
||||
"dataType": "bytes"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"compiledCode": "58db01000032322225333573464646464a666ae6800840045281919198009bac330043005330043005006480012010375c66008600a01090001800800911192999aab9f00114a026464a666ae68cdc78010020a511333006006001004357440046eb8d5d080080119b97323001375c66004600600a900011b9900149010d48656c6c6f2c20576f726c64210022323330010014800000c888cccd5cd19b870040025742466600800866e0000d20023574400200246aae78dd50008a4c2c6002002444a666aae7c008526133573660026ae84008ccc00c00cd5d10010009",
|
||||
"hash": "d478e73c101a53d083f4720d400c876d7441c279168becabab0e0177"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue