charms_data/
lib.rs

1use anyhow::{Result, anyhow, ensure};
2use ark_std::{
3    cmp::Ordering,
4    collections::BTreeMap,
5    format,
6    string::{String, ToString},
7    vec::Vec,
8};
9use ciborium::Value;
10use core::{convert::TryInto, fmt};
11use serde::{
12    Deserialize, Deserializer, Serialize, Serializer, de,
13    de::{DeserializeOwned, SeqAccess, Visitor},
14    ser::SerializeTuple,
15};
16pub mod util;
17
18/// Macro to check a condition and return false (early) if it does not hold.
19/// This is useful for checking pre-requisite conditions in predicate-type functions.
20/// Inspired by the `ensure!` macro from the `anyhow` crate.
21/// The function must return a boolean.
22/// Example:
23/// ```rust
24/// use charms_data::check;
25///
26/// fn b_is_multiple_of_a(a: u32, b: u32) -> bool {
27///     check!(a <= b && a != 0);    // returns false early if `a` is greater than `b` or `a` is zero
28///     match b % a {
29///         0 => true,
30///         _ => false,
31///     }
32/// }
33#[macro_export]
34macro_rules! check {
35    ($condition:expr) => {
36        if !$condition {
37            eprintln!("condition does not hold: {}", stringify!($condition));
38            return false;
39        }
40    };
41}
42
43/// Represents a transaction involving Charms.
44/// A Charms transaction sits on top of a Bitcoin transaction. Therefore, it transforms a set of
45/// input UTXOs into a set of output UTXOs.
46/// A Charms transaction may also reference other valid UTXOs that are not being spent or created.
47#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
48pub struct Transaction {
49    /// Input UTXOs.
50    pub ins: Vec<(UtxoId, Charms)>,
51    /// Reference UTXOs.
52    pub refs: Vec<(UtxoId, Charms)>,
53    /// Output charms.
54    pub outs: Vec<Charms>,
55    /// Amounts of native coin in inputs
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub coin_ins: Option<Vec<NativeOutput>>,
58    /// Amounts of native coin in outputs
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub coin_outs: Option<Vec<NativeOutput>>,
61    /// Previous transactions (creating outputs spent by this transaction) by transaction ID.
62    pub prev_txs: BTreeMap<TxId, Data>,
63    /// All apps used in this transaction with their public inputs.
64    pub app_public_inputs: BTreeMap<App, Data>,
65}
66
67#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
68pub struct NativeOutput {
69    pub amount: u64,
70    pub dest: Vec<u8>,
71}
72
73/// Charms are tokens, NFTs or instances of arbitrary app state.
74/// This type alias represents a collection of charms.
75/// Structurally it is a map of `app -> data`.
76pub type Charms = BTreeMap<App, Data>;
77
78/// ID of a UTXO (Unspent Transaction Output) in the underlying ledger system (e.g. Bitcoin).
79/// A UTXO ID is a pair of `(transaction ID, index of the output)`.
80#[cfg_attr(test, derive(test_strategy::Arbitrary))]
81#[derive(Clone, Default, Eq, Ord, PartialEq, PartialOrd)]
82pub struct UtxoId(pub TxId, pub u32);
83
84impl UtxoId {
85    /// Convert to a byte array (of 36 bytes).
86    pub fn to_bytes(&self) -> [u8; 36] {
87        let mut bytes = [0u8; 36];
88        bytes[..32].copy_from_slice(&self.0.0); // Copy TxId
89        bytes[32..].copy_from_slice(&self.1.to_le_bytes()); // Copy index as little-endian
90        bytes
91    }
92
93    /// Create `UtxoId` from a byte array (of 36 bytes).
94    pub fn from_bytes(bytes: [u8; 36]) -> Self {
95        let mut txid_bytes = [0u8; 32];
96        txid_bytes.copy_from_slice(&bytes[..32]);
97        let index = u32::from_le_bytes(bytes[32..].try_into().expect("exactly 4 bytes expected"));
98        UtxoId(TxId(txid_bytes), index)
99    }
100
101    /// Try to create `UtxoId` from a string in the format `txid_hex:index`.
102    /// Example:
103    /// ```
104    /// use charms_data::UtxoId;
105    /// let utxo_id = UtxoId::from_str("92077a14998b31367efeec5203a00f1080facdb270cbf055f09b66ae0a273c7d:3").unwrap();
106    /// ```
107    pub fn from_str(s: &str) -> Result<Self> {
108        let parts: Vec<&str> = s.split(':').collect();
109        if parts.len() != 2 {
110            return Err(anyhow!("expected format: txid_hex:index"));
111        }
112
113        let txid = TxId::from_str(parts[0])?;
114
115        let index = parts[1]
116            .parse::<u32>()
117            .map_err(|e| anyhow!("invalid index: {}", e))?;
118
119        Ok(UtxoId(txid, index))
120    }
121
122    fn to_string_internal(&self) -> String {
123        format!("{}:{}", self.0.to_string(), self.1)
124    }
125}
126
127impl fmt::Display for UtxoId {
128    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129        self.to_string_internal().fmt(f)
130    }
131}
132
133impl fmt::Debug for UtxoId {
134    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135        write!(f, "UtxoId({})", self.to_string_internal())
136    }
137}
138
139impl Serialize for UtxoId {
140    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
141    where
142        S: Serializer,
143    {
144        if serializer.is_human_readable() {
145            serializer.serialize_str(&self.to_string())
146        } else {
147            serializer.serialize_bytes(self.to_bytes().as_ref())
148        }
149    }
150}
151
152impl<'de> Deserialize<'de> for UtxoId {
153    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
154    where
155        D: Deserializer<'de>,
156    {
157        struct UtxoIdVisitor;
158
159        impl<'de> Visitor<'de> for UtxoIdVisitor {
160            type Value = UtxoId;
161
162            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
163                formatter.write_str("a string in format 'txid_hex:index' or a tuple (TxId, u32)")
164            }
165
166            // Handle human-readable format ("txid_hex:index")
167            fn visit_str<E>(self, value: &str) -> Result<UtxoId, E>
168            where
169                E: de::Error,
170            {
171                UtxoId::from_str(value).map_err(E::custom)
172            }
173
174            // Handle non-human-readable byte format [u8; 36]
175            fn visit_bytes<E>(self, v: &[u8]) -> core::result::Result<Self::Value, E>
176            where
177                E: de::Error,
178            {
179                Ok(UtxoId::from_bytes(v.try_into().map_err(|e| {
180                    E::custom(format!("invalid utxo_id bytes: {}", e))
181                })?))
182            }
183        }
184
185        if deserializer.is_human_readable() {
186            deserializer.deserialize_str(UtxoIdVisitor)
187        } else {
188            deserializer.deserialize_bytes(UtxoIdVisitor)
189        }
190    }
191}
192
193/// App represents an application that can be used to create, transform or destroy charms (tokens,
194/// NFTs and other instances of app data).
195///
196/// An app is identified by a single character `tag`, a 32-byte `identity` and a 32-byte `vk`
197/// (verification key).
198/// The `tag` is a single character that represents the type of the app, with two special values:
199/// - `TOKEN` (tag `t`) for tokens,
200/// - `NFT` (tag `n`) for NFTs.
201///
202/// Other values of `tag` are perfectly legal. The above ones are special: tokens and NFTs can be
203/// transferred without providing the app's implementation (RISC-V binary).
204///
205/// The `vk` is a 32-byte byte string (hash) that is used to verify proofs that the app's contract
206/// is satisfied (against the certain transaction, additional public input and private input).
207///
208/// The `identity` is a 32-byte byte string (hash) that uniquely identifies the app among other apps
209/// implemented using the same code.
210#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
211#[derive(Clone, Default, Eq, Ord, PartialEq, PartialOrd)]
212pub struct App {
213    pub tag: char,
214    pub identity: B32,
215    pub vk: B32,
216}
217
218impl fmt::Display for App {
219    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
220        write!(f, "{}/{}/{}", self.tag, self.identity, self.vk)
221    }
222}
223
224impl fmt::Debug for App {
225    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
226        write!(f, "App({}/{}/{})", self.tag, self.identity, self.vk)
227    }
228}
229
230impl Serialize for App {
231    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
232    where
233        S: Serializer,
234    {
235        if serializer.is_human_readable() {
236            serializer.serialize_str(&self.to_string())
237        } else {
238            let mut s = serializer.serialize_tuple(3)?;
239            s.serialize_element(&self.tag)?;
240            s.serialize_element(&self.identity)?;
241            s.serialize_element(&self.vk)?;
242            s.end()
243        }
244    }
245}
246
247impl<'de> Deserialize<'de> for App {
248    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
249    where
250        D: Deserializer<'de>,
251    {
252        struct AppVisitor;
253
254        impl<'de> Visitor<'de> for AppVisitor {
255            type Value = App;
256
257            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
258                formatter.write_str("a string in format 'tag_char/identity_hex/vk_hex' or a struct with tag, identity and vk fields")
259            }
260
261            // Handle human-readable format ("tag_char/identity_hex/vk_hex")
262            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
263            where
264                E: de::Error,
265            {
266                // Split the string at '/'
267                let mut parts: Vec<&str> = value.split('/').collect();
268                if parts[0].is_empty() && parts[1].is_empty() {
269                    parts.remove(0);
270                    parts[0] = "/";
271                }
272                let parts = parts;
273                if parts.len() != 3 {
274                    return Err(E::custom("expected format: tag_char/identity_hex/vk_hex"));
275                }
276
277                // Decode the hex strings
278                let tag: char = {
279                    let mut chars = parts[0].chars();
280                    let Some(tag) = chars.next() else {
281                        return Err(E::custom("expected tag"));
282                    };
283                    let None = chars.next() else {
284                        return Err(E::custom("tag must be a single character"));
285                    };
286                    tag
287                };
288
289                let identity = B32::from_str(parts[1]).map_err(E::custom)?;
290
291                let vk = B32::from_str(parts[2]).map_err(E::custom)?;
292
293                Ok(App { tag, identity, vk })
294            }
295
296            fn visit_seq<A>(self, mut seq: A) -> core::result::Result<Self::Value, A::Error>
297            where
298                A: SeqAccess<'de>,
299            {
300                let tag = seq
301                    .next_element()?
302                    .ok_or_else(|| de::Error::missing_field("tag"))?;
303                let identity = seq
304                    .next_element()?
305                    .ok_or_else(|| de::Error::missing_field("identity"))?;
306                let vk = seq
307                    .next_element()?
308                    .ok_or_else(|| de::Error::missing_field("vk"))?;
309
310                Ok(App { tag, identity, vk })
311            }
312        }
313
314        if deserializer.is_human_readable() {
315            deserializer.deserialize_str(AppVisitor)
316        } else {
317            deserializer.deserialize_tuple(3, AppVisitor)
318        }
319    }
320}
321
322/// ID (hash) of a transaction in the underlying ledger (Bitcoin).
323#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
324#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
325pub struct TxId(pub [u8; 32]);
326
327impl TxId {
328    /// Try to create `TxId` from a string of 64 hex characters.
329    /// Note that string representation of transaction IDs in Bitcoin is reversed, and so is ours
330    /// (for compatibility).
331    ///
332    /// Example:
333    /// ```
334    /// use charms_data::TxId;
335    /// let tx_id = TxId::from_str("92077a14998b31367efeec5203a00f1080facdb270cbf055f09b66ae0a273c7d").unwrap();
336    /// ```
337    pub fn from_str(s: &str) -> Result<Self> {
338        ensure!(s.len() == 64, "expected 64 hex characters");
339        let bytes = hex::decode(s).map_err(|e| anyhow!("invalid txid hex: {}", e))?;
340        let mut txid: [u8; 32] = bytes.try_into().expect("exactly 32 bytes expected");
341        txid.reverse();
342        Ok(TxId(txid))
343    }
344
345    fn to_string_internal(&self) -> String {
346        let mut txid = self.0;
347        txid.reverse();
348        hex::encode(&txid)
349    }
350}
351
352impl fmt::Display for TxId {
353    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
354        self.to_string_internal().fmt(f)
355    }
356}
357
358impl fmt::Debug for TxId {
359    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
360        write!(f, "TxId({})", self.to_string_internal())
361    }
362}
363
364impl Serialize for TxId {
365    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
366    where
367        S: Serializer,
368    {
369        if serializer.is_human_readable() {
370            serializer.serialize_str(&self.to_string())
371        } else {
372            serializer.serialize_bytes(&self.0)
373        }
374    }
375}
376
377impl<'de> Deserialize<'de> for TxId {
378    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
379    where
380        D: Deserializer<'de>,
381    {
382        struct TxIdVisitor;
383
384        impl<'de> Visitor<'de> for TxIdVisitor {
385            type Value = TxId;
386
387            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
388                formatter.write_str("a string of 64 hex characters or a byte array of 32 bytes")
389            }
390
391            // Handle human-readable format ("txid_hex")
392            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
393            where
394                E: de::Error,
395            {
396                TxId::from_str(value).map_err(E::custom)
397            }
398
399            // Handle non-human-readable byte format [u8; 32]
400            fn visit_bytes<E>(self, v: &[u8]) -> core::result::Result<Self::Value, E>
401            where
402                E: de::Error,
403            {
404                Ok(TxId(v.try_into().map_err(|e| {
405                    E::custom(format!("invalid txid bytes: {}", e))
406                })?))
407            }
408        }
409
410        if deserializer.is_human_readable() {
411            deserializer.deserialize_str(TxIdVisitor)
412        } else {
413            deserializer.deserialize_bytes(TxIdVisitor)
414        }
415    }
416}
417
418/// 32-byte byte string (e.g. a hash, like SHA256).
419#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
420#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
421pub struct B32(pub [u8; 32]);
422
423impl B32 {
424    /// Try to create `B32` from a string of 64 hex characters.
425    pub fn from_str(s: &str) -> Result<Self> {
426        ensure!(s.len() == 64, "expected 64 hex characters");
427        let bytes = hex::decode(s).map_err(|e| anyhow!("invalid hex: {}", e))?;
428        let hash: [u8; 32] = bytes.try_into().expect("exactly 32 bytes expected");
429        Ok(B32(hash))
430    }
431}
432
433impl AsRef<[u8]> for B32 {
434    fn as_ref(&self) -> &[u8] {
435        &self.0
436    }
437}
438
439impl fmt::Display for B32 {
440    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
441        hex::encode(&self.0).fmt(f)
442    }
443}
444
445impl fmt::Debug for B32 {
446    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
447        write!(f, "Bytes32({})", hex::encode(&self.0))
448    }
449}
450
451impl Serialize for B32 {
452    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
453    where
454        S: Serializer,
455    {
456        if serializer.is_human_readable() {
457            serializer.serialize_str(&self.to_string())
458        } else {
459            let mut seq = serializer.serialize_tuple(32)?;
460            for &byte in &self.0 {
461                seq.serialize_element(&byte)?;
462            }
463            seq.end()
464        }
465    }
466}
467
468impl<'de> Deserialize<'de> for B32 {
469    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
470    where
471        D: Deserializer<'de>,
472    {
473        struct B32Visitor;
474
475        impl<'de> Visitor<'de> for B32Visitor {
476            type Value = B32;
477
478            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
479                formatter.write_str("a string of 64 hex characters or a sequence of 32 bytes")
480            }
481
482            // Handle human-readable format ("hex")
483            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
484            where
485                E: de::Error,
486            {
487                B32::from_str(value).map_err(E::custom)
488            }
489
490            // Handle non-human-readable byte format [u8; 32]
491            fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
492            where
493                A: SeqAccess<'de>,
494            {
495                let mut bytes = [0u8; 32];
496                for i in 0..32 {
497                    bytes[i] = seq
498                        .next_element()?
499                        .ok_or_else(|| serde::de::Error::invalid_length(i, &"32 elements"))?;
500                }
501
502                // Check if there are extra elements
503                if seq.next_element::<u8>()?.is_some() {
504                    return Err(serde::de::Error::invalid_length(33, &"exactly 32 elements"));
505                }
506
507                Ok(B32(bytes))
508            }
509        }
510
511        if deserializer.is_human_readable() {
512            deserializer.deserialize_str(B32Visitor)
513        } else {
514            deserializer.deserialize_tuple(32, B32Visitor)
515        }
516    }
517}
518
519/// Represents a data value that is guaranteed to be serialized/deserialized to/from CBOR.
520#[derive(Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
521pub struct Data(Value);
522
523impl Eq for Data {}
524
525impl Ord for Data {
526    fn cmp(&self, other: &Self) -> Ordering {
527        self.0
528            .partial_cmp(&other.0)
529            .expect("Value comparison should have succeeded") // PANIC: will panic if CBOR Value comparison returns None (NaN or incomparable). We expect this to never happen (famous last words).
530    }
531}
532
533impl Data {
534    /// Create an empty data value.
535    pub fn empty() -> Self {
536        Self(Value::Null)
537    }
538
539    /// Check if the data value is empty.
540    pub fn is_empty(&self) -> bool {
541        self.0.is_null()
542    }
543
544    /// Try to cast to a value of a deserializable type (implementing
545    /// `serde::de::DeserializeOwned`).
546    pub fn value<T: DeserializeOwned>(&self) -> Result<T> {
547        self.0
548            .deserialized()
549            .map_err(|e| anyhow!("deserialization error: {}", e))
550    }
551
552    /// Serialize to bytes.
553    pub fn bytes(&self) -> Vec<u8> {
554        util::write(&self).expect("serialization is expected to succeed")
555    }
556}
557
558impl<T> From<&T> for Data
559where
560    T: Serialize,
561{
562    fn from(value: &T) -> Self {
563        Self(Value::serialized(value).expect("casting to a CBOR Value is expected to succeed"))
564    }
565}
566
567impl Default for Data {
568    fn default() -> Self {
569        Self::empty()
570    }
571}
572
573impl fmt::Debug for Data {
574    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
575        write!(f, "Data({})", format!("{:?}", &self.0))
576    }
577}
578
579/// Special `App.tag` value for fungible tokens. See [`App`] for more details.
580pub const TOKEN: char = 't';
581/// Special `App.tag` value for non-fungible tokens (NFTs). See [`App`] for more details.
582pub const NFT: char = 'n';
583
584/// Check if the transaction is a simple transfer of assets specified by `app`.
585pub fn is_simple_transfer(app: &App, tx: &Transaction) -> bool {
586    match app.tag {
587        TOKEN => token_amounts_balanced(app, tx),
588        NFT => nft_state_preserved(app, tx),
589        _ => false,
590    }
591}
592
593/// Check if the provided app's token amounts are balanced in the transaction. This means that the
594/// sum of the token amounts in the `tx` inputs is equal to the sum of the token amounts in the `tx`
595/// outputs.
596pub fn token_amounts_balanced(app: &App, tx: &Transaction) -> bool {
597    match (
598        sum_token_amount(app, tx.ins.iter().map(|(_, v)| v)),
599        sum_token_amount(app, tx.outs.iter()),
600    ) {
601        (Ok(amount_in), Ok(amount_out)) => amount_in == amount_out,
602        (..) => false,
603    }
604}
605
606/// Check if the NFT states are preserved in the transaction. This means that the NFTs (created by
607/// the provided `app`) in the `tx` inputs are the same as the NFTs in the `tx` outputs.
608pub fn nft_state_preserved(app: &App, tx: &Transaction) -> bool {
609    let nft_states_in = app_state_multiset(app, tx.ins.iter().map(|(_, v)| v));
610    let nft_states_out = app_state_multiset(app, tx.outs.iter());
611
612    nft_states_in == nft_states_out
613}
614
615/// Deprecated. Use [charm_values] instead.
616#[deprecated(since = "0.7.0", note = "use `charm_values` instead")]
617pub fn app_datas<'a>(
618    app: &'a App,
619    strings_of_charms: impl Iterator<Item = &'a Charms>,
620) -> impl Iterator<Item = &'a Data> {
621    charm_values(app, strings_of_charms)
622}
623
624/// Iterate over all charm values of a given app (charm key) in the given outputs
625/// (strings of charms).
626pub fn charm_values<'a>(
627    app: &'a App,
628    strings_of_charms: impl Iterator<Item = &'a Charms>,
629) -> impl Iterator<Item = &'a Data> {
630    strings_of_charms.filter_map(|charms| charms.get(app))
631}
632
633fn app_state_multiset<'a>(
634    app: &App,
635    strings_of_charms: impl Iterator<Item = &'a Charms>,
636) -> BTreeMap<&'a Data, usize> {
637    strings_of_charms
638        .filter_map(|charms| charms.get(app))
639        .fold(BTreeMap::new(), |mut r, s| {
640            match r.get_mut(s) {
641                Some(count) => *count += 1,
642                None => {
643                    r.insert(s, 1);
644                }
645            }
646            r
647        })
648}
649
650/// Sum the token amounts in the provided `strings_of_charms`.
651pub fn sum_token_amount<'a>(
652    app: &App,
653    strings_of_charms: impl Iterator<Item = &'a Charms>,
654) -> Result<u64> {
655    ensure!(app.tag == TOKEN);
656    strings_of_charms.fold(Ok(0u64), |amount, charms| match charms.get(app) {
657        Some(state) => Ok(amount? + state.value::<u64>()?),
658        None => amount,
659    })
660}
661
662#[cfg(test)]
663mod tests {
664    use super::*;
665    use ciborium::Value;
666    use proptest::prelude::*;
667    use test_strategy::proptest;
668
669    #[proptest]
670    fn doesnt_crash(s: String) {
671        let _ = TxId::from_str(&s);
672    }
673
674    #[proptest]
675    fn txid_roundtrip(txid: TxId) {
676        let s = txid.to_string();
677        let txid2 = TxId::from_str(&s).unwrap();
678        prop_assert_eq!(txid, txid2);
679    }
680
681    #[proptest]
682    fn vk_serde_roundtrip(vk: B32) {
683        let bytes = util::write(&vk).unwrap();
684        let vk2 = util::read(bytes.as_slice()).unwrap();
685        prop_assert_eq!(vk, vk2);
686    }
687
688    #[proptest]
689    fn vk_serde_json_roundtrip(vk: App) {
690        let json_str = serde_json::to_string(&vk).unwrap();
691        // dbg!(&json_str);
692        let vk2 = serde_json::from_str(&json_str).unwrap();
693        prop_assert_eq!(vk, vk2);
694    }
695
696    #[proptest]
697    fn vk_serde_yaml_roundtrip(vk: App) {
698        let yaml_str = serde_yaml::to_string(&vk).unwrap();
699        // dbg!(&yaml_str);
700        let vk2 = serde_yaml::from_str(&yaml_str).unwrap();
701        prop_assert_eq!(vk, vk2);
702    }
703
704    #[proptest]
705    fn app_serde_roundtrip(app: App) {
706        let bytes = util::write(&app).unwrap();
707        let app2 = util::read(bytes.as_slice()).unwrap();
708        prop_assert_eq!(app, app2);
709    }
710
711    #[proptest]
712    fn utxo_id_serde_roundtrip(utxo_id: UtxoId) {
713        let bytes = util::write(&utxo_id).unwrap();
714        let utxo_id2 = util::read(bytes.as_slice()).unwrap();
715        prop_assert_eq!(utxo_id, utxo_id2);
716    }
717
718    #[proptest]
719    fn tx_id_serde_roundtrip(tx_id: TxId) {
720        let bytes = util::write(&tx_id).unwrap();
721        let tx_id2 = util::read(bytes.as_slice()).unwrap();
722        prop_assert_eq!(tx_id, tx_id2);
723    }
724
725    #[test]
726    fn minimal_txid() {
727        let tx_id_bytes: [u8; 32] = [&[1u8], [0u8; 31].as_ref()].concat().try_into().unwrap();
728        let tx_id = TxId(tx_id_bytes);
729        let tx_id_str = tx_id.to_string();
730        let tx_id_str_expected = "0000000000000000000000000000000000000000000000000000000000000001";
731        assert_eq!(tx_id_str, tx_id_str_expected);
732    }
733
734    #[test]
735    fn data_dbg() {
736        let v = 42u64;
737        let data: Data = Data::from(&v);
738        assert_eq!(format!("{:?}", data), format!("Data({:?})", Value::from(v)));
739
740        let data = Data::empty();
741        assert_eq!(format!("{:?}", data), "Data(Null)");
742
743        let vec1: Vec<u64> = vec![];
744        let data: Data = Data::from(&vec1);
745        assert_eq!(format!("{:?}", data), "Data(Array([]))");
746    }
747
748    #[test]
749    fn data_bytes() {
750        let v = ("42u64", 42u64);
751        let data = Data::from(&v);
752        let value = Value::serialized(&v).expect("serialization should have succeeded");
753
754        let buf = util::write(&value).expect("serialization should have succeeded");
755
756        assert_eq!(data.bytes(), buf);
757    }
758
759    #[test]
760    fn dummy() {}
761}
762
763#[derive(Clone, Debug, Serialize, Deserialize)]
764pub struct AppInput {
765    pub app_binaries: BTreeMap<B32, Vec<u8>>,
766    pub app_private_inputs: BTreeMap<App, Data>,
767}