use bumpalo::collections::Vec as BumpVec;
use minicbor::data::{IanaTag, Tag};
use crate::data::PlutusData;
use super::Ctx;
impl<'a, 'b> minicbor::decode::Decode<'b, Ctx<'a>> for &'a PlutusData<'a> {
fn decode(
decoder: &mut minicbor::Decoder<'b>,
ctx: &mut Ctx<'a>,
) -> Result<Self, minicbor::decode::Error> {
let typ = decoder.datatype()?;
match typ {
minicbor::data::Type::Tag => {
let mut probe = decoder.probe();
let tag = probe.tag()?;
if matches!(tag.as_u64(), 121..=127 | 1280..=1400 | 102) {
let x = decoder.tag()?.as_u64();
return match x {
121..=127 => {
let mut fields = BumpVec::new_in(ctx.arena.as_bump());
for x in decoder.array_iter_with(ctx)? {
fields.push(x?);
}
let fields = ctx.arena.alloc(fields);
let data = PlutusData::constr(ctx.arena, x - 121, fields);
Ok(data)
}
1280..=1400 => {
let mut fields = BumpVec::new_in(ctx.arena.as_bump());
for x in decoder.array_iter_with(ctx)? {
fields.push(x?);
}
let fields = ctx.arena.alloc(fields);
let data = PlutusData::constr(ctx.arena, (x - 1280) + 7, fields);
Ok(data)
}
102 => {
let mut fields = BumpVec::new_in(ctx.arena.as_bump());
let count = decoder.array()?;
if count != Some(2) {
return Err(minicbor::decode::Error::message(
"expected array of length 2 following plutus data tag 102",
));
}
let discriminator_i128: i128 = decoder.int()?.into();
let discriminator: u64 = match u64::try_from(discriminator_i128) {
Ok(n) => n,
Err(_) => {
return Err(minicbor::decode::Error::message(format!(
"could not cast discriminator from plutus data tag 102 into u64: {discriminator_i128}",
)));
}
};
for x in decoder.array_iter_with(ctx)? {
fields.push(x?);
}
let fields = ctx.arena.alloc(fields);
let data = PlutusData::constr(ctx.arena, discriminator, fields);
Ok(data)
}
_ => {
let e = minicbor::decode::Error::message(format!(
"unknown tag for plutus data tag: {tag}",
));
Err(e)
}
};
}
match tag.try_into() {
Ok(x @ IanaTag::PosBignum | x @ IanaTag::NegBignum) => {
let _ = decoder.tag()?;
let mut bytes = BumpVec::new_in(ctx.arena.as_bump());
for chunk in decoder.bytes_iter()? {
let chunk = chunk?;
bytes.extend_from_slice(chunk);
}
let n = num::BigInt::from_bytes_be(num_bigint::Sign::Plus, &bytes);
let integer = ctx.arena.alloc_integer(if x == IanaTag::PosBignum {
n
} else {
-n - 1
});
Ok(PlutusData::integer(ctx.arena, integer))
}
_ => {
let e = minicbor::decode::Error::message(format!(
"unknown tag for plutus data tag: {tag}",
));
Err(e)
}
}
}
minicbor::data::Type::Map | minicbor::data::Type::MapIndef => {
let mut fields = BumpVec::new_in(ctx.arena.as_bump());
for x in decoder.map_iter_with(ctx)? {
let x = x?;
fields.push(x);
}
let fields = ctx.arena.alloc(fields);
Ok(PlutusData::map(ctx.arena, fields))
}
minicbor::data::Type::Bytes | minicbor::data::Type::BytesIndef => {
let mut bs = BumpVec::new_in(ctx.arena.as_bump());
for chunk in decoder.bytes_iter()? {
let chunk = chunk?;
bs.extend_from_slice(chunk);
}
let bs = ctx.arena.alloc(bs);
Ok(PlutusData::byte_string(ctx.arena, bs))
}
minicbor::data::Type::Array | minicbor::data::Type::ArrayIndef => {
let mut fields = BumpVec::new_in(ctx.arena.as_bump());
for x in decoder.array_iter_with(ctx)? {
fields.push(x?);
}
let fields = ctx.arena.alloc(fields);
Ok(PlutusData::list(ctx.arena, fields))
}
minicbor::data::Type::U8
| minicbor::data::Type::U16
| minicbor::data::Type::U32
| minicbor::data::Type::U64
| minicbor::data::Type::I8
| minicbor::data::Type::I16
| minicbor::data::Type::I32
| minicbor::data::Type::I64
| minicbor::data::Type::Int => {
let i: i128 = decoder.int()?.into();
Ok(PlutusData::integer_from(ctx.arena, i))
}
any => {
let e = minicbor::decode::Error::message(format!(
"bad cbor data type ({any:?}) for plutus data"
));
Err(e)
}
}
}
}
fn encode_bytestring<'a, W: minicbor::encode::Write>(
e: &'a mut minicbor::Encoder<W>,
bs: &[u8],
) -> Result<&'a mut minicbor::Encoder<W>, minicbor::encode::Error<W::Error>> {
const CHUNK_SIZE: usize = 64;
if bs.len() <= 64 {
e.bytes(bs)?;
} else {
e.begin_bytes()?;
for b in bs.chunks(CHUNK_SIZE) {
e.bytes(b)?;
}
e.end()?;
}
Ok(e)
}
impl<C> minicbor::encode::Encode<C> for PlutusData<'_> {
fn encode<W: minicbor::encode::Write>(
&self,
e: &mut minicbor::Encoder<W>,
ctx: &mut C,
) -> Result<(), minicbor::encode::Error<W::Error>> {
match self {
PlutusData::Constr { tag, fields } => {
if *tag < 7 {
e.tag(Tag::new(*tag + 121))?;
} else if *tag <= 127 {
e.tag(Tag::new((*tag - 7) + 1280))?;
} else {
e.tag(Tag::new(102))?;
e.array(2)?;
e.u64(*tag)?;
}
if fields.is_empty() {
e.array(0)?;
} else {
e.begin_array()?;
for f in fields.iter() {
f.encode(e, ctx)?;
}
e.end()?;
}
}
PlutusData::Map(map) => {
let len: u64 = map
.len()
.try_into()
.expect("setting map length should work fine");
e.map(len)?;
for (k, v) in map.iter() {
k.encode(e, ctx)?;
v.encode(e, ctx)?;
}
}
PlutusData::Integer(n) => {
let (sign, digits) = n.to_u64_digits();
match sign {
num_bigint::Sign::Plus => {
if digits.len() == 1 {
e.u64(digits[0])?;
} else {
e.tag(Tag::new(2))?;
let (_sign, bytes) = n.to_bytes_be();
encode_bytestring(e, &bytes)?;
}
}
num_bigint::Sign::Minus => {
if digits.len() == 1 {
let integer =
minicbor::data::Int::try_from(-(digits[0] as i128)).unwrap();
e.int(integer)?;
} else {
e.tag(Tag::new(3))?;
let abs_minus_one = (-*n) - num::BigInt::from(1);
let (_sign, bytes) = abs_minus_one.to_bytes_be();
encode_bytestring(e, &bytes)?;
}
}
num_bigint::Sign::NoSign => {
e.u8(0)?;
}
}
}
PlutusData::ByteString(bs) => {
encode_bytestring(e, bs)?;
}
PlutusData::List(xs) => {
if xs.is_empty() {
e.array(0)?;
} else {
e.begin_array()?;
for x in xs.iter() {
x.encode(e, ctx)?;
}
e.end()?;
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arena::Arena;
#[test]
fn encode_empty_record() {
let d = PlutusData::Constr {
tag: 0,
fields: &[],
};
let mut v = vec![];
minicbor::encode(d, &mut v).expect("invalid PlutusData");
assert_eq!(hex::encode(v), "d87980");
}
#[test]
fn encode_record() {
let b1 = PlutusData::ByteString(&[0x00]);
let b2 = PlutusData::ByteString(&[0x00, 0x01]);
let d = PlutusData::Constr {
tag: 1,
fields: &[&b1, &b2],
};
let mut v = vec![];
minicbor::encode(d, &mut v).expect("invalid PlutusData");
assert_eq!(hex::encode(v), "d87a9f4100420001ff");
}
#[test]
fn encode_record_integer() {
let zero = num::BigInt::from(0);
let one = num::BigInt::from(1);
let d = PlutusData::Constr {
tag: 128,
fields: &[&PlutusData::Integer(&zero), &PlutusData::Integer(&one)],
};
let mut v = vec![];
minicbor::encode(d, &mut v).expect("invalid PlutusData");
assert_eq!(hex::encode(v), "d8668218809f0001ff");
}
#[test]
fn encode_cbor_data_bigint() {
let big = num::BigInt::from_bytes_be(
num_bigint::Sign::Plus,
&hex::decode("033b2e3c9fd0803ce7ffffff").unwrap(),
);
let d = PlutusData::Constr {
tag: 0,
fields: &[&PlutusData::Integer(&big)],
};
let mut v = vec![];
minicbor::encode(d, &mut v).expect("invalid PlutusData");
assert_eq!(hex::encode(v), "d8799fc24c033b2e3c9fd0803ce7ffffffff");
}
#[test]
fn encode_cbor_data_negative_bigint() {
let n = -num::BigInt::from_bytes_be(
num_bigint::Sign::Plus,
&hex::decode("033b2e3c9fd0803ce7ffffff").unwrap(),
) - num::BigInt::from(1);
let d = PlutusData::Constr {
tag: 0,
fields: &[&PlutusData::Integer(&n)],
};
let mut v = vec![];
minicbor::encode(d, &mut v).expect("invalid PlutusData");
assert_eq!(hex::encode(v), "d8799fc34c033b2e3c9fd0803ce7ffffffff");
}
#[test]
fn decode_cbor_data_negative_bigint() {
let cbor = hex::decode("c34c033b2e3c9fd0803ce7ffffff").unwrap();
let arena = Arena::new();
let decoded =
PlutusData::from_cbor(&arena, &cbor).expect("failed to decode negative bigint");
let expected = -num::BigInt::from_bytes_be(
num_bigint::Sign::Plus,
&hex::decode("033b2e3c9fd0803ce7ffffff").unwrap(),
) - num::BigInt::from(1);
assert_eq!(decoded, &PlutusData::Integer(&expected));
}
#[test]
fn roundtrip_cbor_data_negative_bigint() {
let n = -num::BigInt::from_bytes_be(
num_bigint::Sign::Plus,
&hex::decode("033b2e3c9fd0803ce7ffffff").unwrap(),
) - num::BigInt::from(1);
let encoded = minicbor::to_vec(&PlutusData::Integer(&n)).expect("encode failed");
let arena = Arena::new();
let decoded = PlutusData::from_cbor(&arena, &encoded).expect("decode failed");
assert_eq!(decoded, &PlutusData::Integer(&n));
}
#[test]
fn encode_cbor_data_list() {
let zero = num::BigInt::from(0);
let one = num::BigInt::from(1);
let list = [&PlutusData::Integer(&zero), &PlutusData::Integer(&one)];
let d = PlutusData::Constr {
tag: 0,
fields: &[&PlutusData::List(&list)],
};
let mut v = vec![];
minicbor::encode(d, &mut v).expect("invalid PlutusData");
assert_eq!(hex::encode(v), "d8799f9f0001ffff");
}
}