cardano_serialization_lib/builders/
mint_builder.rs

1use crate::*;
2use std::collections::BTreeMap;
3
4#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
5enum MintWitnessEnum {
6    Plutus(PlutusScriptSourceEnum, Redeemer),
7    NativeScript(NativeScriptSourceEnum),
8}
9
10#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
11#[wasm_bindgen]
12pub struct MintWitness(MintWitnessEnum);
13
14#[wasm_bindgen]
15impl MintWitness {
16    pub fn new_native_script(native_script: &NativeScriptSource) -> MintWitness {
17        MintWitness(MintWitnessEnum::NativeScript(native_script.0.clone()))
18    }
19
20    pub fn new_plutus_script(
21        plutus_script: &PlutusScriptSource,
22        redeemer: &Redeemer,
23    ) -> MintWitness {
24        MintWitness(MintWitnessEnum::Plutus(
25            plutus_script.0.clone(),
26            redeemer.clone(),
27        ))
28    }
29
30    pub(crate) fn script_hash(&self) -> ScriptHash {
31        match &self.0 {
32            MintWitnessEnum::NativeScript(native_script) => native_script.script_hash(),
33            MintWitnessEnum::Plutus(plutus_script, _) => plutus_script.script_hash(),
34        }
35    }
36}
37
38#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
39struct NativeMints {
40    script: NativeScriptSourceEnum,
41    mints: BTreeMap<AssetName, Int>,
42}
43
44impl NativeMints {
45
46    #[allow(dead_code)]
47    fn script_hash(&self) -> PolicyID {
48        match &self.script {
49            NativeScriptSourceEnum::NativeScript(script, _) => script.hash(),
50            NativeScriptSourceEnum::RefInput(_, script_hash, _, _) => script_hash.clone(),
51        }
52    }
53
54    fn ref_input(&self) -> Option<&TransactionInput> {
55        match &self.script {
56            NativeScriptSourceEnum::RefInput(input, _, _, _) => Some(input),
57            _ => None,
58        }
59    }
60
61    fn native_script(&self) -> Option<&NativeScript> {
62        match &self.script {
63            NativeScriptSourceEnum::NativeScript(script, _) => Some(script),
64            _ => None,
65        }
66    }
67}
68
69#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
70struct PlutusMints {
71    script: PlutusScriptSourceEnum,
72    redeemer: Redeemer,
73    mints: BTreeMap<AssetName, Int>,
74}
75
76#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
77enum ScriptMint {
78    Plutus(PlutusMints),
79    Native(NativeMints),
80}
81
82#[derive(Clone, Debug)]
83#[wasm_bindgen]
84pub struct MintBuilder {
85    mints: BTreeMap<PolicyID, ScriptMint>,
86}
87
88#[wasm_bindgen]
89impl MintBuilder {
90    pub fn new() -> MintBuilder {
91        MintBuilder {
92            mints: BTreeMap::new(),
93        }
94    }
95
96    pub fn add_asset(
97        &mut self,
98        mint: &MintWitness,
99        asset_name: &AssetName,
100        amount: &Int,
101    ) -> Result<(), JsError> {
102        if amount.0 == 0 {
103            return Err(JsError::from_str("Mint cannot be zero."));
104        }
105        self.update_mint_value(mint, asset_name, amount, false)?;
106        Ok(())
107    }
108
109    pub fn set_asset(
110        &mut self,
111        mint: &MintWitness,
112        asset_name: &AssetName,
113        amount: &Int,
114    ) -> Result<(), JsError> {
115        if amount.0 == 0 {
116            return Err(JsError::from_str("Mint cannot be zero."));
117        }
118        self.update_mint_value(mint, asset_name, amount, true)?;
119        Ok(())
120    }
121
122    fn update_mint_value(
123        &mut self,
124        mint_witness: &MintWitness,
125        asset_name: &AssetName,
126        amount: &Int,
127        overwrite: bool,
128    ) -> Result<(), JsError> {
129        if amount.0 == 0 {
130            return Err(JsError::from_str("Mint cannot be zero."));
131        }
132        let script_mint = self.mints.get(&mint_witness.script_hash());
133        Self::validate_mint_witness(mint_witness, script_mint)?;
134
135        match &mint_witness.0 {
136            MintWitnessEnum::NativeScript(native_script) => {
137                let script_mint =
138                    self.mints
139                        .entry(native_script.script_hash())
140                        .or_insert(ScriptMint::Native(NativeMints {
141                            script: native_script.clone(),
142                            mints: BTreeMap::new(),
143                        }));
144                match script_mint {
145                    ScriptMint::Native(native_mints) => {
146                        let mint = native_mints
147                            .mints
148                            .entry(asset_name.clone())
149                            .or_insert(Int::new(&BigNum::zero()));
150                        if overwrite {
151                            mint.0 = amount.0;
152                        } else {
153                            mint.0 += amount.0;
154                        }
155                    }
156                    _ => {}
157                }
158            }
159            MintWitnessEnum::Plutus(plutus_script, redeemer) => {
160                let script_mint =
161                    self.mints
162                        .entry(plutus_script.script_hash())
163                        .or_insert(ScriptMint::Plutus(PlutusMints {
164                            script: plutus_script.clone(),
165                            redeemer: redeemer.clone(),
166                            mints: BTreeMap::new(),
167                        }));
168                match script_mint {
169                    ScriptMint::Plutus(plutus_mints) => {
170                        let mint = plutus_mints
171                            .mints
172                            .entry(asset_name.clone())
173                            .or_insert(Int::new(&BigNum::zero()));
174                        if overwrite {
175                            mint.0 = amount.0;
176                        } else {
177                            mint.0 += amount.0;
178                        }
179                    }
180                    _ => {}
181                }
182            }
183        }
184        Ok(())
185    }
186
187    fn validate_mint_witness(
188        mint_witness: &MintWitness,
189        current_script_mint: Option<&ScriptMint>,
190    ) -> Result<(), JsError> {
191        if let Some(current_script_mint) = current_script_mint {
192            match &mint_witness.0 {
193                MintWitnessEnum::NativeScript(native_script) => {
194                    if let ScriptMint::Native(native_mints) = current_script_mint {
195                        Self::validate_native_source_type(&native_mints.script, &native_script)?;
196                    } else {
197                        return Err(JsError::from_str(
198                            &BuilderError::MintBuilderDifferentScriptType.as_str(),
199                        ));
200                    }
201                }
202                MintWitnessEnum::Plutus(plutus_script, redeemer) => {
203                    if let ScriptMint::Plutus(plutus_mints) = current_script_mint {
204                        Self::validate_plutus_script_source_type(
205                            &plutus_mints.script,
206                            &plutus_script,
207                        )?;
208                        if !plutus_mints.redeemer.partially_eq(redeemer) {
209                            return Err(JsError::from_str(
210                                &BuilderError::MintBuilderDifferentRedeemerDataAndExUnits(
211                                    plutus_mints.redeemer.to_json()?,
212                                    redeemer.to_json()?,
213                                )
214                                .as_str(),
215                            ));
216                        }
217                    } else {
218                        return Err(JsError::from_str(
219                            &BuilderError::MintBuilderDifferentScriptType.as_str(),
220                        ));
221                    }
222                }
223            }
224            Ok(())
225        } else {
226            Ok(())
227        }
228    }
229
230    fn validate_native_source_type(
231        current_script_source: &NativeScriptSourceEnum,
232        input_script_source: &NativeScriptSourceEnum,
233    ) -> Result<(), JsError> {
234        match current_script_source {
235            NativeScriptSourceEnum::NativeScript(_, _) => {
236                if let NativeScriptSourceEnum::NativeScript(_, _) = input_script_source {
237                    Ok(())
238                } else {
239                    Err(JsError::from_str(
240                        &BuilderError::MintBuilderDifferentWitnessTypeNonRef.as_str(),
241                    ))
242                }
243            }
244            NativeScriptSourceEnum::RefInput(_, _, _, _) => {
245                if let NativeScriptSourceEnum::RefInput(_, _, _, _) = input_script_source {
246                    Ok(())
247                } else {
248                    Err(JsError::from_str(
249                        &BuilderError::MintBuilderDifferentWitnessTypeRef.as_str(),
250                    ))
251                }
252            }
253        }
254    }
255
256    fn validate_plutus_script_source_type(
257        current_script_source: &PlutusScriptSourceEnum,
258        input_script_source: &PlutusScriptSourceEnum,
259    ) -> Result<(), JsError> {
260        match current_script_source {
261            PlutusScriptSourceEnum::RefInput(_, _) => {
262                if let PlutusScriptSourceEnum::RefInput(_, _) = input_script_source {
263                    Ok(())
264                } else {
265                    Err(JsError::from_str(
266                        &BuilderError::MintBuilderDifferentWitnessTypeRef.as_str(),
267                    ))
268                }
269            }
270            PlutusScriptSourceEnum::Script(_, _) => {
271                if let PlutusScriptSourceEnum::Script(_, _) = input_script_source {
272                    Ok(())
273                } else {
274                    Err(JsError::from_str(
275                        &BuilderError::MintBuilderDifferentWitnessTypeNonRef.as_str(),
276                    ))
277                }
278            }
279        }
280    }
281
282    pub(crate) fn build_unchecked(&self) -> Mint {
283        let mut mint = Mint::new();
284        for (policy, script_mint) in self.mints.iter() {
285            let mut mint_asset = MintAssets::new();
286            match script_mint {
287                ScriptMint::Native(native_mints) => {
288                    for (asset_name, amount) in &native_mints.mints {
289                        mint_asset.insert_unchecked(asset_name, amount.clone());
290                    }
291                }
292                ScriptMint::Plutus(plutus_mints) => {
293                    for (asset_name, amount) in &plutus_mints.mints {
294                        mint_asset.insert_unchecked(asset_name, amount.clone());
295                    }
296                }
297            }
298            mint.insert(&policy, &mint_asset);
299        }
300        mint
301    }
302
303    pub fn build(&self) -> Result<Mint, JsError> {
304        let mut mint = Mint::new();
305        for (policy, script_mint) in &self.mints {
306            let mut mint_asset = MintAssets::new();
307            match script_mint {
308                ScriptMint::Native(native_mints) => {
309                    for (asset_name, amount) in &native_mints.mints {
310                        mint_asset.insert(asset_name, amount)?;
311                    }
312                }
313                ScriptMint::Plutus(plutus_mints) => {
314                    for (asset_name, amount) in &plutus_mints.mints {
315                        mint_asset.insert(asset_name, amount)?;
316                    }
317                }
318            }
319            mint.insert(&policy, &mint_asset);
320        }
321        Ok(mint)
322    }
323
324    pub fn get_native_scripts(&self) -> NativeScripts {
325        let mut native_scripts = Vec::new();
326        for script_mint in self.mints.values() {
327            match script_mint {
328                ScriptMint::Native(native_mints) => {
329                    if let Some(script) = native_mints.native_script() {
330                        native_scripts.push(script.clone());
331                    }
332                }
333                _ => {}
334            }
335        }
336        NativeScripts::from(native_scripts)
337    }
338
339    pub fn get_plutus_witnesses(&self) -> PlutusWitnesses {
340        let mut plutus_witnesses = Vec::new();
341        let tag = RedeemerTag::new_mint();
342        for (index, (_, script_mint)) in self.mints.iter().enumerate() {
343            match script_mint {
344                ScriptMint::Plutus(plutus_mints) => {
345                    plutus_witnesses.push(PlutusWitness::new_with_ref_without_datum(
346                        &PlutusScriptSource(plutus_mints.script.clone()),
347                        &plutus_mints.redeemer.clone_with_index_and_tag(
348                            &BigNum::from(index),
349                            &tag,
350                        ),
351                    ));
352                }
353                _ => {}
354            }
355        }
356        PlutusWitnesses(plutus_witnesses)
357    }
358
359    pub fn get_ref_inputs(&self) -> TransactionInputs {
360        let mut reference_inputs = Vec::new();
361        for script_mint in self.mints.values() {
362            match script_mint {
363                ScriptMint::Plutus(plutus_mints) => {
364                    if let PlutusScriptSourceEnum::RefInput(ref_script, _) = &plutus_mints.script {
365                        reference_inputs.push(ref_script.input_ref.clone());
366                    }
367                }
368                ScriptMint::Native(native_mints) => {
369                    if let Some(input) = native_mints.ref_input() {
370                        reference_inputs.push(input.clone());
371                    }
372                }
373            }
374        }
375        TransactionInputs::from_vec(reference_inputs)
376    }
377
378    pub fn get_redeemers(&self) -> Result<Redeemers, JsError> {
379        let tag = RedeemerTag::new_mint();
380        let mut redeeemers = Vec::new();
381        let mut index = BigNum::zero();
382        for (_, script_mint) in &self.mints {
383            match script_mint {
384                ScriptMint::Plutus(plutus_mints) => {
385                    redeeemers.push(plutus_mints.redeemer.clone_with_index_and_tag(&index, &tag));
386                    index = index.checked_add(&BigNum::one())?;
387                }
388                _ => {
389                    index = index.checked_add(&BigNum::one())?;
390                }
391            }
392        }
393        Ok(Redeemers::from(redeeemers))
394    }
395
396    pub fn has_plutus_scripts(&self) -> bool {
397        for script_mint in self.mints.values() {
398            match script_mint {
399                ScriptMint::Plutus(_) => {
400                    return true;
401                }
402                _ => {}
403            }
404        }
405        false
406    }
407
408    pub fn has_native_scripts(&self) -> bool {
409        for script_mint in self.mints.values() {
410            match script_mint {
411                ScriptMint::Native(_) => {
412                    return true;
413                }
414                _ => {}
415            }
416        }
417        false
418    }
419
420    pub(crate) fn get_used_plutus_lang_versions(&self) -> BTreeSet<Language> {
421        let mut used_langs = BTreeSet::new();
422        for (_, script_mint) in &self.mints {
423            match script_mint {
424                ScriptMint::Plutus(plutus_mints) => {
425                    used_langs.insert(plutus_mints.script.language());
426                }
427                _ => {}
428            }
429        }
430        used_langs
431    }
432
433    //return only ref inputs that are script refs with added size
434    //used for calculating the fee for the transaction
435    //another ref input and also script ref input without size are filtered out
436    pub(crate) fn get_script_ref_inputs_with_size(
437        &self,
438    ) -> impl Iterator<Item = (&TransactionInput, usize)> {
439        self.mints.iter().filter_map(|(_, script_mint)| {
440            if let ScriptMint::Plutus(plutus_mints) = script_mint {
441                if let PlutusScriptSourceEnum::RefInput(script_ref, _) = &plutus_mints.script {
442                    return Some((&script_ref.input_ref, script_ref.script_size));
443                }
444            }
445            None
446        })
447    }
448}