Skip to main content

pallas_codec/
lib.rs

1//! The encoding foundation the rest of the Pallas workspace builds on.
2//!
3//! Provides [`minicbor`] for CBOR (re-exported as-is) and a Rust port of the
4//! Plutus Core [flat] format. Most users won't depend on this crate directly
5//! — they'll get its types transitively through `pallas-primitives`,
6//! `pallas-traverse`, `pallas-txbuilder`, and so on. Reach for it when you
7//! need to define your own minicbor-encoded type, or when you need the
8//! round-trip helpers ([`utils::KeepRaw`], [`utils::Nullable`],
9//! [`utils::Set`], …) used by the higher-level era types.
10//!
11//! [flat]: https://github.com/Quid2/flat
12//!
13//! # Usage
14//!
15//! ```
16//! use pallas_codec::minicbor;
17//!
18//! #[derive(minicbor::Encode, minicbor::Decode, Debug, PartialEq)]
19//! struct Pair(#[n(0)] u64, #[n(1)] String);
20//!
21//! let bytes = minicbor::to_vec(Pair(1, "hi".into()))?;
22//! let back: Pair = minicbor::decode(&bytes)?;
23//! assert_eq!(back, Pair(1, "hi".into()));
24//! # Ok::<_, Box<dyn std::error::Error>>(())
25//! ```
26//!
27//! # Overview
28//!
29//! - [`minicbor`] — re-exported as-is; this is the workspace's single source
30//!   of truth for CBOR.
31//! - [`flat`] — Rust port of the Haskell [flat] reference implementation,
32//!   used for Plutus Core scripts.
33//! - [`tree`] — stack-safe decoding and traversal of recursive types via
34//!   [`tree::TreeDecode`], [`tree::decode_tree`] and [`tree::TreeNode`].
35//! - [`utils`] — round-trip-friendly helper types ([`utils::KeepRaw`],
36//!   [`utils::KeyValuePairs`], [`utils::MaybeIndefArray`],
37//!   [`utils::NonEmptySet`], [`utils::Nullable`], [`utils::PositiveCoin`],
38//!   …) reused by the higher-level era types.
39//! - [`Fragment`] trait — blanket-implemented for any type that is both
40//!   [`minicbor::Encode`] and [`minicbor::Decode`]; used as a bound where
41//!   the workspace wants "any CBOR-roundtrippable type".
42//! - [`codec_by_datatype!`] macro — derives a tag-free CBOR codec for enums
43//!   whose variants are distinguished by their data-type rather than a
44//!   discriminant.
45//!
46//! # Usage as part of `pallas`
47//!
48//! When depending on the umbrella [`pallas`] crate, this crate is re-exported
49//! as `pallas::codec`.
50//!
51//! [`pallas`]: https://crates.io/crates/pallas
52
53/// Flat encoding/decoding for Plutus Core.
54pub mod flat;
55
56/// Shared re-export of `minicbor` across all Pallas crates.
57pub use minicbor;
58
59/// Stack-safe decoding of recursive CBOR structures.
60pub mod tree;
61
62/// Round-trip friendly common helper structs (`Bytes`, `Nullable`, `Set`, …).
63pub mod utils;
64
65/// Blanket trait for any type that can be CBOR-encoded and decoded with
66/// [`minicbor`]. Implemented automatically for every such type.
67pub trait Fragment: Sized + for<'b> minicbor::Decode<'b, ()> + minicbor::Encode<()> {}
68
69impl<T> Fragment for T where T: for<'b> minicbor::Decode<'b, ()> + minicbor::Encode<()> + Sized {}
70
71/// Derive a `minicbor` [`Decode`]/[`Encode`] implementation for an enum by
72/// dispatching on the incoming CBOR datatype.
73///
74/// Useful for sum types whose variants carry distinct CBOR shapes (e.g. one
75/// variant is an array, another is a map). The macro maps each CBOR datatype
76/// to a single-payload variant, and an `Array` fallback handles a many-field
77/// variant.
78///
79/// [`Decode`]: minicbor::Decode
80/// [`Encode`]: minicbor::Encode
81#[macro_export]
82macro_rules! codec_by_datatype {
83    (
84        $enum_name:ident $( < $lifetime:lifetime > )?,
85        $( $( $cbortype:ident )|* => $one_f:ident ),*,
86        ($( $( $vars:ident ),+ => $many_f:ident )?)
87    ) => {
88        impl<$( $lifetime, )? '__b $(:$lifetime)?,  C> minicbor::decode::Decode<'__b, C> for $enum_name $(<$lifetime>)? {
89            fn decode(d: &mut minicbor::Decoder<'__b>, ctx: &mut C) -> Result<Self, minicbor::decode::Error> {
90                match d.datatype()? {
91                    $( minicbor::data::Type::Array => {
92                        d.array()?;
93                        // Using the identifiers trivially to ensure repetition.
94                        Ok($enum_name::$many_f($({ let $vars = d.decode_with(ctx)?; $vars }, )+ ))
95                    }, )?
96                    $( $( minicbor::data::Type::$cbortype )|* => Ok($enum_name::$one_f(d.decode_with(ctx)?)), )*
97                    _ => Err(minicbor::decode::Error::message(
98                            "Unknown cbor data type for this macro-defined enum.")
99                    ),
100                }
101            }
102        }
103
104        impl< $( $lifetime, )? C> minicbor::encode::Encode<C> for $enum_name $(<$lifetime>)?  {
105            fn encode<W: minicbor::encode::Write>(
106                &self,
107                e: &mut minicbor::Encoder<W>,
108                ctx: &mut C,
109            ) -> Result<(), minicbor::encode::Error<W::Error>> {
110                match self {
111                    $( $enum_name::$many_f ($( $vars ),+) => {
112                        // Counting the number of `$vars`:
113                        let length: u64 = 0 $(+ { let _ = $vars; 1 })+;
114                        e.array(length)?;
115                        $( e.encode_with($vars, ctx)?; )+
116                    }, )?
117                    $( $enum_name::$one_f(__x666) => {
118                        e.encode_with(__x666, ctx)?;
119                    } )*
120                };
121
122                Ok(())
123            }
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::minicbor::{self, Decode, Encode, decode, encode};
131
132    #[derive(Clone, Debug)]
133    enum Thing {
134        Coin(u32),
135        Change(bool),
136        Multiasset(bool, u64, i32),
137    }
138
139    codec_by_datatype! {
140        Thing,
141        U8 | U16 | U32 => Coin,
142        Bool => Change,
143        (b, u, i => Multiasset)
144    }
145
146    #[cfg(test)]
147    pub fn roundtrip_codec<T: Encode<()> + for<'a> Decode<'a, ()> + std::fmt::Debug>(query: T) {
148        let mut cbor = Vec::new();
149        match encode(query, &mut cbor) {
150            Ok(_) => (),
151            Err(err) => panic!("Unable to encode data ({:?})", err),
152        };
153        println!("{:-<70}\nResulting CBOR: {:02x?}", "", cbor);
154
155        let query: T = decode(&cbor).unwrap();
156        println!("Decoded data: {:?}", query);
157    }
158
159    #[test]
160    fn roundtrip_codec_by_datatype() {
161        roundtrip_codec(Thing::Coin(0xfafa));
162        roundtrip_codec(Thing::Change(false));
163        roundtrip_codec(Thing::Multiasset(true, 10, -20));
164    }
165}