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