Skip to main content

amaru_uplc/flat/
data.rs

1use bumpalo::collections::Vec as BumpVec;
2use minicbor::data::{IanaTag, Tag};
3
4use crate::data::PlutusData;
5
6use super::Ctx;
7
8impl<'a, 'b> minicbor::decode::Decode<'b, Ctx<'a>> for &'a PlutusData<'a> {
9    fn decode(
10        decoder: &mut minicbor::Decoder<'b>,
11        ctx: &mut Ctx<'a>,
12    ) -> Result<Self, minicbor::decode::Error> {
13        let typ = decoder.datatype()?;
14
15        match typ {
16            minicbor::data::Type::Tag => {
17                let mut probe = decoder.probe();
18
19                let tag = probe.tag()?;
20
21                if matches!(tag.as_u64(), 121..=127 | 1280..=1400 | 102) {
22                    let x = decoder.tag()?.as_u64();
23
24                    return match x {
25                        121..=127 => {
26                            let mut fields = BumpVec::new_in(ctx.arena.as_bump());
27
28                            for x in decoder.array_iter_with(ctx)? {
29                                fields.push(x?);
30                            }
31
32                            let fields = ctx.arena.alloc(fields);
33
34                            let data = PlutusData::constr(ctx.arena, x - 121, fields);
35
36                            Ok(data)
37                        }
38                        1280..=1400 => {
39                            let mut fields = BumpVec::new_in(ctx.arena.as_bump());
40
41                            for x in decoder.array_iter_with(ctx)? {
42                                fields.push(x?);
43                            }
44
45                            let fields = ctx.arena.alloc(fields);
46
47                            let data = PlutusData::constr(ctx.arena, (x - 1280) + 7, fields);
48
49                            Ok(data)
50                        }
51                        102 => {
52                            let mut fields = BumpVec::new_in(ctx.arena.as_bump());
53
54                            let count = decoder.array()?;
55                            if count != Some(2) {
56                                return Err(minicbor::decode::Error::message(
57                                    "expected array of length 2 following plutus data tag 102",
58                                ));
59                            }
60
61                            let discriminator_i128: i128 = decoder.int()?.into();
62                            let discriminator: u64 = match u64::try_from(discriminator_i128) {
63                                Ok(n) => n,
64                                Err(_) => {
65                                    return Err(minicbor::decode::Error::message(format!(
66                                        "could not cast discriminator from plutus data tag 102 into u64: {discriminator_i128}",
67                                    )));
68                                }
69                            };
70
71                            for x in decoder.array_iter_with(ctx)? {
72                                fields.push(x?);
73                            }
74
75                            let fields = ctx.arena.alloc(fields);
76
77                            let data = PlutusData::constr(ctx.arena, discriminator, fields);
78
79                            Ok(data)
80                        }
81                        _ => {
82                            let e = minicbor::decode::Error::message(format!(
83                                "unknown tag for plutus data tag: {tag}",
84                            ));
85
86                            Err(e)
87                        }
88                    };
89                }
90
91                match tag.try_into() {
92                    Ok(x @ IanaTag::PosBignum | x @ IanaTag::NegBignum) => {
93                        let _ = decoder.tag()?;
94                        let mut bytes = BumpVec::new_in(ctx.arena.as_bump());
95
96                        for chunk in decoder.bytes_iter()? {
97                            let chunk = chunk?;
98
99                            bytes.extend_from_slice(chunk);
100                        }
101
102                        let n = num::BigInt::from_bytes_be(num_bigint::Sign::Plus, &bytes);
103                        let integer = ctx.arena.alloc_integer(if x == IanaTag::PosBignum {
104                            n
105                        } else {
106                            -n - 1
107                        });
108
109                        Ok(PlutusData::integer(ctx.arena, integer))
110                    }
111
112                    _ => {
113                        let e = minicbor::decode::Error::message(format!(
114                            "unknown tag for plutus data tag: {tag}",
115                        ));
116
117                        Err(e)
118                    }
119                }
120            }
121            minicbor::data::Type::Map | minicbor::data::Type::MapIndef => {
122                let mut fields = BumpVec::new_in(ctx.arena.as_bump());
123
124                for x in decoder.map_iter_with(ctx)? {
125                    let x = x?;
126
127                    fields.push(x);
128                }
129
130                let fields = ctx.arena.alloc(fields);
131
132                Ok(PlutusData::map(ctx.arena, fields))
133            }
134            minicbor::data::Type::Bytes | minicbor::data::Type::BytesIndef => {
135                let mut bs = BumpVec::new_in(ctx.arena.as_bump());
136
137                for chunk in decoder.bytes_iter()? {
138                    let chunk = chunk?;
139
140                    bs.extend_from_slice(chunk);
141                }
142
143                let bs = ctx.arena.alloc(bs);
144
145                Ok(PlutusData::byte_string(ctx.arena, bs))
146            }
147            minicbor::data::Type::Array | minicbor::data::Type::ArrayIndef => {
148                let mut fields = BumpVec::new_in(ctx.arena.as_bump());
149
150                for x in decoder.array_iter_with(ctx)? {
151                    fields.push(x?);
152                }
153
154                let fields = ctx.arena.alloc(fields);
155
156                Ok(PlutusData::list(ctx.arena, fields))
157            }
158            minicbor::data::Type::U8
159            | minicbor::data::Type::U16
160            | minicbor::data::Type::U32
161            | minicbor::data::Type::U64
162            | minicbor::data::Type::I8
163            | minicbor::data::Type::I16
164            | minicbor::data::Type::I32
165            | minicbor::data::Type::I64
166            | minicbor::data::Type::Int => {
167                let i: i128 = decoder.int()?.into();
168
169                Ok(PlutusData::integer_from(ctx.arena, i))
170            }
171            any => {
172                let e = minicbor::decode::Error::message(format!(
173                    "bad cbor data type ({any:?}) for plutus data"
174                ));
175
176                Err(e)
177            }
178        }
179    }
180}
181
182fn encode_bytestring<'a, W: minicbor::encode::Write>(
183    e: &'a mut minicbor::Encoder<W>,
184    bs: &[u8],
185) -> Result<&'a mut minicbor::Encoder<W>, minicbor::encode::Error<W::Error>> {
186    const CHUNK_SIZE: usize = 64;
187
188    if bs.len() <= 64 {
189        e.bytes(bs)?;
190    } else {
191        e.begin_bytes()?;
192
193        for b in bs.chunks(CHUNK_SIZE) {
194            e.bytes(b)?;
195        }
196
197        e.end()?;
198    }
199    Ok(e)
200}
201
202impl<C> minicbor::encode::Encode<C> for PlutusData<'_> {
203    fn encode<W: minicbor::encode::Write>(
204        &self,
205        e: &mut minicbor::Encoder<W>,
206        ctx: &mut C,
207    ) -> Result<(), minicbor::encode::Error<W::Error>> {
208        match self {
209            PlutusData::Constr { tag, fields } => {
210                if *tag < 7 {
211                    e.tag(Tag::new(*tag + 121))?;
212                } else if *tag <= 127 {
213                    e.tag(Tag::new((*tag - 7) + 1280))?;
214                } else {
215                    e.tag(Tag::new(102))?;
216                    e.array(2)?;
217                    e.u64(*tag)?;
218                }
219
220                // defaultEncodeList in Codec.Serialise emits definite in case of 0-length list
221                // https://github.com/well-typed/cborg/blob/1e9d079d382f237a1a282e268eecce2b395acb9c/serialise/src/Codec/Serialise/Class.hs#L165-L171
222                if fields.is_empty() {
223                    e.array(0)?;
224                } else {
225                    // TODO: figure out if we need to care about def vs indef
226                    // The encoding implementation in plutus-core uses indefinite here,
227                    // though both forms are accepted when decoding
228                    // https://github.com/IntersectMBO/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L198
229                    e.begin_array()?;
230                    for f in fields.iter() {
231                        f.encode(e, ctx)?;
232                    }
233                    e.end()?;
234                }
235            }
236            // stolen from pallas
237            // we use definite array to match the approach used by haskell's plutus
238            // implementation https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L152
239            PlutusData::Map(map) => {
240                let len: u64 = map
241                    .len()
242                    .try_into()
243                    .expect("setting map length should work fine");
244
245                e.map(len)?;
246
247                for (k, v) in map.iter() {
248                    k.encode(e, ctx)?;
249                    v.encode(e, ctx)?;
250                }
251            }
252            PlutusData::Integer(n) => {
253                let (sign, digits) = n.to_u64_digits();
254                match sign {
255                    num_bigint::Sign::Plus => {
256                        if digits.len() == 1 {
257                            e.u64(digits[0])?;
258                        } else {
259                            e.tag(Tag::new(2))?;
260                            let (_sign, bytes) = n.to_bytes_be();
261                            encode_bytestring(e, &bytes)?;
262                        }
263                    }
264                    num_bigint::Sign::Minus => {
265                        if digits.len() == 1 {
266                            let integer =
267                                minicbor::data::Int::try_from(-(digits[0] as i128)).unwrap();
268                            e.int(integer)?;
269                        } else {
270                            e.tag(Tag::new(3))?;
271                            let abs_minus_one = (-*n) - num::BigInt::from(1);
272                            let (_sign, bytes) = abs_minus_one.to_bytes_be();
273                            encode_bytestring(e, &bytes)?;
274                        }
275                    }
276                    num_bigint::Sign::NoSign => {
277                        e.u8(0)?;
278                    }
279                }
280            }
281            // we match the haskell implementation by encoding bytestrings longer than 64
282            // bytes as indefinite lists of bytes
283            PlutusData::ByteString(bs) => {
284                encode_bytestring(e, bs)?;
285            }
286            PlutusData::List(xs) => {
287                if xs.is_empty() {
288                    e.array(0)?;
289                } else {
290                    e.begin_array()?;
291                    for x in xs.iter() {
292                        x.encode(e, ctx)?;
293                    }
294                    e.end()?;
295                }
296            }
297        }
298
299        Ok(())
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306    use crate::arena::Arena;
307
308    #[test]
309    fn encode_empty_record() {
310        let d = PlutusData::Constr {
311            tag: 0,
312            fields: &[],
313        };
314        let mut v = vec![];
315        minicbor::encode(d, &mut v).expect("invalid PlutusData");
316        assert_eq!(hex::encode(v), "d87980");
317    }
318
319    #[test]
320    fn encode_record() {
321        let b1 = PlutusData::ByteString(&[0x00]);
322        let b2 = PlutusData::ByteString(&[0x00, 0x01]);
323        let d = PlutusData::Constr {
324            tag: 1,
325            fields: &[&b1, &b2],
326        };
327        let mut v = vec![];
328        minicbor::encode(d, &mut v).expect("invalid PlutusData");
329        assert_eq!(hex::encode(v), "d87a9f4100420001ff");
330    }
331
332    #[test]
333    fn encode_record_integer() {
334        let zero = num::BigInt::from(0);
335        let one = num::BigInt::from(1);
336        let d = PlutusData::Constr {
337            tag: 128,
338            fields: &[&PlutusData::Integer(&zero), &PlutusData::Integer(&one)],
339        };
340        let mut v = vec![];
341        minicbor::encode(d, &mut v).expect("invalid PlutusData");
342        assert_eq!(hex::encode(v), "d8668218809f0001ff");
343    }
344
345    #[test]
346    fn encode_cbor_data_bigint() {
347        let big = num::BigInt::from_bytes_be(
348            num_bigint::Sign::Plus,
349            &hex::decode("033b2e3c9fd0803ce7ffffff").unwrap(),
350        );
351        let d = PlutusData::Constr {
352            tag: 0,
353            fields: &[&PlutusData::Integer(&big)],
354        };
355        let mut v = vec![];
356        minicbor::encode(d, &mut v).expect("invalid PlutusData");
357        assert_eq!(hex::encode(v), "d8799fc24c033b2e3c9fd0803ce7ffffffff");
358    }
359
360    #[test]
361    fn encode_cbor_data_negative_bigint() {
362        let n = -num::BigInt::from_bytes_be(
363            num_bigint::Sign::Plus,
364            &hex::decode("033b2e3c9fd0803ce7ffffff").unwrap(),
365        ) - num::BigInt::from(1);
366        let d = PlutusData::Constr {
367            tag: 0,
368            fields: &[&PlutusData::Integer(&n)],
369        };
370        let mut v = vec![];
371        minicbor::encode(d, &mut v).expect("invalid PlutusData");
372        assert_eq!(hex::encode(v), "d8799fc34c033b2e3c9fd0803ce7ffffffff");
373    }
374
375    #[test]
376    fn decode_cbor_data_negative_bigint() {
377        let cbor = hex::decode("c34c033b2e3c9fd0803ce7ffffff").unwrap();
378        let arena = Arena::new();
379        let decoded =
380            PlutusData::from_cbor(&arena, &cbor).expect("failed to decode negative bigint");
381        let expected = -num::BigInt::from_bytes_be(
382            num_bigint::Sign::Plus,
383            &hex::decode("033b2e3c9fd0803ce7ffffff").unwrap(),
384        ) - num::BigInt::from(1);
385        assert_eq!(decoded, &PlutusData::Integer(&expected));
386    }
387
388    #[test]
389    fn roundtrip_cbor_data_negative_bigint() {
390        let n = -num::BigInt::from_bytes_be(
391            num_bigint::Sign::Plus,
392            &hex::decode("033b2e3c9fd0803ce7ffffff").unwrap(),
393        ) - num::BigInt::from(1);
394        let encoded = minicbor::to_vec(&PlutusData::Integer(&n)).expect("encode failed");
395        let arena = Arena::new();
396        let decoded = PlutusData::from_cbor(&arena, &encoded).expect("decode failed");
397        assert_eq!(decoded, &PlutusData::Integer(&n));
398    }
399
400    #[test]
401    fn encode_cbor_data_list() {
402        let zero = num::BigInt::from(0);
403        let one = num::BigInt::from(1);
404        let list = [&PlutusData::Integer(&zero), &PlutusData::Integer(&one)];
405        let d = PlutusData::Constr {
406            tag: 0,
407            fields: &[&PlutusData::List(&list)],
408        };
409        let mut v = vec![];
410        minicbor::encode(d, &mut v).expect("invalid PlutusData");
411        assert_eq!(hex::encode(v), "d8799f9f0001ffff");
412    }
413}