cardano_serialization_lib/
utils.rs

1use cbor_event::{
2    self,
3    de::Deserializer,
4    se::{Serialize, Serializer},
5};
6use hex::FromHex;
7use serde_json;
8use std::fmt::Display;
9use std::{
10    collections::HashMap,
11    io::{BufRead, Seek, Write},
12};
13
14use super::*;
15use crate::error::{DeserializeError, DeserializeFailure};
16use schemars::JsonSchema;
17
18pub fn to_bytes<T: cbor_event::se::Serialize>(data_item: &T) -> Vec<u8> {
19    let mut buf = Serializer::new_vec();
20    data_item.serialize(&mut buf).unwrap();
21    buf.finalize()
22}
23
24pub fn from_bytes<T: Deserialize>(data: &Vec<u8>) -> Result<T, DeserializeError> {
25    let mut raw = Deserializer::from(std::io::Cursor::new(data));
26    T::deserialize(&mut raw)
27}
28
29#[wasm_bindgen]
30#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, JsonSchema)]
31pub struct TransactionUnspentOutput {
32    pub(crate) input: TransactionInput,
33    pub(crate) output: TransactionOutput,
34}
35
36impl_to_from!(TransactionUnspentOutput);
37
38#[wasm_bindgen]
39impl TransactionUnspentOutput {
40    pub fn new(input: &TransactionInput, output: &TransactionOutput) -> TransactionUnspentOutput {
41        Self {
42            input: input.clone(),
43            output: output.clone(),
44        }
45    }
46
47    pub fn input(&self) -> TransactionInput {
48        self.input.clone()
49    }
50
51    pub fn output(&self) -> TransactionOutput {
52        self.output.clone()
53    }
54}
55
56impl cbor_event::se::Serialize for TransactionUnspentOutput {
57    fn serialize<'se, W: Write>(
58        &self,
59        serializer: &'se mut Serializer<W>,
60    ) -> cbor_event::Result<&'se mut Serializer<W>> {
61        serializer.write_array(cbor_event::Len::Len(2))?;
62        self.input.serialize(serializer)?;
63        self.output.serialize(serializer)
64    }
65}
66
67impl Deserialize for TransactionUnspentOutput {
68    fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
69        (|| -> Result<_, DeserializeError> {
70            match raw.cbor_type()? {
71                cbor_event::Type::Array => {
72                    let len = raw.array()?;
73                    let input = (|| -> Result<_, DeserializeError> {
74                        Ok(TransactionInput::deserialize(raw)?)
75                    })()
76                    .map_err(|e| e.annotate("input"))?;
77                    let output = (|| -> Result<_, DeserializeError> {
78                        Ok(TransactionOutput::deserialize(raw)?)
79                    })()
80                    .map_err(|e| e.annotate("output"))?;
81                    let ret = Ok(Self { input, output });
82                    match len {
83                        cbor_event::Len::Len(n) => match n {
84                            2 =>
85                            /* it's ok */
86                            {
87                                ()
88                            }
89                            n => {
90                                return Err(
91                                    DeserializeFailure::DefiniteLenMismatch(n, Some(2)).into()
92                                );
93                            }
94                        },
95                        cbor_event::Len::Indefinite => match raw.special()? {
96                            CBORSpecial::Break =>
97                            /* it's ok */
98                            {
99                                ()
100                            }
101                            _ => return Err(DeserializeFailure::EndingBreakMissing.into()),
102                        },
103                    }
104                    ret
105                }
106                _ => Err(DeserializeFailure::NoVariantMatched.into()),
107            }
108        })()
109        .map_err(|e| e.annotate("TransactionUnspentOutput"))
110    }
111}
112
113#[wasm_bindgen]
114#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, JsonSchema)]
115pub struct TransactionUnspentOutputs(pub(crate) Vec<TransactionUnspentOutput>);
116
117to_from_json!(TransactionUnspentOutputs);
118
119#[wasm_bindgen]
120impl TransactionUnspentOutputs {
121    pub fn new() -> Self {
122        Self(Vec::new())
123    }
124
125    pub fn len(&self) -> usize {
126        self.0.len()
127    }
128
129    pub fn get(&self, index: usize) -> TransactionUnspentOutput {
130        self.0[index].clone()
131    }
132
133    pub fn add(&mut self, elem: &TransactionUnspentOutput) {
134        self.0.push(elem.clone());
135    }
136}
137
138impl<'a> IntoIterator for &'a TransactionUnspentOutputs {
139    type Item = &'a TransactionUnspentOutput;
140    type IntoIter = std::slice::Iter<'a, TransactionUnspentOutput>;
141
142    fn into_iter(self) -> std::slice::Iter<'a, TransactionUnspentOutput> {
143        self.0.iter()
144    }
145}
146
147#[wasm_bindgen]
148#[derive(
149    Clone,
150    Debug,
151    /*Hash,*/ Ord,
152    serde::Serialize,
153    serde::Deserialize,
154    JsonSchema,
155)]
156pub struct Value {
157    pub(crate) coin: Coin,
158    pub(crate) multiasset: Option<MultiAsset>,
159}
160
161impl_to_from!(Value);
162
163#[wasm_bindgen]
164impl Value {
165    pub fn new(coin: &Coin) -> Value {
166        Self {
167            coin: coin.clone(),
168            multiasset: None,
169        }
170    }
171
172    pub fn new_from_assets(multiasset: &MultiAsset) -> Value {
173        Value::new_with_assets(&Coin::zero(), multiasset)
174    }
175
176    pub fn new_with_assets(coin: &Coin, multiasset: &MultiAsset) -> Value {
177        match multiasset.0.is_empty() {
178            true => Value::new(coin),
179            false => Self {
180                coin: coin.clone(),
181                multiasset: Some(multiasset.clone()),
182            },
183        }
184    }
185
186    pub fn zero() -> Value {
187        Value::new(&Coin::zero())
188    }
189
190    pub fn is_zero(&self) -> bool {
191        self.coin.is_zero()
192            && self
193                .multiasset
194                .as_ref()
195                .map(|m| m.len() == 0)
196                .unwrap_or(true)
197    }
198
199    pub fn coin(&self) -> Coin {
200        self.coin
201    }
202
203    pub fn set_coin(&mut self, coin: &Coin) {
204        self.coin = coin.clone();
205    }
206
207    pub fn multiasset(&self) -> Option<MultiAsset> {
208        self.multiasset.clone()
209    }
210
211    pub fn set_multiasset(&mut self, multiasset: &MultiAsset) {
212        self.multiasset = Some(multiasset.clone());
213    }
214
215    pub fn checked_add(&self, rhs: &Value) -> Result<Value, JsError> {
216        use std::collections::btree_map::Entry;
217        let coin = self.coin.checked_add(&rhs.coin)?;
218
219        let multiasset = match (&self.multiasset, &rhs.multiasset) {
220            (Some(lhs_multiasset), Some(rhs_multiasset)) => {
221                let mut multiasset = MultiAsset::new();
222
223                for ma in &[lhs_multiasset, rhs_multiasset] {
224                    for (policy, assets) in &ma.0 {
225                        for (asset_name, amount) in &assets.0 {
226                            match multiasset.0.entry(policy.clone()) {
227                                Entry::Occupied(mut assets) => {
228                                    match assets.get_mut().0.entry(asset_name.clone()) {
229                                        Entry::Occupied(mut assets) => {
230                                            let current = assets.get_mut();
231                                            *current = current.checked_add(&amount)?;
232                                        }
233                                        Entry::Vacant(vacant_entry) => {
234                                            vacant_entry.insert(amount.clone());
235                                        }
236                                    }
237                                }
238                                Entry::Vacant(entry) => {
239                                    let mut assets = Assets::new();
240                                    assets.0.insert(asset_name.clone(), amount.clone());
241                                    entry.insert(assets);
242                                }
243                            }
244                        }
245                    }
246                }
247
248                Some(multiasset)
249            }
250            (None, None) => None,
251            (Some(ma), None) => Some(ma.clone()),
252            (None, Some(ma)) => Some(ma.clone()),
253        };
254
255        Ok(Value { coin, multiasset })
256    }
257
258    pub fn checked_sub(&self, rhs_value: &Value) -> Result<Value, JsError> {
259        let coin = self.coin.checked_sub(&rhs_value.coin)?;
260        let multiasset = match (&self.multiasset, &rhs_value.multiasset) {
261            (Some(lhs_ma), Some(rhs_ma)) => match lhs_ma.sub(rhs_ma).len() {
262                0 => None,
263                _ => Some(lhs_ma.sub(rhs_ma)),
264            },
265            (Some(lhs_ma), None) => Some(lhs_ma.clone()),
266            (None, Some(_rhs_ma)) => None,
267            (None, None) => None,
268        };
269
270        Ok(Value { coin, multiasset })
271    }
272
273    pub fn clamped_sub(&self, rhs_value: &Value) -> Value {
274        let coin = self.coin.clamped_sub(&rhs_value.coin);
275        let multiasset = match (&self.multiasset, &rhs_value.multiasset) {
276            (Some(lhs_ma), Some(rhs_ma)) => match lhs_ma.sub(rhs_ma).len() {
277                0 => None,
278                _ => Some(lhs_ma.sub(rhs_ma)),
279            },
280            (Some(lhs_ma), None) => Some(lhs_ma.clone()),
281            (None, Some(_rhs_ma)) => None,
282            (None, None) => None,
283        };
284
285        Value { coin, multiasset }
286    }
287
288    /// note: values are only partially comparable
289    pub fn compare(&self, rhs_value: &Value) -> Option<i8> {
290        match self.partial_cmp(&rhs_value) {
291            None => None,
292            Some(std::cmp::Ordering::Equal) => Some(0),
293            Some(std::cmp::Ordering::Less) => Some(-1),
294            Some(std::cmp::Ordering::Greater) => Some(1),
295        }
296    }
297}
298
299impl PartialEq for Value {
300    fn eq(&self, other: &Self) -> bool {
301        let self_ma = self.multiasset.as_ref().map(|ma| ma.reduce_empty_to_none()).flatten();
302        let other_ma = other.multiasset.as_ref().map(|ma| ma.reduce_empty_to_none()).flatten();
303        self.coin == other.coin && self_ma == other_ma
304    }
305}
306
307impl Eq for Value {}
308
309impl PartialOrd for Value {
310    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
311        use std::cmp::Ordering::*;
312
313        fn compare_assets(
314            lhs: &Option<MultiAsset>,
315            rhs: &Option<MultiAsset>,
316        ) -> Option<std::cmp::Ordering> {
317            match (lhs, rhs) {
318                (None, None) => Some(Equal),
319                (None, Some(rhs_assets)) => MultiAsset::new().partial_cmp(&rhs_assets),
320                (Some(lhs_assets), None) => lhs_assets.partial_cmp(&MultiAsset::new()),
321                (Some(lhs_assets), Some(rhs_assets)) => lhs_assets.partial_cmp(&rhs_assets),
322            }
323        }
324
325        compare_assets(&self.multiasset(), &other.multiasset()).and_then(|assets_match| {
326            let coin_cmp = self.coin.cmp(&other.coin);
327
328            match (coin_cmp, assets_match) {
329                (coin_order, Equal) => Some(coin_order),
330                (Equal, Less) => Some(Less),
331                (Less, Less) => Some(Less),
332                (Equal, Greater) => Some(Greater),
333                (Greater, Greater) => Some(Greater),
334                (_, _) => None,
335            }
336        })
337    }
338}
339
340impl cbor_event::se::Serialize for Value {
341    fn serialize<'se, W: Write>(
342        &self,
343        serializer: &'se mut Serializer<W>,
344    ) -> cbor_event::Result<&'se mut Serializer<W>> {
345        let multiasset = self.multiasset
346            .as_ref()
347            .map(|ma| ma.reduce_empty_to_none())
348            .flatten();
349
350        if let Some(multiasset) = multiasset {
351            serializer.write_array(cbor_event::Len::Len(2))?;
352            self.coin.serialize(serializer)?;
353            multiasset.serialize(serializer)?;
354        } else {
355            self.coin.serialize(serializer)?;
356        }
357
358        Ok(serializer)
359    }
360}
361
362impl Deserialize for Value {
363    fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
364        (|| -> Result<_, DeserializeError> {
365            match raw.cbor_type()? {
366                cbor_event::Type::UnsignedInteger => Ok(Value::new(&Coin::deserialize(raw)?)),
367                cbor_event::Type::Array => {
368                    let len = raw.array()?;
369                    let coin =
370                        (|| -> Result<_, DeserializeError> { Ok(Coin::deserialize(raw)?) })()
371                            .map_err(|e| e.annotate("coin"))?;
372                    let multiasset =
373                        (|| -> Result<_, DeserializeError> { Ok(MultiAsset::deserialize(raw)?) })()
374                            .map_err(|e| e.annotate("multiasset"))?;
375                    let ret = Ok(Self {
376                        coin,
377                        multiasset: Some(multiasset),
378                    });
379                    match len {
380                        cbor_event::Len::Len(n) => match n {
381                            2 =>
382                            /* it's ok */
383                            {
384                                ()
385                            }
386                            n => {
387                                return Err(
388                                    DeserializeFailure::DefiniteLenMismatch(n, Some(2)).into()
389                                );
390                            }
391                        },
392                        cbor_event::Len::Indefinite => match raw.special()? {
393                            CBORSpecial::Break =>
394                            /* it's ok */
395                            {
396                                ()
397                            }
398                            _ => return Err(DeserializeFailure::EndingBreakMissing.into()),
399                        },
400                    }
401                    ret
402                }
403                _ => Err(DeserializeFailure::NoVariantMatched.into()),
404            }
405        })()
406        .map_err(|e| e.annotate("Value"))
407    }
408}
409
410pub(crate) const BOUNDED_BYTES_CHUNK_SIZE: usize = 64;
411
412pub(crate) fn write_bounded_bytes<'se, W: Write>(
413    serializer: &'se mut Serializer<W>,
414    bytes: &[u8],
415) -> cbor_event::Result<&'se mut Serializer<W>> {
416    if bytes.len() <= BOUNDED_BYTES_CHUNK_SIZE {
417        serializer.write_bytes(bytes)
418    } else {
419        // to get around not having access from outside the library we just write the raw CBOR indefinite byte string code here
420        serializer.write_raw_bytes(&[0x5f])?;
421        for chunk in bytes.chunks(BOUNDED_BYTES_CHUNK_SIZE) {
422            serializer.write_bytes(chunk)?;
423        }
424        serializer.write_special(CBORSpecial::Break)
425    }
426}
427
428pub(crate) fn read_bounded_bytes<R: BufRead + Seek>(
429    raw: &mut Deserializer<R>,
430) -> Result<Vec<u8>, DeserializeError> {
431    use std::io::Read;
432    let t = raw.cbor_type()?;
433    if t != CBORType::Bytes {
434        return Err(cbor_event::Error::Expected(CBORType::Bytes, t).into());
435    }
436    let (len, len_sz) = raw.cbor_len()?;
437    match len {
438        cbor_event::Len::Len(_) => {
439            let bytes = raw.bytes()?;
440            if bytes.len() > BOUNDED_BYTES_CHUNK_SIZE {
441                return Err(DeserializeFailure::OutOfRange {
442                    min: 0,
443                    max: BOUNDED_BYTES_CHUNK_SIZE,
444                    found: bytes.len(),
445                }
446                .into());
447            }
448            Ok(bytes)
449        }
450        cbor_event::Len::Indefinite => {
451            // this is CBOR indefinite encoding, but we must check that each chunk
452            // is at most 64 big so we can't just use cbor_event's implementation
453            // and check after the fact.
454            // This is a slightly adopted version of what I made internally in cbor_event
455            // but with the extra checks and not having access to non-pub methods.
456            let mut bytes = Vec::new();
457            raw.advance(1 + len_sz)?;
458            // TODO: also change this + check at end of loop to the following after we update cbor_event
459            //while raw.cbor_type()? != CBORType::Special || !raw.special_break()? {
460            while raw.cbor_type()? != CBORType::Special {
461                let chunk_t = raw.cbor_type()?;
462                if chunk_t != CBORType::Bytes {
463                    return Err(cbor_event::Error::Expected(CBORType::Bytes, chunk_t).into());
464                }
465                let (chunk_len, chunk_len_sz) = raw.cbor_len()?;
466                match chunk_len {
467                    // TODO: use this error instead once that PR is merged into cbor_event
468                    //cbor_event::Len::Indefinite => return Err(cbor_event::Error::InvalidIndefiniteString.into()),
469                    cbor_event::Len::Indefinite => {
470                        return Err(cbor_event::Error::CustomError(String::from(
471                            "Illegal CBOR: Indefinite string found inside indefinite string",
472                        ))
473                        .into());
474                    }
475                    cbor_event::Len::Len(len) => {
476                        if len as usize > BOUNDED_BYTES_CHUNK_SIZE {
477                            return Err(DeserializeFailure::OutOfRange {
478                                min: 0,
479                                max: BOUNDED_BYTES_CHUNK_SIZE,
480                                found: len as usize,
481                            }
482                            .into());
483                        }
484                        raw.advance(1 + chunk_len_sz)?;
485                        raw.as_mut_ref()
486                            .by_ref()
487                            .take(len)
488                            .read_to_end(&mut bytes)
489                            .map_err(|e| cbor_event::Error::IoError(e))?;
490                    }
491                }
492            }
493            if raw.special()? != CBORSpecial::Break {
494                return Err(DeserializeFailure::EndingBreakMissing.into());
495            }
496            Ok(bytes)
497        }
498    }
499}
500
501
502pub struct CBORReadLen {
503    deser_len: cbor_event::Len,
504    read: u64,
505}
506
507impl CBORReadLen {
508    pub fn new(len: cbor_event::Len) -> Self {
509        Self {
510            deser_len: len,
511            read: 0,
512        }
513    }
514
515    // Marks {n} values as being read, and if we go past the available definite length
516    // given by the CBOR, we return an error.
517    pub fn read_elems(&mut self, count: usize) -> Result<(), DeserializeFailure> {
518        match self.deser_len {
519            cbor_event::Len::Len(n) => {
520                self.read += count as u64;
521                if self.read > n {
522                    Err(DeserializeFailure::DefiniteLenMismatch(n, None))
523                } else {
524                    Ok(())
525                }
526            }
527            cbor_event::Len::Indefinite => Ok(()),
528        }
529    }
530
531    pub fn finish(&self) -> Result<(), DeserializeFailure> {
532        match self.deser_len {
533            cbor_event::Len::Len(n) => {
534                if self.read == n {
535                    Ok(())
536                } else {
537                    Err(DeserializeFailure::DefiniteLenMismatch(n, Some(self.read)))
538                }
539            }
540            cbor_event::Len::Indefinite => Ok(()),
541        }
542    }
543}
544
545#[wasm_bindgen]
546pub fn make_daedalus_bootstrap_witness(
547    tx_body_hash: &TransactionHash,
548    addr: &ByronAddress,
549    key: &LegacyDaedalusPrivateKey,
550) -> BootstrapWitness {
551    let chain_code = key.chaincode();
552
553    let pubkey = Bip32PublicKey::from_bytes(&key.0.to_public().as_ref()).unwrap();
554    let vkey = Vkey::new(&pubkey.to_raw_key());
555    let signature =
556        Ed25519Signature::from_bytes(key.0.sign(&tx_body_hash.to_bytes()).as_ref().to_vec())
557            .unwrap();
558
559    BootstrapWitness::new(&vkey, &signature, chain_code, addr.attributes())
560}
561
562#[wasm_bindgen]
563pub fn make_icarus_bootstrap_witness(
564    tx_body_hash: &TransactionHash,
565    addr: &ByronAddress,
566    key: &Bip32PrivateKey,
567) -> BootstrapWitness {
568    let chain_code = key.chaincode();
569
570    let raw_key = key.to_raw_key();
571    let vkey = Vkey::new(&raw_key.to_public());
572    let signature = raw_key.sign(&tx_body_hash.to_bytes());
573
574    BootstrapWitness::new(&vkey, &signature, chain_code, addr.attributes())
575}
576
577#[wasm_bindgen]
578pub fn make_vkey_witness(tx_body_hash: &TransactionHash, sk: &PrivateKey) -> Vkeywitness {
579    let sig = sk.sign(tx_body_hash.0.as_ref());
580    Vkeywitness::new(&Vkey::new(&sk.to_public()), &sig)
581}
582
583#[wasm_bindgen]
584pub fn hash_auxiliary_data(auxiliary_data: &AuxiliaryData) -> AuxiliaryDataHash {
585    AuxiliaryDataHash::from(blake2b256(&auxiliary_data.to_bytes()))
586}
587
588#[wasm_bindgen]
589pub fn hash_plutus_data(plutus_data: &PlutusData) -> DataHash {
590    DataHash::from(blake2b256(&plutus_data.to_bytes()))
591}
592
593#[wasm_bindgen]
594pub fn hash_script_data(
595    redeemers: &Redeemers,
596    cost_models: &Costmdls,
597    datums: Option<PlutusList>,
598) -> ScriptDataHash {
599    let mut buf = Vec::new();
600    if redeemers.len() == 0 && datums.is_some() {
601        /*
602        ; Finally, note that in the case that a transaction includes datums but does not
603        ; include any redeemers, the script data format becomes (in hex):
604        ; [ A0 | datums | A0 ]
605        ; corresponding to a CBOR empty map and an empty map (our apologies).
606        ; Before Conway first structure was an empty list, but it was changed to empty map since Conway.
607        */
608        buf.push(0xA0);
609        if let Some(d) = &datums {
610            buf.extend(d.to_set_bytes());
611        }
612        buf.push(0xA0);
613    } else {
614        /*
615        ; script data format:
616        ; [ redeemers | datums | language views ]
617        ; The redeemers are exactly the data present in the transaction witness set.
618        ; Similarly for the datums, if present. If no datums are provided, the middle
619        ; field is an empty string.
620        */
621        buf.extend(redeemers.to_bytes());
622        if let Some(d) = &datums {
623            buf.extend(d.to_set_bytes());
624        }
625        buf.extend(cost_models.language_views_encoding());
626    }
627    ScriptDataHash::from(blake2b256(&buf))
628}
629
630// wasm-bindgen can't accept Option without clearing memory, so we avoid exposing this in WASM
631pub fn internal_get_implicit_input(
632    withdrawals: &Option<Withdrawals>,
633    certs: &Option<Certificates>,
634    pool_deposit: &BigNum, // // protocol parameter
635    key_deposit: &BigNum,  // protocol parameter
636) -> Result<Value, JsError> {
637    let withdrawal_sum = match &withdrawals {
638        None => BigNum::zero(),
639        Some(x) => {
640            x.0.values()
641                .try_fold(BigNum::zero(), |acc, ref withdrawal_amt| {
642                    acc.checked_add(&withdrawal_amt)
643                })?
644        }
645    };
646    let certificate_refund = match &certs {
647        None => BigNum::zero(),
648        Some(certs) => certs
649            .certs
650            .iter()
651            .try_fold(BigNum::zero(), |acc, ref cert| match &cert.0 {
652                CertificateEnum::StakeDeregistration(cert) => {
653                    if let Some(coin) = cert.coin {
654                        acc.checked_add(&coin)
655                    } else {
656                        acc.checked_add(&key_deposit)
657                    }
658                }
659                CertificateEnum::PoolRetirement(_) => acc.checked_add(&pool_deposit),
660                CertificateEnum::DRepDeregistration(cert) => acc.checked_add(&cert.coin),
661                _ => Ok(acc),
662            })?,
663    };
664
665    Ok(Value::new(
666        &withdrawal_sum.checked_add(&certificate_refund)?,
667    ))
668}
669
670pub fn internal_get_deposit(
671    certs: &Option<Certificates>,
672    pool_deposit: &BigNum, // // protocol parameter
673    key_deposit: &BigNum,  // protocol parameter
674) -> Result<Coin, JsError> {
675    let certificate_deposit = match &certs {
676        None => BigNum::zero(),
677        Some(certs) => certs
678            .certs
679            .iter()
680            .try_fold(BigNum::zero(), |acc, ref cert| match &cert.0 {
681                CertificateEnum::PoolRegistration(_) => acc.checked_add(&pool_deposit),
682                CertificateEnum::StakeRegistration(cert) => {
683                    if let Some(coin) = cert.coin {
684                        acc.checked_add(&coin)
685                    } else {
686                        acc.checked_add(&key_deposit)
687                    }
688                }
689                CertificateEnum::DRepRegistration(cert) => acc.checked_add(&cert.coin),
690                CertificateEnum::StakeRegistrationAndDelegation(cert) => {
691                    acc.checked_add(&cert.coin)
692                }
693                CertificateEnum::VoteRegistrationAndDelegation(cert) => acc.checked_add(&cert.coin),
694                CertificateEnum::StakeVoteRegistrationAndDelegation(cert) => {
695                    acc.checked_add(&cert.coin)
696                }
697                _ => Ok(acc),
698            })?,
699    };
700    Ok(certificate_deposit)
701}
702
703#[wasm_bindgen]
704pub fn get_implicit_input(
705    txbody: &TransactionBody,
706    pool_deposit: &BigNum, // // protocol parameter
707    key_deposit: &BigNum,  // protocol parameter
708) -> Result<Value, JsError> {
709    internal_get_implicit_input(
710        &txbody.withdrawals,
711        &txbody.certs,
712        &pool_deposit,
713        &key_deposit,
714    )
715}
716
717#[wasm_bindgen]
718pub fn get_deposit(
719    txbody: &TransactionBody,
720    pool_deposit: &BigNum, // // protocol parameter
721    key_deposit: &BigNum,  // protocol parameter
722) -> Result<Coin, JsError> {
723    internal_get_deposit(&txbody.certs, &pool_deposit, &key_deposit)
724}
725
726#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
727pub struct MinOutputAdaCalculator {
728    output: TransactionOutput,
729    data_cost: DataCost,
730}
731
732impl MinOutputAdaCalculator {
733    pub fn new(output: &TransactionOutput, data_cost: &DataCost) -> Self {
734        Self {
735            output: output.clone(),
736            data_cost: data_cost.clone(),
737        }
738    }
739
740    pub fn new_empty(data_cost: &DataCost) -> Result<MinOutputAdaCalculator, JsError> {
741        Ok(Self {
742            output: MinOutputAdaCalculator::create_fake_output()?,
743            data_cost: data_cost.clone(),
744        })
745    }
746
747    pub fn set_address(&mut self, address: &Address) {
748        self.output.address = address.clone();
749    }
750
751    pub fn set_plutus_data(&mut self, data: &PlutusData) {
752        self.output.plutus_data = Some(DataOption::Data(data.clone()));
753    }
754
755    pub fn set_data_hash(&mut self, data_hash: &DataHash) {
756        self.output.plutus_data = Some(DataOption::DataHash(data_hash.clone()));
757    }
758
759    pub fn set_amount(&mut self, amount: &Value) {
760        self.output.amount = amount.clone();
761    }
762
763    pub fn set_script_ref(&mut self, script_ref: &ScriptRef) {
764        self.output.script_ref = Some(script_ref.clone());
765    }
766
767    pub fn calculate_ada(&self) -> Result<BigNum, JsError> {
768        let mut output: TransactionOutput = self.output.clone();
769        for _ in 0..3 {
770            let required_coin = Self::calc_required_coin(&output, &self.data_cost)?;
771            if output.amount.coin.less_than(&required_coin) {
772                output.amount.coin = required_coin.clone();
773            } else {
774                return Ok(required_coin);
775            }
776        }
777        output.amount.coin = BigNum(u64::MAX);
778        Ok(Self::calc_required_coin(&output, &self.data_cost)?)
779    }
780
781    fn create_fake_output() -> Result<TransactionOutput, JsError> {
782        let fake_base_address: Address = Address::from_bech32("addr_test1qpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qum8x5w")?;
783        let fake_value: Value = Value::new(&BigNum(1000000));
784        Ok(TransactionOutput::new(&fake_base_address, &fake_value))
785    }
786
787    pub fn calc_size_cost(data_cost: &DataCost, size: usize) -> Result<Coin, JsError> {
788        //according to https://hydra.iohk.io/build/15339994/download/1/babbage-changes.pdf
789        //See on the page 9 getValue txout
790        BigNum(size as u64)
791            .checked_add(&BigNum(160))?
792            .checked_mul(&data_cost.coins_per_byte())
793    }
794
795    pub fn calc_required_coin(
796        output: &TransactionOutput,
797        data_cost: &DataCost,
798    ) -> Result<Coin, JsError> {
799        //according to https://hydra.iohk.io/build/15339994/download/1/babbage-changes.pdf
800        //See on the page 9 getValue txout
801        Self::calc_size_cost(data_cost, output.to_bytes().len())
802    }
803}
804
805///returns minimal amount of ada for the output for case when the amount is included to the output
806#[wasm_bindgen]
807pub fn min_ada_for_output(
808    output: &TransactionOutput,
809    data_cost: &DataCost,
810) -> Result<BigNum, JsError> {
811    MinOutputAdaCalculator::new(output, data_cost).calculate_ada()
812}
813
814/// Used to choosed the schema for a script JSON string
815#[wasm_bindgen]
816pub enum ScriptSchema {
817    Wallet,
818    Node,
819}
820
821/// Receives a script JSON string
822/// and returns a NativeScript.
823/// Cardano Wallet and Node styles are supported.
824///
825/// * wallet: https://github.com/input-output-hk/cardano-wallet/blob/master/specifications/api/swagger.yaml
826/// * node: https://github.com/input-output-hk/cardano-node/blob/master/doc/reference/simple-scripts.md
827///
828/// self_xpub is expected to be a Bip32PublicKey as hex-encoded bytes
829#[wasm_bindgen]
830pub fn encode_json_str_to_native_script(
831    json: &str,
832    self_xpub: &str,
833    schema: ScriptSchema,
834) -> Result<NativeScript, JsError> {
835    let value: serde_json::Value =
836        serde_json::from_str(&json).map_err(|e| JsError::from_str(&e.to_string()))?;
837
838    let native_script = match schema {
839        ScriptSchema::Wallet => encode_wallet_value_to_native_script(value, self_xpub)?,
840        ScriptSchema::Node => todo!(),
841    };
842
843    Ok(native_script)
844}
845
846fn encode_wallet_value_to_native_script(
847    value: serde_json::Value,
848    self_xpub: &str,
849) -> Result<NativeScript, JsError> {
850    match value {
851        serde_json::Value::Object(map)
852            if map.contains_key("cosigners") && map.contains_key("template") =>
853        {
854            let mut cosigners = HashMap::new();
855
856            if let serde_json::Value::Object(cosigner_map) = map.get("cosigners").unwrap() {
857                for (key, value) in cosigner_map.iter() {
858                    if let serde_json::Value::String(xpub) = value {
859                        if xpub == "self" {
860                            cosigners.insert(key.to_owned(), self_xpub.to_owned());
861                        } else {
862                            cosigners.insert(key.to_owned(), xpub.to_owned());
863                        }
864                    } else {
865                        return Err(JsError::from_str("cosigner value must be a string"));
866                    }
867                }
868            } else {
869                return Err(JsError::from_str("cosigners must be a map"));
870            }
871
872            let template = map.get("template").unwrap();
873
874            let template_native_script = encode_template_to_native_script(template, &cosigners)?;
875
876            Ok(template_native_script)
877        }
878        _ => Err(JsError::from_str(
879            "top level must be an object. cosigners and template keys are required",
880        )),
881    }
882}
883
884fn encode_template_to_native_script(
885    template: &serde_json::Value,
886    cosigners: &HashMap<String, String>,
887) -> Result<NativeScript, JsError> {
888    match template {
889        serde_json::Value::String(cosigner) => {
890            if let Some(xpub) = cosigners.get(cosigner) {
891                let bytes = Vec::from_hex(xpub).map_err(|e| JsError::from_str(&e.to_string()))?;
892
893                let public_key = Bip32PublicKey::from_bytes(&bytes)?;
894
895                Ok(NativeScript::new_script_pubkey(&ScriptPubkey::new(
896                    &public_key.to_raw_key().hash(),
897                )))
898            } else {
899                Err(JsError::from_str(&format!(
900                    "cosigner {} not found",
901                    cosigner
902                )))
903            }
904        }
905        serde_json::Value::Object(map) if map.contains_key("all") => {
906            let mut all = NativeScripts::new();
907
908            if let serde_json::Value::Array(array) = map.get("all").unwrap() {
909                for val in array {
910                    all.add(&encode_template_to_native_script(val, cosigners)?);
911                }
912            } else {
913                return Err(JsError::from_str("all must be an array"));
914            }
915
916            Ok(NativeScript::new_script_all(&ScriptAll::new(&all)))
917        }
918        serde_json::Value::Object(map) if map.contains_key("any") => {
919            let mut any = NativeScripts::new();
920
921            if let serde_json::Value::Array(array) = map.get("any").unwrap() {
922                for val in array {
923                    any.add(&encode_template_to_native_script(val, cosigners)?);
924                }
925            } else {
926                return Err(JsError::from_str("any must be an array"));
927            }
928
929            Ok(NativeScript::new_script_any(&ScriptAny::new(&any)))
930        }
931        serde_json::Value::Object(map) if map.contains_key("some") => {
932            if let serde_json::Value::Object(some) = map.get("some").unwrap() {
933                if some.contains_key("at_least") && some.contains_key("from") {
934                    let n = if let serde_json::Value::Number(at_least) =
935                        some.get("at_least").unwrap()
936                    {
937                        if let Some(n) = at_least.as_u64() {
938                            n as u32
939                        } else {
940                            return Err(JsError::from_str("at_least must be an integer"));
941                        }
942                    } else {
943                        return Err(JsError::from_str("at_least must be an integer"));
944                    };
945
946                    let mut from_scripts = NativeScripts::new();
947
948                    if let serde_json::Value::Array(array) = some.get("from").unwrap() {
949                        for val in array {
950                            from_scripts.add(&encode_template_to_native_script(val, cosigners)?);
951                        }
952                    } else {
953                        return Err(JsError::from_str("from must be an array"));
954                    }
955
956                    Ok(NativeScript::new_script_n_of_k(&ScriptNOfK::new(
957                        n,
958                        &from_scripts,
959                    )))
960                } else {
961                    Err(JsError::from_str("some must contain at_least and from"))
962                }
963            } else {
964                Err(JsError::from_str("some must be an object"))
965            }
966        }
967        serde_json::Value::Object(map) if map.contains_key("active_from") => {
968            if let serde_json::Value::Number(active_from) = map.get("active_from").unwrap() {
969                if let Some(n) = active_from.as_u64() {
970                    let slot: SlotBigNum = n.into();
971
972                    let time_lock_start = TimelockStart::new_timelockstart(&slot);
973
974                    Ok(NativeScript::new_timelock_start(&time_lock_start))
975                } else {
976                    Err(JsError::from_str(
977                        "active_from slot must be an integer greater than or equal to 0",
978                    ))
979                }
980            } else {
981                Err(JsError::from_str("active_from slot must be a number"))
982            }
983        }
984        serde_json::Value::Object(map) if map.contains_key("active_until") => {
985            if let serde_json::Value::Number(active_until) = map.get("active_until").unwrap() {
986                if let Some(n) = active_until.as_u64() {
987                    let slot: SlotBigNum = n.into();
988
989                    let time_lock_expiry = TimelockExpiry::new_timelockexpiry(&slot);
990
991                    Ok(NativeScript::new_timelock_expiry(&time_lock_expiry))
992                } else {
993                    Err(JsError::from_str(
994                        "active_until slot must be an integer greater than or equal to 0",
995                    ))
996                }
997            } else {
998                Err(JsError::from_str("active_until slot must be a number"))
999            }
1000        }
1001        _ => Err(JsError::from_str("invalid template format")),
1002    }
1003}
1004
1005pub(crate) fn opt64<T>(o: &Option<T>) -> u64 {
1006    o.is_some() as u64
1007}
1008
1009pub(crate) fn opt64_non_empty<T: NoneOrEmpty>(o: &Option<T>) -> u64 {
1010    (!o.is_none_or_empty()) as u64
1011}
1012
1013pub struct ValueShortage {
1014    pub(crate) ada_shortage: Option<(Coin, Coin, Coin)>,
1015    pub(crate) asset_shortage: Vec<(PolicyID, AssetName, Coin, Coin)>,
1016}
1017
1018impl Display for ValueShortage {
1019    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1020        write!(f, "shortage: {{")?;
1021        if let Some((input_data, out_data, fee)) = self.ada_shortage {
1022            writeln!(
1023                f,
1024                "ada in inputs: {}, ada in outputs: {}, fee {}",
1025                input_data, out_data, fee
1026            )?;
1027            writeln!(f, "NOTE! \"ada in inputs\" must be >= (\"ada in outputs\" + fee) before adding change")?;
1028            writeln!(
1029                f,
1030                "and  \"ada in inputs\" must be == (\"ada in outputs\" + fee) after adding change"
1031            )?;
1032        }
1033        for (policy_id, asset_name, asset_shortage, asset_available) in &self.asset_shortage {
1034            write!(
1035                f,
1036                "policy id: \"{}\", asset name: \"{}\" ",
1037                policy_id, asset_name
1038            )?;
1039            writeln!(
1040                f,
1041                "coins in inputs: {}, coins in outputs: {}",
1042                asset_shortage, asset_available
1043            )?;
1044        }
1045        write!(f, " }}")
1046    }
1047}
1048
1049pub(crate) fn get_input_shortage(
1050    all_inputs_value: &Value,
1051    all_outputs_value: &Value,
1052    fee: &Coin,
1053) -> Result<Option<ValueShortage>, JsError> {
1054    let mut shortage = ValueShortage {
1055        ada_shortage: None,
1056        asset_shortage: Vec::new(),
1057    };
1058    if all_inputs_value.coin < all_outputs_value.coin.checked_add(fee)? {
1059        shortage.ada_shortage = Some((
1060            all_inputs_value.coin.clone(),
1061            all_outputs_value.coin.clone(),
1062            fee.clone(),
1063        ));
1064    }
1065
1066    if let Some(policies) = &all_outputs_value.multiasset {
1067        for (policy_id, assets) in &policies.0 {
1068            for (asset_name, coins) in &assets.0 {
1069                let inputs_coins = match &all_inputs_value.multiasset {
1070                    Some(multiasset) => multiasset.get_asset(policy_id, asset_name),
1071                    None => Coin::zero(),
1072                };
1073
1074                if inputs_coins < *coins {
1075                    shortage.asset_shortage.push((
1076                        policy_id.clone(),
1077                        asset_name.clone(),
1078                        inputs_coins,
1079                        coins.clone(),
1080                    ));
1081                }
1082            }
1083        }
1084    }
1085
1086    if shortage.ada_shortage.is_some() || shortage.asset_shortage.len() > 0 {
1087        Ok(Some(shortage))
1088    } else {
1089        Ok(None)
1090    }
1091}
1092
1093#[wasm_bindgen]
1094#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1095pub enum TransactionSetsState {
1096    AllSetsHaveTag = 0,
1097    AllSetsHaveNoTag = 1,
1098    MixedSets = 2,
1099}
1100
1101/// Returns the state of the transaction sets.
1102/// If all sets have a tag, it returns AllSetsHaveTag.
1103/// If all sets have no tag, it returns AllSetsHaveNoTag.
1104/// If there is a mix of tagged and untagged sets, it returns MixedSets.
1105/// This function is useful for checking if a transaction might be signed by a hardware wallet.
1106/// And for checking which parameter should be used in a hardware wallet api.
1107/// WARNING this function will be deleted after all tags for set types will be mandatory. Approx after next hf
1108#[wasm_bindgen]
1109pub fn has_transaction_set_tag(tx_bytes: Vec<u8>) -> Result<TransactionSetsState, JsError> {
1110    let tx = Transaction::from_bytes(tx_bytes)?;
1111    has_transaction_set_tag_internal(&tx.body, Some(&tx.witness_set))
1112}
1113
1114pub(crate) fn has_transaction_set_tag_internal(body: &TransactionBody, witnesses_set: Option<&TransactionWitnessSet>) -> Result<TransactionSetsState, JsError> {
1115    let body_tag = has_transaction_body_set_tag(&body)?;
1116    let witness_tag = witnesses_set.map(has_transaction_witnesses_set_tag).flatten();
1117
1118    match (body_tag, witness_tag) {
1119        (TransactionSetsState::AllSetsHaveTag, Some(TransactionSetsState::AllSetsHaveTag)) => Ok(TransactionSetsState::AllSetsHaveTag),
1120        (TransactionSetsState::AllSetsHaveNoTag, Some(TransactionSetsState::AllSetsHaveNoTag)) => Ok(TransactionSetsState::AllSetsHaveNoTag),
1121        (TransactionSetsState::AllSetsHaveTag, None) => Ok(TransactionSetsState::AllSetsHaveTag),
1122        (TransactionSetsState::AllSetsHaveNoTag, None) => Ok(TransactionSetsState::AllSetsHaveNoTag),
1123        _ => Ok(TransactionSetsState::MixedSets),
1124    }
1125}
1126
1127pub(crate) fn has_transaction_body_set_tag(body: &TransactionBody) -> Result<TransactionSetsState, JsError> {
1128    let mut has_tag = false;
1129    let mut has_no_tag  = false;
1130
1131    match body.inputs.get_set_type() {
1132        CborSetType::Tagged => has_tag = true,
1133        CborSetType::Untagged => has_no_tag = true,
1134    }
1135    body.reference_inputs.as_ref().map(|ref_inputs| {
1136        match ref_inputs.get_set_type() {
1137            CborSetType::Tagged => has_tag = true,
1138            CborSetType::Untagged => has_no_tag = true,
1139        }
1140    });
1141    body.required_signers.as_ref().map(|required_signers| {
1142        match required_signers.get_set_type() {
1143            CborSetType::Tagged => has_tag = true,
1144            CborSetType::Untagged => has_no_tag = true,
1145        }
1146    });
1147    body.voting_proposals.as_ref().map(|voting_proposals| {
1148        match voting_proposals.get_set_type() {
1149            CborSetType::Tagged => has_tag = true,
1150            CborSetType::Untagged => has_no_tag = true,
1151        }
1152    });
1153    body.collateral.as_ref().map(|collateral_inputs| {
1154        match collateral_inputs.get_set_type() {
1155            CborSetType::Tagged => has_tag = true,
1156            CborSetType::Untagged => has_no_tag = true,
1157        }
1158    });
1159    body.certs.as_ref().map(|certs| {
1160        match certs.get_set_type() {
1161            CborSetType::Tagged => has_tag = true,
1162            CborSetType::Untagged => has_no_tag = true,
1163        }
1164    });
1165
1166    body.certs.as_ref().map(|certs| {
1167        for cert in certs {
1168            match &cert.0 {
1169                CertificateEnum::PoolRegistration(pool_reg) => {
1170                    match pool_reg.pool_params.pool_owners.get_set_type() {
1171                        CborSetType::Tagged => has_tag = true,
1172                        CborSetType::Untagged => has_no_tag = true,
1173                    }
1174                }
1175                _ => {}
1176            }
1177        }
1178    });
1179
1180    body.voting_proposals.as_ref().map(|voting_proposals| {
1181        for proposal in voting_proposals {
1182            match &proposal.governance_action.0 {
1183                GovernanceActionEnum::UpdateCommitteeAction(upd_action) => {
1184                    match upd_action.members_to_remove.get_set_type() {
1185                        CborSetType::Tagged => has_tag = true,
1186                        CborSetType::Untagged => has_no_tag = true,
1187                    }
1188                }
1189                _ => {}
1190            }
1191        }
1192    });
1193
1194    match (has_tag, has_no_tag) {
1195        (true, true) => Ok(TransactionSetsState::MixedSets),
1196        (true, false) => Ok(TransactionSetsState::AllSetsHaveTag),
1197        (false, true) => Ok(TransactionSetsState::AllSetsHaveNoTag),
1198        (false, false) => Err(JsError::from_str("Transaction has invalid state")),
1199    }
1200}
1201
1202pub(crate) fn has_transaction_witnesses_set_tag(witness_set: &TransactionWitnessSet) -> Option<TransactionSetsState> {
1203    let mut has_tag = false;
1204    let mut has_no_tag  = false;
1205
1206    witness_set.bootstraps.as_ref().map(|bs| {
1207        match bs.get_set_type() {
1208            CborSetType::Tagged => has_tag = true,
1209            CborSetType::Untagged => has_no_tag = true,
1210        }
1211    });
1212    witness_set.vkeys.as_ref().map(|vkeys| {
1213        match vkeys.get_set_type() {
1214            CborSetType::Tagged => has_tag = true,
1215            CborSetType::Untagged => has_no_tag = true,
1216        }
1217    });
1218    witness_set.plutus_data.as_ref().map(|plutus_data| {
1219        match plutus_data.get_set_type() {
1220            Some(CborSetType::Tagged) => has_tag = true,
1221            Some(CborSetType::Untagged) => has_no_tag = true,
1222            None => has_tag = true,
1223        }
1224    });
1225    witness_set.native_scripts.as_ref().map(|native_scripts| {
1226        match native_scripts.get_set_type() {
1227            Some(CborSetType::Tagged) => has_tag = true,
1228            Some(CborSetType::Untagged) => has_no_tag = true,
1229            None => has_tag = true,
1230        }
1231    });
1232    witness_set.plutus_scripts.as_ref().map(|plutus_scripts| {
1233        match plutus_scripts.get_set_type(&Language::new_plutus_v1()) {
1234            Some(CborSetType::Tagged) => has_tag = true,
1235            Some(CborSetType::Untagged) => has_no_tag = true,
1236            None => has_tag = true,
1237        }
1238        match plutus_scripts.get_set_type(&Language::new_plutus_v2()) {
1239            Some(CborSetType::Tagged) => has_tag = true,
1240            Some(CborSetType::Untagged) => has_no_tag = true,
1241            None => has_tag = true,
1242        }
1243        match plutus_scripts.get_set_type(&Language::new_plutus_v3()) {
1244            Some(CborSetType::Tagged) => has_tag = true,
1245            Some(CborSetType::Untagged) => has_no_tag = true,
1246            None => has_tag = true,
1247        }
1248    });
1249
1250    match (has_tag, has_no_tag) {
1251        (true, true) => Some(TransactionSetsState::MixedSets),
1252        (true, false) => Some(TransactionSetsState::AllSetsHaveTag),
1253        (false, true) => Some(TransactionSetsState::AllSetsHaveNoTag),
1254        (false, false) => None,
1255    }
1256}