1use crate::PolicyId;
2use cbor_event::{de::Deserializer, se::Serializer};
3use cml_core::{
4 error::{DeserializeError, DeserializeFailure, Key},
5 ordered_hash_map::OrderedHashMap,
6 serialization::{fit_sz, CBORReadLen, Deserialize, LenEncoding, Serialize, StringEncoding},
7 ArithmeticError,
8};
9use cml_crypto::{RawBytesEncoding, ScriptHash};
10use std::io::{BufRead, Seek, Write};
11use std::{
12 cmp::PartialOrd,
13 convert::{TryFrom, TryInto},
14};
15
16use std::collections::BTreeMap;
17
18use super::AssetName;
19
20pub type Coin = u64;
21
22pub type NonZeroInt64 = i64;
26
27pub type PositiveCoin = Coin;
31
32#[derive(Debug, thiserror::Error)]
33pub enum AssetArithmeticError {
34 #[error("Arithmetic failed: {0}")]
35 Arithmetic(#[from] ArithmeticError),
36 #[error("Asset {0:?} doesn't exist")]
37 AssetDoesntExist(AssetName),
38 #[error("PolicyId {0:?} doesn't exist")]
39 PolicyIdDoesntExist(PolicyId),
40}
41
42impl TryFrom<&str> for AssetName {
43 type Error = DeserializeError;
44
45 fn try_from(utf8_str: &str) -> Result<Self, Self::Error> {
46 Self::new(utf8_str.as_bytes().to_vec())
47 }
48}
49
50impl<'a> TryInto<&'a str> for &'a AssetName {
51 type Error = std::str::Utf8Error;
52
53 fn try_into(self) -> Result<&'a str, Self::Error> {
54 std::str::from_utf8(self.to_raw_bytes())
55 }
56}
57
58impl RawBytesEncoding for AssetName {
59 fn to_raw_bytes(&self) -> &[u8] {
60 self.inner.as_ref()
61 }
62
63 fn from_raw_bytes(bytes: &[u8]) -> Result<Self, DeserializeError> {
64 Self::new(bytes.to_vec())
65 }
66}
67
68#[derive(
70 Clone, Default, PartialEq, Hash, serde::Deserialize, serde::Serialize, schemars::JsonSchema,
71)]
72pub struct AssetBundle<T>(OrderedHashMap<PolicyId, OrderedHashMap<AssetName, T>>);
73
74impl<T: std::fmt::Debug> std::fmt::Debug for AssetBundle<T> {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
76 let mut ds = f.debug_struct(if self.0.iter().any(|(_, a)| !a.is_empty()) {
77 ""
78 } else {
79 "{}"
80 });
81 for (pid, assets) in self.0.iter() {
82 let pid_hex = hex::encode(pid.to_raw_bytes());
83 for (an, val) in assets.iter() {
84 let an_hex = hex::encode(an.to_raw_bytes());
85 let an_name = if an_hex.len() > 8 {
86 format!(
87 "{}..{}",
88 an_hex.get(0..3).unwrap(),
89 an_hex.get(an_hex.len() - 2..an_hex.len() - 1).unwrap()
90 )
91 } else {
92 an_hex
93 };
94 ds.field(
95 &format!(
96 "{}..{}:{}",
97 pid_hex.get(0..3).unwrap(),
98 pid_hex.get(pid_hex.len() - 4..pid_hex.len() - 1).unwrap(),
99 an_name
100 ),
101 val,
102 );
103 }
104 }
105 ds.finish()
106 }
107}
108
109impl<T: Default> AssetBundle<T> {
110 pub fn new() -> Self {
111 Self::default()
112 }
113}
114
115impl<T> From<OrderedHashMap<PolicyId, OrderedHashMap<AssetName, T>>> for AssetBundle<T> {
116 fn from(bundle: OrderedHashMap<PolicyId, OrderedHashMap<AssetName, T>>) -> Self {
117 Self(bundle)
118 }
119}
120
121impl<T> std::ops::Deref for AssetBundle<T> {
122 type Target = OrderedHashMap<PolicyId, OrderedHashMap<AssetName, T>>;
123
124 fn deref(&self) -> &Self::Target {
125 &self.0
126 }
127}
128
129impl<T> std::ops::DerefMut for AssetBundle<T> {
130 fn deref_mut(&mut self) -> &mut Self::Target {
131 &mut self.0
132 }
133}
134
135pub trait ClampedSub {
137 fn clamped_sub(&self, rhs: &Self) -> Self;
138}
139
140impl<T: num::CheckedSub + num::Bounded + num::Zero + Ord> ClampedSub for T {
141 fn clamped_sub(&self, rhs: &Self) -> Self {
142 self.checked_sub(rhs).unwrap_or_else(|| {
143 if *rhs < T::zero() {
144 T::max_value()
145 } else {
146 T::min_value()
147 }
148 })
149 }
150}
151
152impl<T> ClampedSub for AssetBundle<T>
153where
154 T: num::CheckedAdd + num::CheckedSub + num::Zero + num::Bounded + Copy + Clone + ClampedSub,
155{
156 fn clamped_sub(&self, rhs: &Self) -> Self {
157 let mut bundle = self.clone();
158 for (policy, rhs_assets) in rhs.iter() {
159 for (asset_name, rhs_amount) in rhs_assets.iter() {
160 match bundle.get_mut(policy) {
161 Some(lhs_assets) => match lhs_assets.get_mut(asset_name) {
162 Some(lhs_amount) => match lhs_amount.checked_sub(rhs_amount) {
163 Some(new_lhs_amount) => {
164 if new_lhs_amount.is_zero() {
165 lhs_assets.remove(asset_name);
166 if lhs_assets.is_empty() {
167 bundle.remove(policy);
168 }
169 } else {
170 *lhs_amount = new_lhs_amount;
171 }
172 }
173 None => {
174 if T::min_value().is_zero() {
175 lhs_assets.remove(asset_name);
177 if lhs_assets.is_empty() {
178 bundle.remove(policy);
179 }
180 } else {
181 *lhs_amount = T::min_value();
183 }
184 }
185 },
186 None => {
187 if !T::min_value().is_zero() {
189 bundle.set(
190 *policy,
191 asset_name.clone(),
192 T::zero().clamped_sub(rhs_amount),
193 );
194 }
195 }
196 },
197 None => {
198 if !T::min_value().is_zero() {
200 bundle.set(
201 *policy,
202 asset_name.clone(),
203 T::zero().clamped_sub(rhs_amount),
204 );
205 }
206 }
207 }
208 }
209 }
210 bundle
211 }
212}
213
214impl<T> AssetBundle<T>
215where
216 T: num::CheckedAdd + num::CheckedSub + num::Zero + num::Bounded + Copy + Clone,
217{
218 pub fn set(&mut self, policy_id: PolicyId, asset_name: AssetName, value: T) -> Option<T> {
221 self.0
222 .entry(policy_id)
223 .or_default()
224 .insert(asset_name, value)
225 }
226
227 pub fn get(&self, policy_id: &PolicyId, asset_name: &AssetName) -> Option<T> {
229 self.0
230 .get(policy_id)
231 .and_then(|assets| assets.get(asset_name))
232 .copied()
233 }
234
235 pub fn checked_add(&self, rhs: &Self) -> Result<Self, AssetArithmeticError> {
238 use linked_hash_map::Entry;
239 let mut bundle = self.0.clone();
240 for (policy, assets) in rhs.0.iter() {
241 for (asset_name, amount) in assets.iter() {
242 match bundle.entry(*policy) {
243 Entry::Occupied(mut assets) => {
244 match assets.get_mut().entry(asset_name.clone()) {
245 Entry::Occupied(mut assets2) => {
246 let current = assets2.get_mut();
247 *current = current
248 .checked_add(amount)
249 .ok_or(ArithmeticError::IntegerOverflow)?;
250 }
251 Entry::Vacant(vacant_entry) => {
252 vacant_entry.insert(*amount);
253 }
254 }
255 }
256 Entry::Vacant(entry) => {
257 let mut assets = OrderedHashMap::new();
258 assets.insert(asset_name.clone(), *amount);
259 entry.insert(assets);
260 }
261 }
262 }
263 }
264 Ok(Self(bundle))
265 }
266
267 pub fn checked_sub(&self, rhs: &Self) -> Result<Self, AssetArithmeticError> {
272 let mut bundle = self.0.clone();
273 for (policy, rhs_assets) in rhs.iter() {
274 for (asset_name, rhs_amount) in rhs_assets.iter() {
275 match bundle.get_mut(policy) {
276 Some(lhs_assets) => match lhs_assets.get_mut(asset_name) {
277 Some(lhs_amount) => match lhs_amount.checked_sub(rhs_amount) {
278 Some(new_lhs_amount) => {
279 if new_lhs_amount.is_zero() {
280 lhs_assets.remove(asset_name);
281 if lhs_assets.is_empty() {
282 bundle.remove(policy);
283 }
284 } else {
285 *lhs_amount = new_lhs_amount;
286 }
287 }
288 None => {
289 return Err(ArithmeticError::IntegerUnderflow.into());
291 }
292 },
293 None => {
294 return Err(AssetArithmeticError::AssetDoesntExist(asset_name.clone()));
296 }
297 },
298 None => {
299 return Err(AssetArithmeticError::PolicyIdDoesntExist(*policy));
301 }
302 }
303 }
304 }
305 Ok(Self(bundle))
306 }
307}
308
309pub type Mint = AssetBundle<NonZeroInt64>;
310
311pub type MultiAsset = AssetBundle<PositiveCoin>;
312
313impl Mint {
314 fn as_multiasset(&self, is_positive: bool) -> MultiAsset {
315 self.0
316 .iter()
317 .fold(MultiAsset::default(), |mut acc, (policy, assets)| {
318 let new_assets =
319 assets
320 .iter()
321 .fold(OrderedHashMap::new(), |mut acc, (asset, value)| {
322 if (*value >= 0) == is_positive {
323 acc.insert(asset.clone(), value.unsigned_abs());
324 }
325 acc
326 });
327 if !assets.is_empty() {
328 acc.insert(*policy, new_assets);
329 }
330 acc
331 })
332 }
333
334 pub fn as_positive_multiasset(&self) -> MultiAsset {
336 self.as_multiasset(true)
337 }
338
339 pub fn as_negative_multiasset(&self) -> MultiAsset {
341 self.as_multiasset(false)
342 }
343}
344
345#[derive(
349 Clone, Debug, derivative::Derivative, serde::Deserialize, serde::Serialize, schemars::JsonSchema,
350)]
351#[derivative(Hash, Eq, PartialEq, PartialOrd)]
352pub struct Value {
353 pub coin: PositiveCoin,
354 pub multiasset: MultiAsset,
355 #[serde(skip)]
356 #[derivative(Hash = "ignore", PartialEq = "ignore", PartialOrd = "ignore")]
357 pub encodings: Option<ValueEncoding>,
358}
359
360impl Value {
361 pub fn new(coin: PositiveCoin, multiasset: MultiAsset) -> Self {
362 Self {
363 coin,
364 multiasset,
365 encodings: None,
366 }
367 }
368
369 pub fn zero() -> Value {
370 0u64.into()
371 }
372
373 pub fn is_zero(&self) -> bool {
374 self.coin == 0 && !self.has_multiassets()
375 }
376
377 pub fn has_multiassets(&self) -> bool {
378 self.multiasset
379 .values()
380 .any(|assets| assets.values().any(|amount| *amount != 0))
381 }
382
383 pub fn checked_add(&self, rhs: &Value) -> Result<Self, AssetArithmeticError> {
384 let coin = self
385 .coin
386 .checked_add(rhs.coin)
387 .ok_or(ArithmeticError::IntegerOverflow)?;
388 let multiasset = self.multiasset.checked_add(&rhs.multiasset)?;
389 Ok(Value {
390 coin,
391 multiasset,
392 encodings: None,
393 })
394 }
395
396 pub fn checked_sub(&self, rhs: &Value) -> Result<Self, AssetArithmeticError> {
401 let coin = self
402 .coin
403 .checked_sub(rhs.coin)
404 .ok_or(ArithmeticError::IntegerUnderflow)?;
405 let multiasset = self.multiasset.checked_sub(&rhs.multiasset)?;
406 Ok(Value {
407 coin,
408 multiasset,
409 encodings: None,
410 })
411 }
412
413 pub fn clamped_sub(&self, rhs: &Value) -> Value {
414 let coin = self.coin.clamped_sub(&rhs.coin);
415 let multiasset = self.multiasset.clamped_sub(&rhs.multiasset);
416 Value {
417 coin,
418 multiasset,
419 encodings: None,
420 }
421 }
422}
423
424impl<T> PartialOrd for AssetBundle<T>
431where
432 T: num::CheckedAdd + num::CheckedSub + num::Zero + num::Bounded + Copy + Clone + PartialOrd,
433{
434 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
435 fn is_all_zeros<T>(lhs: &AssetBundle<T>, rhs: &AssetBundle<T>) -> bool
437 where
438 T: num::CheckedAdd
439 + num::CheckedSub
440 + num::Zero
441 + num::Bounded
442 + Copy
443 + Clone
444 + PartialOrd,
445 {
446 for (pid, assets) in lhs.0.iter() {
447 for (aname, amount) in assets.iter() {
448 match amount
449 .checked_sub(&rhs.get(pid, aname).unwrap_or(T::zero()))
450 .and_then(|o| o.partial_cmp(&T::zero()))
451 {
452 Some(std::cmp::Ordering::Equal) => (),
453 _ => return false,
454 }
455 }
456 }
457 true
458 }
459
460 match (is_all_zeros(self, other), is_all_zeros(other, self)) {
461 (true, true) => Some(std::cmp::Ordering::Equal),
462 (true, false) => Some(std::cmp::Ordering::Less),
463 (false, true) => Some(std::cmp::Ordering::Greater),
464 (false, false) => None,
465 }
466 }
467}
468
469impl From<PositiveCoin> for Value {
470 fn from(coin: PositiveCoin) -> Self {
471 Self {
472 coin,
473 multiasset: AssetBundle::default(),
474 encodings: None,
475 }
476 }
477}
478
479impl From<MultiAsset> for Value {
480 fn from(multiasset: MultiAsset) -> Self {
481 Self {
482 coin: 0,
483 multiasset,
484 encodings: None,
485 }
486 }
487}
488
489impl Serialize for Value {
490 fn serialize<'se, W: Write>(
491 &self,
492 serializer: &'se mut Serializer<W>,
493 force_canonical: bool,
494 ) -> cbor_event::Result<&'se mut Serializer<W>> {
495 if self.multiasset.is_empty()
496 && self
497 .encodings
498 .as_ref()
499 .map(|encs| !encs.use_multiasset_format)
500 .unwrap_or(true)
501 {
502 serializer.write_unsigned_integer_sz(
504 self.coin,
505 fit_sz(
506 self.coin,
507 self.encodings
508 .as_ref()
509 .map(|encs| encs.coin_encoding)
510 .unwrap_or_default(),
511 force_canonical,
512 ),
513 )
514 } else {
515 serializer.write_array_sz(
517 self.encodings
518 .as_ref()
519 .map(|encs| encs.len_encoding)
520 .unwrap_or_default()
521 .to_len_sz(2, force_canonical),
522 )?;
523 serializer.write_unsigned_integer_sz(
524 self.coin,
525 fit_sz(
526 self.coin,
527 self.encodings
528 .as_ref()
529 .map(|encs| encs.coin_encoding)
530 .unwrap_or_default(),
531 force_canonical,
532 ),
533 )?;
534 serializer.write_map_sz(
535 self.encodings
536 .as_ref()
537 .map(|encs| encs.multiasset_encoding)
538 .unwrap_or_default()
539 .to_len_sz(self.multiasset.len() as u64, force_canonical),
540 )?;
541 let mut key_order = self
542 .multiasset
543 .iter()
544 .map(|(k, v)| {
545 let mut buf = cbor_event::se::Serializer::new_vec();
546 let multiasset_key_encoding = self
547 .encodings
548 .as_ref()
549 .and_then(|encs| encs.multiasset_key_encodings.get(k))
550 .cloned()
551 .unwrap_or_default();
552 buf.write_bytes_sz(
553 k.to_raw_bytes(),
554 multiasset_key_encoding
555 .to_str_len_sz(k.to_raw_bytes().len() as u64, force_canonical),
556 )?;
557 Ok((buf.finalize(), k, v))
558 })
559 .collect::<Result<Vec<(Vec<u8>, &_, &_)>, cbor_event::Error>>()?;
560 if force_canonical {
561 key_order.sort_by(|(lhs_bytes, _, _), (rhs_bytes, _, _)| {
562 match lhs_bytes.len().cmp(&rhs_bytes.len()) {
563 std::cmp::Ordering::Equal => lhs_bytes.cmp(rhs_bytes),
564 diff_ord => diff_ord,
565 }
566 });
567 }
568 for (key_bytes, key, value) in key_order {
569 serializer.write_raw_bytes(&key_bytes)?;
570 let (multiasset_value_encoding, multiasset_value_value_encodings) = self
571 .encodings
572 .as_ref()
573 .and_then(|encs| encs.multiasset_value_encodings.get(key))
574 .cloned()
575 .unwrap_or_else(|| (LenEncoding::default(), BTreeMap::new()));
576 serializer.write_map_sz(
577 multiasset_value_encoding.to_len_sz(value.len() as u64, force_canonical),
578 )?;
579 let mut key_order = value
580 .iter()
581 .map(|(k, v)| {
582 let mut buf = cbor_event::se::Serializer::new_vec();
583 k.serialize(&mut buf, force_canonical)?;
584 Ok((buf.finalize(), k, v))
585 })
586 .collect::<Result<Vec<(Vec<u8>, &_, &_)>, cbor_event::Error>>()?;
587 if force_canonical {
588 key_order.sort_by(|(lhs_bytes, _, _), (rhs_bytes, _, _)| {
589 match lhs_bytes.len().cmp(&rhs_bytes.len()) {
590 std::cmp::Ordering::Equal => lhs_bytes.cmp(rhs_bytes),
591 diff_ord => diff_ord,
592 }
593 });
594 }
595 for (key_bytes, key, value) in key_order {
596 serializer.write_raw_bytes(&key_bytes)?;
597 let multiasset_value_value_encoding = multiasset_value_value_encodings
598 .get(key)
599 .cloned()
600 .unwrap_or_default();
601 serializer.write_unsigned_integer_sz(
602 *value,
603 fit_sz(*value, multiasset_value_value_encoding, force_canonical),
604 )?;
605 }
606 multiasset_value_encoding.end(serializer, force_canonical)?;
607 }
608 self.encodings
609 .as_ref()
610 .map(|encs| encs.multiasset_encoding)
611 .unwrap_or_default()
612 .end(serializer, force_canonical)?;
613 self.encodings
614 .as_ref()
615 .map(|encs| encs.len_encoding)
616 .unwrap_or_default()
617 .end(serializer, force_canonical)
618 }
619 }
620}
621
622impl Deserialize for Value {
623 fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
624 (|| -> Result<_, DeserializeError> {
625 match raw.cbor_type()? {
626 cbor_event::Type::UnsignedInteger => {
628 let (coin, coin_encoding) = raw
629 .unsigned_integer_sz()
630 .map(|(x, enc)| (x, Some(enc)))
631 .map_err(Into::<DeserializeError>::into)
632 .map_err(|e: DeserializeError| e.annotate("coin"))?;
633 Ok(Value {
634 coin,
635 multiasset: AssetBundle::default(),
636 encodings: Some(ValueEncoding {
637 len_encoding: LenEncoding::default(),
638 coin_encoding,
639 multiasset_encoding: LenEncoding::default(),
640 multiasset_key_encodings: BTreeMap::default(),
641 multiasset_value_encodings: BTreeMap::default(),
642 use_multiasset_format: false,
643 }),
644 })
645 }
646 cbor_event::Type::Array => {
648 let len = raw.array_sz()?;
649 let len_encoding: LenEncoding = len.into();
650 let mut read_len = CBORReadLen::new(len);
651 read_len.read_elems(2)?;
652 let (coin, coin_encoding) = raw
653 .unsigned_integer_sz()
654 .map(|(x, enc)| (x, Some(enc)))
655 .map_err(Into::<DeserializeError>::into)
656 .map_err(|e: DeserializeError| e.annotate("coin"))?;
657 let (
658 multiasset,
659 multiasset_encoding,
660 multiasset_key_encodings,
661 multiasset_value_encodings,
662 ) = (|| -> Result<_, DeserializeError> {
663 let mut multiasset_table = OrderedHashMap::new();
664 let multiasset_len = raw.map_sz()?;
665 let multiasset_encoding = multiasset_len.into();
666 let mut multiasset_key_encodings = BTreeMap::new();
667 let mut multiasset_value_encodings = BTreeMap::new();
668 while match multiasset_len {
669 cbor_event::LenSz::Len(n, _) => (multiasset_table.len() as u64) < n,
670 cbor_event::LenSz::Indefinite => true,
671 } {
672 if raw.cbor_type()? == cbor_event::Type::Special {
673 assert_eq!(raw.special()?, cbor_event::Special::Break);
674 break;
675 }
676 let (multiasset_key, multiasset_key_encoding) = raw
677 .bytes_sz()
678 .map_err(Into::<DeserializeError>::into)
679 .and_then(|(bytes, enc)| {
680 ScriptHash::from_raw_bytes(&bytes)
681 .map(|bytes| (bytes, StringEncoding::from(enc)))
682 .map_err(|e| {
683 DeserializeFailure::InvalidStructure(Box::new(e)).into()
684 })
685 })?;
686 let mut multiasset_value_table = OrderedHashMap::new();
687 let multiasset_value_len = raw.map_sz()?;
688 let multiasset_value_encoding = multiasset_value_len.into();
689 let mut multiasset_value_value_encodings = BTreeMap::new();
690 while match multiasset_value_len {
691 cbor_event::LenSz::Len(n, _) => {
692 (multiasset_value_table.len() as u64) < n
693 }
694 cbor_event::LenSz::Indefinite => true,
695 } {
696 if raw.cbor_type()? == cbor_event::Type::Special {
697 assert_eq!(raw.special()?, cbor_event::Special::Break);
698 break;
699 }
700 let multiasset_value_key = AssetName::deserialize(raw)?;
701 let (multiasset_value_value, multiasset_value_value_encoding) =
702 raw.unsigned_integer_sz().map(|(x, enc)| (x, Some(enc)))?;
703 if multiasset_value_table
704 .insert(multiasset_value_key.clone(), multiasset_value_value)
705 .is_some()
706 {
707 return Err(DeserializeFailure::DuplicateKey(Key::Str(
708 String::from("some complicated/unsupported type"),
709 ))
710 .into());
711 }
712 multiasset_value_value_encodings
713 .insert(multiasset_value_key, multiasset_value_value_encoding);
714 }
715 let (
716 multiasset_value,
717 multiasset_value_encoding,
718 multiasset_value_value_encodings,
719 ) = (
720 multiasset_value_table,
721 multiasset_value_encoding,
722 multiasset_value_value_encodings,
723 );
724 if multiasset_table
725 .insert(multiasset_key, multiasset_value)
726 .is_some()
727 {
728 return Err(DeserializeFailure::DuplicateKey(Key::Str(
729 String::from("some complicated/unsupported type"),
730 ))
731 .into());
732 }
733 multiasset_key_encodings
734 .insert(multiasset_key, multiasset_key_encoding);
735 multiasset_value_encodings.insert(
736 multiasset_key,
737 (multiasset_value_encoding, multiasset_value_value_encodings),
738 );
739 }
740 Ok((
741 multiasset_table,
742 multiasset_encoding,
743 multiasset_key_encodings,
744 multiasset_value_encodings,
745 ))
746 })()
747 .map_err(|e| e.annotate("multiasset"))?;
748 match len {
749 cbor_event::LenSz::Len(_, _) => (),
750 cbor_event::LenSz::Indefinite => match raw.special()? {
751 cbor_event::Special::Break => (),
752 _ => return Err(DeserializeFailure::EndingBreakMissing.into()),
753 },
754 }
755 Ok(Value {
756 coin,
757 multiasset: multiasset.into(),
758 encodings: Some(ValueEncoding {
759 len_encoding,
760 coin_encoding,
761 multiasset_encoding,
762 multiasset_key_encodings,
763 multiasset_value_encodings,
764 use_multiasset_format: true,
765 }),
766 })
767 }
768 _ => Err(DeserializeFailure::NoVariantMatched.into()),
769 }
770 })()
771 .map_err(|e| e.annotate("Value"))
772 }
773}
774
775#[derive(Clone, Debug, Default)]
776pub struct ValueEncoding {
777 pub len_encoding: LenEncoding,
778 pub coin_encoding: Option<cbor_event::Sz>,
779 pub multiasset_encoding: LenEncoding,
780 pub multiasset_key_encodings: BTreeMap<ScriptHash, StringEncoding>,
781 pub multiasset_value_encodings:
782 BTreeMap<ScriptHash, (LenEncoding, BTreeMap<AssetName, Option<cbor_event::Sz>>)>,
783 pub use_multiasset_format: bool,
786}