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//! - [`utils`] — round-trip-friendly helper types ([`utils::KeepRaw`],
34//! [`utils::KeyValuePairs`], [`utils::MaybeIndefArray`],
35//! [`utils::NonEmptySet`], [`utils::Nullable`], [`utils::PositiveCoin`],
36//! …) reused by the higher-level era types.
37//! - [`Fragment`] trait — blanket-implemented for any type that is both
38//! [`minicbor::Encode`] and [`minicbor::Decode`]; used as a bound where
39//! the workspace wants "any CBOR-roundtrippable type".
40//! - [`codec_by_datatype!`] macro — derives a tag-free CBOR codec for enums
41//! whose variants are distinguished by their data-type rather than a
42//! discriminant.
43//!
44//! # Usage as part of `pallas`
45//!
46//! When depending on the umbrella [`pallas`] crate, this crate is re-exported
47//! as `pallas::codec`.
48//!
49//! [`pallas`]: https://crates.io/crates/pallas
50
51/// Flat encoding/decoding for Plutus Core.
52pub mod flat;
53
54/// Shared re-export of `minicbor` across all Pallas crates.
55pub use minicbor;
56
57/// Round-trip friendly common helper structs (`Bytes`, `Nullable`, `Set`, …).
58pub mod utils;
59
60/// Blanket trait for any type that can be CBOR-encoded and decoded with
61/// [`minicbor`]. Implemented automatically for every such type.
62pub trait Fragment: Sized + for<'b> minicbor::Decode<'b, ()> + minicbor::Encode<()> {}
63
64impl<T> Fragment for T where T: for<'b> minicbor::Decode<'b, ()> + minicbor::Encode<()> + Sized {}
65
66/// Derive a `minicbor` [`Decode`]/[`Encode`] implementation for an enum by
67/// dispatching on the incoming CBOR datatype.
68///
69/// Useful for sum types whose variants carry distinct CBOR shapes (e.g. one
70/// variant is an array, another is a map). The macro maps each CBOR datatype
71/// to a single-payload variant, and an `Array` fallback handles a many-field
72/// variant.
73///
74/// [`Decode`]: minicbor::Decode
75/// [`Encode`]: minicbor::Encode
76#[macro_export]
77macro_rules! codec_by_datatype {
78 (
79 $enum_name:ident $( < $lifetime:lifetime > )?,
80 $( $( $cbortype:ident )|* => $one_f:ident ),*,
81 ($( $( $vars:ident ),+ => $many_f:ident )?)
82 ) => {
83 impl<$( $lifetime, )? '__b $(:$lifetime)?, C> minicbor::decode::Decode<'__b, C> for $enum_name $(<$lifetime>)? {
84 fn decode(d: &mut minicbor::Decoder<'__b>, ctx: &mut C) -> Result<Self, minicbor::decode::Error> {
85 match d.datatype()? {
86 $( minicbor::data::Type::Array => {
87 d.array()?;
88 // Using the identifiers trivially to ensure repetition.
89 Ok($enum_name::$many_f($({ let $vars = d.decode_with(ctx)?; $vars }, )+ ))
90 }, )?
91 $( $( minicbor::data::Type::$cbortype )|* => Ok($enum_name::$one_f(d.decode_with(ctx)?)), )*
92 _ => Err(minicbor::decode::Error::message(
93 "Unknown cbor data type for this macro-defined enum.")
94 ),
95 }
96 }
97 }
98
99 impl< $( $lifetime, )? C> minicbor::encode::Encode<C> for $enum_name $(<$lifetime>)? {
100 fn encode<W: minicbor::encode::Write>(
101 &self,
102 e: &mut minicbor::Encoder<W>,
103 ctx: &mut C,
104 ) -> Result<(), minicbor::encode::Error<W::Error>> {
105 match self {
106 $( $enum_name::$many_f ($( $vars ),+) => {
107 // Counting the number of `$vars`:
108 let length: u64 = 0 $(+ { let _ = $vars; 1 })+;
109 e.array(length)?;
110 $( e.encode_with($vars, ctx)?; )+
111 }, )?
112 $( $enum_name::$one_f(__x666) => {
113 e.encode_with(__x666, ctx)?;
114 } )*
115 };
116
117 Ok(())
118 }
119 }
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::minicbor::{self, Decode, Encode, decode, encode};
126
127 #[derive(Clone, Debug)]
128 enum Thing {
129 Coin(u32),
130 Change(bool),
131 Multiasset(bool, u64, i32),
132 }
133
134 codec_by_datatype! {
135 Thing,
136 U8 | U16 | U32 => Coin,
137 Bool => Change,
138 (b, u, i => Multiasset)
139 }
140
141 #[cfg(test)]
142 pub fn roundtrip_codec<T: Encode<()> + for<'a> Decode<'a, ()> + std::fmt::Debug>(query: T) {
143 let mut cbor = Vec::new();
144 match encode(query, &mut cbor) {
145 Ok(_) => (),
146 Err(err) => panic!("Unable to encode data ({:?})", err),
147 };
148 println!("{:-<70}\nResulting CBOR: {:02x?}", "", cbor);
149
150 let query: T = decode(&cbor).unwrap();
151 println!("Decoded data: {:?}", query);
152 }
153
154 #[test]
155 fn roundtrip_codec_by_datatype() {
156 roundtrip_codec(Thing::Coin(0xfafa));
157 roundtrip_codec(Thing::Change(false));
158 roundtrip_codec(Thing::Multiasset(true, 10, -20));
159 }
160}