aiken_project/blueprint/
mod.rs

1pub mod definitions;
2pub mod error;
3mod memo_program;
4pub mod parameter;
5pub mod schema;
6pub mod validator;
7
8use crate::{
9    config::{self, PlutusVersion, ProjectConfig},
10    module::CheckedModules,
11};
12use aiken_lang::gen_uplc::CodeGenerator;
13use definitions::Definitions;
14pub use error::Error;
15use pallas_addresses::ScriptHash;
16use schema::{Annotated, Schema};
17use std::{
18    collections::{BTreeSet, HashMap},
19    fmt::Debug,
20};
21use uplc::{PlutusData, ast::SerializableProgram, tx::script_context::PlutusScript};
22use validator::Validator;
23
24#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
25pub struct Blueprint {
26    pub preamble: Preamble,
27    pub validators: Vec<Validator<SerializableProgram>>,
28    #[serde(skip_serializing_if = "Definitions::is_empty", default)]
29    pub definitions: Definitions<Annotated<Schema>>,
30}
31
32#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub struct Preamble {
35    pub title: String,
36
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub description: Option<String>,
39
40    pub version: String,
41
42    pub plutus_version: PlutusVersion,
43
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub compiler: Option<Compiler>,
46
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub license: Option<String>,
49}
50
51#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub struct Compiler {
54    pub name: String,
55    pub version: String,
56}
57
58#[derive(Debug, PartialEq, Clone)]
59pub enum LookupResult<'a, T> {
60    One(String, &'a T),
61    Many,
62}
63
64impl Blueprint {
65    pub fn new(
66        config: &ProjectConfig,
67        modules: &CheckedModules,
68        generator: &mut CodeGenerator,
69    ) -> Result<Self, Error> {
70        let preamble = config.into();
71
72        let mut definitions = Definitions::new();
73
74        let validators: Result<Vec<_>, Error> = modules
75            .validators()
76            .map(|(validator, def)| {
77                Ok(Validator::from_checked_module(
78                    modules,
79                    generator,
80                    validator,
81                    def,
82                    &config.plutus,
83                )?
84                .into_iter()
85                .map(|mut schema| {
86                    definitions.merge(&mut schema.definitions);
87                    schema.definitions = Definitions::new();
88                    schema
89                })
90                .collect::<Vec<_>>())
91            })
92            .collect();
93
94        Ok(Blueprint {
95            preamble,
96            validators: validators?.into_iter().flatten().collect(),
97            definitions,
98        })
99    }
100}
101
102impl Blueprint {
103    pub fn lookup(
104        &self,
105        want_module_name: Option<&str>,
106        want_validator_name: Option<&str>,
107    ) -> Option<LookupResult<'_, Validator<SerializableProgram>>> {
108        let mut validator = None;
109
110        for v in self.validators.iter() {
111            let (known_module_name, known_validator_name) = v.get_module_and_name();
112
113            let is_target = match (want_module_name, want_validator_name) {
114                (None, None) => true,
115                (Some(want_module_name), None) => want_module_name == known_module_name,
116                (None, Some(want_validator_name)) => want_validator_name == known_validator_name,
117                (Some(want_module_name), Some(want_validator_name)) => {
118                    want_module_name == known_module_name
119                        && want_validator_name == known_validator_name
120                }
121            };
122
123            let title = format!("{known_module_name}.{known_validator_name}");
124
125            if is_target {
126                match validator {
127                    Some(LookupResult::Many) => (),
128                    None => {
129                        validator = Some(LookupResult::One(title, v));
130                    }
131                    Some(LookupResult::One(ref known_title, _)) => {
132                        if title.as_str() != known_title {
133                            validator = Some(LookupResult::Many)
134                        }
135                    }
136                }
137            }
138        }
139
140        validator
141    }
142
143    pub fn construct_parameter_incrementally<F>(
144        &self,
145        module_name: Option<&str>,
146        validator_name: Option<&str>,
147        ask: F,
148    ) -> Result<PlutusData, Error>
149    where
150        F: Fn(&Annotated<Schema>, &Definitions<Annotated<Schema>>) -> Result<PlutusData, Error>,
151    {
152        // Construct parameter
153        let when_too_many =
154            |known_validators| Error::MoreThanOneValidatorFound { known_validators };
155        let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators };
156
157        self.with_validator(
158            module_name,
159            validator_name,
160            when_too_many,
161            when_missing,
162            |validator| validator.ask_next_parameter(&self.definitions, &ask),
163        )
164    }
165
166    pub fn apply_parameter(
167        &mut self,
168        module_name: Option<&str>,
169        validator_name: Option<&str>,
170        param: &PlutusData,
171    ) -> Result<(), Error> {
172        let when_too_many =
173            |known_validators| Error::MoreThanOneValidatorFound { known_validators };
174        let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators };
175
176        let applied_validator = self.with_validator(
177            module_name,
178            validator_name,
179            when_too_many,
180            when_missing,
181            |validator| validator.clone().apply(&self.definitions, param),
182        )?;
183
184        // Overwrite validator
185        let prefix = |v: &str| v.split('.').take(2).collect::<Vec<&str>>().join(".");
186        for validator in &mut self.validators {
187            if prefix(&applied_validator.title) == prefix(&validator.title) {
188                validator.program = applied_validator.program.clone();
189                validator.parameters = applied_validator.parameters.clone();
190            }
191        }
192
193        Ok(())
194    }
195
196    pub fn with_validator<F, A, E>(
197        &self,
198        module_name: Option<&str>,
199        validator_name: Option<&str>,
200        when_too_many: fn(BTreeSet<(String, String, bool)>) -> E,
201        when_missing: fn(BTreeSet<(String, String, bool)>) -> E,
202        action: F,
203    ) -> Result<A, E>
204    where
205        F: Fn(&Validator<SerializableProgram>) -> Result<A, E>,
206    {
207        match self.lookup(module_name, validator_name) {
208            Some(LookupResult::One(_, validator)) => action(validator),
209            Some(LookupResult::Many) => Err(when_too_many(
210                self.validators
211                    .iter()
212                    .filter_map(|v| {
213                        let (l, r) = v.get_module_and_name();
214
215                        if let Some(module_name) = module_name {
216                            if l != module_name {
217                                return None;
218                            }
219                        }
220
221                        if let Some(validator_name) = validator_name {
222                            if r != validator_name {
223                                return None;
224                            }
225                        }
226
227                        Some((l.to_string(), r.to_string(), !v.parameters.is_empty()))
228                    })
229                    .collect(),
230            )),
231            None => Err(when_missing(
232                self.validators
233                    .iter()
234                    .map(|v| {
235                        let (l, r) = v.get_module_and_name();
236                        (l.to_string(), r.to_string(), !v.parameters.is_empty())
237                    })
238                    .collect(),
239            )),
240        }
241    }
242}
243
244impl From<Blueprint> for HashMap<ScriptHash, PlutusScript> {
245    fn from(blueprint: Blueprint) -> Self {
246        blueprint
247            .validators
248            .iter()
249            .map(|validator| validator.program.compiled_code_and_hash())
250            .collect()
251    }
252}
253impl From<&ProjectConfig> for Preamble {
254    fn from(config: &ProjectConfig) -> Self {
255        Preamble {
256            title: config.name.to_string(),
257            description: if config.description.is_empty() {
258                None
259            } else {
260                Some(config.description.clone())
261            },
262            compiler: Some(Compiler {
263                name: "Aiken".to_string(),
264                version: config::compiler_version(true),
265            }),
266            plutus_version: config.plutus,
267            version: config.version.clone(),
268            license: config.license.clone(),
269        }
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use aiken_lang::tipo::Type;
277    use schema::{Data, Declaration, Items, Schema};
278    use serde_json::{self, json};
279    use std::collections::HashMap;
280
281    #[test]
282    fn serialize_no_description() {
283        let blueprint = Blueprint {
284            preamble: Preamble {
285                title: "Foo".to_string(),
286                description: None,
287                version: "1.0.0".to_string(),
288                plutus_version: PlutusVersion::V2,
289                compiler: Some(Compiler {
290                    name: "Aiken".to_string(),
291                    version: "1.0.0".to_string(),
292                }),
293                license: Some("Apache-2.0".to_string()),
294            },
295            validators: vec![],
296            definitions: Definitions::new(),
297        };
298        assert_eq!(
299            serde_json::to_value(&blueprint).unwrap(),
300            json!({
301                "preamble": {
302                    "title": "Foo",
303                    "version": "1.0.0",
304                    "plutusVersion": "v2",
305                    "compiler": {
306                        "name": "Aiken",
307                        "version": "1.0.0"
308                    },
309                    "license": "Apache-2.0"
310                },
311                "validators": []
312            }),
313        );
314    }
315
316    #[test]
317    fn serialize_with_description() {
318        let blueprint = Blueprint {
319            preamble: Preamble {
320                title: "Foo".to_string(),
321                description: Some("Lorem ipsum".to_string()),
322                version: "1.0.0".to_string(),
323                plutus_version: PlutusVersion::V2,
324                compiler: None,
325                license: None,
326            },
327            validators: vec![],
328            definitions: Definitions::new(),
329        };
330        assert_eq!(
331            serde_json::to_value(&blueprint).unwrap(),
332            json!({
333                "preamble": {
334                    "title": "Foo",
335                    "description": "Lorem ipsum",
336                    "version": "1.0.0",
337                    "plutusVersion": "v2"
338                },
339                "validators": []
340            }),
341        );
342    }
343
344    #[test]
345    fn serialize_with_definitions() {
346        let mut definitions = Definitions::new();
347        definitions
348            .register::<_, Error>(&Type::int(), &HashMap::new(), |_| {
349                Ok(Schema::Data(Data::Integer).into())
350            })
351            .unwrap();
352        definitions
353            .register::<_, Error>(
354                &Type::list(Type::byte_array()),
355                &HashMap::new(),
356                |definitions| {
357                    let ref_bytes = definitions.register::<_, Error>(
358                        &Type::byte_array(),
359                        &HashMap::new(),
360                        |_| Ok(Schema::Data(Data::Bytes).into()),
361                    )?;
362                    Ok(
363                        Schema::Data(Data::List(Items::One(Declaration::Referenced(ref_bytes))))
364                            .into(),
365                    )
366                },
367            )
368            .unwrap();
369
370        let blueprint = Blueprint {
371            preamble: Preamble {
372                title: "Foo".to_string(),
373                description: None,
374                version: "1.0.0".to_string(),
375                plutus_version: PlutusVersion::V2,
376                compiler: None,
377                license: None,
378            },
379            validators: vec![],
380            definitions,
381        };
382        assert_eq!(
383            serde_json::to_value(&blueprint).unwrap(),
384            json!({
385                "preamble": {
386                    "title": "Foo",
387                    "version": "1.0.0",
388                    "plutusVersion": "v2"
389                },
390                "validators": [],
391                "definitions": {
392                    "ByteArray": {
393                        "dataType": "bytes"
394                    },
395                    "Int": {
396                        "dataType": "integer"
397                    },
398                    "List<ByteArray>": {
399                        "dataType": "list",
400                        "items": {
401                            "$ref": "#/definitions/ByteArray"
402                        }
403                    }
404                }
405            }),
406        );
407    }
408}