Skip to main content

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