charms_data/
lib.rs

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