diff --git a/src/kernel_sdk/CHANGES.md b/src/kernel_sdk/CHANGES.md index 273899a348b6599a9119507de2ffca4481174c60..fc545073fdfc39be32e35ca3411279748f2f7f8d 100644 --- a/src/kernel_sdk/CHANGES.md +++ b/src/kernel_sdk/CHANGES.md @@ -8,6 +8,8 @@ - Add an `OutboxQueue` that can be used when more than 100 outbox messages are produced at a given level. - Add `From OutboxMessageTransaction`, `From OutboxMessageTransactionBatch` for `OutboxMessage` to simplify construction. - Fix the incomplete inbox on the first level of using `MockHost::default()`. +- Add support for new michelson `Ticket` constructor. +- Add michelson `nat`. ### Installer client/kernel diff --git a/src/kernel_sdk/encoding/src/michelson.rs b/src/kernel_sdk/encoding/src/michelson.rs index bbcdde25742fa28a232e3d81eb8975f0a618f597..eafd7ee681359464b9be3935f936d29a0b8384bc 100644 --- a/src/kernel_sdk/encoding/src/michelson.rs +++ b/src/kernel_sdk/encoding/src/michelson.rs @@ -1,10 +1,14 @@ // SPDX-FileCopyrightText: 2022-2023 TriliTech // SPDX-FileCopyrightText: 2023 Nomadic Labs +// SPDX-FileCopyrightText: 2024 Marigold +// // SPDX-License-Identifier: MIT //! Definitions & tezos-encodings for *michelson* data. +use micheline::annots::Annotations; use nom::branch::alt; use nom::combinator::map; +use prim::*; use std::fmt::Debug; use tezos_data_encoding::enc::{self, BinResult, BinWriter}; use tezos_data_encoding::encoding::{Encoding, HasEncoding}; @@ -15,6 +19,7 @@ mod micheline; #[cfg(feature = "alloc")] pub mod ticket; +use self::micheline::Node; use super::contract::Contract; use micheline::{ bin_write_micheline_bytes, bin_write_micheline_int, bin_write_micheline_string, @@ -47,8 +52,37 @@ pub mod v1_primitives { /// unit encoding case tag. pub const UNIT_TAG: u8 = 11; + + /// int type tag + pub const INT_TYPE_TAG: u8 = 91; + + /// nat type tag + pub const NAT_TYPE_TAG: u8 = 98; + + /// option type tag + pub const OPTION_TYPE_TAG: u8 = 99; + + /// or type tag + pub const OR_TYPE_TAG: u8 = 100; + + /// pair type tag + pub const PAIR_TYPE_TAG: u8 = 101; + + /// string type tag + pub const STRING_TYPE_TAG: u8 = 104; + + /// bytes type tag + pub const BYTES_TYPE_TAG: u8 = 105; + + /// unit type tag + pub const UNIT_TYPE_TAG: u8 = 108; + + /// ticket encoding case tag. + pub const TICKET_TAG: u8 = 157; } +// TODO: +// combine MichelsonTicketContent and Michelson traits /// marker trait for michelson encoding pub trait Michelson: HasEncoding + BinWriter + NomReader + Debug + PartialEq + Eq @@ -58,6 +92,7 @@ pub trait Michelson: impl Michelson for MichelsonUnit {} impl Michelson for MichelsonContract {} impl Michelson for MichelsonInt {} +impl Michelson for MichelsonNat {} impl Michelson for MichelsonString {} impl Michelson for MichelsonBytes {} impl Michelson for MichelsonPair @@ -121,6 +156,26 @@ pub struct MichelsonBytes(pub Vec); #[derive(Debug, PartialEq, Eq)] pub struct MichelsonInt(pub Zarith); +/// Michelson Nat encoding. +#[derive(Debug, PartialEq, Eq)] +pub struct MichelsonNat(Zarith); +impl MichelsonNat { + /// Create a new nat, returns none if z is negative + pub fn new(z: Zarith) -> Option { + if z.0 >= 0.into() { + Some(Self(z)) + } else { + None + } + } +} + +impl AsRef for MichelsonNat { + fn as_ref(&self) -> &Zarith { + &self.0 + } +} + // ---------- // CONVERSION // ---------- @@ -147,6 +202,11 @@ impl From for MichelsonInt { MichelsonInt(Zarith(value.into())) } } +impl From for MichelsonNat { + fn from(value: u32) -> MichelsonNat { + MichelsonNat(Zarith(value.into())) + } +} // -------- // ENCODING @@ -209,6 +269,11 @@ impl HasEncoding for MichelsonInt { Encoding::Custom } } +impl HasEncoding for MichelsonNat { + fn encoding() -> Encoding { + Encoding::Custom + } +} // -------- // DECODING @@ -243,6 +308,306 @@ where )(input) } } +/// Functions needed to ensure a safe conversion to/from a valid MichelsonTicket and to/from a Node. +#[doc(hidden)] +pub trait MichelsonTicketContent: Michelson { + fn typecheck_node(node: &Node) -> bool; + fn node_of_type() -> Node; + fn of_node(node: Node) -> Option; + fn to_node(&self) -> Node; +} + +macro_rules! typed_prim { + ($ty:ty, $tag:expr) => { + fn typecheck_node(node: &Node) -> bool { + matches!(node, Node::Prim { + prim_tag, + args, + annots, + } if *prim_tag == $tag && args.is_empty() && annots.is_empty()) + } + fn node_of_type() -> Node { + Node::Prim { + prim_tag: $tag, + args: vec![], + annots: Annotations(vec![]), + } + } + } +} + +impl MichelsonTicketContent for MichelsonUnit { + typed_prim!(MichelsonUnit, UNIT_TYPE_TAG); + + fn of_node(node: Node) -> Option { + match node { + Node::Prim { + prim_tag, + args, + annots, + } if prim_tag == UNIT_TAG && args.is_empty() && annots.is_empty() => { + Some(MichelsonUnit) + } + _ => None, + } + } + + fn to_node(&self) -> Node { + Node::Prim { + prim_tag: UNIT_TAG, + args: vec![], + annots: Annotations(vec![]), + } + } +} + +impl MichelsonTicketContent for MichelsonNat { + typed_prim!(MichelsonNat, NAT_TYPE_TAG); + + fn of_node(value: Node) -> Option { + match value { + Node::Int(value) if value.0 >= 0.into() => Some(MichelsonNat(value)), + _ => None, + } + } + + fn to_node(&self) -> Node { + Node::Int(self.0.clone()) + } +} + +impl MichelsonTicketContent for MichelsonInt { + typed_prim!(MichelsonInt, INT_TYPE_TAG); + + fn of_node(value: Node) -> Option { + match value { + Node::Int(value) => Some(MichelsonInt(value)), + _ => None, + } + } + + fn to_node(&self) -> Node { + Node::Int(self.0.clone()) + } +} + +impl MichelsonTicketContent for MichelsonString { + typed_prim!(MichelsonString, STRING_TYPE_TAG); + + fn of_node(value: Node) -> Option { + match value { + Node::String(value) => Some(MichelsonString(value)), + _ => None, + } + } + + fn to_node(&self) -> Node { + Node::String(self.0.clone()) + } +} + +impl MichelsonTicketContent for MichelsonBytes { + typed_prim!(MichelsonBytes, BYTES_TYPE_TAG); + + fn of_node(value: Node) -> Option { + match value { + Node::Bytes(value) => Some(MichelsonBytes(value)), + _ => None, + } + } + + fn to_node(&self) -> Node { + Node::Bytes(self.0.clone()) + } +} + +impl MichelsonTicketContent for MichelsonOption +where + Expr: MichelsonTicketContent, +{ + fn typecheck_node(node: &Node) -> bool { + let args = match node { + Node::Prim { + prim_tag, + args, + annots, + } if *prim_tag == OPTION_TYPE_TAG && annots.is_empty() => args, + _ => return false, + }; + + match args.as_slice() { + [arg] => Expr::typecheck_node(arg), + _ => false, + } + } + + fn node_of_type() -> Node { + Node::Prim { + prim_tag: OPTION_TYPE_TAG, + args: vec![Expr::node_of_type()], + annots: Annotations(vec![]), + } + } + + fn to_node(&self) -> Node { + match &self.0 { + Some(v) => Node::Prim { + prim_tag: SOME_TAG, + args: vec![Expr::to_node(v)], + annots: Annotations(vec![]), + }, + None => Node::Prim { + prim_tag: NONE_TAG, + args: vec![], + annots: Annotations(vec![]), + }, + } + } + fn of_node(value: Node) -> Option { + match value { + Node::Prim { + prim_tag, + args, + annots, + } if prim_tag == NONE_TAG && args.is_empty() && annots.is_empty() => { + Some(MichelsonOption(None)) + } + Node::Prim { + prim_tag, + args, + annots, + } if prim_tag == SOME_TAG && annots.is_empty() && args.len() == 1 => { + let [v]: [Node; 1] = args.try_into().unwrap(); + let v = Expr::of_node(v)?; + Some(MichelsonOption(Some(v))) + } + _ => None, + } + } +} + +impl MichelsonTicketContent for MichelsonPair +where + Arg0: MichelsonTicketContent, + Arg1: MichelsonTicketContent, +{ + fn typecheck_node(node: &Node) -> bool { + let args = match node { + Node::Prim { + prim_tag, + args, + annots, + } if *prim_tag == PAIR_TYPE_TAG && annots.is_empty() => args, + _ => return false, + }; + + match args.as_slice() { + [arg0, arg1] => Arg0::typecheck_node(arg0) && Arg1::typecheck_node(arg1), + _ => false, + } + } + + fn node_of_type() -> Node { + Node::Prim { + prim_tag: PAIR_TYPE_TAG, + args: vec![Arg0::node_of_type(), Arg1::node_of_type()], + annots: Annotations(vec![]), + } + } + + fn of_node(value: Node) -> Option { + match value { + Node::Prim { + prim_tag, + args, + annots, + } if prim_tag == PAIR_TAG && annots.is_empty() && args.len() == 2 => { + let [v0, v1]: [Node; 2] = args.try_into().unwrap(); + let v0 = Arg0::of_node(v0)?; + let v1 = Arg1::of_node(v1)?; + Some(MichelsonPair(v0, v1)) + } + _ => None, + } + } + + fn to_node(&self) -> Node { + let MichelsonPair(v0, v1) = self; + Node::Prim { + prim_tag: PAIR_TAG, + args: vec![Arg0::to_node(v0), Arg1::to_node(v1)], + annots: Annotations(vec![]), + } + } +} + +impl MichelsonTicketContent for MichelsonOr +where + Arg0: MichelsonTicketContent, + Arg1: MichelsonTicketContent, +{ + fn typecheck_node(node: &Node) -> bool { + let args = match node { + Node::Prim { + prim_tag, + args, + annots, + } if *prim_tag == OR_TYPE_TAG && annots.is_empty() => args, + _ => return false, + }; + + match args.as_slice() { + [left, right] => Arg0::typecheck_node(left) && Arg1::typecheck_node(right), + _ => false, + } + } + + fn node_of_type() -> Node { + Node::Prim { + prim_tag: OR_TYPE_TAG, + args: vec![Arg0::node_of_type(), Arg1::node_of_type()], + annots: Annotations(vec![]), + } + } + + fn to_node(&self) -> Node { + match &self { + MichelsonOr::Left(v) => Node::Prim { + prim_tag: LEFT_TAG, + args: vec![Arg0::to_node(v)], + annots: Annotations(vec![]), + }, + MichelsonOr::Right(v) => Node::Prim { + prim_tag: RIGHT_TAG, + args: vec![Arg1::to_node(v)], + annots: Annotations(vec![]), + }, + } + } + fn of_node(value: Node) -> Option { + match value { + Node::Prim { + prim_tag, + args, + annots, + } if prim_tag == LEFT_TAG && annots.is_empty() && args.len() == 1 => { + let [v]: [Node; 1] = args.try_into().unwrap(); + let v = Arg0::of_node(v)?; + Some(MichelsonOr::Left(v)) + } + Node::Prim { + prim_tag, + args, + annots, + } if prim_tag == RIGHT_TAG && annots.is_empty() && args.len() == 1 => { + let [v]: [Node; 1] = args.try_into().unwrap(); + let v = Arg1::of_node(v)?; + Some(MichelsonOr::Right(v)) + } + _ => None, + } + } +} impl NomReader for MichelsonOr where @@ -299,6 +664,23 @@ impl NomReader for MichelsonInt { } } +impl NomReader for MichelsonNat { + fn nom_read(input: &[u8]) -> NomResult { + use nom::error::{ErrorKind, ParseError}; + use tezos_data_encoding::nom::error::*; + + let (rest, i) = nom_read_micheline_int(input)?; + if i.0 >= 0.into() { + Ok((rest, MichelsonNat(i))) + } else { + Err(nom::Err::Error(DecodeError::from_error_kind( + input, + ErrorKind::MapRes, + ))) + } + } +} + // -------- // ENCODING // -------- @@ -423,3 +805,8 @@ impl BinWriter for MichelsonInt { bin_write_micheline_int(&self.0, output) } } +impl BinWriter for MichelsonNat { + fn bin_write(&self, output: &mut Vec) -> BinResult { + bin_write_micheline_int(&self.0, output) + } +} diff --git a/src/kernel_sdk/encoding/src/michelson/micheline.rs b/src/kernel_sdk/encoding/src/michelson/micheline.rs index 433998b8409fe1858a9e686720e2d7f38391c3a1..69108a3fcb1263d5d6a716eabd9dd0b6813d7866 100644 --- a/src/kernel_sdk/encoding/src/michelson/micheline.rs +++ b/src/kernel_sdk/encoding/src/michelson/micheline.rs @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2022 TriliTech // SPDX-FileCopyrightText: 2023 Nomadic Labs +// SPDX-FileCopyrightText: 2024 Marigold // // SPDX-License-Identifier: MIT @@ -46,7 +47,7 @@ pub const MICHELINE_BYTES_TAG: u8 = 10; // Annotations // ----------- -mod annots { +pub(crate) mod annots { use regex::Regex; use std::fmt; use tezos_data_encoding::enc::{self, BinResult, BinWriter}; @@ -56,7 +57,7 @@ mod annots { pub struct Annotation(String); #[derive(Debug, PartialEq, Eq, Clone, Default)] - pub struct Annotations(Vec); + pub struct Annotations(pub(crate) Vec); impl fmt::Display for Annotation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/kernel_sdk/encoding/src/michelson/ticket.rs b/src/kernel_sdk/encoding/src/michelson/ticket.rs index 819c1617808fd68dbe19b5c088f720aa36aa79f3..fa9908e40b2fc9cd88bb90d43f666d2dffc7600e 100644 --- a/src/kernel_sdk/encoding/src/michelson/ticket.rs +++ b/src/kernel_sdk/encoding/src/michelson/ticket.rs @@ -1,16 +1,20 @@ // SPDX-FileCopyrightText: 2022-2023 TriliTech // SPDX-FileCopyrightText: 2023 Nomadic Labs -// SPDX-FileCopyrightText: 2023 Marigold +// SPDX-FileCopyrightText: 2023-2024 Marigold // // SPDX-License-Identifier: MIT //! Michelson-ticket encoding. +use super::{ + micheline::{annots::Annotations, Node}, + MichelsonTicketContent, TICKET_TAG, +}; use crate::{ contract::Contract, michelson::{ - Michelson, MichelsonBytes, MichelsonContract, MichelsonInt, MichelsonOption, - MichelsonPair, MichelsonString, MichelsonUnit, + Michelson, MichelsonBytes, MichelsonContract, MichelsonInt, MichelsonNat, + MichelsonOption, MichelsonPair, MichelsonString, MichelsonUnit, }, }; use core::{ @@ -19,14 +23,17 @@ use core::{ }; use crypto::blake2b::{digest_256, Blake2bError}; use hex::FromHexError; -use nom::combinator::map; +use nom::{ + combinator::map, + error::{ErrorKind, ParseError}, +}; use num_bigint::BigInt; use num_traits::Signed; use std::fmt::Debug; use tezos_data_encoding::{ enc::{BinError, BinResult, BinWriter}, - encoding::HasEncoding, - nom::{NomReader, NomResult}, + encoding::{Encoding, HasEncoding}, + nom::{error::DecodeError, NomReader, NomResult}, types::{SizedBytes, Zarith}, }; use thiserror::Error; @@ -109,36 +116,180 @@ pub enum TicketError { InvalidAmount(BigInt), } +// TODO: +// Switch from `MichelsonInt` to `MichelsonNat` for the amount field +// for consistency with the OCaml part. +// This will be a breaking change. +/// Michelson *ticket* encoding. +#[derive(Debug, PartialEq, Eq)] +struct TypedTicket(pub MichelsonContract, pub Contents, pub MichelsonInt) +where + Contents: Debug + PartialEq + Eq; + +impl HasEncoding for TypedTicket +where + Arg: Debug + PartialEq + Eq, +{ + fn encoding() -> Encoding { + Encoding::Custom + } +} + +/// Extract the four fields of a ticket from the given input +/// and return them as `Node` +fn get_ticket_arguments(input: &[u8]) -> NomResult<[Node; 4]> { + let (fst, node) = Node::nom_read(input)?; + let Node::Prim { + prim_tag, + args, + annots, + } = node + else { + return Err(nom::Err::Error(DecodeError::from_error_kind( + input, + ErrorKind::MapRes, + ))); + }; + + if prim_tag != TICKET_TAG || !annots.is_empty() || args.len() != 4 { + return Err(nom::Err::Error(DecodeError::from_error_kind( + input, + ErrorKind::MapRes, + ))); + }; + let [arg0, arg1, arg2, arg3]: [_; 4] = args.try_into().unwrap(); + Ok((fst, ([arg0, arg1, arg2, arg3]))) +} + +impl NomReader for TypedTicket { + fn nom_read(input: &[u8]) -> NomResult { + // 1st: extract each field of the input + let (fst, [arg0, arg1, arg2, arg3]) = get_ticket_arguments(input)?; + + // Ensure the `arg1` has the correct type + if !Contents::typecheck_node(&arg1) { + return Err(nom::Err::Error(DecodeError::from_error_kind( + input, + ErrorKind::MapRes, + ))); + }; + let Node::Bytes(ticketer) = arg0 else { + return Err(nom::Err::Error(DecodeError::from_error_kind( + input, + ErrorKind::MapRes, + ))); + }; + + let (rest, contract) = Contract::nom_read(&ticketer).unwrap(); + assert!(rest.is_empty()); + let Node::Int(amount) = arg3 else { + return Err(nom::Err::Error(DecodeError::from_error_kind( + input, + ErrorKind::MapRes, + ))); + }; + + // Extract MichelsonUnit, which is the contents of the ticket + let contents = Contents::of_node(arg2).unwrap(); + + // Data retrieved from input is correct, build the corresponding MichelsonTicket + let ticket = + TypedTicket(MichelsonContract(contract), contents, MichelsonInt(amount)); + Ok((fst, ticket)) + } +} + +impl BinWriter for TypedTicket { + fn bin_write(&self, output: &mut Vec) -> BinResult { + let TypedTicket(ticketer, contents, amount) = self; + + let mut ticketer_bytes = Vec::new(); + ticketer.0.bin_write(&mut ticketer_bytes).unwrap(); + + let ticketer = Node::Bytes(ticketer_bytes); + let amount = Node::Int(amount.0.clone()); + + let contents = contents.to_node(); + let contents_type = Contents::node_of_type(); + + let args = vec![ticketer, contents_type, contents, amount]; + + let node = Node::Prim { + prim_tag: TICKET_TAG, + args, + annots: Annotations(vec![]), + }; + node.bin_write(output) + } +} + // Expr is guarantee by construction to implement `Michelson` even though // rust does not enforce it in type aliases `type TicketRepr`. -type TicketRepr = +type LegacyTicketRepr = MichelsonPair>; +enum EncodedTicketRepr +where + Expr: super::MichelsonTicketContent + Debug + Eq, +{ + Legacy(LegacyTicketRepr), + Typed(TypedTicket), +} + +impl From> + for LegacyTicketRepr +{ + fn from(s: EncodedTicketRepr) -> Self { + match s { + EncodedTicketRepr::Legacy(l) => l, + EncodedTicketRepr::Typed(TypedTicket(contract, contents, amount)) => { + MichelsonPair(contract, MichelsonPair(contents, amount)) + } + } + } +} + /// Michelson ticket representative. #[derive(Debug, PartialEq, Eq)] -pub struct Ticket(pub TicketRepr); +pub struct Ticket(pub(crate) LegacyTicketRepr); -impl Michelson for Ticket {} +impl Michelson for Ticket where Expr: MichelsonTicketContent {} -impl NomReader for Ticket { +impl NomReader for Ticket +where + Expr: MichelsonTicketContent, +{ fn nom_read(bytes: &[u8]) -> NomResult { - map(>::nom_read, Ticket)(bytes) + use nom::branch::alt; + + map( + alt(( + map( + >::nom_read, + EncodedTicketRepr::Legacy, + ), + map(TypedTicket::::nom_read, EncodedTicketRepr::Typed), + )), + |repr| Ticket(repr.into()), + )(bytes) } } -impl BinWriter for Ticket { +impl BinWriter for Ticket { + // TODO: + // Switch the output to the new Ticket constructor. fn bin_write(&self, output: &mut Vec) -> BinResult { self.0.bin_write(output) } } -impl HasEncoding for Ticket { +impl HasEncoding for Ticket { fn encoding() -> tezos_data_encoding::encoding::Encoding { - >::encoding() + >::encoding() } } -impl Ticket { +impl Ticket { /// creates a new ticket with `creator`, `contents` and `amount`. pub fn new, Amount: Into>( creator: Contract, @@ -221,10 +372,12 @@ pub type UnitTicket = Ticket; /// Specialized version of ticket for FA2.1 tokens pub type FA2_1Ticket = - Ticket>>; + Ticket>>; #[cfg(test)] mod test { + use crate::michelson::MichelsonOr; + use super::*; use tezos_data_encoding::enc::BinWriter; use tezos_data_encoding::nom::NomReader; @@ -302,7 +455,7 @@ mod test { assert_encode_decode(ticket); } - fn assert_encode_decode(ticket: Ticket) { + fn assert_encode_decode(ticket: Ticket) { let mut bin = Vec::new(); ticket.bin_write(&mut bin).unwrap(); @@ -311,4 +464,196 @@ mod test { assert_eq!(ticket, parsed); assert!(remaining.is_empty()); } + + #[test] + fn basic_encode_decode_on_unit_ticket() { + let expected_ticket = TypedTicket( + MichelsonContract( + Contract::from_b58check("KT1FHqsvc7vRS3u54L66DdMX4gb6QKqxJ1JW").unwrap(), + ), + MichelsonUnit, + 11_i32.into(), + ); + let mut output = Vec::new(); + let result = expected_ticket.bin_write(&mut output); + assert!(result.is_ok()); + let result = TypedTicket::::nom_read(output.as_slice()); + assert!(result.is_ok()); + let (remaining, ticket) = result.unwrap(); + assert_eq!(expected_ticket, ticket); + assert!(remaining.is_empty()); + } + + #[test] + fn basic_encode_decode_on_int_ticket() { + let expected_ticket: TypedTicket = TypedTicket( + MichelsonContract( + Contract::from_b58check("KT1FHqsvc7vRS3u54L66DdMX4gb6QKqxJ1JW").unwrap(), + ), + 17_i32.into(), + 11_i32.into(), + ); + let mut output = Vec::new(); + let result = expected_ticket.bin_write(&mut output); + assert!(result.is_ok()); + let result = TypedTicket::::nom_read(output.as_slice()); + assert!(result.is_ok()); + let (remaining, ticket) = result.unwrap(); + assert_eq!(expected_ticket, ticket); + assert!(remaining.is_empty()); + } + + #[test] + fn basic_encode_decode_on_nat_ticket() { + let expected_ticket: TypedTicket = TypedTicket( + MichelsonContract( + Contract::from_b58check("KT1FHqsvc7vRS3u54L66DdMX4gb6QKqxJ1JW").unwrap(), + ), + 17_u32.into(), + 11_i32.into(), + ); + let mut output = Vec::new(); + let result = expected_ticket.bin_write(&mut output); + assert!(result.is_ok()); + let result = TypedTicket::::nom_read(output.as_slice()); + assert!(result.is_ok()); + let (remaining, ticket) = result.unwrap(); + assert_eq!(expected_ticket, ticket); + assert!(remaining.is_empty()); + } + + #[test] + fn basic_encode_decode_on_string_ticket() { + let expected_ticket = TypedTicket( + MichelsonContract( + Contract::from_b58check("KT1FHqsvc7vRS3u54L66DdMX4gb6QKqxJ1JW").unwrap(), + ), + MichelsonString("string".into()), + 11_i32.into(), + ); + let mut output = Vec::new(); + let result = expected_ticket.bin_write(&mut output); + assert!(result.is_ok()); + let result = TypedTicket::::nom_read(output.as_slice()); + assert!(result.is_ok()); + let (remaining, ticket) = result.unwrap(); + assert_eq!(expected_ticket, ticket); + assert!(remaining.is_empty()); + } + + #[test] + fn basic_encode_decode_on_bytes_ticket() { + let expected_ticket = TypedTicket( + MichelsonContract( + Contract::from_b58check("KT1FHqsvc7vRS3u54L66DdMX4gb6QKqxJ1JW").unwrap(), + ), + MichelsonBytes(vec![62, 79, 74, 65, 73]), + 11_i32.into(), + ); + let mut output = Vec::new(); + let result = expected_ticket.bin_write(&mut output); + assert!(result.is_ok()); + let result = TypedTicket::::nom_read(output.as_slice()); + assert!(result.is_ok()); + let (remaining, ticket) = result.unwrap(); + assert_eq!(expected_ticket, ticket); + assert!(remaining.is_empty()); + } + + #[test] + fn basic_encode_decode_on_option_ticket() { + // Some + let expected_ticket = TypedTicket( + MichelsonContract( + Contract::from_b58check("KT1FHqsvc7vRS3u54L66DdMX4gb6QKqxJ1JW").unwrap(), + ), + MichelsonOption(Some(MichelsonBytes(vec![62, 79, 74, 65, 73]))), + 11_i32.into(), + ); + let mut output = Vec::new(); + let result = expected_ticket.bin_write(&mut output); + assert!(result.is_ok()); + let result = TypedTicket::nom_read(output.as_slice()); + assert!(result.is_ok()); + let (remaining, ticket) = result.unwrap(); + assert_eq!(expected_ticket, ticket); + assert!(remaining.is_empty()); + + // None + let expected_ticket = TypedTicket::>( + MichelsonContract( + Contract::from_b58check("KT1FHqsvc7vRS3u54L66DdMX4gb6QKqxJ1JW").unwrap(), + ), + MichelsonOption(None), + 11_i32.into(), + ); + let mut output = Vec::new(); + let result = expected_ticket.bin_write(&mut output); + assert!(result.is_ok()); + let result = TypedTicket::nom_read(output.as_slice()); + assert!(result.is_ok()); + let (remaining, ticket) = result.unwrap(); + assert_eq!(expected_ticket, ticket); + assert!(remaining.is_empty()); + } + + #[test] + fn basic_encode_decode_on_or_ticket() { + // Left + let left: MichelsonOr = + MichelsonOr::Left(MichelsonBytes(vec![62, 79, 74, 65, 73])); + let expected_ticket = TypedTicket( + MichelsonContract( + Contract::from_b58check("KT1FHqsvc7vRS3u54L66DdMX4gb6QKqxJ1JW").unwrap(), + ), + left, + 11_i32.into(), + ); + let mut output = Vec::new(); + let result = expected_ticket.bin_write(&mut output); + assert!(result.is_ok()); + let result = TypedTicket::nom_read(output.as_slice()); + assert!(result.is_ok()); + let (remaining, ticket) = result.unwrap(); + assert_eq!(expected_ticket, ticket); + assert!(remaining.is_empty()); + + // Right + let right: MichelsonOr = + MichelsonOr::Right(MichelsonBytes(vec![62, 79, 74, 65, 73])); + let expected_ticket = TypedTicket( + MichelsonContract( + Contract::from_b58check("KT1FHqsvc7vRS3u54L66DdMX4gb6QKqxJ1JW").unwrap(), + ), + right, + 11_i32.into(), + ); + let mut output = Vec::new(); + let result = expected_ticket.bin_write(&mut output); + assert!(result.is_ok()); + let result = TypedTicket::nom_read(output.as_slice()); + assert!(result.is_ok()); + let (remaining, ticket) = result.unwrap(); + assert_eq!(expected_ticket, ticket); + assert!(remaining.is_empty()); + } + + #[test] + fn basic_encode_decode_on_pair_ticket() { + let expected_ticket = TypedTicket( + MichelsonContract( + Contract::from_b58check("KT1FHqsvc7vRS3u54L66DdMX4gb6QKqxJ1JW").unwrap(), + ), + MichelsonPair(MichelsonInt::from(5_i32), MichelsonNat::from(11_u32)), + 11_i32.into(), + ); + let mut output = Vec::new(); + let result = expected_ticket.bin_write(&mut output); + assert!(result.is_ok()); + let result = TypedTicket::nom_read(output.as_slice()); + assert!(result.is_ok()); + let (remaining, ticket) = result.unwrap(); + assert_eq!(expected_ticket, ticket); + assert!(remaining.is_empty()); + } }