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 {
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, 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 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 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 {
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 {
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 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 let mut bytes = Vec::new();
457 raw.advance(1 + len_sz)?;
458 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 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 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 buf.push(0xA0);
609 if let Some(d) = &datums {
610 buf.extend(d.to_set_bytes());
611 }
612 buf.push(0xA0);
613 } else {
614 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
630pub fn internal_get_implicit_input(
632 withdrawals: &Option<Withdrawals>,
633 certs: &Option<Certificates>,
634 pool_deposit: &BigNum, key_deposit: &BigNum, ) -> 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, key_deposit: &BigNum, ) -> 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, key_deposit: &BigNum, ) -> 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, key_deposit: &BigNum, ) -> 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 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 Self::calc_size_cost(data_cost, output.to_bytes().len())
802 }
803}
804
805#[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#[wasm_bindgen]
816pub enum ScriptSchema {
817 Wallet,
818 Node,
819}
820
821#[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#[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}