cardano_sdk/cardano/
plutus_data.rs

1//  This Source Code Form is subject to the terms of the Mozilla Public
2//  License, v. 2.0. If a copy of the MPL was not distributed with this
3//  file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5use crate::{cbor, cbor::ToCbor, pallas};
6use anyhow::anyhow;
7use num::ToPrimitive;
8use num_bigint::BigInt;
9use std::{borrow::Cow, fmt};
10
11/// An arbitrary data format used by Plutus smart contracts.
12///
13/// It can be constructed directly using one of the two variants:
14///
15/// - [`Self::integer`]
16/// - [`Self::bytes`]
17///
18/// And combine to form larger objects using:
19///
20/// - [`Self::list`]
21/// - [`Self::map`]
22/// - [`Self::constr`]
23#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
24#[repr(transparent)]
25pub struct PlutusData<'a>(Cow<'a, pallas::PlutusData>);
26
27impl<'a> fmt::Display for PlutusData<'a> {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "CBOR({})", hex::encode(self.to_cbor()))
30    }
31}
32
33// -------------------------------------------------------------------- Building
34
35impl<'a> PlutusData<'a> {
36    /// Construct a data value from an arbitrarily-sized integer.
37    ///
38    /// # examples
39    ///
40    /// ```rust
41    /// # use cardano_sdk::PlutusData;
42    /// # use num_bigint::BigInt;
43    /// assert_eq!(
44    ///     format!("{}", PlutusData::integer(42)),
45    ///     "CBOR(182a)",
46    /// );
47    ///
48    /// assert_eq!(
49    ///     format!("{}", PlutusData::integer(-14)),
50    ///     "CBOR(2d)",
51    /// );
52    ///
53    /// assert_eq!(
54    ///     format!("{}", PlutusData::integer(BigInt::from(u128::MAX) + BigInt::from(u128::MAX))),
55    ///     "CBOR(c25101fffffffffffffffffffffffffffffffe)",
56    /// );
57    /// ```
58    pub fn integer(i: impl Into<BigInt>) -> Self {
59        let i: BigInt = i.into();
60
61        Self(Cow::Owned(match i.to_i128().map(pallas::Int::try_from) {
62            Some(Ok(i)) => pallas::PlutusData::BigInt(pallas::BigInt::Int(i)),
63            _ => {
64                let (sign, bytes) = i.to_bytes_be();
65                match sign {
66                    num_bigint::Sign::Minus => {
67                        pallas::PlutusData::BigInt(pallas::BigInt::BigNInt(bytes.into()))
68                    }
69                    _ => pallas::PlutusData::BigInt(pallas::BigInt::BigUInt(bytes.into())),
70                }
71            }
72        }))
73    }
74
75    /// Construct an arbitrarily-sized byte-array value.
76    ///
77    /// # examples
78    ///
79    /// ```rust
80    /// # use cardano_sdk::PlutusData;
81    /// assert_eq!(
82    ///     format!("{}", PlutusData::bytes(b"foo")),
83    ///     "CBOR(43666f6f)"
84    /// );
85    ///
86    /// assert_eq!(
87    ///     format!(
88    ///         "{}",
89    ///         PlutusData::bytes(
90    ///             b"Rerum deleniti nisi ea exercitationem architecto. Quia architecto voluptates error."
91    ///         )
92    ///     ),
93    ///     "CBOR(5f5840526572756d2064656c656e697469206e69736920656120657865726369746174696f6e656d206172636869746563746f2e205175696120617263686974656374536f20766f6c75707461746573206572726f722eff)"
94    /// );
95    /// ```
96    pub fn bytes(bytes: impl AsRef<[u8]>) -> Self {
97        Self(Cow::Owned(pallas::PlutusData::BoundedBytes(
98            pallas::BoundedBytes::from(bytes.as_ref().to_vec()),
99        )))
100    }
101
102    /// Construct an arbitrarily-sized list of [`self::PlutusData`] values.
103    ///
104    /// # examples
105    ///
106    /// ```rust
107    /// # use cardano_sdk::PlutusData;
108    ///
109    /// assert_eq!(
110    ///     format!("{}", PlutusData::list::<PlutusData>([])),
111    ///     "CBOR(80)"
112    /// );
113    ///
114    /// assert_eq!(
115    ///     format!("{}", PlutusData::list([b"foo", b"bar"])),
116    ///     "CBOR(9f43666f6f43626172ff)"
117    /// );
118    ///
119    /// assert_eq!(
120    ///     format!("{}", PlutusData::list([
121    ///         PlutusData::bytes(b"foo"),
122    ///         PlutusData::list([1, 2]),
123    ///     ])),
124    ///     "CBOR(9f43666f6f9f0102ffff)"
125    /// );
126    /// ```
127    pub fn list<T: Into<Self>>(elems: impl IntoIterator<Item = T>) -> Self {
128        let elems = elems
129            .into_iter()
130            .map(Into::<Self>::into)
131            .map(pallas::PlutusData::from)
132            .collect::<Vec<_>>();
133
134        Self(Cow::Owned(pallas::PlutusData::Array(if elems.is_empty() {
135            pallas::MaybeIndefArray::Def(elems)
136        } else {
137            pallas::MaybeIndefArray::Indef(elems)
138        })))
139    }
140
141    /// Construct an arbitrarily-sized list of [`self::PlutusData`] values.
142    ///
143    /// # examples
144    ///
145    /// ```rust
146    /// # use cardano_sdk::PlutusData;
147    ///
148    /// assert_eq!(
149    ///     format!("{}", PlutusData::map::<PlutusData, PlutusData>([])),
150    ///     "CBOR(a0)"
151    /// );
152    ///
153    /// assert_eq!(
154    ///     format!(
155    ///         "{}",
156    ///         PlutusData::map([
157    ///             (b"FOO", 1),
158    ///             (b"BAR", 2),
159    ///         ]),
160    ///     ),
161    ///     "CBOR(a243464f4f014342415202)"
162    /// );
163    pub fn map<K: Into<Self>, V: Into<Self>>(kvs: impl IntoIterator<Item = (K, V)>) -> Self {
164        let kvs = kvs
165            .into_iter()
166            .map(|(k, v)| {
167                (
168                    pallas::PlutusData::from(Into::<Self>::into(k)),
169                    pallas::PlutusData::from(Into::<Self>::into(v)),
170                )
171            })
172            .collect::<Vec<_>>();
173
174        Self(Cow::Owned(pallas::PlutusData::Map(
175            pallas::KeyValuePairs::from(kvs),
176        )))
177    }
178
179    /// Construct a tagged variant with [`self::PlutusData`] fields.
180    ///
181    /// <div class="warning">The constructor index `ix` will typically starts at `0`, and be
182    /// encoded accordingly. You may sometimes see libraries or tools working off encoded indexes
183    /// (e.g. starting at `121`). This is not the case here.</div>
184    ///
185    /// # examples
186    ///
187    /// ```rust
188    /// # use cardano_sdk::PlutusData;
189    ///
190    /// assert_eq!(
191    ///     format!("{}", PlutusData::constr::<PlutusData>(0, [])),
192    ///     "CBOR(d87980)"
193    /// );
194    ///
195    /// assert_eq!(
196    ///     format!(
197    ///         "{}",
198    ///         PlutusData::constr(0, [
199    ///             PlutusData::constr::<PlutusData>(1, []),
200    ///             PlutusData::integer(1337),
201    ///         ]),
202    ///     ),
203    ///     "CBOR(d8799fd87a80190539ff)"
204    /// );
205    /// ```
206    pub fn constr<T: Into<Self>>(ix: u64, fields: impl IntoIterator<Item = T>) -> Self {
207        let fields = fields
208            .into_iter()
209            .map(Into::<Self>::into)
210            .map(pallas::PlutusData::from)
211            .collect::<Vec<_>>();
212
213        let fields = if fields.is_empty() {
214            pallas::MaybeIndefArray::Def(fields)
215        } else {
216            pallas::MaybeIndefArray::Indef(fields)
217        };
218
219        // NOTE: see https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L139-L155
220        Self(Cow::Owned(if ix < 7 {
221            pallas::PlutusData::Constr(pallas::Constr {
222                tag: 121 + ix,
223                any_constructor: None,
224                fields,
225            })
226        } else if ix < 128 {
227            pallas::PlutusData::Constr(pallas::Constr {
228                tag: 1280 + ix - 7,
229                any_constructor: None,
230                fields,
231            })
232        } else {
233            pallas::PlutusData::Constr(pallas::Constr {
234                tag: 102,
235                any_constructor: Some(ix),
236                fields,
237            })
238        }))
239    }
240}
241
242// ---------------------------------------------------------------------- Macros
243
244#[macro_export]
245/// A handy macro for constructing [`PlutusData`](crate::PlutusData) constructors from a known set
246/// of fields. The macro is variadic. The first argument refers to the constructor variant index,
247/// while other arguments indicates the constructor fields.
248///
249/// ```rust
250/// use cardano_sdk::{PlutusData, constr};
251/// assert_eq!(
252///   constr!(1),
253///   PlutusData::constr::<PlutusData>(1, []),
254/// );
255/// ```
256///
257/// ```rust
258/// use cardano_sdk::{PlutusData, constr};
259/// assert_eq!(
260///   constr!(0, b"foo"),
261///   PlutusData::constr(0, [b"foo"]),
262/// );
263/// ```
264///
265/// ```rust
266/// use cardano_sdk::{PlutusData, constr};
267/// assert_eq!(
268///   constr!(0, 42, b"foo"),
269///   PlutusData::constr::<PlutusData>(0, [42.into(), b"foo".into()]),
270/// );
271/// ```
272///
273macro_rules! constr {
274    ($ix:expr $(,)?) => {
275        $crate::PlutusData::constr::<$crate::PlutusData>($ix, [])
276    };
277
278    // comma-separated arguments: constr!(ix, a, b, c)
279    ($ix:expr $(, $field:expr)+ $(,)?) => {
280        $crate::PlutusData::constr($ix, [ $( ::core::convert::Into::<$crate::PlutusData>::into($field) ),* ])
281    };
282}
283
284// ------------------------------------------------------------------ Inspecting
285
286impl<'a> PlutusData<'a> {
287    pub fn as_integer<T>(&'a self) -> Option<T>
288    where
289        T: TryFrom<BigInt>,
290    {
291        match self.0.as_ref() {
292            pallas::PlutusData::BigInt(big_int) => from_pallas_bigint(big_int).try_into().ok(),
293            _ => None,
294        }
295    }
296
297    pub fn as_bytes(&'a self) -> Option<&'a [u8]> {
298        match self.0.as_ref() {
299            pallas::PlutusData::BoundedBytes(bounded_bytes) => Some(bounded_bytes.as_slice()),
300            _ => None,
301        }
302    }
303
304    pub fn as_list(&'a self) -> Option<impl Iterator<Item = Self>> {
305        match self.0.as_ref() {
306            pallas::PlutusData::Array(array) => Some(from_pallas_array(array)),
307            _ => None,
308        }
309    }
310
311    pub fn as_map(&'a self) -> Option<impl Iterator<Item = (Self, Self)>> {
312        match self.0.as_ref() {
313            pallas::PlutusData::Map(map) => Some(from_pallas_map(map)),
314            _ => None,
315        }
316    }
317
318    pub fn as_constr(&'a self) -> Option<(u64, impl Iterator<Item = Self>)> {
319        match self.0.as_ref() {
320            pallas::PlutusData::Constr(constr) => Some(from_pallas_constr(constr)),
321            _ => None,
322        }
323    }
324}
325
326// --------------------------------------------------------------------- Helpers
327
328fn from_pallas_bigint(big_int: &pallas::BigInt) -> BigInt {
329    match big_int {
330        pallas::BigInt::Int(int) => BigInt::from(i128::from(*int)),
331        pallas::BigInt::BigUInt(bounded_bytes) => {
332            BigInt::from_bytes_be(num_bigint::Sign::Plus, bounded_bytes)
333        }
334        pallas::BigInt::BigNInt(bounded_bytes) => {
335            BigInt::from_bytes_be(num_bigint::Sign::Minus, bounded_bytes)
336        }
337    }
338}
339
340fn from_pallas_array<'a>(
341    array: &'a pallas::MaybeIndefArray<pallas::PlutusData>,
342) -> impl Iterator<Item = PlutusData<'a>> {
343    match array {
344        pallas::MaybeIndefArray::Def(elems) => elems,
345        pallas::MaybeIndefArray::Indef(elems) => elems,
346    }
347    .iter()
348    .map(|x| PlutusData(Cow::Borrowed(x)))
349}
350
351fn from_pallas_map<'a>(
352    map: &'a pallas::KeyValuePairs<pallas::PlutusData, pallas::PlutusData>,
353) -> impl Iterator<Item = (PlutusData<'a>, PlutusData<'a>)> {
354    match map {
355        pallas::KeyValuePairs::Def(items) => items,
356        pallas::KeyValuePairs::Indef(items) => items,
357    }
358    .iter()
359    .map(|(k, v)| (PlutusData(Cow::Borrowed(k)), PlutusData(Cow::Borrowed(v))))
360}
361
362fn from_pallas_constr<'a>(
363    constr: &'a pallas::Constr<pallas::PlutusData>,
364) -> (u64, impl Iterator<Item = PlutusData<'a>>) {
365    let fields = match &constr.fields {
366        pallas::MaybeIndefArray::Def(fields) => fields,
367        pallas::MaybeIndefArray::Indef(fields) => fields,
368    }
369    .iter()
370    .map(|x| PlutusData(Cow::Borrowed(x)));
371
372    let ix = if constr.tag == 102 {
373        constr
374            .any_constructor
375            .expect("'any_constructor' was 'None' but 'tag' was set to 102? This is absurd.")
376    } else if constr.tag >= 1280 {
377        constr.tag - 1280 + 7
378    } else {
379        constr.tag - 121
380    };
381
382    (ix, fields)
383}
384
385// ----------------------------------------------------------- Converting (from)
386
387impl From<pallas::PlutusData> for PlutusData<'static> {
388    fn from(data: pallas::PlutusData) -> Self {
389        Self(Cow::Owned(data))
390    }
391}
392
393#[macro_export]
394macro_rules! impl_from_int {
395    ($($t:ty),+ $(,)?) => {
396        $(
397            impl From<$t> for PlutusData<'static> {
398                #[inline]
399                fn from(int: $t) -> Self {
400                    Self::integer(int)
401                }
402            }
403        )+
404    };
405}
406
407impl_from_int!(
408    u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, BigInt
409);
410
411impl From<&str> for PlutusData<'static> {
412    fn from(bytes: &str) -> Self {
413        Self::bytes(bytes.as_bytes())
414    }
415}
416
417impl From<String> for PlutusData<'static> {
418    fn from(bytes: String) -> Self {
419        Self::bytes(bytes.as_bytes())
420    }
421}
422
423impl From<Vec<u8>> for PlutusData<'static> {
424    fn from(bytes: Vec<u8>) -> Self {
425        Self::bytes(bytes)
426    }
427}
428
429impl From<&[u8]> for PlutusData<'static> {
430    fn from(bytes: &[u8]) -> Self {
431        Self::bytes(bytes)
432    }
433}
434
435impl<const T: usize> From<[u8; T]> for PlutusData<'static> {
436    fn from(bytes: [u8; T]) -> Self {
437        Self::bytes(bytes)
438    }
439}
440
441impl<const T: usize> From<&[u8; T]> for PlutusData<'static> {
442    fn from(bytes: &[u8; T]) -> Self {
443        Self::bytes(bytes)
444    }
445}
446
447// ------------------------------------------------------------- Converting (to)
448
449impl From<PlutusData<'_>> for pallas::PlutusData {
450    fn from(data: PlutusData<'_>) -> Self {
451        data.0.into_owned()
452    }
453}
454
455#[macro_export]
456macro_rules! impl_try_into_int {
457    ($($t:ty),+ $(,)?) => {
458        $(
459            impl<'a> TryFrom<&'a PlutusData<'a>> for $t {
460                type Error = anyhow::Error;
461
462                            #[inline]
463                fn try_from(data: &'a PlutusData<'a>) -> anyhow::Result<Self> {
464                    data.as_integer().ok_or(anyhow!("expected an integer"))
465                }
466            }
467        )+
468    };
469}
470
471impl_try_into_int!(
472    u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, BigInt,
473);
474
475impl<'a> TryFrom<&'a PlutusData<'a>> for &'a [u8] {
476    type Error = anyhow::Error;
477
478    fn try_from(data: &'a PlutusData<'a>) -> anyhow::Result<Self> {
479        data.as_bytes().ok_or(anyhow!("expected bytes"))
480    }
481}
482
483impl<'a, const T: usize> TryFrom<&'a PlutusData<'a>> for &'a [u8; T] {
484    type Error = anyhow::Error;
485
486    fn try_from(data: &'a PlutusData<'a>) -> anyhow::Result<Self> {
487        data.as_bytes()
488            .ok_or(anyhow!("expected bytes"))?
489            .try_into()
490            .map_err(|_| anyhow!("bytes length mismatch"))
491    }
492}
493
494impl<'a> TryFrom<&'a PlutusData<'a>> for Vec<PlutusData<'a>> {
495    type Error = anyhow::Error;
496
497    fn try_from(data: &'a PlutusData<'a>) -> anyhow::Result<Self> {
498        Ok(data.as_list().ok_or(anyhow!("expected a list"))?.collect())
499    }
500}
501
502impl<'a, const T: usize> TryFrom<&'a PlutusData<'a>> for [PlutusData<'a>; T] {
503    type Error = anyhow::Error;
504
505    fn try_from(data: &'a PlutusData<'a>) -> anyhow::Result<Self> {
506        let list: Vec<PlutusData<'_>> = data.try_into()?;
507        <[PlutusData<'_>; T]>::try_from(list).map_err(|_| anyhow!("tuple length mismatch"))
508    }
509}
510
511impl<'a> TryFrom<&'a PlutusData<'a>> for Vec<(PlutusData<'a>, PlutusData<'a>)> {
512    type Error = anyhow::Error;
513
514    fn try_from(data: &'a PlutusData<'a>) -> anyhow::Result<Self> {
515        Ok(data.as_map().ok_or(anyhow!("expected a map"))?.collect())
516    }
517}
518
519impl<'a> TryFrom<&'a PlutusData<'a>> for (u64, Vec<PlutusData<'a>>) {
520    type Error = anyhow::Error;
521
522    fn try_from(data: &'a PlutusData<'a>) -> anyhow::Result<Self> {
523        let (ix, fields) = data.as_constr().ok_or(anyhow!("expected a constr"))?;
524        Ok((ix, fields.collect()))
525    }
526}
527
528impl<'a, const T: usize> TryFrom<&'a PlutusData<'a>> for (u64, [PlutusData<'a>; T]) {
529    type Error = anyhow::Error;
530
531    fn try_from(data: &'a PlutusData<'a>) -> anyhow::Result<Self> {
532        let (ix, fields) = data.as_constr().ok_or(anyhow!("expected a constr"))?;
533        Ok((
534            ix,
535            <[PlutusData<'a>; T]>::try_from(fields.collect::<Vec<_>>()).map_err(|vec| {
536                anyhow!(
537                    "expected a constr with {T} field(s), but found {} field(s)",
538                    vec.len()
539                )
540            })?,
541        ))
542    }
543}
544
545// -------------------------------------------------------------------- Encoding
546
547impl<C> cbor::Encode<C> for PlutusData<'_> {
548    fn encode<W: cbor::encode::write::Write>(
549        &self,
550        e: &mut cbor::Encoder<W>,
551        ctx: &mut C,
552    ) -> Result<(), cbor::encode::Error<W::Error>> {
553        e.encode_with(self.0.as_ref(), ctx)?;
554        Ok(())
555    }
556}
557
558impl<'d, C> cbor::Decode<'d, C> for PlutusData<'static> {
559    fn decode(d: &mut cbor::Decoder<'d>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
560        Ok(Self(Cow::Owned(d.decode_with(ctx)?)))
561    }
562}