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, Eq, PartialEq, 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 {
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 {
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, Eq, PartialEq, 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_vec_wrapper!(TransactionUnspentOutputs, TransactionUnspentOutput);
139
140#[wasm_bindgen]
141#[derive(
142 Clone,
143 Debug,
144 Default,
145 Ord,
146 serde::Serialize,
147 serde::Deserialize,
148 JsonSchema,
149)]
150pub struct Value {
151 pub(crate) coin: Coin,
152 pub(crate) multiasset: Option<MultiAsset>,
153}
154
155impl_to_from!(Value);
156
157#[wasm_bindgen]
158impl Value {
159 pub fn new(coin: &Coin) -> Value {
160 Self::from(coin.clone())
161 }
162
163 pub fn new_from_assets(multiasset: &MultiAsset) -> Value {
164 Value::from(multiasset.clone())
165 }
166
167 pub fn new_with_assets(coin: &Coin, multiasset: &MultiAsset) -> Value {
168 Self::from(multiasset.clone()).with_coin(*coin)
169 }
170
171 pub fn zero() -> Value {
172 Self::default()
173 }
174
175 pub fn is_zero(&self) -> bool {
176 self.coin.is_zero() && self.multiasset.as_ref().is_none_or(MultiAsset::is_zero)
177 }
178
179 pub fn coin(&self) -> Coin {
180 self.coin
181 }
182
183 pub fn set_coin(&mut self, coin: &Coin) {
184 self.coin = coin.clone();
185 }
186
187 pub fn multiasset(&self) -> Option<MultiAsset> {
188 self.multiasset.clone()
189 }
190
191 pub fn set_multiasset(&mut self, multiasset: &MultiAsset) {
192 self.multiasset = Some(multiasset.clone());
193 }
194
195 pub fn checked_add(&self, rhs: &Value) -> Result<Value, JsError> {
196 <Self as num::CheckedAdd>::checked_add(self, rhs)
197 .ok_or_else(|| JsError::from_str("overflow"))
198 }
199
200 pub fn checked_sub(&self, rhs_value: &Value) -> Result<Value, JsError> {
201 <Self as num::CheckedSub>::checked_sub(self, rhs_value)
202 .ok_or_else(|| JsError::from_str("underflow"))
203 }
204
205 pub fn clamped_sub(&self, rhs_value: &Value) -> Value {
206 <Self as num_traits::SaturatingSub>::saturating_sub(&self, rhs_value)
207 }
208
209 pub fn compare(&self, rhs_value: &Value) -> Option<i8> {
211 match self.partial_cmp(&rhs_value) {
212 None => None,
213 Some(std::cmp::Ordering::Equal) => Some(0),
214 Some(std::cmp::Ordering::Less) => Some(-1),
215 Some(std::cmp::Ordering::Greater) => Some(1),
216 }
217 }
218}
219
220impl Value {
221 pub fn lovelace(amount: impl Into<Coin>) -> Self {
222 Self::from(amount.into())
223 }
224
225 pub fn with_asset(
226 self,
227 policy: PolicyID,
228 name: AssetName,
229 amount: BigNum,
230 ) -> Self {
231 let multiasset = self.multiasset.unwrap_or_default().with_asset(policy, name, amount);
232 let multiasset = (!multiasset.is_zero()).then_some(multiasset);
233 Self { multiasset, ..self }
234 }
235
236 pub fn with_assets(self, policy: PolicyID, assets: Assets) -> Self {
237 let multiasset = self.multiasset.unwrap_or_default().with_assets(policy, assets);
238 let multiasset = (!multiasset.is_zero()).then_some(multiasset);
239 Self { multiasset, ..self }
240 }
241
242 pub fn with_multiasset(self, multiasset: MultiAsset) -> Self {
243 let multiasset = (!multiasset.is_zero()).then_some(multiasset);
244 Self { multiasset, ..self }
245 }
246
247 pub fn with_coin(self, coin: Coin) -> Self {
248 Self { coin, ..self }
249 }
250}
251
252impl From<MultiAsset> for Value {
253 fn from(ma: MultiAsset) -> Self {
254 Self { coin: Coin::zero(), multiasset: (ma.len() > 0).then_some(ma) }
255
256 }
257}
258
259impl From<Coin> for Value {
260 fn from(coin: Coin) -> Self {
261 Self { coin, multiasset: None }
262
263 }
264}
265
266impl PartialEq for Value {
267 fn eq(&self, other: &Self) -> bool {
268 let self_ma = self.multiasset.as_ref().map(|ma| ma.reduce_empty_to_none()).flatten();
269 let other_ma = other.multiasset.as_ref().map(|ma| ma.reduce_empty_to_none()).flatten();
270 self.coin == other.coin && self_ma == other_ma
271 }
272}
273
274impl Eq for Value {}
275
276impl PartialOrd for Value {
277 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
278 use std::cmp::Ordering::*;
279
280 fn compare_assets(
281 lhs: &Option<MultiAsset>,
282 rhs: &Option<MultiAsset>,
283 ) -> Option<std::cmp::Ordering> {
284 match (lhs, rhs) {
285 (None, None) => Some(Equal),
286 (None, Some(rhs_assets)) => MultiAsset::new().partial_cmp(&rhs_assets),
287 (Some(lhs_assets), None) => lhs_assets.partial_cmp(&MultiAsset::new()),
288 (Some(lhs_assets), Some(rhs_assets)) => lhs_assets.partial_cmp(&rhs_assets),
289 }
290 }
291
292 compare_assets(&self.multiasset(), &other.multiasset()).and_then(|assets_match| {
293 let coin_cmp = self.coin.cmp(&other.coin);
294
295 match (coin_cmp, assets_match) {
296 (coin_order, Equal) => Some(coin_order),
297 (Equal, Less) => Some(Less),
298 (Less, Less) => Some(Less),
299 (Equal, Greater) => Some(Greater),
300 (Greater, Greater) => Some(Greater),
301 (_, _) => None,
302 }
303 })
304 }
305}
306
307impl std::ops::Add for Value {
308 type Output = Value;
309
310 fn add(self, rhs: Self) -> Self::Output {
311 <Self as num_traits::CheckedAdd>::checked_add(&self, &rhs).expect("Value overflow")
312 }
313}
314
315impl std::ops::Add<Coin> for Value {
316 type Output = Value;
317
318 fn add(self, rhs: Coin) -> Self::Output {
319 let coin = num::CheckedAdd::checked_add(&self.coin, &rhs).expect("Value overflow");
320 Value { coin, ..self }
321 }
322}
323
324impl std::ops::Add<MultiAsset> for Value {
325 type Output = Value;
326
327 fn add(self, rhs: MultiAsset) -> Self::Output {
328 self + Value::from(rhs)
329 }
330}
331
332impl std::ops::Sub for Value {
333 type Output = Value;
334
335 fn sub(self, rhs: Self) -> Self::Output {
336 <Self as num_traits::CheckedSub>::checked_sub(&self, &rhs).expect("Value underflow")
337 }
338}
339
340impl std::ops::Sub<Coin> for Value {
341 type Output = Value;
342
343 fn sub(self, rhs: Coin) -> Self::Output {
344 let coin = num::CheckedSub::checked_sub(&self.coin, &rhs).expect("Value underflow");
345 Value { coin, ..self }
346 }
347}
348
349impl std::ops::Sub<MultiAsset> for Value {
350 type Output = Value;
351
352 fn sub(self, rhs: MultiAsset) -> Self::Output {
353 self - Value::from(rhs)
354 }
355}
356
357impl num_traits::CheckedAdd for Value {
358 fn checked_add(&self, other: &Self) -> Option<Self> {
359 let coin = num::CheckedAdd::checked_add(&self.coin, &other.coin)?;
360 let multiasset = match (&self.multiasset, &other.multiasset) {
361 (Some(first), Some(second)) => Some(num::CheckedAdd::checked_add(first, second)?),
362 (Some(first), None) => Some(first.clone()),
363 (None, Some(second)) => Some(second.clone()),
364 (None, None) => None,
365 }
366 .filter(|ma| !ma.is_zero());
367 Some(Self { coin, multiasset })
368 }
369}
370
371impl num_traits::CheckedSub for Value {
372 fn checked_sub(&self, other: &Self) -> Option<Self> {
373 let coin = num::CheckedSub::checked_sub(&self.coin, &other.coin)?;
374 let multiasset = match (&self.multiasset, &other.multiasset) {
375 (Some(first), Some(second)) => Some(num::CheckedSub::checked_sub(first, second)?),
376 (Some(first), None) => Some(first.clone()),
377 (None, Some(second)) if !second.is_zero() => return None,
378 (None, Some(_zero)) => None,
379 (None, None) => None,
380 }
381 .filter(|ma| !ma.is_zero());
382 Some(Self { coin, multiasset })
383 }
384}
385
386impl num_traits::SaturatingSub for Value {
387 fn saturating_sub(&self, v: &Self) -> Self {
388 let coin = self.coin.saturating_sub(&v.coin);
389 let multiasset = match (&self.multiasset, &v.multiasset) {
390 (Some(first), Some(second)) => Some(first.saturating_sub(second)),
391 (Some(first), None) => Some(first.clone()),
392 (None, _) => None,
393 }
394 .filter(|ma| !ma.is_zero());
395 Self { coin, multiasset }
396 }
397}
398
399impl num_traits::SaturatingAdd for Value {
400 fn saturating_add(&self, v: &Self) -> Self {
401 let coin = self.coin.saturating_add(&v.coin);
402 let multiasset = match (&self.multiasset, &v.multiasset) {
403 (Some(first), Some(second)) => Some(first.saturating_add(second)),
404 (Some(first), None) => Some(first.clone()),
405 (None, Some(second)) => Some(second.clone()),
406 (None, None) => None,
407 }
408 .filter(|ma| !ma.is_zero());
409 Self { coin, multiasset }
410 }
411}
412
413impl cbor_event::se::Serialize for Value {
414 fn serialize<'se, W: Write>(
415 &self,
416 serializer: &'se mut Serializer<W>,
417 ) -> cbor_event::Result<&'se mut Serializer<W>> {
418 let multiasset = self.multiasset
419 .as_ref()
420 .map(|ma| ma.reduce_empty_to_none())
421 .flatten();
422
423 if let Some(multiasset) = multiasset {
424 serializer.write_array(cbor_event::Len::Len(2))?;
425 self.coin.serialize(serializer)?;
426 multiasset.serialize(serializer)?;
427 } else {
428 self.coin.serialize(serializer)?;
429 }
430
431 Ok(serializer)
432 }
433}
434
435impl Deserialize for Value {
436 fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
437 (|| -> Result<_, DeserializeError> {
438 match raw.cbor_type()? {
439 cbor_event::Type::UnsignedInteger => Ok(Value::new(&Coin::deserialize(raw)?)),
440 cbor_event::Type::Array => {
441 let len = raw.array()?;
442 let coin =
443 (|| -> Result<_, DeserializeError> { Ok(Coin::deserialize(raw)?) })()
444 .map_err(|e| e.annotate("coin"))?;
445 let multiasset =
446 (|| -> Result<_, DeserializeError> { Ok(MultiAsset::deserialize(raw)?) })()
447 .map_err(|e| e.annotate("multiasset"))?;
448 let ret = Ok(Self {
449 coin,
450 multiasset: Some(multiasset),
451 });
452 match len {
453 cbor_event::Len::Len(n) => match n {
454 2 =>
455 {
457 ()
458 }
459 n => {
460 return Err(
461 DeserializeFailure::DefiniteLenMismatch(n, Some(2)).into()
462 );
463 }
464 },
465 cbor_event::Len::Indefinite => match raw.special()? {
466 CBORSpecial::Break =>
467 {
469 ()
470 }
471 _ => return Err(DeserializeFailure::EndingBreakMissing.into()),
472 },
473 }
474 ret
475 }
476 _ => Err(DeserializeFailure::NoVariantMatched.into()),
477 }
478 })()
479 .map_err(|e| e.annotate("Value"))
480 }
481}
482
483pub(crate) const BOUNDED_BYTES_CHUNK_SIZE: usize = 64;
484
485pub(crate) fn write_bounded_bytes<'se, W: Write>(
486 serializer: &'se mut Serializer<W>,
487 bytes: &[u8],
488) -> cbor_event::Result<&'se mut Serializer<W>> {
489 if bytes.len() <= BOUNDED_BYTES_CHUNK_SIZE {
490 serializer.write_bytes(bytes)
491 } else {
492 serializer.write_raw_bytes(&[0x5f])?;
494 for chunk in bytes.chunks(BOUNDED_BYTES_CHUNK_SIZE) {
495 serializer.write_bytes(chunk)?;
496 }
497 serializer.write_special(CBORSpecial::Break)
498 }
499}
500
501pub(crate) fn read_bounded_bytes<R: BufRead + Seek>(
502 raw: &mut Deserializer<R>,
503) -> Result<Vec<u8>, DeserializeError> {
504 use std::io::Read;
505 let t = raw.cbor_type()?;
506 if t != CBORType::Bytes {
507 return Err(cbor_event::Error::Expected(CBORType::Bytes, t).into());
508 }
509 let (len, len_sz) = raw.cbor_len()?;
510 match len {
511 cbor_event::Len::Len(_) => {
512 let bytes = raw.bytes()?;
513 if bytes.len() > BOUNDED_BYTES_CHUNK_SIZE {
514 return Err(DeserializeFailure::OutOfRange {
515 min: 0,
516 max: BOUNDED_BYTES_CHUNK_SIZE,
517 found: bytes.len(),
518 }
519 .into());
520 }
521 Ok(bytes)
522 }
523 cbor_event::Len::Indefinite => {
524 let mut bytes = Vec::new();
530 raw.advance(1 + len_sz)?;
531 while raw.cbor_type()? != CBORType::Special {
534 let chunk_t = raw.cbor_type()?;
535 if chunk_t != CBORType::Bytes {
536 return Err(cbor_event::Error::Expected(CBORType::Bytes, chunk_t).into());
537 }
538 let (chunk_len, chunk_len_sz) = raw.cbor_len()?;
539 match chunk_len {
540 cbor_event::Len::Indefinite => {
543 return Err(cbor_event::Error::CustomError(String::from(
544 "Illegal CBOR: Indefinite string found inside indefinite string",
545 ))
546 .into());
547 }
548 cbor_event::Len::Len(len) => {
549 if len as usize > BOUNDED_BYTES_CHUNK_SIZE {
550 return Err(DeserializeFailure::OutOfRange {
551 min: 0,
552 max: BOUNDED_BYTES_CHUNK_SIZE,
553 found: len as usize,
554 }
555 .into());
556 }
557 raw.advance(1 + chunk_len_sz)?;
558 raw.as_mut_ref()
559 .by_ref()
560 .take(len)
561 .read_to_end(&mut bytes)
562 .map_err(|e| cbor_event::Error::IoError(e))?;
563 }
564 }
565 }
566 if raw.special()? != CBORSpecial::Break {
567 return Err(DeserializeFailure::EndingBreakMissing.into());
568 }
569 Ok(bytes)
570 }
571 }
572}
573
574
575pub struct CBORReadLen {
576 deser_len: cbor_event::Len,
577 read: u64,
578}
579
580impl CBORReadLen {
581 pub fn new(len: cbor_event::Len) -> Self {
582 Self {
583 deser_len: len,
584 read: 0,
585 }
586 }
587
588 pub fn read_elems(&mut self, count: usize) -> Result<(), DeserializeFailure> {
591 match self.deser_len {
592 cbor_event::Len::Len(n) => {
593 self.read += count as u64;
594 if self.read > n {
595 Err(DeserializeFailure::DefiniteLenMismatch(n, None))
596 } else {
597 Ok(())
598 }
599 }
600 cbor_event::Len::Indefinite => Ok(()),
601 }
602 }
603
604 pub fn finish(&self) -> Result<(), DeserializeFailure> {
605 match self.deser_len {
606 cbor_event::Len::Len(n) => {
607 if self.read == n {
608 Ok(())
609 } else {
610 Err(DeserializeFailure::DefiniteLenMismatch(n, Some(self.read)))
611 }
612 }
613 cbor_event::Len::Indefinite => Ok(()),
614 }
615 }
616}
617
618#[wasm_bindgen]
619pub fn make_daedalus_bootstrap_witness(
620 tx_body_hash: &TransactionHash,
621 addr: &ByronAddress,
622 key: &LegacyDaedalusPrivateKey,
623) -> BootstrapWitness {
624 let chain_code = key.chaincode();
625
626 let pubkey = Bip32PublicKey::from_bytes(&key.0.to_public().as_ref()).unwrap();
627 let vkey = Vkey::new(&pubkey.to_raw_key());
628 let signature =
629 Ed25519Signature::from_bytes(key.0.sign(&tx_body_hash.to_bytes()).as_ref().to_vec())
630 .unwrap();
631
632 BootstrapWitness::new(&vkey, &signature, chain_code, addr.attributes())
633}
634
635#[wasm_bindgen]
636pub fn make_icarus_bootstrap_witness(
637 tx_body_hash: &TransactionHash,
638 addr: &ByronAddress,
639 key: &Bip32PrivateKey,
640) -> BootstrapWitness {
641 let chain_code = key.chaincode();
642
643 let raw_key = key.to_raw_key();
644 let vkey = Vkey::new(&raw_key.to_public());
645 let signature = raw_key.sign(&tx_body_hash.to_bytes());
646
647 BootstrapWitness::new(&vkey, &signature, chain_code, addr.attributes())
648}
649
650#[wasm_bindgen]
651pub fn make_vkey_witness(tx_body_hash: &TransactionHash, sk: &PrivateKey) -> Vkeywitness {
652 let sig = sk.sign(tx_body_hash.0.as_ref());
653 Vkeywitness::new(&Vkey::new(&sk.to_public()), &sig)
654}
655
656#[wasm_bindgen]
657pub fn hash_auxiliary_data(auxiliary_data: &AuxiliaryData) -> AuxiliaryDataHash {
658 AuxiliaryDataHash::from(blake2b256(&auxiliary_data.to_bytes()))
659}
660
661#[wasm_bindgen]
662pub fn hash_plutus_data(plutus_data: &PlutusData) -> DataHash {
663 DataHash::from(blake2b256(&plutus_data.to_bytes()))
664}
665
666#[wasm_bindgen]
667pub fn hash_script_data(
668 redeemers: &Redeemers,
669 cost_models: &Costmdls,
670 datums: Option<PlutusList>,
671) -> ScriptDataHash {
672 let mut buf = Vec::new();
673 if redeemers.len() == 0 && datums.is_some() {
674 buf.push(0xA0);
682 if let Some(d) = &datums {
683 buf.extend(d.to_set_bytes());
684 }
685 buf.push(0xA0);
686 } else {
687 buf.extend(redeemers.to_bytes());
695 if let Some(d) = &datums {
696 buf.extend(d.to_set_bytes());
697 }
698 buf.extend(cost_models.language_views_encoding());
699 }
700 ScriptDataHash::from(blake2b256(&buf))
701}
702
703pub fn internal_get_implicit_input(
705 withdrawals: &Option<Withdrawals>,
706 certs: &Option<Certificates>,
707 pool_deposit: &BigNum, key_deposit: &BigNum, ) -> Result<Value, JsError> {
710 let withdrawal_sum = match &withdrawals {
711 None => BigNum::zero(),
712 Some(x) => {
713 x.0.values()
714 .try_fold(BigNum::zero(), |acc, ref withdrawal_amt| {
715 acc.checked_add(&withdrawal_amt)
716 })?
717 }
718 };
719 let certificate_refund = match &certs {
720 None => BigNum::zero(),
721 Some(certs) => certs
722 .certs
723 .iter()
724 .try_fold(BigNum::zero(), |acc, ref cert| match &cert.0 {
725 CertificateEnum::StakeDeregistration(cert) => {
726 if let Some(coin) = cert.coin {
727 acc.checked_add(&coin)
728 } else {
729 acc.checked_add(&key_deposit)
730 }
731 }
732 CertificateEnum::PoolRetirement(_) => acc.checked_add(&pool_deposit),
733 CertificateEnum::DRepDeregistration(cert) => acc.checked_add(&cert.coin),
734 _ => Ok(acc),
735 })?,
736 };
737
738 Ok(Value::new(
739 &withdrawal_sum.checked_add(&certificate_refund)?,
740 ))
741}
742
743pub fn internal_get_deposit(
744 certs: &Option<Certificates>,
745 pool_deposit: &BigNum, key_deposit: &BigNum, ) -> Result<Coin, JsError> {
748 let certificate_deposit = match &certs {
749 None => BigNum::zero(),
750 Some(certs) => certs
751 .certs
752 .iter()
753 .try_fold(BigNum::zero(), |acc, ref cert| match &cert.0 {
754 CertificateEnum::PoolRegistration(_) => acc.checked_add(&pool_deposit),
755 CertificateEnum::StakeRegistration(cert) => {
756 if let Some(coin) = cert.coin {
757 acc.checked_add(&coin)
758 } else {
759 acc.checked_add(&key_deposit)
760 }
761 }
762 CertificateEnum::DRepRegistration(cert) => acc.checked_add(&cert.coin),
763 CertificateEnum::StakeRegistrationAndDelegation(cert) => {
764 acc.checked_add(&cert.coin)
765 }
766 CertificateEnum::VoteRegistrationAndDelegation(cert) => acc.checked_add(&cert.coin),
767 CertificateEnum::StakeVoteRegistrationAndDelegation(cert) => {
768 acc.checked_add(&cert.coin)
769 }
770 _ => Ok(acc),
771 })?,
772 };
773 Ok(certificate_deposit)
774}
775
776#[wasm_bindgen]
777pub fn get_implicit_input(
778 txbody: &TransactionBody,
779 pool_deposit: &BigNum, key_deposit: &BigNum, ) -> Result<Value, JsError> {
782 internal_get_implicit_input(
783 &txbody.withdrawals,
784 &txbody.certs,
785 &pool_deposit,
786 &key_deposit,
787 )
788}
789
790#[wasm_bindgen]
791pub fn get_deposit(
792 txbody: &TransactionBody,
793 pool_deposit: &BigNum, key_deposit: &BigNum, ) -> Result<Coin, JsError> {
796 internal_get_deposit(&txbody.certs, &pool_deposit, &key_deposit)
797}
798
799#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
800pub struct MinOutputAdaCalculator {
801 output: TransactionOutput,
802 data_cost: DataCost,
803}
804
805impl MinOutputAdaCalculator {
806 pub fn new(output: &TransactionOutput, data_cost: &DataCost) -> Self {
807 Self {
808 output: output.clone(),
809 data_cost: data_cost.clone(),
810 }
811 }
812
813 pub fn new_empty(data_cost: &DataCost) -> Result<MinOutputAdaCalculator, JsError> {
814 Ok(Self {
815 output: MinOutputAdaCalculator::create_fake_output()?,
816 data_cost: data_cost.clone(),
817 })
818 }
819
820 pub fn set_address(&mut self, address: &Address) {
821 self.output.address = address.clone();
822 }
823
824 pub fn set_plutus_data(&mut self, data: &PlutusData) {
825 self.output.plutus_data = Some(DataOption::Data(data.clone()));
826 }
827
828 pub fn set_data_hash(&mut self, data_hash: &DataHash) {
829 self.output.plutus_data = Some(DataOption::DataHash(data_hash.clone()));
830 }
831
832 pub fn set_amount(&mut self, amount: &Value) {
833 self.output.amount = amount.clone();
834 }
835
836 pub fn set_script_ref(&mut self, script_ref: &ScriptRef) {
837 self.output.script_ref = Some(script_ref.clone());
838 }
839
840 pub fn calculate_ada(&self) -> Result<BigNum, JsError> {
841 let mut output: TransactionOutput = self.output.clone();
842 for _ in 0..3 {
843 let required_coin = Self::calc_required_coin(&output, &self.data_cost)?;
844 if output.amount.coin.less_than(&required_coin) {
845 output.amount.coin = required_coin.clone();
846 } else {
847 return Ok(required_coin);
848 }
849 }
850 output.amount.coin = BigNum(u64::MAX);
851 Ok(Self::calc_required_coin(&output, &self.data_cost)?)
852 }
853
854 fn create_fake_output() -> Result<TransactionOutput, JsError> {
855 let fake_base_address: Address = Address::from_bech32("addr_test1qpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qum8x5w")?;
856 let fake_value: Value = Value::new(&BigNum(1000000));
857 Ok(TransactionOutput::new(&fake_base_address, &fake_value))
858 }
859
860 pub fn calc_size_cost(data_cost: &DataCost, size: usize) -> Result<Coin, JsError> {
861 BigNum(size as u64)
864 .checked_add(&BigNum(160))?
865 .checked_mul(&data_cost.coins_per_byte())
866 }
867
868 pub fn calc_required_coin(
869 output: &TransactionOutput,
870 data_cost: &DataCost,
871 ) -> Result<Coin, JsError> {
872 Self::calc_size_cost(data_cost, output.to_bytes().len())
875 }
876}
877
878#[wasm_bindgen]
880pub fn min_ada_for_output(
881 output: &TransactionOutput,
882 data_cost: &DataCost,
883) -> Result<BigNum, JsError> {
884 MinOutputAdaCalculator::new(output, data_cost).calculate_ada()
885}
886
887#[wasm_bindgen]
889pub enum ScriptSchema {
890 Wallet,
891 Node,
892}
893
894#[wasm_bindgen]
903pub fn encode_json_str_to_native_script(
904 json: &str,
905 self_xpub: &str,
906 schema: ScriptSchema,
907) -> Result<NativeScript, JsError> {
908 let value: serde_json::Value =
909 serde_json::from_str(&json).map_err(|e| JsError::from_str(&e.to_string()))?;
910
911 let native_script = match schema {
912 ScriptSchema::Wallet => encode_wallet_value_to_native_script(value, self_xpub)?,
913 ScriptSchema::Node => todo!(),
914 };
915
916 Ok(native_script)
917}
918
919fn encode_wallet_value_to_native_script(
920 value: serde_json::Value,
921 self_xpub: &str,
922) -> Result<NativeScript, JsError> {
923 match value {
924 serde_json::Value::Object(map)
925 if map.contains_key("cosigners") && map.contains_key("template") =>
926 {
927 let mut cosigners = HashMap::new();
928
929 if let serde_json::Value::Object(cosigner_map) = map.get("cosigners").unwrap() {
930 for (key, value) in cosigner_map.iter() {
931 if let serde_json::Value::String(xpub) = value {
932 if xpub == "self" {
933 cosigners.insert(key.to_owned(), self_xpub.to_owned());
934 } else {
935 cosigners.insert(key.to_owned(), xpub.to_owned());
936 }
937 } else {
938 return Err(JsError::from_str("cosigner value must be a string"));
939 }
940 }
941 } else {
942 return Err(JsError::from_str("cosigners must be a map"));
943 }
944
945 let template = map.get("template").unwrap();
946
947 let template_native_script = encode_template_to_native_script(template, &cosigners)?;
948
949 Ok(template_native_script)
950 }
951 _ => Err(JsError::from_str(
952 "top level must be an object. cosigners and template keys are required",
953 )),
954 }
955}
956
957fn encode_template_to_native_script(
958 template: &serde_json::Value,
959 cosigners: &HashMap<String, String>,
960) -> Result<NativeScript, JsError> {
961 match template {
962 serde_json::Value::String(cosigner) => {
963 if let Some(xpub) = cosigners.get(cosigner) {
964 let bytes = Vec::from_hex(xpub).map_err(|e| JsError::from_str(&e.to_string()))?;
965
966 let public_key = Bip32PublicKey::from_bytes(&bytes)?;
967
968 Ok(NativeScript::new_script_pubkey(&ScriptPubkey::new(
969 &public_key.to_raw_key().hash(),
970 )))
971 } else {
972 Err(JsError::from_str(&format!(
973 "cosigner {} not found",
974 cosigner
975 )))
976 }
977 }
978 serde_json::Value::Object(map) if map.contains_key("all") => {
979 let mut all = NativeScripts::new();
980
981 if let serde_json::Value::Array(array) = map.get("all").unwrap() {
982 for val in array {
983 all.add(&encode_template_to_native_script(val, cosigners)?);
984 }
985 } else {
986 return Err(JsError::from_str("all must be an array"));
987 }
988
989 Ok(NativeScript::new_script_all(&ScriptAll::new(&all)))
990 }
991 serde_json::Value::Object(map) if map.contains_key("any") => {
992 let mut any = NativeScripts::new();
993
994 if let serde_json::Value::Array(array) = map.get("any").unwrap() {
995 for val in array {
996 any.add(&encode_template_to_native_script(val, cosigners)?);
997 }
998 } else {
999 return Err(JsError::from_str("any must be an array"));
1000 }
1001
1002 Ok(NativeScript::new_script_any(&ScriptAny::new(&any)))
1003 }
1004 serde_json::Value::Object(map) if map.contains_key("some") => {
1005 if let serde_json::Value::Object(some) = map.get("some").unwrap() {
1006 if some.contains_key("at_least") && some.contains_key("from") {
1007 let n = if let serde_json::Value::Number(at_least) =
1008 some.get("at_least").unwrap()
1009 {
1010 if let Some(n) = at_least.as_u64() {
1011 n as u32
1012 } else {
1013 return Err(JsError::from_str("at_least must be an integer"));
1014 }
1015 } else {
1016 return Err(JsError::from_str("at_least must be an integer"));
1017 };
1018
1019 let mut from_scripts = NativeScripts::new();
1020
1021 if let serde_json::Value::Array(array) = some.get("from").unwrap() {
1022 for val in array {
1023 from_scripts.add(&encode_template_to_native_script(val, cosigners)?);
1024 }
1025 } else {
1026 return Err(JsError::from_str("from must be an array"));
1027 }
1028
1029 Ok(NativeScript::new_script_n_of_k(&ScriptNOfK::new(
1030 n,
1031 &from_scripts,
1032 )))
1033 } else {
1034 Err(JsError::from_str("some must contain at_least and from"))
1035 }
1036 } else {
1037 Err(JsError::from_str("some must be an object"))
1038 }
1039 }
1040 serde_json::Value::Object(map) if map.contains_key("active_from") => {
1041 if let serde_json::Value::Number(active_from) = map.get("active_from").unwrap() {
1042 if let Some(n) = active_from.as_u64() {
1043 let slot: SlotBigNum = n.into();
1044
1045 let time_lock_start = TimelockStart::new_timelockstart(&slot);
1046
1047 Ok(NativeScript::new_timelock_start(&time_lock_start))
1048 } else {
1049 Err(JsError::from_str(
1050 "active_from slot must be an integer greater than or equal to 0",
1051 ))
1052 }
1053 } else {
1054 Err(JsError::from_str("active_from slot must be a number"))
1055 }
1056 }
1057 serde_json::Value::Object(map) if map.contains_key("active_until") => {
1058 if let serde_json::Value::Number(active_until) = map.get("active_until").unwrap() {
1059 if let Some(n) = active_until.as_u64() {
1060 let slot: SlotBigNum = n.into();
1061
1062 let time_lock_expiry = TimelockExpiry::new_timelockexpiry(&slot);
1063
1064 Ok(NativeScript::new_timelock_expiry(&time_lock_expiry))
1065 } else {
1066 Err(JsError::from_str(
1067 "active_until slot must be an integer greater than or equal to 0",
1068 ))
1069 }
1070 } else {
1071 Err(JsError::from_str("active_until slot must be a number"))
1072 }
1073 }
1074 _ => Err(JsError::from_str("invalid template format")),
1075 }
1076}
1077
1078pub(crate) fn opt64<T>(o: &Option<T>) -> u64 {
1079 o.is_some() as u64
1080}
1081
1082pub(crate) fn opt64_non_empty<T: NoneOrEmpty>(o: &Option<T>) -> u64 {
1083 (!o.is_none_or_empty()) as u64
1084}
1085
1086pub struct ValueShortage {
1087 pub(crate) ada_shortage: Option<(Coin, Coin, Coin)>,
1088 pub(crate) asset_shortage: Vec<(PolicyID, AssetName, Coin, Coin)>,
1089}
1090
1091impl Display for ValueShortage {
1092 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1093 write!(f, "shortage: {{")?;
1094 if let Some((input_data, out_data, fee)) = self.ada_shortage {
1095 writeln!(
1096 f,
1097 "ada in inputs: {}, ada in outputs: {}, fee {}",
1098 input_data, out_data, fee
1099 )?;
1100 writeln!(f, "NOTE! \"ada in inputs\" must be >= (\"ada in outputs\" + fee) before adding change")?;
1101 writeln!(
1102 f,
1103 "and \"ada in inputs\" must be == (\"ada in outputs\" + fee) after adding change"
1104 )?;
1105 }
1106 for (policy_id, asset_name, asset_shortage, asset_available) in &self.asset_shortage {
1107 write!(
1108 f,
1109 "policy id: \"{}\", asset name: \"{}\" ",
1110 policy_id, asset_name
1111 )?;
1112 writeln!(
1113 f,
1114 "coins in inputs: {}, coins in outputs: {}",
1115 asset_shortage, asset_available
1116 )?;
1117 }
1118 write!(f, " }}")
1119 }
1120}
1121
1122pub(crate) fn get_input_shortage(
1123 all_inputs_value: &Value,
1124 all_outputs_value: &Value,
1125 fee: &Coin,
1126) -> Result<Option<ValueShortage>, JsError> {
1127 let mut shortage = ValueShortage {
1128 ada_shortage: None,
1129 asset_shortage: Vec::new(),
1130 };
1131 if all_inputs_value.coin < all_outputs_value.coin.checked_add(fee)? {
1132 shortage.ada_shortage = Some((
1133 all_inputs_value.coin.clone(),
1134 all_outputs_value.coin.clone(),
1135 fee.clone(),
1136 ));
1137 }
1138
1139 if let Some(policies) = &all_outputs_value.multiasset {
1140 for (policy_id, assets) in &policies.0 {
1141 for (asset_name, coins) in &assets.0 {
1142 let inputs_coins = match &all_inputs_value.multiasset {
1143 Some(multiasset) => multiasset.get_asset(policy_id, asset_name),
1144 None => Coin::zero(),
1145 };
1146
1147 if inputs_coins < *coins {
1148 shortage.asset_shortage.push((
1149 policy_id.clone(),
1150 asset_name.clone(),
1151 inputs_coins,
1152 coins.clone(),
1153 ));
1154 }
1155 }
1156 }
1157 }
1158
1159 if shortage.ada_shortage.is_some() || shortage.asset_shortage.len() > 0 {
1160 Ok(Some(shortage))
1161 } else {
1162 Ok(None)
1163 }
1164}
1165
1166#[wasm_bindgen]
1167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1168pub enum TransactionSetsState {
1169 AllSetsHaveTag = 0,
1170 AllSetsHaveNoTag = 1,
1171 MixedSets = 2,
1172}
1173
1174#[wasm_bindgen]
1182pub fn has_transaction_set_tag(tx_bytes: Vec<u8>) -> Result<TransactionSetsState, JsError> {
1183 let tx = Transaction::from_bytes(tx_bytes)?;
1184 has_transaction_set_tag_internal(&tx.body, Some(&tx.witness_set))
1185}
1186
1187pub(crate) fn has_transaction_set_tag_internal(body: &TransactionBody, witnesses_set: Option<&TransactionWitnessSet>) -> Result<TransactionSetsState, JsError> {
1188 let body_tag = has_transaction_body_set_tag(&body)?;
1189 let witness_tag = witnesses_set.map(has_transaction_witnesses_set_tag).flatten();
1190
1191 match (body_tag, witness_tag) {
1192 (TransactionSetsState::AllSetsHaveTag, Some(TransactionSetsState::AllSetsHaveTag)) => Ok(TransactionSetsState::AllSetsHaveTag),
1193 (TransactionSetsState::AllSetsHaveNoTag, Some(TransactionSetsState::AllSetsHaveNoTag)) => Ok(TransactionSetsState::AllSetsHaveNoTag),
1194 (TransactionSetsState::AllSetsHaveTag, None) => Ok(TransactionSetsState::AllSetsHaveTag),
1195 (TransactionSetsState::AllSetsHaveNoTag, None) => Ok(TransactionSetsState::AllSetsHaveNoTag),
1196 _ => Ok(TransactionSetsState::MixedSets),
1197 }
1198}
1199
1200pub(crate) fn has_transaction_body_set_tag(body: &TransactionBody) -> Result<TransactionSetsState, JsError> {
1201 let mut has_tag = false;
1202 let mut has_no_tag = false;
1203
1204 match body.inputs.get_set_type() {
1205 CborSetType::Tagged => has_tag = true,
1206 CborSetType::Untagged => has_no_tag = true,
1207 }
1208 body.reference_inputs.as_ref().map(|ref_inputs| {
1209 match ref_inputs.get_set_type() {
1210 CborSetType::Tagged => has_tag = true,
1211 CborSetType::Untagged => has_no_tag = true,
1212 }
1213 });
1214 body.required_signers.as_ref().map(|required_signers| {
1215 match required_signers.get_set_type() {
1216 CborSetType::Tagged => has_tag = true,
1217 CborSetType::Untagged => has_no_tag = true,
1218 }
1219 });
1220 body.voting_proposals.as_ref().map(|voting_proposals| {
1221 match voting_proposals.get_set_type() {
1222 CborSetType::Tagged => has_tag = true,
1223 CborSetType::Untagged => has_no_tag = true,
1224 }
1225 });
1226 body.collateral.as_ref().map(|collateral_inputs| {
1227 match collateral_inputs.get_set_type() {
1228 CborSetType::Tagged => has_tag = true,
1229 CborSetType::Untagged => has_no_tag = true,
1230 }
1231 });
1232 body.certs.as_ref().map(|certs| {
1233 match certs.get_set_type() {
1234 CborSetType::Tagged => has_tag = true,
1235 CborSetType::Untagged => has_no_tag = true,
1236 }
1237 });
1238
1239 body.certs.as_ref().map(|certs| {
1240 for cert in certs {
1241 match &cert.0 {
1242 CertificateEnum::PoolRegistration(pool_reg) => {
1243 match pool_reg.pool_params.pool_owners.get_set_type() {
1244 CborSetType::Tagged => has_tag = true,
1245 CborSetType::Untagged => has_no_tag = true,
1246 }
1247 }
1248 _ => {}
1249 }
1250 }
1251 });
1252
1253 body.voting_proposals.as_ref().map(|voting_proposals| {
1254 for proposal in voting_proposals {
1255 match &proposal.governance_action.0 {
1256 GovernanceActionEnum::UpdateCommitteeAction(upd_action) => {
1257 match upd_action.members_to_remove.get_set_type() {
1258 CborSetType::Tagged => has_tag = true,
1259 CborSetType::Untagged => has_no_tag = true,
1260 }
1261 }
1262 _ => {}
1263 }
1264 }
1265 });
1266
1267 match (has_tag, has_no_tag) {
1268 (true, true) => Ok(TransactionSetsState::MixedSets),
1269 (true, false) => Ok(TransactionSetsState::AllSetsHaveTag),
1270 (false, true) => Ok(TransactionSetsState::AllSetsHaveNoTag),
1271 (false, false) => Err(JsError::from_str("Transaction has invalid state")),
1272 }
1273}
1274
1275pub(crate) fn has_transaction_witnesses_set_tag(witness_set: &TransactionWitnessSet) -> Option<TransactionSetsState> {
1276 let mut has_tag = false;
1277 let mut has_no_tag = false;
1278
1279 witness_set.bootstraps.as_ref().map(|bs| {
1280 match bs.get_set_type() {
1281 CborSetType::Tagged => has_tag = true,
1282 CborSetType::Untagged => has_no_tag = true,
1283 }
1284 });
1285 witness_set.vkeys.as_ref().map(|vkeys| {
1286 match vkeys.get_set_type() {
1287 CborSetType::Tagged => has_tag = true,
1288 CborSetType::Untagged => has_no_tag = true,
1289 }
1290 });
1291 witness_set.plutus_data.as_ref().map(|plutus_data| {
1292 match plutus_data.get_set_type() {
1293 Some(CborSetType::Tagged) => has_tag = true,
1294 Some(CborSetType::Untagged) => has_no_tag = true,
1295 None => has_tag = true,
1296 }
1297 });
1298 witness_set.native_scripts.as_ref().map(|native_scripts| {
1299 match native_scripts.get_set_type() {
1300 Some(CborSetType::Tagged) => has_tag = true,
1301 Some(CborSetType::Untagged) => has_no_tag = true,
1302 None => has_tag = true,
1303 }
1304 });
1305 witness_set.plutus_scripts.as_ref().map(|plutus_scripts| {
1306 match plutus_scripts.get_set_type(&Language::new_plutus_v1()) {
1307 Some(CborSetType::Tagged) => has_tag = true,
1308 Some(CborSetType::Untagged) => has_no_tag = true,
1309 None => has_tag = true,
1310 }
1311 match plutus_scripts.get_set_type(&Language::new_plutus_v2()) {
1312 Some(CborSetType::Tagged) => has_tag = true,
1313 Some(CborSetType::Untagged) => has_no_tag = true,
1314 None => has_tag = true,
1315 }
1316 match plutus_scripts.get_set_type(&Language::new_plutus_v3()) {
1317 Some(CborSetType::Tagged) => has_tag = true,
1318 Some(CborSetType::Untagged) => has_no_tag = true,
1319 None => has_tag = true,
1320 }
1321 });
1322
1323 match (has_tag, has_no_tag) {
1324 (true, true) => Some(TransactionSetsState::MixedSets),
1325 (true, false) => Some(TransactionSetsState::AllSetsHaveTag),
1326 (false, true) => Some(TransactionSetsState::AllSetsHaveNoTag),
1327 (false, false) => None,
1328 }
1329}