charms_data/
lib.rs

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