From 26a1006f72c173e9bb6e63dd4d18c83d1fd5f45e Mon Sep 17 00:00:00 2001 From: Arseny Mitin Date: Tue, 11 Jun 2024 20:56:29 +0400 Subject: [PATCH 1/2] wip: rustdoc --- Cargo.lock | 2 + crates/bits/README.md | 3 + crates/bits/src/adapters.rs | 5 + crates/bits/src/as/args.rs | 3 + crates/bits/src/as/bits.rs | 4 + crates/bits/src/as/default.rs | 23 ++- crates/bits/src/as/from_into.rs | 89 ++++++++++++ crates/bits/src/as/integer.rs | 24 +++- crates/bits/src/as/mod.rs | 118 +++++++++++++++- crates/bits/src/as/same.rs | 1 + crates/{tlb-ton/src => bits/src/as}/unary.rs | 22 ++- crates/bits/src/de/args/as.rs | 62 ++++---- crates/bits/src/de/args/mod.rs | 17 ++- crates/bits/src/de/as.rs | 91 +++++------- crates/bits/src/de/mod.rs | 19 ++- crates/bits/src/de/reader.rs | 29 +++- crates/bits/src/error.rs | 7 + crates/bits/src/integer.rs | 108 ++++++++++++++ crates/bits/src/lib.rs | 127 +++++++++++++++++ crates/bits/src/ser/args/as.rs | 60 ++++---- crates/bits/src/ser/args/mod.rs | 16 ++- crates/bits/src/ser/as.rs | 86 ++++-------- crates/bits/src/ser/mod.rs | 28 +++- crates/bits/src/ser/writer.rs | 78 +++++++---- crates/contracts/Cargo.toml | 2 +- crates/contracts/README.md | 7 + crates/contracts/src/jetton/mod.rs | 1 + crates/contracts/src/jetton/wallet.rs | 31 +++- crates/contracts/src/lib.rs | 3 + crates/contracts/src/wallet/mnemonic.rs | 29 ++-- crates/contracts/src/wallet/mod.rs | 130 +++++++++++------ crates/contracts/src/wallet/v4r2/mod.rs | 6 +- crates/tlb-ton/Cargo.toml | 3 +- crates/tlb-ton/README.md | 3 + crates/tlb-ton/src/address.rs | 32 ++++- crates/tlb-ton/src/bin_tree/aug.rs | 14 +- crates/tlb-ton/src/bin_tree/mod.rs | 2 + crates/tlb-ton/src/boc.rs | 139 ++++++++++++++---- crates/tlb-ton/src/currency.rs | 20 +++ crates/tlb-ton/src/hashmap/aug.rs | 87 +++--------- crates/tlb-ton/src/hashmap/hm_label.rs | 5 +- crates/tlb-ton/src/hashmap/mod.rs | 1 + crates/tlb-ton/src/hashmap/pfx.rs | 3 + crates/tlb-ton/src/lib.rs | 12 +- crates/tlb-ton/src/message.rs | 61 +++++++- crates/tlb-ton/src/state_init.rs | 4 + crates/tlb-ton/src/timestamp.rs | 1 + crates/tlb/Cargo.toml | 3 +- crates/tlb/README.md | 3 + crates/tlb/src/as/args.rs | 22 +++ crates/tlb/src/as/data.rs | 50 +++++++ crates/tlb/src/as/default.rs | 19 ++- crates/tlb/src/as/from_into.rs | 65 ++++++++- crates/tlb/src/as/fully.rs | 2 + crates/tlb/src/as/mod.rs | 74 ++++++++++ crates/tlb/src/as/reference.rs | 1 + crates/tlb/src/cell.rs | 11 +- crates/tlb/src/de/args/as.rs | 60 ++++---- crates/tlb/src/de/args/mod.rs | 17 ++- crates/tlb/src/de/as.rs | 52 ++++--- crates/tlb/src/de/mod.rs | 15 +- crates/tlb/src/de/parser.rs | 19 +++ crates/tlb/src/lib.rs | 140 +++++++++++++++++++ crates/tlb/src/ser/args/as.rs | 58 ++++---- crates/tlb/src/ser/args/mod.rs | 16 ++- crates/tlb/src/ser/as.rs | 54 +++---- crates/tlb/src/ser/builder.rs | 22 +++ crates/tlb/src/ser/mod.rs | 14 +- crates/toner/src/lib.rs | 11 +- 69 files changed, 1839 insertions(+), 507 deletions(-) create mode 100644 crates/bits/README.md rename crates/{tlb-ton/src => bits/src/as}/unary.rs (70%) create mode 100644 crates/contracts/README.md create mode 100644 crates/tlb-ton/README.md create mode 100644 crates/tlb/README.md diff --git a/Cargo.lock b/Cargo.lock index e47e78a..00ef6e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -698,6 +698,7 @@ dependencies = [ "hex", "hex-literal", "impl-tools", + "num-bigint", "sha2", "tlbits", ] @@ -711,6 +712,7 @@ dependencies = [ "crc", "hex", "impl-tools", + "lazy_static", "num-bigint", "num-traits", "serde_json", diff --git a/crates/bits/README.md b/crates/bits/README.md new file mode 100644 index 0000000..a7a03ab --- /dev/null +++ b/crates/bits/README.md @@ -0,0 +1,3 @@ +# Binary [TL-B](https://docs.ton.org/develop/data-formats/tl-b-language) **de**/**ser**ialization +[![docs.rs](https://img.shields.io/docsrs/tlbits)](https://docs.rs/tlbits/latest/tlbits) +[![crates.io](https://img.shields.io/crates/v/tlbits)](https://crates.io/crates/tlbits) diff --git a/crates/bits/src/adapters.rs b/crates/bits/src/adapters.rs index ca4d27a..be2547d 100644 --- a/crates/bits/src/adapters.rs +++ b/crates/bits/src/adapters.rs @@ -1,11 +1,14 @@ +//! Adapters for [`BitReader`](crate::de::BitReader) / [`BitWriter`](crate::ser::BitWriter) use impl_tools::autoimpl; +/// Adapter that maps an error using given closure #[autoimpl(Deref using self.inner)] pub struct MapErr { pub(crate) inner: T, pub(crate) f: F, } +/// `tee`-like adapter for mirroring data read/written #[autoimpl(Deref using self.inner)] pub struct Tee { pub(crate) inner: T, @@ -24,6 +27,7 @@ impl Tee { } } +/// Adapter for counting the number of bits read/written. #[autoimpl(Deref using self.inner)] pub struct BitCounter { pub(crate) inner: T, @@ -36,6 +40,7 @@ impl BitCounter { Self { inner, counter: 0 } } + /// Return total number of recorded bits #[inline] pub const fn bit_count(&self) -> usize { self.counter diff --git a/crates/bits/src/as/args.rs b/crates/bits/src/as/args.rs index 582d91a..0c828f4 100644 --- a/crates/bits/src/as/args.rs +++ b/crates/bits/src/as/args.rs @@ -7,6 +7,8 @@ use crate::{ use super::Same; +/// Adapter to implement **de**/**ser**ialize with dynamic args for types +/// that do not require args for seralization. pub struct NoArgs(PhantomData<(Args, As)>); impl BitPackAsWithArgs for NoArgs @@ -39,6 +41,7 @@ where } } +/// Adapter to implement **de**/**ser**ialize with [`Default`] args. pub struct DefaultArgs(PhantomData); impl BitPackAs for DefaultArgs diff --git a/crates/bits/src/as/bits.rs b/crates/bits/src/as/bits.rs index 619becd..63ac7e2 100644 --- a/crates/bits/src/as/bits.rs +++ b/crates/bits/src/as/bits.rs @@ -5,6 +5,7 @@ use crate::{ ser::{r#as::BitPackAs, BitPack, BitWriter, BitWriterExt}, }; +/// **Ser**ialize value by taking a reference to [`BitSlice`] on it. pub struct AsBitSlice; impl BitPackAs for AsBitSlice @@ -20,6 +21,7 @@ where } } +/// **Ser**ialize value by taking a reference to `[u8]` on it. pub struct AsBytes; impl BitPackAs for AsBytes @@ -35,8 +37,10 @@ where } } +/// **De**/**ser**ialize value from/into exactly `N` bits. pub struct NBits; +/// **De**/**ser**ialize bytes by prefixing its length with `N`-bit integer. pub struct VarBytes; impl BitPackAs for VarBytes diff --git a/crates/bits/src/as/default.rs b/crates/bits/src/as/default.rs index 49b5fbb..3b3d0ee 100644 --- a/crates/bits/src/as/default.rs +++ b/crates/bits/src/as/default.rs @@ -1,11 +1,32 @@ use core::marker::PhantomData; -use crate::de::{r#as::BitUnpackAs, BitReader, BitReaderExt}; +use crate::{ + de::{r#as::BitUnpackAs, BitReader, BitReaderExt}, + ser::{r#as::BitPackAs, BitWriter, BitWriterExt}, +}; use super::Same; +/// **De**/**ser**ialize [`Default`] on `None` values pub struct DefaultOnNone(PhantomData); +impl BitPackAs> for DefaultOnNone +where + T: Default, + As: BitPackAs, +{ + fn pack_as(source: &Option, mut writer: W) -> Result<(), W::Error> + where + W: BitWriter, + { + match source { + Some(v) => writer.pack_as::<_, &As>(v)?, + None => writer.pack_as::<_, As>(T::default())?, + }; + Ok(()) + } +} + impl BitUnpackAs for DefaultOnNone where T: Default, diff --git a/crates/bits/src/as/from_into.rs b/crates/bits/src/as/from_into.rs index 3aa6943..bb97c8a 100644 --- a/crates/bits/src/as/from_into.rs +++ b/crates/bits/src/as/from_into.rs @@ -14,6 +14,11 @@ use crate::{ Error, }; +/// Serialize value by converting it to/from a proxy type +/// with serialization support. +/// +/// See [`TryFromInto`] for more generalized version of this adapter +/// which uses [`TryFrom`] trait instead pub struct FromInto(PhantomData); impl BitPackAs for FromInto @@ -74,6 +79,11 @@ where } } +/// Serialize a reference value by converting it to/from a proxy type +/// with serialization support. +/// +/// See [`TryFromIntoRef`] for more generalized version of this adapter +/// which uses [`TryFrom`] trait instead pub struct FromIntoRef(PhantomData); impl BitPackAs for FromIntoRef @@ -134,6 +144,11 @@ where } } +/// Serialize value by converting it to/from a proxy type +/// with serialization support. +/// +/// **Note:** [`FromInto`] is more specialized version of this adapter +/// which the infailable [`Into`] trait instead. pub struct TryFromInto(PhantomData); impl BitPackAs for TryFromInto @@ -207,3 +222,77 @@ where .map_err(Error::custom) } } + +/// Serialize a reference value by converting it to/from a proxy type +/// with serialization support. +/// +/// **Note:** [`FromIntoRef`] is more specialized version of this adapter +/// which the infailable [`Into`] trait instead. +pub struct TryFromIntoRef(PhantomData); + +impl BitPackAs for TryFromIntoRef +where + for<'a> &'a T: TryInto, + for<'a> <&'a T as TryInto>::Error: Display, + As: BitPack, +{ + #[inline] + fn pack_as(source: &T, writer: W) -> Result<(), W::Error> + where + W: BitWriter, + { + source.try_into().map_err(Error::custom)?.pack(writer) + } +} + +impl BitPackAsWithArgs for TryFromIntoRef +where + for<'a> &'a T: TryInto, + for<'a> <&'a T as TryInto>::Error: Display, + As: BitPackWithArgs, +{ + type Args = As::Args; + + #[inline] + fn pack_as_with(source: &T, writer: W, args: Self::Args) -> Result<(), W::Error> + where + W: BitWriter, + { + source + .try_into() + .map_err(Error::custom)? + .pack_with(writer, args) + } +} + +impl BitUnpackAs for TryFromIntoRef +where + As: TryInto + BitUnpack, + >::Error: Display, +{ + #[inline] + fn unpack_as(reader: R) -> Result + where + R: BitReader, + { + As::unpack(reader)?.try_into().map_err(Error::custom) + } +} + +impl BitUnpackAsWithArgs for TryFromIntoRef +where + As: TryInto + BitUnpackWithArgs, + >::Error: Display, +{ + type Args = As::Args; + + #[inline] + fn unpack_as_with(reader: R, args: Self::Args) -> Result + where + R: BitReader, + { + As::unpack_with(reader, args)? + .try_into() + .map_err(Error::custom) + } +} diff --git a/crates/bits/src/as/integer.rs b/crates/bits/src/as/integer.rs index 77d249a..d6ad22f 100644 --- a/crates/bits/src/as/integer.rs +++ b/crates/bits/src/as/integer.rs @@ -33,7 +33,7 @@ impl BitPackAs for NBits { let bytes = source.to_bytes_be(); let mut bits = bytes.as_bits::(); bits = &bits[bits.len() - used_bits..]; - writer.with_bits(bits)?; + writer.pack(bits)?; Ok(()) } } @@ -70,7 +70,7 @@ impl BitPackAs for NBits { let bytes = source.to_signed_bytes_be(); let mut bits = bytes.as_bits::(); bits = &bits[bits.len() - used_bits..]; - writer.with_bits(bits)?; + writer.pack(bits)?; Ok(()) } } @@ -89,6 +89,14 @@ impl BitUnpackAs for NBits { } } +/// Adapter for [`Var[U]Integer n`](https://docs.ton.org/develop/data-formats/msg-tlb#varuinteger-n) +/// where `n` is *constant*. +/// +/// ```tlb +/// var_uint$_ {n:#} len:(#< n) value:(uint (len * 8)) = VarUInteger n; +/// var_int$_ {n:#} len:(#< n) value:(int (len * 8)) = VarInteger n; +/// ``` +/// See [`VarNBits`] for *dynamic* version. pub struct VarInt; impl BitPackAs for VarInt { @@ -153,6 +161,12 @@ impl BitUnpackAs for VarInt BitPackAsWithArgs for VarNBits @@ -207,6 +221,12 @@ where } } +/// Adapter for [`Var[U]Integer n`](https://docs.ton.org/develop/data-formats/msg-tlb#varuinteger-n) where `n` is *dynamic*. +/// ```tlb +/// var_uint$_ {n:#} len:(#< n) value:(uint (len * 8)) = VarUInteger n; +/// var_int$_ {n:#} len:(#< n) value:(int (len * 8)) = VarInteger n; +/// ``` +/// See [`VarInt`] for *constant* version. pub struct VarNBytes; impl BitPackAsWithArgs for VarNBytes diff --git a/crates/bits/src/as/mod.rs b/crates/bits/src/as/mod.rs index 99ea233..414be74 100644 --- a/crates/bits/src/as/mod.rs +++ b/crates/bits/src/as/mod.rs @@ -1,8 +1,124 @@ +//! **De**/**ser**ialization helpers for +//! [TL-B](https://docs.ton.org/develop/data-formats/tl-b-language). +//! +//! This approach is heavily inspired by +//! [serde_with](https://docs.rs/serde_with/latest/serde_with). +//! Please, read their docs for more usage examples. pub mod args; mod bits; mod default; mod from_into; mod integer; mod same; +mod unary; -pub use self::{bits::*, default::*, from_into::*, integer::*, same::*}; +use std::marker::PhantomData; + +use impl_tools::autoimpl; + +use crate::{ + de::{ + args::{r#as::BitUnpackAsWithArgs, BitUnpackWithArgs}, + r#as::BitUnpackAs, + BitReader, BitUnpack, + }, + ser::{ + args::{r#as::BitPackAsWithArgs, BitPackWithArgs}, + r#as::BitPackAs, + BitPack, BitWriter, + }, +}; + +pub use self::{bits::*, default::*, from_into::*, integer::*, same::*, unary::*}; + +/// Helper to implement **de**/**ser**ialize trait for adapters +#[autoimpl(Clone where T: Clone)] +#[autoimpl(Copy where T: Copy)] +pub struct AsWrap +where + As: ?Sized, +{ + value: T, + _phantom: PhantomData, +} + +impl AsWrap +where + As: ?Sized, +{ + // Wrap given value + #[inline] + pub const fn new(value: T) -> Self { + Self { + value, + _phantom: PhantomData, + } + } + + /// Unwrap inner value + #[inline] + pub fn into_inner(self) -> T { + self.value + } +} + +impl<'a, T, As> BitPack for AsWrap<&'a T, As> +where + T: ?Sized, + As: BitPackAs + ?Sized, +{ + #[inline] + fn pack(&self, writer: W) -> Result<(), W::Error> + where + W: BitWriter, + { + As::pack_as(self.value, writer) + } +} + +impl<'a, T, As> BitPackWithArgs for AsWrap<&'a T, As> +where + T: ?Sized, + As: BitPackAsWithArgs + ?Sized, +{ + type Args = As::Args; + + #[inline] + fn pack_with(&self, writer: W, args: Self::Args) -> Result<(), W::Error> + where + W: BitWriter, + { + As::pack_as_with(self.into_inner(), writer, args) + } +} + +impl BitUnpack for AsWrap +where + As: BitUnpackAs + ?Sized, +{ + #[inline] + fn unpack(reader: R) -> Result + where + R: BitReader, + { + As::unpack_as(reader).map(|value| Self { + value, + _phantom: PhantomData, + }) + } +} + +impl BitUnpackWithArgs for AsWrap +where + As: BitUnpackAsWithArgs + ?Sized, +{ + type Args = As::Args; + + #[inline] + fn unpack_with(reader: R, args: Self::Args) -> Result + where + R: BitReader, + { + As::unpack_as_with(reader, args).map(Self::new) + } +} diff --git a/crates/bits/src/as/same.rs b/crates/bits/src/as/same.rs index 9841054..fe4c38e 100644 --- a/crates/bits/src/as/same.rs +++ b/crates/bits/src/as/same.rs @@ -11,6 +11,7 @@ use crate::{ }, }; +/// Adapter to convert from `*As` to regular **de**/**ser**ialization traits. pub struct Same; impl BitPackAs for Same diff --git a/crates/tlb-ton/src/unary.rs b/crates/bits/src/as/unary.rs similarity index 70% rename from crates/tlb-ton/src/unary.rs rename to crates/bits/src/as/unary.rs index 4104dde..d8c2ee9 100644 --- a/crates/tlb-ton/src/unary.rs +++ b/crates/bits/src/as/unary.rs @@ -1,14 +1,13 @@ -use core::iter; - use num_traits::{ConstZero, One, ToPrimitive, Unsigned}; -use tlb::{ - bits::{ - de::{r#as::BitUnpackAs, BitReader}, - ser::{r#as::BitPackAs, BitWriter, BitWriterExt}, - }, + +use crate::{ + de::{r#as::BitUnpackAs, BitReader}, + ser::{r#as::BitPackAs, BitWriter, BitWriterExt}, Error, }; +/// [`Unary ~n`](https://docs.ton.org/develop/data-formats/tl-b-types#unary) +/// adapter /// ```tlb /// unary_zero$0 = Unary ~0; /// unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1); @@ -26,11 +25,10 @@ where { writer // unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1); - .pack_many( - iter::repeat(true).take( - num.to_usize() - .ok_or_else(|| Error::custom("cannot be represented as usize"))?, - ), + .with_repeat_bit( + num.to_usize() + .ok_or_else(|| Error::custom("cannot be represented as usize"))?, + true, )? // unary_zero$0 = Unary ~0; .pack(false)?; diff --git a/crates/bits/src/de/args/as.rs b/crates/bits/src/de/args/as.rs index 3c3e7db..6a1ee31 100644 --- a/crates/bits/src/de/args/as.rs +++ b/crates/bits/src/de/args/as.rs @@ -1,36 +1,42 @@ use core::mem::MaybeUninit; use std::{rc::Rc, sync::Arc}; +use bitvec::{order::Msb0, slice::BitSlice}; use either::Either; use super::{ - super::{r#as::UnpackAsWrap, BitReader, BitReaderExt}, + super::{BitReader, BitReaderExt}, BitUnpackWithArgs, }; -use crate::{r#as::args::NoArgs, ResultExt}; +use crate::{ + r#as::{args::NoArgs, AsWrap}, + ResultExt, StringError, +}; +/// Adapter to **de**serialize `T` with args. +/// See [`as`](crate::as) module-level documentation for more. +/// +/// For version without arguments, see [`BitUnpackAs`](super::super::as::BitUnpackAs). pub trait BitUnpackAsWithArgs { type Args; + /// Unpacks value with args using an adapter fn unpack_as_with(reader: R, args: Self::Args) -> Result where R: BitReader; } -impl BitUnpackWithArgs for UnpackAsWrap +/// **De**serialize value from [`BitSlice`] with args using an adapter +#[inline] +pub fn unpack_as_with( + bits: impl AsRef>, + args: As::Args, +) -> Result where - As: BitUnpackAsWithArgs + ?Sized, + As: BitUnpackAsWithArgs, { - type Args = As::Args; - - #[inline] - fn unpack_with(reader: R, args: Self::Args) -> Result - where - R: BitReader, - { - As::unpack_as_with(reader, args).map(Self::new) - } + bits.as_ref().unpack_as_with::<_, As>(args) } impl BitUnpackAsWithArgs<[T; N]> for [As; N] @@ -116,8 +122,8 @@ where where R: BitReader, { - UnpackAsWrap::::unpack_with(reader, args) - .map(UnpackAsWrap::into_inner) + AsWrap::::unpack_with(reader, args) + .map(AsWrap::into_inner) .map(Into::into) } } @@ -133,8 +139,8 @@ where where R: BitReader, { - UnpackAsWrap::::unpack_with(reader, args) - .map(UnpackAsWrap::into_inner) + AsWrap::::unpack_with(reader, args) + .map(AsWrap::into_inner) .map(Into::into) } } @@ -150,12 +156,17 @@ where where R: BitReader, { - UnpackAsWrap::::unpack_with(reader, args) - .map(UnpackAsWrap::into_inner) + AsWrap::::unpack_with(reader, args) + .map(AsWrap::into_inner) .map(Into::into) } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl BitUnpackAsWithArgs> for Either where @@ -170,10 +181,8 @@ where R: BitReader, { Ok( - Either::, UnpackAsWrap>::unpack_with( - reader, args, - )? - .map_either(UnpackAsWrap::into_inner, UnpackAsWrap::into_inner), + Either::, AsWrap>::unpack_with(reader, args)? + .map_either(AsWrap::into_inner, AsWrap::into_inner), ) } } @@ -195,6 +204,11 @@ where } } +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl BitUnpackAsWithArgs> for Option where As: BitUnpackAsWithArgs, @@ -206,6 +220,6 @@ where where R: BitReader, { - Ok(Option::>::unpack_with(reader, args)?.map(UnpackAsWrap::into_inner)) + Ok(Option::>::unpack_with(reader, args)?.map(AsWrap::into_inner)) } } diff --git a/crates/bits/src/de/args/mod.rs b/crates/bits/src/de/args/mod.rs index 2e68de7..9c6ad05 100644 --- a/crates/bits/src/de/args/mod.rs +++ b/crates/bits/src/de/args/mod.rs @@ -13,9 +13,14 @@ use crate::{ use super::{BitReader, BitReaderExt}; +/// A type that can be bitwise-**de**serialized from any [`BitReader`]. +/// In contrast with [`BitUnpack`](super::BitUnpack) it allows to pass +/// [`Args`](BitUnpackWithArgs::Args) and these arguments can be +/// calculated dynamically in runtime. pub trait BitUnpackWithArgs: Sized { type Args; + /// Unpacks the value with args fn unpack_with(reader: R, args: Self::Args) -> Result where R: BitReader; @@ -78,6 +83,7 @@ where T: BitUnpackWithArgs, T::Args: Clone, { + /// (len, T::Args) type Args = (usize, T::Args); #[inline] @@ -134,6 +140,11 @@ where } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl BitUnpackWithArgs for Either where Left: BitUnpackWithArgs, @@ -153,7 +164,11 @@ where } } -/// [Maybe](https://docs.ton.org/develop/data-formats/tl-b-types#maybe) +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl BitUnpackWithArgs for Option where T: BitUnpackWithArgs, diff --git a/crates/bits/src/de/as.rs b/crates/bits/src/de/as.rs index 28f2115..7f13cf4 100644 --- a/crates/bits/src/de/as.rs +++ b/crates/bits/src/de/as.rs @@ -1,19 +1,26 @@ -use core::{marker::PhantomData, mem::MaybeUninit}; +use core::mem::MaybeUninit; use std::{rc::Rc, sync::Arc}; use bitvec::{order::Msb0, slice::BitSlice, view::AsBits}; use either::Either; -use crate::{Error, ResultExt, StringError}; +use crate::{r#as::AsWrap, Error, ResultExt, StringError}; use super::{BitReader, BitReaderExt, BitUnpack}; +/// Adapter to **de**serialize `T`. +/// See [`as`](crate::as) module-level documentation for more. +/// +/// For dynamic arguments, see +/// [`BitUnackAsWithArgs`](super::args::as::BitUnpackAsWithArgs). pub trait BitUnpackAs { + /// Unpacks value using an adapter fn unpack_as(reader: R) -> Result where R: BitReader; } +/// **De**serialize value from [`BitSlice`] using an adapter #[inline] pub fn unpack_as(bits: impl AsRef>) -> Result where @@ -22,6 +29,7 @@ where bits.as_ref().unpack_as::() } +/// **De**serialize value from bytes slice using an adapter #[inline] pub fn unpack_bytes_as(bytes: impl AsRef<[u8]>) -> Result where @@ -30,6 +38,8 @@ where unpack_as::<_, As>(bytes.as_bits()) } +/// **De**serialize value from [`BitSlice`] using an adapter +/// and ensure that no more data left. #[inline] pub fn unpack_fully_as(bits: impl AsRef>) -> Result where @@ -43,6 +53,8 @@ where Ok(v) } +/// **De**serialize value from bytes slice using an adapter +/// and ensure that no more data left. #[inline] pub fn unpack_bytes_fully_as(bytes: impl AsRef<[u8]>) -> Result where @@ -51,49 +63,6 @@ where unpack_fully_as::<_, As>(bytes.as_bits()) } -pub struct UnpackAsWrap -where - As: ?Sized, -{ - value: T, - _phantom: PhantomData, -} - -impl UnpackAsWrap -where - As: ?Sized, -{ - #[inline] - pub fn new(value: T) -> Self { - Self { - value, - _phantom: PhantomData, - } - } - - /// Return the inner value of type `T`. - #[inline] - pub fn into_inner(self) -> T { - self.value - } -} - -impl BitUnpack for UnpackAsWrap -where - As: BitUnpackAs + ?Sized, -{ - #[inline] - fn unpack(reader: R) -> Result - where - R: BitReader, - { - As::unpack_as(reader).map(|value| Self { - value, - _phantom: PhantomData, - }) - } -} - impl BitUnpackAs<[T; N]> for [As; N] where As: BitUnpackAs, @@ -151,8 +120,8 @@ where where R: BitReader, { - UnpackAsWrap::::unpack(reader) - .map(UnpackAsWrap::into_inner) + AsWrap::::unpack(reader) + .map(AsWrap::into_inner) .map(Box::new) } } @@ -166,8 +135,8 @@ where where R: BitReader, { - UnpackAsWrap::::unpack(reader) - .map(UnpackAsWrap::into_inner) + AsWrap::::unpack(reader) + .map(AsWrap::into_inner) .map(Rc::new) } } @@ -181,12 +150,17 @@ where where R: BitReader, { - UnpackAsWrap::::unpack(reader) - .map(UnpackAsWrap::into_inner) + AsWrap::::unpack(reader) + .map(AsWrap::into_inner) .map(Arc::new) } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl BitUnpackAs> for Either where AsLeft: BitUnpackAs, @@ -198,8 +172,8 @@ where R: BitReader, { Ok( - Either::, UnpackAsWrap>::unpack(reader)? - .map_either(UnpackAsWrap::into_inner, UnpackAsWrap::into_inner), + Either::, AsWrap>::unpack(reader)? + .map_either(AsWrap::into_inner, AsWrap::into_inner), ) } } @@ -213,12 +187,17 @@ where where R: BitReader, { - Ok(Either::<(), UnpackAsWrap>::unpack(reader)? - .map_right(UnpackAsWrap::into_inner) + Ok(Either::<(), AsWrap>::unpack(reader)? + .map_right(AsWrap::into_inner) .right()) } } +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl BitUnpackAs> for Option where As: BitUnpackAs, @@ -228,6 +207,6 @@ where where R: BitReader, { - Ok(Option::>::unpack(reader)?.map(UnpackAsWrap::into_inner)) + Ok(Option::>::unpack(reader)?.map(AsWrap::into_inner)) } } diff --git a/crates/bits/src/de/mod.rs b/crates/bits/src/de/mod.rs index ea659aa..e48b267 100644 --- a/crates/bits/src/de/mod.rs +++ b/crates/bits/src/de/mod.rs @@ -1,3 +1,4 @@ +//! Binary **de**serialization for [TL-B](https://docs.ton.org/develop/data-formats/tl-b-language) pub mod args; pub mod r#as; mod reader; @@ -15,12 +16,15 @@ use crate::{ Error, ResultExt, StringError, }; +/// A type that can be bitwise-**de**serialized from any [`BitReader`]. pub trait BitUnpack: Sized { + /// Unpack value from the reader. fn unpack(reader: R) -> Result where R: BitReader; } +/// **De**serialize the value from [`BitSlice`] #[inline] pub fn unpack(bits: impl AsRef>) -> Result where @@ -29,6 +33,7 @@ where bits.as_ref().unpack() } +/// **De**serialize the value from bytes slice #[inline] pub fn unpack_bytes(bytes: impl AsRef<[u8]>) -> Result where @@ -37,6 +42,7 @@ where unpack(bytes.as_bits()) } +/// **De**serialize the value from [`BitSlice`] and ensure that no more data left. #[inline] pub fn unpack_fully(bits: impl AsRef>) -> Result where @@ -50,6 +56,8 @@ where Ok(v) } +/// **De**serialize the value from bytes slice and ensure that no more data left. + #[inline] pub fn unpack_bytes_fully(bytes: impl AsRef<[u8]>) -> Result where @@ -164,6 +172,11 @@ where } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl BitUnpack for Either where Left: BitUnpack, @@ -181,7 +194,11 @@ where } } -/// [Maybe](https://docs.ton.org/develop/data-formats/tl-b-types#maybe) +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl BitUnpack for Option where T: BitUnpack, diff --git a/crates/bits/src/de/reader.rs b/crates/bits/src/de/reader.rs index 1691113..23db4dd 100644 --- a/crates/bits/src/de/reader.rs +++ b/crates/bits/src/de/reader.rs @@ -15,12 +15,17 @@ use super::{ BitUnpack, }; +/// Bitwise reader. #[autoimpl(for &mut R, Box)] pub trait BitReader { + // An error ocurred while reading type Error: Error; + /// Reads only one bit. fn read_bit(&mut self) -> Result; + /// Reads `dst.len()` bits into given bitslice. + /// Might be optimized by the implementation. #[inline] fn read_bits_into(&mut self, dst: &mut BitSlice) -> Result<(), Self::Error> { for mut bit in dst.iter_mut() { @@ -29,6 +34,7 @@ pub trait BitReader { Ok(()) } + /// Reads and discards `n` bits #[inline] fn skip(&mut self, n: usize) -> Result<(), Self::Error> { for _ in 0..n { @@ -38,7 +44,9 @@ pub trait BitReader { } } +/// Extension helper for [`BitReader`]. pub trait BitReaderExt: BitReader { + /// Reads `n` bits and returns newly created [`BitVec`] #[inline] fn read_bitvec(&mut self, n: usize) -> Result, Self::Error> { let mut dst = BitVec::with_capacity(n); @@ -47,11 +55,13 @@ pub trait BitReaderExt: BitReader { Ok(dst) } + /// Reads `dst.len()` bytes into given byte slice #[inline] fn read_bytes_into(&mut self, mut dst: impl AsMut<[u8]>) -> Result<(), Self::Error> { self.read_bits_into(dst.as_mut_bits()) } + /// Read `N` bytes and return array #[inline] fn read_bytes_array(&mut self) -> Result<[u8; N], Self::Error> { let mut arr = [0; N]; @@ -59,6 +69,7 @@ pub trait BitReaderExt: BitReader { Ok(arr) } + /// Read `n` bytes and return [`Vec`] #[inline] fn read_bytes_vec(&mut self, n: usize) -> Result, Self::Error> { let mut v = vec![0; n]; @@ -66,6 +77,7 @@ pub trait BitReaderExt: BitReader { Ok(v) } + /// Unpack value using its [`BitUnpack`] implementation #[inline] fn unpack(&mut self) -> Result where @@ -74,6 +86,7 @@ pub trait BitReaderExt: BitReader { T::unpack(self) } + /// Unpack value witg args using its [`BitUnpackWithArgs`] implementation #[inline] fn unpack_with(&mut self, args: T::Args) -> Result where @@ -82,6 +95,7 @@ pub trait BitReaderExt: BitReader { T::unpack_with(self, args) } + /// Return iterator that unpacks values using [`BitUnpack`] implementation #[inline] fn unpack_iter(&mut self) -> impl Iterator> + '_ where @@ -92,6 +106,7 @@ pub trait BitReaderExt: BitReader { .map(|(i, v)| v.with_context(|| format!("[{i}]"))) } + /// Return iterator that unpacks values with args using [`BitUnpackWithArgs`] implementation #[inline] fn unpack_iter_with<'a, T>( &'a mut self, @@ -106,6 +121,8 @@ pub trait BitReaderExt: BitReader { .map(|(i, v)| v.with_context(|| format!("[{i}]"))) } + /// Unpack value using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] fn unpack_as(&mut self) -> Result where @@ -114,6 +131,8 @@ pub trait BitReaderExt: BitReader { As::unpack_as(self) } + /// Unpack value with args using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] fn unpack_as_with(&mut self, args: As::Args) -> Result where @@ -122,6 +141,8 @@ pub trait BitReaderExt: BitReader { As::unpack_as_with(self, args) } + /// Returns iterator that unpacks values using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] fn unpack_iter_as(&mut self) -> impl Iterator> + '_ where @@ -132,6 +153,8 @@ pub trait BitReaderExt: BitReader { .map(|(i, v)| v.with_context(|| format!("[{i}]"))) } + /// Returns iterator that unpacks values with args using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] fn unpack_iter_as_with<'a, T, As>( &'a mut self, @@ -146,11 +169,13 @@ pub trait BitReaderExt: BitReader { .map(|(i, v)| v.with_context(|| format!("[{i}]"))) } + /// Borrows reader, rather than consuming it. #[inline] fn as_mut(&mut self) -> &mut Self { self } + /// Map [`Error`](BitReader::Error) by given closure #[inline] fn map_err(self, f: F) -> MapErr where @@ -159,6 +184,7 @@ pub trait BitReaderExt: BitReader { MapErr { inner: self, f } } + /// Mirror all read data to given writer as well. #[inline] fn tee(self, writer: W) -> Tee where @@ -171,6 +197,7 @@ pub trait BitReaderExt: BitReader { } } } +impl BitReaderExt for T where T: BitReader {} impl BitReader for MapErr where @@ -195,8 +222,6 @@ where } } -impl BitReaderExt for T where T: BitReader {} - impl BitReader for Tee where R: BitReader, diff --git a/crates/bits/src/error.rs b/crates/bits/src/error.rs index 8017524..6e027cf 100644 --- a/crates/bits/src/error.rs +++ b/crates/bits/src/error.rs @@ -3,21 +3,27 @@ use std::error::Error as StdError; use thiserror::Error as ThisError; +/// **De**/**ser**ialization error pub trait Error: StdError + Sized { + /// Returns a custom error from given message fn custom(msg: T) -> Self where T: Display; + /// Wraps current error in given context fn context(self, context: C) -> Self where C: Display; } +/// Adapter for providing context on [`Result`] pub trait ResultExt: Sized { + /// Wrap [`Err`] in context by calling given function fn with_context(self, context: impl FnOnce() -> C) -> Self where C: Display; + /// Wrap [`Err`] in given context #[inline] fn context(self, context: C) -> Self where @@ -40,6 +46,7 @@ where } } +/// [`String`]-backed [`Error`] #[derive(Debug, ThisError)] #[error("{0}")] pub struct StringError(String); diff --git a/crates/bits/src/integer.rs b/crates/bits/src/integer.rs index 6ec7d08..7fd5083 100644 --- a/crates/bits/src/integer.rs +++ b/crates/bits/src/integer.rs @@ -1,3 +1,4 @@ +//! Collection of **de**/**ser**ialization helpers for integers use core::mem; use bitvec::{ @@ -13,6 +14,52 @@ use crate::{ Error, }; +/// Constant version of `bool` +/// +/// ## Deserialization +/// +/// Reads `bool` and returns an error if it didn't match the +/// type parameter. +/// +/// ```rust +/// # use tlbits::{ +/// # bitvec::{bits, order::Msb0}, +/// # de::{BitReaderExt}, +/// # Error, +/// # integer::ConstBit, +/// # StringError, +/// # }; +/// # fn main() -> Result<(), StringError> { +/// # let mut reader = bits![u8, Msb0; 1, 1]; +/// reader.unpack::>()?; +/// // is equivalent of: +/// if !reader.unpack::()? { +/// return Err(Error::custom("expected 1, got 0")); +/// } +/// # Ok(()) +/// # } +/// ``` +/// +/// ## Serialization +/// +/// Writes `bool` specified in type parameter. +/// +/// ```rust +/// # use tlbits::{ +/// # bitvec::{bits, vec::BitVec, order::Msb0}, +/// # integer::ConstBit, +/// # ser::BitWriterExt, +/// # StringError, +/// # }; +/// # fn main() -> Result<(), StringError> { +/// # let mut writer = BitVec::::new(); +/// writer.pack(ConstBit::)?; +/// // is equivalent of: +/// writer.pack(true)?; +/// # assert_eq!(writer, bits![u8, Msb0; 1, 1]); +/// # Ok(()) +/// # } +/// ``` pub struct ConstBit; impl BitPack for ConstBit { @@ -108,6 +155,62 @@ impl_bit_serde_for_integers! { macro_rules! const_uint { ($($vis:vis $name:ident<$typ:tt, $bits:literal>)+) => {$( + #[doc = concat!("Constant version of `", stringify!($typ), "`")] + /// ## Deserialization + #[doc = concat!( + "Reads `", stringify!($typ), "` and returns an error + if it didn't match the type parameter.", + )] + /// + /// ```rust + /// # use tlbits::{ + /// # bitvec::{vec::BitVec, order::Msb0}, + /// # de::BitReaderExt, + /// # Error, + #[doc = concat!("# integer::", stringify!($name), ",")] + /// # ser::BitWriterExt, + /// # StringError, + /// # }; + /// # fn main() -> Result<(), StringError> { + /// # let mut buff = BitVec::::new(); + #[doc = concat!("# buff.pack::<[", stringify!($typ), "; 2]>([123; 2])?;")] + /// # let mut reader = buff.as_bitslice(); + #[doc = concat!("reader.unpack::<", stringify!($name), "<123>>()?;")] + /// // is equivalent of: + #[doc = concat!("let got: ", stringify!($typ), " = reader.unpack()?;")] + /// if got != 123 { + /// return Err(Error::custom(format!("expected 123, got {got}"))); + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Serialization + /// + #[doc = concat!( + "Writes `", stringify!($typ), "` as specified in type parameter." + )] + /// + /// ```rust + /// # use tlbits::{ + /// # bitvec::{bits, vec::BitVec, order::Msb0}, + /// # de::BitReaderExt, + #[doc = concat!("# integer::", stringify!($name), ",")] + /// # ser::BitWriterExt, + /// # StringError, + /// # }; + /// # fn main() -> Result<(), StringError> { + /// # let mut writer = BitVec::::new(); + #[doc = concat!("writer.pack(", stringify!($name), "::<123>)?;")] + /// // is equivalent of: + #[doc = concat!("writer.pack::<", stringify!($typ), ">(123)?;")] + /// # let mut reader = writer.as_bitslice(); + #[doc = concat!( + "# assert_eq!(reader.unpack::<[", stringify!($typ), "; 2]>()?, [123; 2]);" + )] + /// # Ok(()) + /// # } + /// ``` $vis struct $name; impl BitPack for $name { @@ -141,10 +244,15 @@ macro_rules! const_uint { const_uint! { pub ConstU8 + pub ConstI8 pub ConstU16 + pub ConstI16 pub ConstU32 + pub ConstI32 pub ConstU64 + pub ConstI64 pub ConstU128 + pub ConstI128 } #[cfg(test)] diff --git a/crates/bits/src/lib.rs b/crates/bits/src/lib.rs index 0fcef2b..54384ed 100644 --- a/crates/bits/src/lib.rs +++ b/crates/bits/src/lib.rs @@ -1,3 +1,130 @@ +#![doc = include_str!("../README.md")] +//! ## Example +//! +//! Consider the following TL-B schema: +//! +//! ```tlb +//! tag$10 query_id:uint64 amount:(VarUInteger 16) = Hello; +//! ``` +//! +//! Let's first define a struct `Hello` that holds these parameters: +//! +//! ```rust +//! # use num_bigint::BigUint; +//! struct Hello { +//! pub query_id: u64, +//! pub amount: BigUint, +//! } +//! ``` +//! +//! ### **Ser**ialization +//! +//! To be able to **ser**ialize a type to [`BitWriter`](crate::ser::BitWriter), +//! we should implement [`BitPack`](crate::ser::BitPack) on it: +//! +//! ``` +//! # use bitvec::{vec::BitVec, order::Msb0}; +//! # use num_bigint::BigUint; +//! # use tlbits::{ +//! # r#as::{NBits, VarInt}, +//! # ser::{BitPack, BitWriter, BitWriterExt, pack}, +//! # StringError, +//! # }; +//! # +//! # struct Hello { +//! # pub query_id: u64, +//! # pub amount: BigUint, +//! # } +//! impl BitPack for Hello { +//! fn pack(&self, mut writer: W) -> Result<(), W::Error> +//! where W: BitWriter, +//! { +//! writer +//! // tag$10 +//! .pack_as::<_, NBits<2>>(0b10)? +//! // query_id:uint64 +//! .pack(self.query_id)? +//! // amount:(VarUInteger 16) +//! .pack_as::<_, &VarInt<4>>(&self.amount)?; +//! Ok(()) +//! } +//! } +//! +//! # fn main() -> Result<(), StringError> { +//! # let mut writer = BitVec::::new().counted(); +//! writer.pack(Hello { +//! query_id: 0, +//! amount: 1_000u64.into(), +//! })?; +//! # Ok(()) +//! # } +//! ``` +//! +//! ### **De**serialization +//! +//! To be able to **de**serialize a type from [`BitReader`](crate::de::BitReader), +//! we should implement [`BitUnpack`](crate::de::BitUnpack) on it: +//! +//! ```rust +//! # use bitvec::{vec::BitVec, order::Msb0}; +//! # use num_bigint::BigUint; +//! # use tlbits::{ +//! # r#as::{NBits, VarInt}, +//! # de::{BitReaderExt, BitReader, BitUnpack}, +//! # Error, +//! # ser::{BitPack, BitWriter, BitWriterExt, pack}, +//! # StringError, +//! # }; +//! # #[derive(Debug, PartialEq)] +//! # struct Hello { +//! # pub query_id: u64, +//! # pub amount: BigUint, +//! # } +//! # impl BitPack for Hello { +//! # fn pack(&self, mut writer: W) -> Result<(), W::Error> +//! # where W: BitWriter, +//! # { +//! # writer +//! # // tag$10 +//! # .pack_as::<_, NBits<2>>(0b10)? +//! # // query_id:uint64 +//! # .pack(self.query_id)? +//! # // amount:(VarUInteger 16) +//! # .pack_as::<_, &VarInt<4>>(&self.amount)?; +//! # Ok(()) +//! # } +//! # } +//! impl BitUnpack for Hello { +//! fn unpack(mut reader: R) -> Result +//! where R: BitReader, +//! { +//! // tag$10 +//! let tag: u8 = reader.unpack_as::<_, NBits<2>>()?; +//! if tag != 0b10 { +//! return Err(Error::custom(format!("unknown tag: {tag:#b}"))); +//! } +//! Ok(Self { +//! // query_id:uint64 +//! query_id: reader.unpack()?, +//! // amount:(VarUInteger 16) +//! amount: reader.unpack_as::<_, VarInt<4>>()?, +//! }) +//! } +//! } +//! +//! # fn main() -> Result<(), StringError> { +//! # let orig = Hello { +//! # query_id: 0, +//! # amount: 1_000u64.into(), +//! # }; +//! # let mut writer = BitVec::::new().counted(); +//! # writer.pack(&orig)?; +//! # let mut parser = writer.as_bitslice(); +//! let hello: Hello = parser.unpack()?; +//! # assert_eq!(hello, orig); +//! # Ok(()) +//! # } +//! ``` pub mod adapters; pub mod r#as; pub mod de; diff --git a/crates/bits/src/ser/args/as.rs b/crates/bits/src/ser/args/as.rs index 5b1c5b0..56be0ca 100644 --- a/crates/bits/src/ser/args/as.rs +++ b/crates/bits/src/ser/args/as.rs @@ -1,37 +1,38 @@ use std::{rc::Rc, sync::Arc}; +use bitvec::{order::Msb0, vec::BitVec}; use either::Either; use crate::{ - r#as::args::NoArgs, - ser::{r#as::PackAsWrap, BitWriter, BitWriterExt}, + r#as::{args::NoArgs, AsWrap}, + ser::{BitWriter, BitWriterExt}, + StringError, }; use super::BitPackWithArgs; +/// Adapter to **ser**ialize `T` with args. +/// See [`as`](crate::as) module-level documentation for more. +/// +/// For version without arguments, see [`BitPackAs`](super::super::as::BitPackAs). pub trait BitPackAsWithArgs { type Args; + /// Packs the value with args using an adapter fn pack_as_with(source: &T, writer: W, args: Self::Args) -> Result<(), W::Error> where W: BitWriter; } -impl<'a, T, As> BitPackWithArgs for PackAsWrap<'a, T, As> +/// **Ser**ialize given value into [`BitVec`] with argmuments using an adapter +#[inline] +pub fn pack_as_with(value: T, args: As::Args) -> Result, StringError> where - T: ?Sized, - As: ?Sized, - As: BitPackAsWithArgs, + As: BitPackAsWithArgs + ?Sized, { - type Args = As::Args; - - #[inline] - fn pack_with(&self, writer: W, args: Self::Args) -> Result<(), W::Error> - where - W: BitWriter, - { - As::pack_as_with(self.into_inner(), writer, args) - } + let mut writer = BitVec::new(); + writer.pack_as_with::<_, As>(value, args)?; + Ok(writer) } impl<'a, T, As> BitPackAsWithArgs<&'a T> for &'a As @@ -46,7 +47,7 @@ where where W: BitWriter, { - PackAsWrap::::new(source).pack_with(writer, args) + AsWrap::<&T, As>::new(source).pack_with(writer, args) } } @@ -62,7 +63,7 @@ where where W: BitWriter, { - PackAsWrap::::new(source).pack_with(writer, args) + AsWrap::<&T, As>::new(source).pack_with(writer, args) } } @@ -142,7 +143,7 @@ where where W: BitWriter, { - PackAsWrap::::new(source).pack_with(writer, args) + AsWrap::<&T, As>::new(source).pack_with(writer, args) } } @@ -157,10 +158,15 @@ where where W: BitWriter, { - PackAsWrap::::new(source).pack_with(writer, args) + AsWrap::<&T, As>::new(source).pack_with(writer, args) } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl BitPackAsWithArgs> for Either where @@ -180,10 +186,7 @@ where { source .as_ref() - .map_either( - PackAsWrap::::new, - PackAsWrap::::new, - ) + .map_either(AsWrap::<&Left, AsLeft>::new, AsWrap::<&Right, AsRight>::new) .pack_with(writer, args) } } @@ -201,8 +204,8 @@ where { BitPackWithArgs::pack_with( &match source.as_ref() { - None => Either::Left(PackAsWrap::<_, NoArgs<_>>::new(&())), - Some(v) => Either::Right(PackAsWrap::::new(v)), + None => Either::Left(AsWrap::<_, NoArgs<_>>::new(&())), + Some(v) => Either::Right(AsWrap::<&T, As>::new(v)), }, writer, args, @@ -210,6 +213,11 @@ where } } +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl BitPackAsWithArgs> for Option where As: BitPackAsWithArgs, @@ -223,7 +231,7 @@ where { source .as_ref() - .map(PackAsWrap::::new) + .map(AsWrap::<&T, As>::new) .pack_with(writer, args) } } diff --git a/crates/bits/src/ser/args/mod.rs b/crates/bits/src/ser/args/mod.rs index 367a7e2..663ecf1 100644 --- a/crates/bits/src/ser/args/mod.rs +++ b/crates/bits/src/ser/args/mod.rs @@ -9,10 +9,15 @@ use crate::{r#as::Same, ResultExt}; use super::{BitWriter, BitWriterExt}; +/// A type that can be bitwise-**ser**ialized into any [`BitWriter`]. +/// In contrast with [`BitPack`](super::BitPack) it allows to pass +/// [`Args`](BitPackWithArgs::Args) and these arguments can be +/// calculated dynamically in runtime. #[autoimpl(for &S, &mut S, Box, Rc, Arc)] pub trait BitPackWithArgs { type Args; + /// Packs the value into given writer with args fn pack_with(&self, writer: W, args: Self::Args) -> Result<(), W::Error> where W: BitWriter; @@ -98,6 +103,11 @@ impl_bit_pack_with_args_for_tuple!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7); impl_bit_pack_with_args_for_tuple!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8); impl_bit_pack_with_args_for_tuple!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8,9:T9); +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl BitPackWithArgs for Either where L: BitPackWithArgs, @@ -126,7 +136,11 @@ where } } -/// [Maybe](https://docs.ton.org/develop/data-formats/tl-b-types#maybe) +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl BitPackWithArgs for Option where T: BitPackWithArgs, diff --git a/crates/bits/src/ser/as.rs b/crates/bits/src/ser/as.rs index 2e2c690..0916a55 100644 --- a/crates/bits/src/ser/as.rs +++ b/crates/bits/src/ser/as.rs @@ -1,19 +1,25 @@ -use core::marker::PhantomData; use std::{rc::Rc, sync::Arc}; use bitvec::{order::Msb0, vec::BitVec}; use either::Either; -use crate::{ResultExt, StringError}; +use crate::{r#as::AsWrap, ResultExt, StringError}; use super::{BitPack, BitWriter, BitWriterExt}; +/// Adapter to **ser**ialize `T`. +/// See [`as`](crate::as) module-level documentation for more. +/// +/// For dynamic arguments, see +/// [`BitPackAsWithArgs`](super::args::as::BitPackAsWithArgs). pub trait BitPackAs { + /// Packs given value using an adapter. fn pack_as(source: &T, writer: W) -> Result<(), W::Error> where W: BitWriter; } +/// **Ser**ialize given value into [`BitVec`] using an adapter #[inline] pub fn pack_as(value: T) -> Result, StringError> where @@ -24,49 +30,6 @@ where Ok(writer) } -pub struct PackAsWrap<'a, T, As> -where - As: ?Sized, - T: ?Sized, -{ - value: &'a T, - _phantom: PhantomData, -} - -impl<'a, T, As> PackAsWrap<'a, T, As> -where - T: ?Sized, - As: ?Sized, -{ - #[inline] - pub const fn new(value: &'a T) -> Self { - Self { - value, - _phantom: PhantomData, - } - } - - #[inline] - pub const fn into_inner(&'a self) -> &'a T { - self.value - } -} - -impl<'a, T, As> BitPack for PackAsWrap<'a, T, As> -where - T: ?Sized, - As: ?Sized, - As: BitPackAs, -{ - #[inline] - fn pack(&self, writer: W) -> Result<(), W::Error> - where - W: BitWriter, - { - As::pack_as(self.value, writer) - } -} - impl<'a, T, As> BitPackAs<&'a T> for &'a As where As: BitPackAs + ?Sized, @@ -77,7 +40,7 @@ where where W: BitWriter, { - PackAsWrap::::new(source).pack(writer) + AsWrap::<&T, As>::new(source).pack(writer) } } @@ -91,7 +54,7 @@ where where W: BitWriter, { - PackAsWrap::::new(source).pack(writer) + AsWrap::<&T, As>::new(source).pack(writer) } } @@ -166,7 +129,7 @@ where where W: BitWriter, { - PackAsWrap::::new(source).pack(writer) + AsWrap::<&T, As>::new(source).pack(writer) } } @@ -179,7 +142,7 @@ where where W: BitWriter, { - PackAsWrap::::new(source).pack(writer) + AsWrap::<&T, As>::new(source).pack(writer) } } @@ -192,10 +155,15 @@ where where W: BitWriter, { - PackAsWrap::::new(source).pack(writer) + AsWrap::<&T, As>::new(source).pack(writer) } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl BitPackAs> for Either where AsLeft: BitPackAs, @@ -208,10 +176,7 @@ where { source .as_ref() - .map_either( - PackAsWrap::::new, - PackAsWrap::::new, - ) + .map_either(AsWrap::<&Left, AsLeft>::new, AsWrap::<&Right, AsRight>::new) .pack(writer) } } @@ -227,12 +192,17 @@ where { match source.as_ref() { None => Either::Left(()), - Some(v) => Either::Right(PackAsWrap::::new(v)), + Some(v) => Either::Right(AsWrap::<&T, As>::new(v)), } .pack(writer) } } +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl BitPackAs> for Option where As: BitPackAs, @@ -242,17 +212,17 @@ where where W: BitWriter, { - source.as_ref().map(PackAsWrap::::new).pack(writer) + source.as_ref().map(AsWrap::<&T, As>::new).pack(writer) } } pub trait BitPackWrapAsExt { #[inline] - fn wrap_as(&self) -> PackAsWrap<'_, Self, As> + fn wrap_as(&self) -> AsWrap<&'_ Self, As> where As: BitPackAs + ?Sized, { - PackAsWrap::new(self) + AsWrap::new(self) } } impl BitPackWrapAsExt for T {} diff --git a/crates/bits/src/ser/mod.rs b/crates/bits/src/ser/mod.rs index 32e47f1..c4566da 100644 --- a/crates/bits/src/ser/mod.rs +++ b/crates/bits/src/ser/mod.rs @@ -1,3 +1,4 @@ +//! Binary **ser**ialization for [TL-B](https://docs.ton.org/develop/data-formats/tl-b-language) pub mod args; pub mod r#as; mod writer; @@ -17,13 +18,16 @@ use crate::{ use self::args::BitPackWithArgs; +/// A type that can be bitwise-**ser**ilalized into any [`BitWriter`]. #[autoimpl(for &S, &mut S, Box, Rc, Arc)] pub trait BitPack { + /// Pack value into the writer. fn pack(&self, writer: W) -> Result<(), W::Error> where W: BitWriter; } +/// **Ser**ialize given value into [`BitVec`] #[inline] pub fn pack(value: T) -> Result, StringError> where @@ -34,6 +38,7 @@ where Ok(writer) } +/// Serialize given value with args into [`BitVec`] #[inline] pub fn pack_with(value: T, args: T::Args) -> Result, StringError> where @@ -133,6 +138,11 @@ impl_bit_pack_for_tuple!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7); impl_bit_pack_for_tuple!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8); impl_bit_pack_for_tuple!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8,9:T9); +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl BitPack for Either where L: BitPack, @@ -151,7 +161,11 @@ where } } -/// [Maybe](https://docs.ton.org/develop/data-formats/tl-b-types#maybe) +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl BitPack for Option where T: BitPack, @@ -166,7 +180,7 @@ where } } -impl<'a> BitPack for &'a BitSlice { +impl BitPack for BitSlice { #[inline] fn pack(&self, mut writer: W) -> Result<(), W::Error> where @@ -196,3 +210,13 @@ impl BitPack for str { Ok(()) } } + +impl BitPack for String { + #[inline] + fn pack(&self, writer: W) -> Result<(), W::Error> + where + W: BitWriter, + { + self.as_str().pack(writer) + } +} diff --git a/crates/bits/src/ser/writer.rs b/crates/bits/src/ser/writer.rs index 7dea63b..c24fcb5 100644 --- a/crates/bits/src/ser/writer.rs +++ b/crates/bits/src/ser/writer.rs @@ -1,8 +1,8 @@ -use ::bitvec::{order::Msb0, slice::BitSlice, store::BitStore, vec::BitVec, view::AsBits}; +use ::bitvec::{order::Msb0, slice::BitSlice, store::BitStore, vec::BitVec}; use impl_tools::autoimpl; use crate::{ - adapters::{BitCounter, Tee}, + adapters::{BitCounter, MapErr, Tee}, Error, ResultExt, StringError, }; @@ -12,12 +12,17 @@ use super::{ BitPack, }; +/// Bitwise writer. #[autoimpl(for &mut W, Box)] pub trait BitWriter { + // An error ocurred while writing type Error: Error; + /// Writes a single bit. fn write_bit(&mut self, bit: bool) -> Result<(), Self::Error>; + /// Writes given bitslice. + /// Might be optimized by the implementation. #[inline] fn write_bitslice(&mut self, bits: &BitSlice) -> Result<(), Self::Error> { for bit in bits { @@ -26,6 +31,8 @@ pub trait BitWriter { Ok(()) } + /// Writes given `bit` exactly `n` times. + /// Might be optimized by the implementation. #[inline] fn repeat_bit(&mut self, n: usize, bit: bool) -> Result<(), Self::Error> { for _ in 0..n { @@ -35,34 +42,17 @@ pub trait BitWriter { } } +/// Extension helper for [`BitWriter`]. pub trait BitWriterExt: BitWriter { - #[inline] - fn with_bit(&mut self, bit: bool) -> Result<&mut Self, Self::Error> { - self.write_bit(bit)?; - Ok(self) - } - - #[inline] - fn with_bits( - &mut self, - bits: impl AsRef>, - ) -> Result<&mut Self, Self::Error> { - self.write_bitslice(bits.as_ref())?; - Ok(self) - } - + /// Same as [`.repeat_bit()`](BitWriter::repeat_bit) but can be used + /// for chaining #[inline] fn with_repeat_bit(&mut self, n: usize, bit: bool) -> Result<&mut Self, Self::Error> { self.repeat_bit(n, bit)?; Ok(self) } - #[inline] - fn with_bytes(&mut self, bytes: impl AsRef<[u8]>) -> Result<&mut Self, Self::Error> { - self.with_bits(bytes.as_bits::())?; - Ok(self) - } - + /// Pack given value using its [`BitPack`] implementation #[inline] fn pack(&mut self, value: T) -> Result<&mut Self, Self::Error> where @@ -72,6 +62,7 @@ pub trait BitWriterExt: BitWriter { Ok(self) } + /// Pack given value with args using its [`BitPackWithArgs`] implementation #[inline] fn pack_with(&mut self, value: T, args: T::Args) -> Result<&mut Self, Self::Error> where @@ -81,6 +72,8 @@ pub trait BitWriterExt: BitWriter { Ok(self) } + /// Pack all values from given iterator using [`BitPack`] implementation + /// of its item type. #[inline] fn pack_many( &mut self, @@ -95,6 +88,8 @@ pub trait BitWriterExt: BitWriter { Ok(self) } + /// Pack all values with args from given iterator using [`BitPackWithArgs`] + /// implementation of its item type. #[inline] fn pack_many_with( &mut self, @@ -112,6 +107,8 @@ pub trait BitWriterExt: BitWriter { Ok(self) } + /// Pack given value using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] fn pack_as(&mut self, value: T) -> Result<&mut Self, Self::Error> where @@ -121,6 +118,8 @@ pub trait BitWriterExt: BitWriter { Ok(self) } + /// Pack given value with args using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] fn pack_as_with(&mut self, value: T, args: As::Args) -> Result<&mut Self, Self::Error> where @@ -130,6 +129,8 @@ pub trait BitWriterExt: BitWriter { Ok(self) } + /// Pack all values from iterator using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] fn pack_many_as( &mut self, @@ -144,6 +145,8 @@ pub trait BitWriterExt: BitWriter { Ok(self) } + /// Pack all values from iterator with args using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] fn pack_many_as_with( &mut self, @@ -161,11 +164,23 @@ pub trait BitWriterExt: BitWriter { Ok(self) } + /// Borrows writer, rather than consuming it. #[inline] fn as_mut(&mut self) -> &mut Self { self } + /// Map [`Error`](BitWriter::Error) by given closure + #[inline] + fn map_err(self, f: F) -> MapErr + where + Self: Sized, + { + MapErr { inner: self, f } + } + + /// Wrap this writer to count written bits by using + /// [`.bit_count()`](BitCounter::bit_count). #[inline] fn counted(self) -> BitCounter where @@ -174,6 +189,10 @@ pub trait BitWriterExt: BitWriter { BitCounter::new(self) } + /// Sets given limit on this writer. + /// Returned wrapped writer will return an error when caller tries to + /// write value which will exceed the total limit by using + /// [`.pack()`](BitWriterExt::pack) or any similar method. #[inline] fn limit(self, n: usize) -> LimitWriter where @@ -182,6 +201,7 @@ pub trait BitWriterExt: BitWriter { LimitWriter::new(self, n) } + /// Mirror all written data to given writer as well. #[inline] fn tee(self, writer: W) -> Tee where @@ -194,7 +214,6 @@ pub trait BitWriterExt: BitWriter { } } } - impl BitWriterExt for T where T: BitWriter {} impl BitWriter for BitCounter @@ -225,6 +244,7 @@ where } } +/// Adapter returned by [`.limit()`](BitWriterExt::limit) #[autoimpl(Deref using self.inner)] pub struct LimitWriter { inner: BitCounter, @@ -341,3 +361,13 @@ where Ok(()) } } + +/// Binary string, e.g. `"0010110...."` +impl BitWriter for String { + type Error = StringError; + + fn write_bit(&mut self, bit: bool) -> Result<(), Self::Error> { + self.push(if bit { '1' } else { '0' }); + Ok(()) + } +} diff --git a/crates/contracts/Cargo.toml b/crates/contracts/Cargo.toml index 893039d..8e7adad 100644 --- a/crates/contracts/Cargo.toml +++ b/crates/contracts/Cargo.toml @@ -6,7 +6,7 @@ repository.workspace = true license-file.workspace = true keywords.workspace = true categories.workspace = true -description = "Common smart-contracts for TON blockchain" +description = "Bindings for common smart-contracts on TON blockchain" [dependencies] tlb.workspace = true diff --git a/crates/contracts/README.md b/crates/contracts/README.md new file mode 100644 index 0000000..ed25702 --- /dev/null +++ b/crates/contracts/README.md @@ -0,0 +1,7 @@ +# Bindings for common smart-contracts on TON blockchain +[![docs.rs](https://img.shields.io/docsrs/ton-contracts)](https://docs.rs/ton-contracts/latest/ton_contracts) +[![crates.io](https://img.shields.io/crates/v/ton-contracts)](https://crates.io/crates/ton-contracts) + +## Features +* `wallet`: Generic wallet for signing messages +* `jetton`: Jetton standard [TEP-74](https://github.com/ton-blockchain/TEPs/blob/b7fffeb8d20006e2d47149c3a20cf2e4fac3269c/text/0074-jettons-standard.md) \ No newline at end of file diff --git a/crates/contracts/src/jetton/mod.rs b/crates/contracts/src/jetton/mod.rs index bead9ce..be97657 100644 --- a/crates/contracts/src/jetton/mod.rs +++ b/crates/contracts/src/jetton/mod.rs @@ -1,3 +1,4 @@ +//! Jetton standard [TEP-74](https://github.com/ton-blockchain/TEPs/blob/b7fffeb8d20006e2d47149c3a20cf2e4fac3269c/text/0074-jettons-standard.md) mod wallet; pub use self::wallet::*; diff --git a/crates/contracts/src/jetton/wallet.rs b/crates/contracts/src/jetton/wallet.rs index 45209a1..8fae90e 100644 --- a/crates/contracts/src/jetton/wallet.rs +++ b/crates/contracts/src/jetton/wallet.rs @@ -4,11 +4,12 @@ use tlb::{ de::{CellDeserialize, CellParser, CellParserError}, either::Either, r#as::{ParseFully, Ref, Same}, - ser::{CellBuilder, CellBuilderError, CellSerialize}, + ser::{CellBuilder, CellBuilderError, CellSerialize, CellSerializeExt}, Cell, }; use tlb_ton::MsgAddress; +/// Jetton Transfer message from [TEP-74](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#tl-b-schema) /// ```tlb /// transfer#0f8a7ea5 query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress /// response_destination:MsgAddress custom_payload:(Maybe ^Cell) @@ -34,14 +35,26 @@ where { fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { builder + // transfer#0f8a7ea5 .pack(JETTON_TRANSFER_TAG)? + // query_id:uint64 .pack(self.query_id)? + // amount:(VarUInteger 16) .pack_as::<_, &VarInt<4>>(&self.amount)? + // destination:MsgAddress .pack(self.dst)? + // response_destination:MsgAddress .pack(self.response_dst)? + // custom_payload:(Maybe ^Cell) .store_as::<_, Option>(self.custom_payload.as_ref())? + // forward_ton_amount:(VarUInteger 16) .pack_as::<_, &VarInt<4>>(&self.forward_ton_amount)? - .store_as::, Either>(Either::Right(&self.forward_payload))?; + // forward_payload:(Either Cell ^Cell) + .store_as::<_, Either<(), Ref>>( + Some(&self.forward_payload.to_cell()?) + // store empty cell inline + .filter(|cell| !cell.is_empty()), + )?; Ok(()) } } @@ -52,21 +65,30 @@ where F: CellDeserialize<'de>, { fn parse(parser: &mut CellParser<'de>) -> Result> { + // transfer#0f8a7ea5 parser.unpack::>()?; Ok(Self { + // query_id:uint64 query_id: parser.unpack()?, + // amount:(VarUInteger 16) amount: parser.unpack_as::<_, VarInt<4>>()?, + // destination:MsgAddress dst: parser.unpack()?, + // response_destination:MsgAddress response_dst: parser.unpack()?, + // custom_payload:(Maybe ^Cell) custom_payload: parser.parse_as::<_, Option>>()?, + // forward_ton_amount:(VarUInteger 16) forward_ton_amount: parser.unpack_as::<_, VarInt<4>>()?, + // forward_payload:(Either Cell ^Cell) forward_payload: parser - .parse_as::, Either>>()? + .parse_as::, Either>>()? .into_inner(), }) } } +/// Jetton Transfer Notification message from[TEP-74](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#tl-b-schema) /// ```tlb /// transfer_notification#7362d09c query_id:uint64 amount:(VarUInteger 16) /// sender:MsgAddress forward_payload:(Either Cell ^Cell) @@ -113,12 +135,13 @@ where } } +/// Jetton Burn message from [TEP-74](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#tl-b-schema) /// ```tlb /// burn#595f07bc query_id:uint64 amount:(VarUInteger 16) /// response_destination:MsgAddress custom_payload:(Maybe ^Cell) /// = InternalMsgBody; /// ``` -pub struct JettonBurn

{ +pub struct JettonBurn

{ pub query_id: u64, pub amount: BigUint, pub response_dst: MsgAddress, diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 6918cf1..3cff023 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -1,5 +1,8 @@ +#![doc = include_str!("../README.md")] #[cfg(feature = "wallet")] +#[cfg_attr(docsrs, doc(cfg(feature = "wallet")))] pub mod wallet; #[cfg(feature = "jetton")] +#[cfg_attr(docsrs, doc(cfg(feature = "jetton")))] pub mod jetton; diff --git a/crates/contracts/src/wallet/mnemonic.rs b/crates/contracts/src/wallet/mnemonic.rs index fe12512..a2238bf 100644 --- a/crates/contracts/src/wallet/mnemonic.rs +++ b/crates/contracts/src/wallet/mnemonic.rs @@ -16,12 +16,25 @@ lazy_static! { .collect(); } +/// An ordered set of 24 words used to deterministically generate keypair +/// according to [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). +/// +/// ```rust +/// # use hex_literal::hex; +/// # use ton_contracts::wallet::mnemonic::{Keypair, Mnemonic}; +/// let mnemonic: Mnemonic = "dose ice enrich trigger test dove century still betray gas diet dune use other base gym mad law immense village world example praise game" +/// .parse() +/// .unwrap(); +/// let kp: Keypair = mnemonic.generate_keypair(None).unwrap(); +/// # assert_eq!(kp.skey, hex!("119dcf2840a3d56521d260b2f125eedc0d4f3795b9e627269a4b5a6dca8257bdc04ad1885c127fe863abb00752fa844e6439bb04f264d70de7cea580b32637ab")); +/// ``` #[derive(Debug, Clone)] pub struct Mnemonic([&'static str; 24]); impl Mnemonic { const PBKDF_ITERATIONS: u32 = 100000; + /// Generate [`Keypair`] with optional password pub fn generate_keypair(&self, password: impl Into>) -> anyhow::Result { let entropy = self.entropy(password)?; let seed = Self::pbkdf2_sha512( @@ -75,19 +88,3 @@ impl FromStr for Mnemonic { })?)) } } - -#[cfg(test)] -mod tests { - use hex_literal::hex; - - use super::*; - - #[test] - fn key_pair() { - let mnemonic: Mnemonic = - "dose ice enrich trigger test dove century still betray gas diet dune use other base gym mad law immense village world example praise game" - .parse().unwrap(); - let kp = mnemonic.generate_keypair(None).unwrap(); - assert_eq!(kp.skey, hex!("119dcf2840a3d56521d260b2f125eedc0d4f3795b9e627269a4b5a6dca8257bdc04ad1885c127fe863abb00752fa844e6439bb04f264d70de7cea580b32637ab")); - } -} diff --git a/crates/contracts/src/wallet/mod.rs b/crates/contracts/src/wallet/mod.rs index 40101dd..318bebc 100644 --- a/crates/contracts/src/wallet/mod.rs +++ b/crates/contracts/src/wallet/mod.rs @@ -1,3 +1,4 @@ +//! TON [Wallet](https://docs.ton.org/participate/wallets/contracts) pub mod mnemonic; pub mod v4r2; @@ -14,10 +15,29 @@ use tlb::{ ser::{CellBuilder, CellBuilderError, CellSerialize, CellSerializeExt}, Cell, }; -use tlb_ton::{CommonMsgInfo, ExternalInMsgInfo, Message, MsgAddress, StateInit}; +use tlb_ton::{ + message::{CommonMsgInfo, ExternalInMsgInfo, Message}, + state_init::StateInit, + MsgAddress, +}; pub const DEFAULT_WALLET_ID: u32 = 0x29a9a317; +/// Generic wallet for signing messages +/// +/// ```rust +/// # use ton_contracts::wallet::{mnemonic::Mnemonic, Wallet, v4r2::V4R2}; +/// let mnemonic: Mnemonic = "jewel loop vast intact snack drip fatigue lunch erode green indoor balance together scrub hen monster hour narrow banner warfare increase panel sound spell" +/// .parse() +/// .unwrap(); +/// let keypair = mnemonic.generate_keypair(None).unwrap(); +/// let wallet = Wallet::::derive_default(keypair).unwrap(); +/// +/// assert_eq!( +/// wallet.address(), +/// "UQA7RMTgzvcyxNNLmK2HdklOvFE8_KNMa-btKZ0dPU1UsqfC".parse().unwrap(), +/// ) +/// ``` pub struct Wallet { address: MsgAddress, wallet_id: u32, @@ -29,38 +49,77 @@ impl Wallet where V: WalletVersion, { + /// Derive wallet from its workchain, keypair and id pub fn derive(workchain_id: i32, key_pair: Keypair, wallet_id: u32) -> anyhow::Result { - let state_init = StateInit::<_, _> { - code: Some(V::code()), - data: Some(V::init_data(wallet_id, key_pair.pkey)), - ..Default::default() - } - .to_cell()?; - - let state_init_hash = state_init.hash(); Ok(Self { - address: MsgAddress { + address: MsgAddress::derive( workchain_id, - address: state_init_hash, - }, + StateInit::<_, _> { + code: Some(V::code()), + data: Some(V::init_data(wallet_id, key_pair.pkey)), + ..Default::default() + } + .normalize()?, + )?, wallet_id, key_pair, _phantom: PhantomData, }) } + /// Shortcut for [`Wallet::derive()`] with default workchain and wallet id pub fn derive_default(key_pair: Keypair) -> anyhow::Result { Self::derive(0, key_pair, DEFAULT_WALLET_ID) } - pub fn address(&self) -> MsgAddress { + /// Address of the wallet + #[inline] + pub const fn address(&self) -> MsgAddress { self.address } - pub fn wallet_id(&self) -> u32 { + /// ID of the wallet + #[inline] + pub const fn wallet_id(&self) -> u32 { self.wallet_id } + /// Shortcut to [create](Wallet::create_external_body), + /// [sign](Wallet::sign_body) and [wrap](Wallet::wrap_signed) external + /// message ready for sending to TON blockchain. + /// + /// ```rust + /// # use tlb_ton::{message::Message, currency::ONE_TON}; + /// # use ton_contracts::wallet::{ + /// # mnemonic::Mnemonic, + /// # v4r2::V4R2, + /// # Wallet, + /// # WalletOpSendMessage, + /// # }; + /// # let mnemonic: Mnemonic = "jewel loop vast intact snack drip fatigue lunch erode green indoor balance together scrub hen monster hour narrow banner warfare increase panel sound spell" + /// # .parse() + /// # .unwrap(); + /// # let keypair = mnemonic.generate_keypair(None).unwrap(); + /// # let wallet = Wallet::::derive_default(keypair).unwrap(); + /// let msg = wallet.create_external_message( + /// Default::default(), // DateTime::UNIX_EPOCH means no deadline + /// 0, // seqno + /// [WalletOpSendMessage { + /// mode: 3, + /// message: Message::<()>::transfer( + /// "EQAWezezpqKTbO6xjCussXDdIeJ7XxTcErjA6uD3T3r7AwTk" + /// .parse() + /// .unwrap(), + /// ONE_TON.clone(), + /// false, + /// ) + /// .normalize() + /// .unwrap(), + /// }], + /// false, // do not deploy wallet + /// ); + /// ``` + #[inline] pub fn create_external_message( &self, expire_at: DateTime, @@ -74,6 +133,8 @@ where Ok(wrapped) } + /// Create external body for this wallet. + #[inline] pub fn create_external_body( &self, expire_at: DateTime, @@ -83,6 +144,8 @@ where V::create_external_body(self.wallet_id, expire_at, seqno, msgs) } + /// Sign body from [`.create_external_body()`](Wallet::create_external_body) + /// using this wallet's private key pub fn sign_body(&self, msg: &V::MessageBody) -> anyhow::Result { let msg = msg.to_cell()?; Ok(SignedBody { @@ -99,6 +162,9 @@ where }) } + /// Wrap signed body from [`.sign_body()`](Wallet::sign_body) in a message + /// ready for sending to TON blockchain. + #[inline] pub fn wrap_signed( &self, body: SignedBody, @@ -120,6 +186,7 @@ where } } +/// Signed body retuned from [`Wallet::sign_body()`]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SignedBody { pub sig: [u8; 64], @@ -130,6 +197,7 @@ impl CellSerialize for SignedBody where T: CellSerialize, { + #[inline] fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { builder.pack(self.sig)?.store(&self.msg)?; Ok(()) @@ -140,6 +208,7 @@ impl<'de, T> CellDeserialize<'de> for SignedBody where T: CellDeserialize<'de>, { + #[inline] fn parse(parser: &mut CellParser<'de>) -> Result> { Ok(Self { sig: parser.unpack()?, @@ -148,14 +217,18 @@ where } } +/// Version of [`Wallet`] pub trait WalletVersion { type Data: CellSerialize; type MessageBody: CellSerialize; + /// Code of the wallet for use with [`StateInit`] fn code() -> Arc; + /// Init data for use with [`StateInit`] fn init_data(wallet_id: u32, pubkey: [u8; PUBLIC_KEY_LENGTH]) -> Self::Data; + /// Creates external body for [`Wallet::sign_body()`] fn create_external_body( wallet_id: u32, expire_at: DateTime, @@ -164,6 +237,7 @@ pub trait WalletVersion { ) -> Self::MessageBody; } +/// Operation for [`Wallet`] to send message pub struct WalletOpSendMessage { /// See pub mode: u8, @@ -176,6 +250,7 @@ where IC: CellSerialize, ID: CellSerialize, { + #[inline] pub fn normalize(&self) -> Result { Ok(WalletOpSendMessage { mode: self.mode, @@ -190,34 +265,9 @@ where IC: CellSerialize, ID: CellSerialize, { + #[inline] fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { builder.pack(self.mode)?.store_as::<_, Ref>(&self.message)?; Ok(()) } } - -#[cfg(test)] -mod tests { - use tlb_ton::MsgAddress; - - use crate::wallet::{mnemonic::Mnemonic, v4r2::V4R2, Wallet}; - - #[test] - fn derive() { - const MNEMONIC: &str = "jewel loop vast intact snack drip fatigue lunch erode green indoor balance together scrub hen monster hour narrow banner warfare increase panel sound spell"; - - let expected_address: MsgAddress = "UQA7RMTgzvcyxNNLmK2HdklOvFE8_KNMa-btKZ0dPU1UsqfC" - .parse() - .unwrap(); - - let key_pair = MNEMONIC - .parse::() - .unwrap() - .generate_keypair(None) - .unwrap(); - - let wallet = Wallet::::derive_default(key_pair).unwrap(); - - assert_eq!(wallet.address, expected_address) - } -} diff --git a/crates/contracts/src/wallet/v4r2/mod.rs b/crates/contracts/src/wallet/v4r2/mod.rs index 48f035f..1bc1ce7 100644 --- a/crates/contracts/src/wallet/v4r2/mod.rs +++ b/crates/contracts/src/wallet/v4r2/mod.rs @@ -12,7 +12,8 @@ use tlb::{ Cell, }; use tlb_ton::{ - currency::Grams, hashmap::HashmapE, BagOfCells, MsgAddress, StateInit, UnixTimestamp, + boc::BagOfCells, currency::Grams, hashmap::HashmapE, state_init::StateInit, MsgAddress, + UnixTimestamp, }; use super::{WalletOpSendMessage, WalletVersion}; @@ -27,6 +28,7 @@ lazy_static! { }; } +/// Wallet [v4r2](https://github.com/ton-blockchain/wallet-contract/blob/4111fd9e3313ec17d99ca9b5b1656445b5b49d8f/README.md). pub struct V4R2; impl WalletVersion for V4R2 { @@ -209,7 +211,7 @@ impl<'de> CellDeserialize<'de> for WalletV4R2OpPlugin { #[cfg(test)] mod tests { use tlb::bits::{de::unpack_fully, ser::pack_with}; - use tlb_ton::{BagOfCellsArgs, BoC}; + use tlb_ton::boc::{BagOfCellsArgs, BoC}; use super::*; diff --git a/crates/tlb-ton/Cargo.toml b/crates/tlb-ton/Cargo.toml index ea26385..c688037 100644 --- a/crates/tlb-ton/Cargo.toml +++ b/crates/tlb-ton/Cargo.toml @@ -14,8 +14,9 @@ tlb.workspace = true base64.workspace = true chrono.workspace = true crc = "3" -impl-tools.workspace = true hex.workspace = true +impl-tools.workspace = true +lazy_static.workspace = true num-bigint.workspace = true num-traits.workspace = true serde_with = { workspace = true, optional = true } diff --git a/crates/tlb-ton/README.md b/crates/tlb-ton/README.md new file mode 100644 index 0000000..bee40d6 --- /dev/null +++ b/crates/tlb-ton/README.md @@ -0,0 +1,3 @@ +# [TON-specific](https://docs.ton.org/develop/data-formats/msg-tlb) types for [TL-B](https://docs.rs/tlb/latest/tlb) +[![docs.rs](https://img.shields.io/docsrs/tlb-ton)](https://docs.rs/tlb-ton/latest/tlb_ton) +[![crates.io](https://img.shields.io/crates/v/tlb-ton)](https://crates.io/crates/tlb-ton) diff --git a/crates/tlb-ton/src/address.rs b/crates/tlb-ton/src/address.rs index c335cef..07b86cf 100644 --- a/crates/tlb-ton/src/address.rs +++ b/crates/tlb-ton/src/address.rs @@ -18,10 +18,23 @@ use tlb::{ Error, ResultExt, StringError, }; -use crate::StateInit; +use crate::state_init::StateInit; const CRC_16_XMODEM: Crc = Crc::::new(&crc::CRC_16_XMODEM); +/// [MsgAddress](https://docs.ton.org/develop/data-formats/msg-tlb#msgaddressext-tl-b) +/// ```tlb +/// addr_none$00 = MsgAddressExt; +/// addr_extern$01 len:(## 9) external_address:(bits len) = MsgAddressExt; +/// +/// addr_std$10 anycast:(Maybe Anycast) +/// workchain_id:int8 address:bits256 = MsgAddressInt; +/// addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) +/// workchain_id:int32 address:(bits addr_len) = MsgAddressInt; +/// +/// _ _:MsgAddressInt = MsgAddress; +/// _ _:MsgAddressExt = MsgAddress; +/// ``` #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr( feature = "serde", @@ -38,6 +51,8 @@ impl MsgAddress { address: [0; 32], }; + /// [Derive](https://docs.ton.org/learn/overviews/addresses#address-of-smart-contract) + /// [`MsgAddress`] of a smart-contract by its workchain and [`StateInit`] #[inline] pub fn derive(workchain_id: i32, state_init: StateInit) -> Result { Ok(Self { @@ -60,46 +75,60 @@ impl MsgAddress { }) } + /// [Raw Address](https://docs.ton.org/learn/overviews/addresses#raw-address) + /// representation #[inline] pub fn to_hex(&self) -> String { format!("{}:{}", self.workchain_id, hex::encode(self.address)) } + /// Shortcut for [`.from_base64_url_flags()?.0`](MsgAddress::from_base64_url_flags) #[inline] pub fn from_base64_url(s: impl AsRef) -> Result { Self::from_base64_url_flags(s).map(|(addr, _, _)| addr) } + /// Parse address from URL-base64 + /// [user-friendly](https://docs.ton.org/learn/overviews/addresses#user-friendly-address) + /// representation and its flags: `(address, non_bouncible, non_production)` #[inline] pub fn from_base64_url_flags(s: impl AsRef) -> Result<(Self, bool, bool), StringError> { Self::from_base64_repr(URL_SAFE_NO_PAD, s) } + /// Shortcut for [`.from_base64_std_flags()?.0`](MsgAddress::from_base64_std_flags) #[inline] pub fn from_base64_std(s: impl AsRef) -> Result { Self::from_base64_std_flags(s).map(|(addr, _, _)| addr) } + /// Parse address from standard base64 + /// [user-friendly](https://docs.ton.org/learn/overviews/addresses#user-friendly-address) + /// representation and its flags: `(address, non_bouncible, non_production)` #[inline] pub fn from_base64_std_flags(s: impl AsRef) -> Result<(Self, bool, bool), StringError> { Self::from_base64_repr(STANDARD_NO_PAD, s) } + /// Shortcut for [`.to_base64_url_flags(false, false)`](MsgAddress::to_base64_url_flags) #[inline] pub fn to_base64_url(self) -> String { self.to_base64_url_flags(false, false) } + /// Encode address as URL base64 #[inline] pub fn to_base64_url_flags(self, non_bounceable: bool, non_production: bool) -> String { self.to_base64_flags(non_bounceable, non_production, URL_SAFE_NO_PAD) } + /// Shortcut for [`.to_base64_std_flags(false, false)`](MsgAddress::to_base64_std_flags) #[inline] pub fn to_base64_std(self) -> String { self.to_base64_std_flags(false, false) } + /// Encode address as standard base64 #[inline] pub fn to_base64_std_flags(self, non_bounceable: bool, non_production: bool) -> String { self.to_base64_flags(non_bounceable, non_production, STANDARD_NO_PAD) @@ -169,6 +198,7 @@ impl MsgAddress { engine.encode(bytes) } + /// Returns whether this address is [`NULL`](MsgAddress::NULL) #[inline] pub fn is_null(&self) -> bool { *self == Self::NULL diff --git a/crates/tlb-ton/src/bin_tree/aug.rs b/crates/tlb-ton/src/bin_tree/aug.rs index 68b8a41..bd69241 100644 --- a/crates/tlb-ton/src/bin_tree/aug.rs +++ b/crates/tlb-ton/src/bin_tree/aug.rs @@ -5,9 +5,12 @@ use tlb::{ ser::{args::r#as::CellSerializeAsWithArgs, CellBuilder, CellBuilderError}, }; +/// [`BinTreeAug X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#bintree) +/// ```tlb /// bta_leaf$0 {X:Type} {Y:Type} extra:Y leaf:X = BinTreeAug X Y; /// bta_fork$1 {X:Type} {Y:Type} left:^(BinTreeAug X Y) /// right:^(BinTreeAug X Y) extra:Y = BinTreeAug X Y; +/// ``` pub struct BinTreeAug { pub node: BinTreeNode, pub extra: E, @@ -57,9 +60,14 @@ where } } -/// bt_leaf$0 {X:Type} {Y:Type} leaf:X = BinTree X Y; -/// bt_fork$1 {X:Type} {Y:Type} left:^(BinTree X Y) -/// right:^(BinTreeAug X Y) = BinTree X Y; +/// [`BinTreeAugNode X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#bintree) +/// Type parameter `E` is optional and stands for `extra`, so it can be reused +/// for [`BinTree X`](super::BinTree) +/// ```tlb +/// bta_leaf$0 {X:Type} {Y:Type} extra:Y leaf:X = BinTreeAug X Y; +/// bta_fork$1 {X:Type} {Y:Type} left:^(BinTreeAug X Y) +/// right:^(BinTreeAug X Y) extra:Y = BinTreeAug X Y; +/// ``` pub enum BinTreeNode { Leaf(T), Fork([Box>; 2]), diff --git a/crates/tlb-ton/src/bin_tree/mod.rs b/crates/tlb-ton/src/bin_tree/mod.rs index 64a7a49..8324d4e 100644 --- a/crates/tlb-ton/src/bin_tree/mod.rs +++ b/crates/tlb-ton/src/bin_tree/mod.rs @@ -1,3 +1,4 @@ +//! Collection of bintree-like **de**/**ser**ializable data structures pub mod aug; use std::ops::Deref; @@ -7,6 +8,7 @@ use tlb::de::args::r#as::CellDeserializeAsWithArgs; use tlb::de::{CellParser, CellParserError}; use tlb::r#as::Ref; +/// [`BinTree X`](https://docs.ton.org/develop/data-formats/tl-b-types#bintree) /// ```tlb /// bt_leaf$0 {X:Type} leaf:X = BinTree X; /// bt_fork$1 {X:Type} left:^(BinTree X) right:^(BinTree X) = BinTree X; diff --git a/crates/tlb-ton/src/boc.rs b/crates/tlb-ton/src/boc.rs index 816686b..cd4e73a 100644 --- a/crates/tlb-ton/src/boc.rs +++ b/crates/tlb-ton/src/boc.rs @@ -1,3 +1,4 @@ +//! Collection of types related to [Bag Of Cells](https://docs.ton.org/develop/data-formats/cell-boc#bag-of-cells) use std::{ collections::{HashMap, HashSet}, sync::Arc, @@ -15,14 +16,50 @@ use tlb::{ Cell, Error, ResultExt, StringError, }; +/// Alias to [`BagOfCells`] pub type BoC = BagOfCells; +/// [Bag Of Cells](https://docs.ton.org/develop/data-formats/cell-boc#bag-of-cells) is used to **de**/**ser**ialize a set of cells from/into +/// bytes. +/// +/// ```rust +/// # use tlb::{ +/// # r#as::Data, +/// # bits::{de::unpack_fully, ser::{BitWriterExt, pack_with}}, +/// # Cell, +/// # ser::CellSerializeExt, +/// # StringError, +/// # }; +/// # use tlb_ton::{boc::{BagOfCells, BagOfCellsArgs}, MsgAddress}; +/// # fn main() -> Result<(), StringError> { +/// let addr = MsgAddress::NULL; +/// let mut builder = Cell::builder(); +/// builder.pack(addr)?; +/// let root = builder.into_cell(); +/// +/// let boc = BagOfCells::from_root(root); +/// let packed = pack_with(boc, BagOfCellsArgs { +/// has_idx: false, +/// has_crc32c: true, +/// })?; +/// +/// let unpacked: BagOfCells = unpack_fully(packed)?; +/// let got: MsgAddress = unpacked +/// .single_root() +/// .unwrap() +/// .parse_fully_as::<_, Data>()?; +/// +/// assert_eq!(got, addr); +/// # Ok(()) +/// # } +/// ``` #[derive(Debug, Clone)] pub struct BagOfCells { roots: Vec>, } impl BagOfCells { + /// Create from single root cell #[inline] pub fn from_root(root: impl Into>) -> Self { Self { @@ -30,11 +67,13 @@ impl BagOfCells { } } + /// Add root #[inline] pub fn add_root(&mut self, root: impl Into>) { self.roots.push(root.into()) } + /// Return single root or `None` otherwise #[inline] pub fn single_root(&self) -> Option<&Arc> { let [root]: &[_; 1] = self.roots.as_slice().try_into().ok()?; @@ -59,23 +98,61 @@ impl BagOfCells { Ok(()) } + /// Parse hexadecimal string pub fn parse_hex(s: impl AsRef<[u8]>) -> Result { let bytes = hex::decode(s).map_err(Error::custom)?; Self::unpack(bytes.as_bits()) } + /// Parse base64-encoded string pub fn parse_base64(s: impl AsRef<[u8]>) -> Result { let bytes = STANDARD.decode(s).map_err(Error::custom)?; Self::unpack(bytes.as_bits()) } } +/// [`BitPackWithArgs::Args`] for [`BagOfCells`] #[derive(Debug, Clone, Copy, Default)] pub struct BagOfCellsArgs { pub has_idx: bool, pub has_crc32c: bool, } +/// ```tlb +/// serialized_boc_idx#68ff65f3 size:(## 8) { size <= 4 } +/// off_bytes:(## 8) { off_bytes <= 8 } +/// cells:(##(size * 8)) +/// roots:(##(size * 8)) { roots = 1 } +/// absent:(##(size * 8)) { roots + absent <= cells } +/// tot_cells_size:(##(off_bytes * 8)) +/// index:(cells * ##(off_bytes * 8)) +/// cell_data:(tot_cells_size * [ uint8 ]) +/// = BagOfCells;/// + +/// serialized_boc_idx_crc32c#acc3a728 size:(## 8) { size <= 4 } +/// off_bytes:(## 8) { off_bytes <= 8 } +/// cells:(##(size * 8)) +/// roots:(##(size * 8)) { roots = 1 } +/// absent:(##(size * 8)) { roots + absent <= cells } +/// tot_cells_size:(##(off_bytes * 8)) +/// index:(cells * ##(off_bytes * 8)) +/// cell_data:(tot_cells_size * [ uint8 ]) +/// crc32c:uint32 = BagOfCells;/// + +/// serialized_boc#b5ee9c72 has_idx:(## 1) has_crc32c:(## 1) +/// has_cache_bits:(## 1) flags:(## 2) { flags = 0 } +/// size:(## 3) { size <= 4 } +/// off_bytes:(## 8) { off_bytes <= 8 } +/// cells:(##(size * 8)) +/// roots:(##(size * 8)) { roots >= 1 } +/// absent:(##(size * 8)) { roots + absent <= cells } +/// tot_cells_size:(##(off_bytes * 8)) +/// root_list:(roots * ##(size * 8)) +/// index:has_idx?(cells * ##(off_bytes * 8)) +/// cell_data:(tot_cells_size * [ uint8 ]) +/// crc32c:has_crc32c?uint32 +/// = BagOfCells; +/// ``` impl BitPackWithArgs for BagOfCells { type Args = BagOfCellsArgs; @@ -137,6 +214,41 @@ impl BitPackWithArgs for BagOfCells { } } +/// ```tlb +/// serialized_boc_idx#68ff65f3 size:(## 8) { size <= 4 } +/// off_bytes:(## 8) { off_bytes <= 8 } +/// cells:(##(size * 8)) +/// roots:(##(size * 8)) { roots = 1 } +/// absent:(##(size * 8)) { roots + absent <= cells } +/// tot_cells_size:(##(off_bytes * 8)) +/// index:(cells * ##(off_bytes * 8)) +/// cell_data:(tot_cells_size * [ uint8 ]) +/// = BagOfCells;/// + +/// serialized_boc_idx_crc32c#acc3a728 size:(## 8) { size <= 4 } +/// off_bytes:(## 8) { off_bytes <= 8 } +/// cells:(##(size * 8)) +/// roots:(##(size * 8)) { roots = 1 } +/// absent:(##(size * 8)) { roots + absent <= cells } +/// tot_cells_size:(##(off_bytes * 8)) +/// index:(cells * ##(off_bytes * 8)) +/// cell_data:(tot_cells_size * [ uint8 ]) +/// crc32c:uint32 = BagOfCells;/// + +/// serialized_boc#b5ee9c72 has_idx:(## 1) has_crc32c:(## 1) +/// has_cache_bits:(## 1) flags:(## 2) { flags = 0 } +/// size:(## 3) { size <= 4 } +/// off_bytes:(## 8) { off_bytes <= 8 } +/// cells:(##(size * 8)) +/// roots:(##(size * 8)) { roots >= 1 } +/// absent:(##(size * 8)) { roots + absent <= cells } +/// tot_cells_size:(##(off_bytes * 8)) +/// root_list:(roots * ##(size * 8)) +/// index:has_idx?(cells * ##(off_bytes * 8)) +/// cell_data:(tot_cells_size * [ uint8 ]) +/// crc32c:has_crc32c?uint32 +/// = BagOfCells; +/// ``` impl BitUnpack for BagOfCells { fn unpack(reader: R) -> Result where @@ -440,30 +552,3 @@ impl RawCell { 2 + data_len + self.references.len() as u32 * ref_size_bytes } } - -#[cfg(test)] -mod tests { - use tlb::{ - bits::{de::unpack_fully, ser::pack_with}, - ser::CellSerializeExt, - }; - - use super::*; - - #[test] - fn boc_serde() { - let packed = pack_with( - BoC::from_root(().to_cell().unwrap()), - BagOfCellsArgs { - has_idx: false, - has_crc32c: true, - }, - ) - .unwrap(); - - let unpacked: BoC = unpack_fully(packed).unwrap(); - - let got: () = unpacked.single_root().unwrap().parse_fully().unwrap(); - assert_eq!(got, ()); - } -} diff --git a/crates/tlb-ton/src/currency.rs b/crates/tlb-ton/src/currency.rs index 7960fb5..69a7670 100644 --- a/crates/tlb-ton/src/currency.rs +++ b/crates/tlb-ton/src/currency.rs @@ -1,4 +1,7 @@ +//! Collection of types to work with currencies +use lazy_static::lazy_static; use num_bigint::BigUint; +use num_traits::One; use tlb::{ bits::{de::BitReaderExt, r#as::VarInt, ser::BitWriterExt}, de::{CellDeserialize, CellParser, CellParserError}, @@ -8,9 +11,26 @@ use tlb::{ use crate::hashmap::HashmapE; +lazy_static! { + /// 1 gram (nano-TON) + pub static ref ONE_GRAM: BigUint = BigUint::one(); + /// 1 TON + pub static ref ONE_TON: BigUint = &*ONE_GRAM * 1_000_000_000u64; +} + +/// Alias for `VarUInteger 16` +/// ```tlb +/// nanograms$_ amount:(VarUInteger 16) = Grams; +/// ``` pub type Coins = VarInt<4>; + +/// Alias for `VarUInteger 16` +/// ```tlb +/// nanograms$_ amount:(VarUInteger 16) = Grams; +/// ``` pub type Grams = Coins; +/// [`CurrencyCollection`](https://docs.ton.org/develop/data-formats/msg-tlb#currencycollection) /// ```tlb /// currencies$_ grams:Grams other:ExtraCurrencyCollection = CurrencyCollection; /// ``` diff --git a/crates/tlb-ton/src/hashmap/aug.rs b/crates/tlb-ton/src/hashmap/aug.rs index 3a541be..d508600 100644 --- a/crates/tlb-ton/src/hashmap/aug.rs +++ b/crates/tlb-ton/src/hashmap/aug.rs @@ -19,6 +19,8 @@ use tlb::{ use super::hm_label::HmLabel; +/// [`HashmapAugE n X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#hashmapauge). +/// When `E = ()` it is equivalent to [`HashmapE n X`](https://docs.ton.org/develop/data-formats/tl-b-types#hashmap) /// ```tlb /// ahme_empty$0 {n:#} {X:Type} {Y:Type} extra:Y = HashmapAugE n X Y; /// ahme_root$1 {n:#} {X:Type} {Y:Type} root:^(HashmapAug n X Y) @@ -84,6 +86,9 @@ where } } +/// [`HashmapE n X`](https://docs.ton.org/develop/data-formats/tl-b-types#hashmap). +/// Type parameter `E` is optional and stands for `extra`, so it can be reused +/// for [`HashmapAugE n X E`](HashmapAugE) /// ```tlb /// hme_empty$0 {n:#} {X:Type} = HashmapE n X; /// hme_root$1 {n:#} {X:Type} root:^(Hashmap n X) = HashmapE n X; @@ -102,16 +107,19 @@ impl Default for HashmapE { } impl HashmapE { + /// Create empty hashmap #[inline] pub const fn new() -> Self { Self::Empty } + /// Return whether this hashmap is empty #[inline] pub fn is_empty(&self) -> bool { matches!(self, Self::Empty) } + /// Return number of leaf nodes in this hashmap #[inline] pub fn len(&self) -> usize { match self { @@ -120,6 +128,7 @@ impl HashmapE { } } + /// Returns whether this hashmap contains given key #[inline] pub fn contains_key(&self, key: impl AsRef>) -> bool { match self { @@ -128,6 +137,7 @@ impl HashmapE { } } + /// Returns reference to leaf value associated with given key #[inline] pub fn get(&self, key: impl AsRef>) -> Option<&T> { match self { @@ -136,6 +146,7 @@ impl HashmapE { } } + /// Returns mutable reference to leaf value associated with given key #[inline] pub fn get_mut(&mut self, key: impl AsRef>) -> Option<&mut T> { match self { @@ -240,73 +251,12 @@ where } } -// pub struct HashmapEN(PhantomData<(AsT, AsE)>); - -// impl CellSerializeAsWithArgs> -// for HashmapEN -// where -// AsT: CellSerializeAsWithArgs, -// AsT::Args: Clone, -// AsE: CellSerializeAsWithArgs, -// AsE::Args: Clone, -// { -// type Args = (AsT::Args, AsE::Args); - -// fn store_as_with( -// source: &HashmapE, -// builder: &mut CellBuilder, -// (node_args, extra_args): Self::Args, -// ) -> Result<(), CellBuilderError> { -// builder.store_as_with::<_, &HashmapE>(source, (N, node_args, extra_args))?; -// Ok(()) -// } -// } - -// impl CellSerializeAs> for HashmapEN -// where -// AsT: CellSerializeAs, -// AsE: CellSerializeAs, -// { -// fn store_as( -// source: &HashmapE, -// builder: &mut CellBuilder, -// ) -> Result<(), CellBuilderError> { -// builder -// .store_as_with::<_, &HashmapE, NoArgs<_, AsE>>>(source, (N, (), ()))?; -// Ok(()) -// } -// } -// -// impl<'de, const N: u32, T, As> CellDeserializeAsWithArgs<'de, HashmapE> for HashmapEN -// where -// As: CellDeserializeAsWithArgs<'de, T>, -// As::Args: Clone, -// { -// type Args = As::Args; - -// #[inline] -// fn parse_as_with( -// parser: &mut CellParser<'de>, -// args: Self::Args, -// ) -> Result, CellParserError<'de>> { -// parser.parse_as_with::<_, HashmapE>((N, args)) -// } -// } - -// impl<'de, const N: u32, T, As> CellDeserializeAs<'de, HashmapE> for HashmapEN -// where -// As: CellDeserializeAs<'de, T>, -// { -// #[inline] -// fn parse_as(parser: &mut CellParser<'de>) -> Result, CellParserError<'de>> { -// parser.parse_as_with::<_, HashmapE>>((N, ())) -// } -// } - +/// [`Hashmap n X`](https://docs.ton.org/develop/data-formats/tl-b-types#hashmap) +/// Type parameter `E` is optional and stands for `extra`, so it can be reused +/// for [`HashmapAug n X E`](HashmapAugE) /// ```tlb -/// ahm_edge#_ {n:#} {X:Type} {Y:Type} {l:#} {m:#} -/// label:(HmLabel ~l n) {n = (~m) + l} -/// node:(HashmapAugNode m X Y) = HashmapAug n X Y; +/// hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n) +/// {n = (~m) + l} node:(HashmapNode m X) = Hashmap n X; /// ``` #[derive(Debug, Clone, PartialEq, Eq)] pub struct Hashmap { @@ -415,6 +365,9 @@ where } } +/// [`HashmapNode n X`](https://docs.ton.org/develop/data-formats/tl-b-types#hashmap) +/// Type parameter `E` is optional and stands for `extra`, so it can be reused +/// for [`HashmapAugNode n X E`](HashmapAugNode) /// ```tlb /// hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X; /// hmn_fork#_ {n:#} {X:Type} left:^(Hashmap n X) @@ -555,6 +508,8 @@ where } } +/// [`HashmapAugNode n X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#hashmapauge) +/// When `E = ()` it is equivalent to [`HashmapNode n X`](https://docs.ton.org/develop/data-formats/tl-b-types#hashmap) /// ```tlb /// ahmn_leaf#_ {X:Type} {Y:Type} extra:Y value:X = HashmapAugNode 0 X Y; /// ahmn_fork#_ {n:#} {X:Type} {Y:Type} left:^(HashmapAug n X Y) diff --git a/crates/tlb-ton/src/hashmap/hm_label.rs b/crates/tlb-ton/src/hashmap/hm_label.rs index ab46a3c..e56069f 100644 --- a/crates/tlb-ton/src/hashmap/hm_label.rs +++ b/crates/tlb-ton/src/hashmap/hm_label.rs @@ -2,14 +2,13 @@ use tlb::{ bits::{ bitvec::{order::Msb0, slice::BitSlice, vec::BitVec}, de::{args::r#as::BitUnpackAsWithArgs, BitReader, BitReaderExt}, - r#as::{NBits, VarNBits}, + r#as::{NBits, Unary, VarNBits}, ser::{args::r#as::BitPackAsWithArgs, BitWriter, BitWriterExt}, }, Error, }; -use crate::Unary; - +/// `HmLabel ~n m` for [`Hashmap`](super::Hashmap) /// ```tlb /// hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m; /// hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; diff --git a/crates/tlb-ton/src/hashmap/mod.rs b/crates/tlb-ton/src/hashmap/mod.rs index a45d0f4..e624e75 100644 --- a/crates/tlb-ton/src/hashmap/mod.rs +++ b/crates/tlb-ton/src/hashmap/mod.rs @@ -1,3 +1,4 @@ +//! Collection of hashmap-like **de**/**ser**ializable data structures pub mod aug; pub use aug::{Hashmap, HashmapE, HashmapNode}; mod hm_label; diff --git a/crates/tlb-ton/src/hashmap/pfx.rs b/crates/tlb-ton/src/hashmap/pfx.rs index c874e6c..201bcbf 100644 --- a/crates/tlb-ton/src/hashmap/pfx.rs +++ b/crates/tlb-ton/src/hashmap/pfx.rs @@ -14,6 +14,7 @@ use tlb::{ use super::{aug::HashmapAugNode, hm_label::HmLabel, Hashmap, HashmapE, HashmapNode}; +/// [`PfxHashmapE n X`](https://docs.ton.org/develop/data-formats/tl-b-types#pfxhashmap) /// ```tlb /// phme_empty$0 {n:#} {X:Type} = PfxHashmapE n X; /// phme_root$1 {n:#} {X:Type} root:^(PfxHashmap n X) = PfxHashmapE n X; @@ -73,6 +74,7 @@ where } } +/// [`PfxHashmap n X`](https://docs.ton.org/develop/data-formats/tl-b-types#pfxhashmap) /// ```tlb /// phm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n) /// {n = (~m) + l} node:(PfxHashmapNode m X) = PfxHashmap n X; @@ -140,6 +142,7 @@ where } } +/// [`PfxHashmapNode n X`](https://docs.ton.org/develop/data-formats/tl-b-types#pfxhashmap) /// ```tlb /// phmn_leaf$0 {n:#} {X:Type} value:X = PfxHashmapNode n X; /// phmn_fork$1 {n:#} {X:Type} left:^(PfxHashmap n X) diff --git a/crates/tlb-ton/src/lib.rs b/crates/tlb-ton/src/lib.rs index beb91ee..182ab29 100644 --- a/crates/tlb-ton/src/lib.rs +++ b/crates/tlb-ton/src/lib.rs @@ -1,11 +1,11 @@ +#![doc = include_str!("../README.md")] +mod address; pub mod bin_tree; +pub mod boc; pub mod currency; pub mod hashmap; -mod address; -mod boc; -mod message; -mod state_init; +pub mod message; +pub mod state_init; mod timestamp; -mod unary; -pub use self::{address::*, boc::*, message::*, state_init::*, timestamp::*, unary::*,}; +pub use self::{address::*, timestamp::*}; diff --git a/crates/tlb-ton/src/message.rs b/crates/tlb-ton/src/message.rs index 8381f97..1bc1f1f 100644 --- a/crates/tlb-ton/src/message.rs +++ b/crates/tlb-ton/src/message.rs @@ -1,3 +1,4 @@ +//! Collection of typs related to [Message](https://docs.ton.org/develop/data-formats/msg-tlb#message-tl-b) use chrono::{DateTime, Utc}; use num_bigint::BigUint; use tlb::{ @@ -8,16 +9,19 @@ use tlb::{ }, de::{CellDeserialize, CellParser, CellParserError}, either::Either, - r#as::{Ref, Same}, + r#as::{DefaultOnNone, Ref, Same}, ser::{CellBuilder, CellBuilderError, CellSerialize, CellSerializeExt}, Cell, }; use crate::{ - currency::{CurrencyCollection, Grams}, - MsgAddress, StateInit, UnixTimestamp, + currency::{CurrencyCollection, ExtraCurrencyCollection, Grams}, + hashmap::HashmapE, + state_init::StateInit, + MsgAddress, UnixTimestamp, }; +/// [Message](https://docs.ton.org/develop/data-formats/msg-tlb#message-tl-b) /// ```tlb /// message$_ {X:Type} info:CommonMsgInfo /// init:(Maybe (Either StateInit ^StateInit)) @@ -36,6 +40,11 @@ where IC: CellSerialize, ID: CellSerialize, { + pub fn with_state_init(mut self, state_init: impl Into>>) -> Self { + self.init = state_init.into(); + self + } + pub fn normalize(&self) -> Result { Ok(Message { info: self.info.clone(), @@ -45,6 +54,18 @@ where } } +impl Message<()> { + /// Simple native transfer message + #[inline] + pub const fn transfer(dst: MsgAddress, grams: BigUint, bounce: bool) -> Self { + Self { + info: CommonMsgInfo::transfer(dst, grams, bounce), + init: None, + body: (), + } + } +} + impl CellSerialize for Message where T: CellSerialize, @@ -83,6 +104,7 @@ where } } +/// `info` field for [`Message`] #[derive(Debug, Clone, PartialEq, Eq)] pub enum CommonMsgInfo { /// ```tlb @@ -101,6 +123,13 @@ pub enum CommonMsgInfo { ExternalOut(ExternalOutMsgInfo), } +impl CommonMsgInfo { + #[inline] + pub const fn transfer(dst: MsgAddress, grams: BigUint, bounce: bool) -> Self { + Self::Internal(InternalMsgInfo::transfer(dst, grams, bounce)) + } +} + impl CellSerialize for CommonMsgInfo { fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { match self { @@ -136,6 +165,7 @@ impl<'de> CellDeserialize<'de> for CommonMsgInfo { } } +/// [`int_msg_info$0`](https://docs.ton.org/develop/data-formats/msg-tlb#int_msg_info0) /// ```tlb /// int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool /// src:MsgAddressInt dest:MsgAddressInt @@ -167,6 +197,27 @@ pub struct InternalMsgInfo { pub created_at: Option>, } +impl InternalMsgInfo { + #[inline] + pub const fn transfer(dst: MsgAddress, grams: BigUint, bounce: bool) -> Self { + InternalMsgInfo { + ihr_disabled: true, + bounce, + bounced: false, + src: MsgAddress::NULL, + dst, + value: CurrencyCollection { + grams, + other: ExtraCurrencyCollection(HashmapE::Empty), + }, + ihr_fee: BigUint::ZERO, + fwd_fee: BigUint::ZERO, + created_lt: 0, + created_at: None, + } + } +} + impl CellSerialize for InternalMsgInfo { fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { builder @@ -179,7 +230,7 @@ impl CellSerialize for InternalMsgInfo { .pack_as::<_, &Grams>(&self.ihr_fee)? .pack_as::<_, &Grams>(&self.fwd_fee)? .pack(self.created_lt)? - .pack_as::<_, UnixTimestamp>(self.created_at.unwrap_or(DateTime::UNIX_EPOCH))?; + .pack_as::<_, DefaultOnNone>(self.created_at)?; Ok(()) } } @@ -202,6 +253,7 @@ impl<'de> CellDeserialize<'de> for InternalMsgInfo { } } +/// [`ext_in_msg_info$10`](https://docs.ton.org/develop/data-formats/msg-tlb#ext_in_msg_info10) /// ```tlb /// ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt /// import_fee:Grams = CommonMsgInfo; @@ -239,6 +291,7 @@ impl BitUnpack for ExternalInMsgInfo { } } +/// [`ext_out_msg_info$11`](https://docs.ton.org/develop/data-formats/msg-tlb#ext_out_msg_info11) /// ```tlb /// ext_out_msg_info$11 src:MsgAddressInt dest:MsgAddressExt /// created_lt:uint64 created_at:uint32 = CommonMsgInfo; diff --git a/crates/tlb-ton/src/state_init.rs b/crates/tlb-ton/src/state_init.rs index 742bdb2..c25fb8f 100644 --- a/crates/tlb-ton/src/state_init.rs +++ b/crates/tlb-ton/src/state_init.rs @@ -1,3 +1,4 @@ +//! Collection of types related to [StateInit](https://docs.ton.org/develop/data-formats/msg-tlb#stateinit-tl-b) use impl_tools::autoimpl; use tlb::{ bits::{ @@ -13,6 +14,7 @@ use tlb::{ use crate::hashmap::HashmapE; +/// [StateInit](https://docs.ton.org/develop/data-formats/msg-tlb#stateinit-tl-b) /// ```tlb /// _ split_depth:(Maybe (## 5)) special:(Maybe TickTock) /// code:(Maybe ^Cell) data:(Maybe ^Cell) @@ -97,6 +99,7 @@ where } } +/// `tick_tock` field for [`StateInit`] /// ```tlb /// tick_tock$_ tick:Bool tock:Bool = TickTock; /// ``` @@ -130,6 +133,7 @@ impl BitUnpack for TickTock { } } +/// `library` field for [`StateInit`] /// ```tlb /// simple_lib$_ public:Bool root:^Cell = SimpleLib; /// ``` diff --git a/crates/tlb-ton/src/timestamp.rs b/crates/tlb-ton/src/timestamp.rs index 0f495f9..df12b7d 100644 --- a/crates/tlb-ton/src/timestamp.rs +++ b/crates/tlb-ton/src/timestamp.rs @@ -7,6 +7,7 @@ use tlb::{ Error, }; +/// Adapter to **de**/**ser**ialize UNIX timestamp as `u32` from [`DateTime`] pub struct UnixTimestamp; impl BitPackAs> for UnixTimestamp { diff --git a/crates/tlb/Cargo.toml b/crates/tlb/Cargo.toml index 0d6b60c..9c569d2 100644 --- a/crates/tlb/Cargo.toml +++ b/crates/tlb/Cargo.toml @@ -17,4 +17,5 @@ impl-tools.workspace = true sha2.workspace = true [dev-dependencies] -hex-literal = "0.4" +hex-literal.workspace = true +num-bigint.workspace = true diff --git a/crates/tlb/README.md b/crates/tlb/README.md new file mode 100644 index 0000000..613b62c --- /dev/null +++ b/crates/tlb/README.md @@ -0,0 +1,3 @@ +# [TL-B](https://docs.ton.org/develop/data-formats/tl-b-language) **de**/**ser**ialization +[![docs.rs](https://img.shields.io/docsrs/tlb)](https://docs.rs/tlb/latest/tlb) +[![crates.io](https://img.shields.io/crates/v/tlb)](https://crates.io/crates/tlb) diff --git a/crates/tlb/src/as/args.rs b/crates/tlb/src/as/args.rs index b3ffe52..0a8a853 100644 --- a/crates/tlb/src/as/args.rs +++ b/crates/tlb/src/as/args.rs @@ -39,3 +39,25 @@ where As::parse_as(parser) } } + +impl CellSerializeAs for DefaultArgs +where + As: CellSerializeAsWithArgs, + As::Args: Default, +{ + #[inline] + fn store_as(source: &T, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + As::store_as_with(source, builder, ::default()) + } +} + +impl<'de, T, As> CellDeserializeAs<'de, T> for DefaultArgs +where + As: CellDeserializeAsWithArgs<'de, T>, + As::Args: Default, +{ + #[inline] + fn parse_as(parser: &mut CellParser<'de>) -> Result> { + As::parse_as_with(parser, ::default()) + } +} diff --git a/crates/tlb/src/as/data.rs b/crates/tlb/src/as/data.rs index da0ceef..c450507 100644 --- a/crates/tlb/src/as/data.rs +++ b/crates/tlb/src/as/data.rs @@ -14,6 +14,56 @@ use crate::{ use super::Same; +/// Adapter to implement cell **de**/**ser**ialization from/into binary data. +/// +/// ```rust +/// # use tlb::{ +/// # r#as::Data, +/// # bits::{ +/// # de::{BitUnpack, BitReader, BitReaderExt}, +/// # ser::{BitPack, BitWriter, BitWriterExt}, +/// # }, +/// # Cell, +/// # StringError, +/// # }; +/// # #[derive(Debug, Clone, Copy, PartialEq)] +/// struct BinaryData { +/// field: u8, +/// } +/// +/// impl BitPack for BinaryData { +/// fn pack(&self, mut writer: W) -> Result<(), W::Error> +/// where W: BitWriter, +/// { +/// writer.pack(self.field)?; +/// Ok(()) +/// } +/// } +/// +/// impl BitUnpack for BinaryData { +/// fn unpack(mut reader: R) -> Result +/// where R: BitReader, +/// { +/// Ok(Self { +/// field: reader.unpack()?, +/// }) +/// } +/// } +/// +/// # fn main() -> Result<(), StringError> { +/// let v = BinaryData { field: 123 }; +/// # let mut builder = Cell::builder(); +/// // store as binary data +/// builder.store_as::<_, Data>(v)?; +/// # let cell = builder.into_cell(); +/// # let mut parser = cell.parser(); +/// # let got = +/// // parse as binary data +/// parser.parse_as::()?; +/// # assert_eq!(got, v); +/// # Ok(()) +/// # } +/// ``` pub struct Data(PhantomData); impl CellSerializeAs for Data diff --git a/crates/tlb/src/as/default.rs b/crates/tlb/src/as/default.rs index 03d603e..4478d44 100644 --- a/crates/tlb/src/as/default.rs +++ b/crates/tlb/src/as/default.rs @@ -1,7 +1,24 @@ -use crate::de::{r#as::CellDeserializeAs, CellParser, CellParserError}; +use crate::{ + de::{r#as::CellDeserializeAs, CellParser, CellParserError}, + ser::{r#as::CellSerializeAs, CellBuilder, CellBuilderError}, +}; pub use crate::bits::r#as::DefaultOnNone; +impl CellSerializeAs> for DefaultOnNone +where + As: CellSerializeAs, + T: Default, +{ + fn store_as(source: &Option, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + match source { + Some(v) => builder.store_as::<_, &As>(v)?, + None => builder.store_as::<_, As>(T::default())?, + }; + Ok(()) + } +} + impl<'de, T, As> CellDeserializeAs<'de, T> for DefaultOnNone where T: Default, diff --git a/crates/tlb/src/as/from_into.rs b/crates/tlb/src/as/from_into.rs index 9fd6f49..e64d062 100644 --- a/crates/tlb/src/as/from_into.rs +++ b/crates/tlb/src/as/from_into.rs @@ -14,7 +14,7 @@ use crate::{ Error, }; -pub use crate::bits::r#as::{FromInto, FromIntoRef, TryFromInto}; +pub use crate::bits::r#as::{FromInto, FromIntoRef, TryFromInto, TryFromIntoRef}; impl CellSerializeAs for FromInto where @@ -188,3 +188,66 @@ where .map_err(Error::custom) } } + +impl CellSerializeAs for TryFromIntoRef +where + for<'a> &'a T: TryInto, + for<'a> <&'a T as TryInto>::Error: Display, + As: CellSerialize, +{ + #[inline] + fn store_as(source: &T, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + source.try_into().map_err(Error::custom)?.store(builder) + } +} + +impl CellSerializeAsWithArgs for TryFromIntoRef +where + for<'a> &'a T: TryInto + Clone, + for<'a> <&'a T as TryInto>::Error: Display, + As: CellSerializeWithArgs, +{ + type Args = As::Args; + + #[inline] + fn store_as_with( + source: &T, + builder: &mut CellBuilder, + args: Self::Args, + ) -> Result<(), CellBuilderError> { + source + .clone() + .try_into() + .map_err(Error::custom)? + .store_with(builder, args) + } +} + +impl<'de, T, As> CellDeserializeAs<'de, T> for TryFromIntoRef +where + As: TryInto + CellDeserialize<'de>, + >::Error: Display, +{ + #[inline] + fn parse_as(parser: &mut CellParser<'de>) -> Result> { + As::parse(parser)?.try_into().map_err(Error::custom) + } +} + +impl<'de, T, As> CellDeserializeAsWithArgs<'de, T> for TryFromIntoRef +where + As: TryInto + CellDeserializeWithArgs<'de>, + >::Error: Display, +{ + type Args = As::Args; + + #[inline] + fn parse_as_with( + parser: &mut CellParser<'de>, + args: Self::Args, + ) -> Result> { + As::parse_with(parser, args)? + .try_into() + .map_err(Error::custom) + } +} diff --git a/crates/tlb/src/as/fully.rs b/crates/tlb/src/as/fully.rs index 4dfe452..6f5ade7 100644 --- a/crates/tlb/src/as/fully.rs +++ b/crates/tlb/src/as/fully.rs @@ -6,6 +6,8 @@ use crate::de::{ use super::Same; +/// Adapter to **de**serialize value and ensure that no more data and references +/// left. pub struct ParseFully(PhantomData); impl<'de, T, As> CellDeserializeAs<'de, T> for ParseFully diff --git a/crates/tlb/src/as/mod.rs b/crates/tlb/src/as/mod.rs index af79c41..d21f43f 100644 --- a/crates/tlb/src/as/mod.rs +++ b/crates/tlb/src/as/mod.rs @@ -1,3 +1,9 @@ +//! **De**/**ser**ialization helpers for +//! [TL-B](https://docs.ton.org/develop/data-formats/tl-b-language). +//! +//! This approach is heavily inspired by +//! [serde_with](https://docs.rs/serde_with/latest/serde_with). +//! Please, read their docs for more usage examples. mod args; mod data; mod default; @@ -7,3 +13,71 @@ mod reference; mod same; pub use self::{args::*, data::*, default::*, from_into::*, fully::*, reference::*, same::*}; + +use crate::{ + de::{ + args::{r#as::CellDeserializeAsWithArgs, CellDeserializeWithArgs}, + r#as::CellDeserializeAs, + CellDeserialize, CellParser, CellParserError, + }, + ser::{ + args::{r#as::CellSerializeAsWithArgs, CellSerializeWithArgs}, + r#as::CellSerializeAs, + CellBuilder, CellBuilderError, CellSerialize, + }, +}; + +pub use tlbits::r#as::AsWrap; + +impl<'a, T, As> CellSerialize for AsWrap<&'a T, As> +where + T: ?Sized, + As: ?Sized, + As: CellSerializeAs, +{ + #[inline] + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + As::store_as(self.into_inner(), builder) + } +} + +impl<'a, T, As> CellSerializeWithArgs for AsWrap<&'a T, As> +where + T: ?Sized, + As: CellSerializeAsWithArgs + ?Sized, +{ + type Args = As::Args; + + #[inline] + fn store_with( + &self, + builder: &mut CellBuilder, + args: Self::Args, + ) -> Result<(), CellBuilderError> { + As::store_as_with(self.into_inner(), builder, args) + } +} + +impl<'de, T, As> CellDeserialize<'de> for AsWrap +where + As: CellDeserializeAs<'de, T> + ?Sized, +{ + #[inline] + fn parse(parser: &mut CellParser<'de>) -> Result> { + As::parse_as(parser).map(Self::new) + } +} + +impl<'de, T, As> CellDeserializeWithArgs<'de> for AsWrap +where + As: CellDeserializeAsWithArgs<'de, T> + ?Sized, +{ + type Args = As::Args; + + fn parse_with( + parser: &mut CellParser<'de>, + args: Self::Args, + ) -> Result> { + As::parse_as_with(parser, args).map(Self::new) + } +} diff --git a/crates/tlb/src/as/reference.rs b/crates/tlb/src/as/reference.rs index 5fb643a..575929b 100644 --- a/crates/tlb/src/as/reference.rs +++ b/crates/tlb/src/as/reference.rs @@ -12,6 +12,7 @@ use crate::{ use super::Same; +/// Adapter to **de**/**ser**ialize value from/into reference to the child cell. pub struct Ref(PhantomData); impl CellSerializeAs for Ref diff --git a/crates/tlb/src/cell.rs b/crates/tlb/src/cell.rs index 9046e89..a6f261d 100644 --- a/crates/tlb/src/cell.rs +++ b/crates/tlb/src/cell.rs @@ -17,6 +17,7 @@ use crate::{ ser::CellBuilder, }; +/// A [Cell](https://docs.ton.org/develop/data-formats/cell-boc#cell). #[derive(Clone, Default, PartialEq, Eq, Hash)] pub struct Cell { pub data: BitVec, @@ -24,12 +25,14 @@ pub struct Cell { } impl Cell { + /// Create new [`CellBuilder`] #[inline] #[must_use] pub const fn builder() -> CellBuilder { CellBuilder::new() } + /// Create empty cell #[inline] #[must_use] pub const fn new() -> Self { @@ -39,12 +42,14 @@ impl Cell { } } + /// Return [`CellParser`] for this cell #[inline] #[must_use] pub fn parser(&self) -> CellParser<'_> { CellParser::new(&self.data, &self.references) } + /// Shortcut for [`.parser()`](Cell::parser)[`.parse()`](CellParser::parse)[`.ensure_empty()`](CellParser::ensure_empty). #[inline] pub fn parse_fully<'de, T>(&'de self) -> Result> where @@ -56,6 +61,7 @@ impl Cell { Ok(v) } + /// Shortcut for [`.parser()`](Cell::parser)[`.parse_with()`](CellParser::parse_with)[`.ensure_empty()`](CellParser::ensure_empty). #[inline] pub fn parse_fully_with<'de, T>(&'de self, args: T::Args) -> Result> where @@ -67,6 +73,7 @@ impl Cell { Ok(v) } + /// Shortcut for [`.parser()`](Cell::parser)[`.parse_as()`](CellParser::parse_as)[`.ensure_empty()`](CellParser::ensure_empty). #[inline] pub fn parse_fully_as<'de, T, As>(&'de self) -> Result> where @@ -78,6 +85,7 @@ impl Cell { Ok(v) } + /// Shortcut for [`.parser()`](Cell::parser)[`.parse_as_with()`](CellParser::parse_as_with)[`.ensure_empty()`](CellParser::ensure_empty). #[inline] pub fn parse_fully_as_with<'de, T, As>( &'de self, @@ -92,6 +100,7 @@ impl Cell { Ok(v) } + /// Returns whether this cell has no data and zero references. #[inline] pub fn is_empty(&self) -> bool { self.data.is_empty() && self.references.is_empty() @@ -175,7 +184,7 @@ impl Cell { buf } - /// See [cell hash](https://docs.ton.org/develop/data-formats/cell-boc#cell-hash) + /// Calculates [standard Cell representation hash](https://docs.ton.org/develop/data-formats/cell-boc#cell-hash) #[inline] pub fn hash(&self) -> [u8; 32] { let mut hasher = Sha256::new(); diff --git a/crates/tlb/src/de/args/as.rs b/crates/tlb/src/de/args/as.rs index 642c7e4..d48de12 100644 --- a/crates/tlb/src/de/args/as.rs +++ b/crates/tlb/src/de/args/as.rs @@ -1,39 +1,37 @@ use core::mem::MaybeUninit; use std::{rc::Rc, sync::Arc}; -use crate::{bits::de::r#as::UnpackAsWrap, either::Either, r#as::NoArgs, ResultExt}; +use crate::{ + either::Either, + r#as::{AsWrap, NoArgs}, + ResultExt, +}; use super::{ super::{CellParser, CellParserError}, CellDeserializeWithArgs, }; +/// Adaper to **de**serialize `T` with args. +/// See [`as`](crate::as) module-level documentation for more. +/// +/// For version without arguments, see +/// [`CellDeserializeAs`](super::super::as::CellDeserializeAs). pub trait CellDeserializeAsWithArgs<'de, T> { type Args; + /// Parse value with args using an adapter fn parse_as_with( parser: &mut CellParser<'de>, args: Self::Args, ) -> Result>; } +/// Owned version of [`CellDeserializeAsWithArgs`] pub trait CellDeserializeAsWithArgsOwned: for<'de> CellDeserializeAsWithArgs<'de, T> {} -impl CellDeserializeAsWithArgsOwned for T - where T: for<'de> CellDeserializeAsWithArgs<'de, As> + ?Sized { -} - -impl<'de, T, As> CellDeserializeWithArgs<'de> for UnpackAsWrap -where - As: CellDeserializeAsWithArgs<'de, T> + ?Sized, +impl CellDeserializeAsWithArgsOwned for T where + T: for<'de> CellDeserializeAsWithArgs<'de, As> + ?Sized { - type Args = As::Args; - - fn parse_with( - parser: &mut CellParser<'de>, - args: Self::Args, - ) -> Result> { - As::parse_as_with(parser, args).map(Self::new) - } } impl<'de, T, As, const N: usize> CellDeserializeAsWithArgs<'de, [T; N]> for [As; N] @@ -114,8 +112,8 @@ where parser: &mut CellParser<'de>, args: Self::Args, ) -> Result, CellParserError<'de>> { - UnpackAsWrap::::parse_with(parser, args) - .map(UnpackAsWrap::into_inner) + AsWrap::::parse_with(parser, args) + .map(AsWrap::into_inner) .map(Into::into) } } @@ -131,8 +129,8 @@ where parser: &mut CellParser<'de>, args: Self::Args, ) -> Result, CellParserError<'de>> { - UnpackAsWrap::::parse_with(parser, args) - .map(UnpackAsWrap::into_inner) + AsWrap::::parse_with(parser, args) + .map(AsWrap::into_inner) .map(Into::into) } } @@ -148,12 +146,17 @@ where parser: &mut CellParser<'de>, args: Self::Args, ) -> Result, CellParserError<'de>> { - UnpackAsWrap::::parse_with(parser, args) - .map(UnpackAsWrap::into_inner) + AsWrap::::parse_with(parser, args) + .map(AsWrap::into_inner) .map(Into::into) } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl<'de, Left, Right, AsLeft, AsRight> CellDeserializeAsWithArgs<'de, Either> for Either where @@ -168,10 +171,8 @@ where args: Self::Args, ) -> Result, CellParserError<'de>> { Ok( - Either::, UnpackAsWrap>::parse_with( - parser, args, - )? - .map_either(UnpackAsWrap::into_inner, UnpackAsWrap::into_inner), + Either::, AsWrap>::parse_with(parser, args)? + .map_either(AsWrap::into_inner, AsWrap::into_inner), ) } } @@ -193,6 +194,11 @@ where } } +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl<'de, T, As> CellDeserializeAsWithArgs<'de, Option> for Option where As: CellDeserializeAsWithArgs<'de, T>, @@ -204,6 +210,6 @@ where parser: &mut CellParser<'de>, args: Self::Args, ) -> Result, CellParserError<'de>> { - Ok(Option::>::parse_with(parser, args)?.map(UnpackAsWrap::into_inner)) + Ok(Option::>::parse_with(parser, args)?.map(AsWrap::into_inner)) } } diff --git a/crates/tlb/src/de/args/mod.rs b/crates/tlb/src/de/args/mod.rs index 57e63a8..8557284 100644 --- a/crates/tlb/src/de/args/mod.rs +++ b/crates/tlb/src/de/args/mod.rs @@ -11,15 +11,21 @@ use crate::{ use super::{CellParser, CellParserError}; +/// A type that can be **de**serialized. +/// In contrast with [`CellDeserialize`](super::CellDeserialize) it allows to +/// pass [`Args`](CellDeserializeWithArgs::Args) and these arguments can be +/// calculated dynamically in runtime. pub trait CellDeserializeWithArgs<'de>: Sized { type Args; + /// Parses the value with args fn parse_with( parser: &mut CellParser<'de>, args: Self::Args, ) -> Result>; } +/// Owned version of [`CellDeserializeWithArgs`] pub trait CellDeserializeWithArgsOwned: for<'de> CellDeserializeWithArgs<'de> {} impl CellDeserializeWithArgsOwned for T where T: for<'de> CellDeserializeWithArgs<'de> {} @@ -133,6 +139,11 @@ where } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl<'de, Left, Right> CellDeserializeWithArgs<'de> for Either where Left: CellDeserializeWithArgs<'de>, @@ -152,7 +163,11 @@ where } } -/// [Maybe](https://docs.ton.org/develop/data-formats/tl-b-types#maybe) +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl<'de, T> CellDeserializeWithArgs<'de> for Option where T: CellDeserializeWithArgs<'de>, diff --git a/crates/tlb/src/de/as.rs b/crates/tlb/src/de/as.rs index 4d1491f..5d7369e 100644 --- a/crates/tlb/src/de/as.rs +++ b/crates/tlb/src/de/as.rs @@ -1,27 +1,23 @@ use core::mem::MaybeUninit; use std::{rc::Rc, sync::Arc}; -use crate::{bits::de::r#as::UnpackAsWrap, either::Either, ResultExt}; +use crate::{either::Either, r#as::AsWrap, ResultExt}; use super::{CellDeserialize, CellParser, CellParserError}; +/// Adapter to **de**serialize `T`. +/// See [`as`](crate::as) module-level documentation for more. +/// +/// For dynamic arguments, see +/// [`CellDeserializeAsWithArgs`](super::args::as::CellDeserializeAsWithArgs). pub trait CellDeserializeAs<'de, T> { fn parse_as(parser: &mut CellParser<'de>) -> Result>; } +/// Owned version of [`CellDeserializeAs`] pub trait CellDeserializeAsOwned: for<'de> CellDeserializeAs<'de, T> {} impl CellDeserializeAsOwned for T where T: for<'de> CellDeserializeAs<'de, As> + ?Sized {} -impl<'de, T, As> CellDeserialize<'de> for UnpackAsWrap -where - As: CellDeserializeAs<'de, T> + ?Sized, -{ - #[inline] - fn parse(parser: &mut CellParser<'de>) -> Result> { - As::parse_as(parser).map(Self::new) - } -} - impl<'de, T, As, const N: usize> CellDeserializeAs<'de, [T; N]> for [As; N] where As: CellDeserializeAs<'de, T>, @@ -46,7 +42,7 @@ macro_rules! impl_cell_deserialize_as_for_tuple { #[inline] fn parse_as(parser: &mut CellParser<'de>) -> Result<($($t,)+), CellParserError<'de>> { Ok(($( - UnpackAsWrap::<$t, $a>::parse(parser) + AsWrap::<$t, $a>::parse(parser) .context(concat!(".", stringify!($n)))? .into_inner(), )+)) @@ -71,8 +67,8 @@ where { #[inline] fn parse_as(parser: &mut CellParser<'de>) -> Result, CellParserError<'de>> { - UnpackAsWrap::::parse(parser) - .map(UnpackAsWrap::into_inner) + AsWrap::::parse(parser) + .map(AsWrap::into_inner) .map(Box::new) } } @@ -83,8 +79,8 @@ where { #[inline] fn parse_as(parser: &mut CellParser<'de>) -> Result, CellParserError<'de>> { - UnpackAsWrap::::parse(parser) - .map(UnpackAsWrap::into_inner) + AsWrap::::parse(parser) + .map(AsWrap::into_inner) .map(Rc::new) } } @@ -95,12 +91,17 @@ where { #[inline] fn parse_as(parser: &mut CellParser<'de>) -> Result, CellParserError<'de>> { - UnpackAsWrap::::parse(parser) - .map(UnpackAsWrap::into_inner) + AsWrap::::parse(parser) + .map(AsWrap::into_inner) .map(Arc::new) } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl<'de, Left, Right, AsLeft, AsRight> CellDeserializeAs<'de, Either> for Either where @@ -110,8 +111,8 @@ where #[inline] fn parse_as(parser: &mut CellParser<'de>) -> Result, CellParserError<'de>> { Ok( - Either::, UnpackAsWrap>::parse(parser)? - .map_either(UnpackAsWrap::into_inner, UnpackAsWrap::into_inner), + Either::, AsWrap>::parse(parser)? + .map_either(AsWrap::into_inner, AsWrap::into_inner), ) } } @@ -122,18 +123,23 @@ where { #[inline] fn parse_as(parser: &mut CellParser<'de>) -> Result, CellParserError<'de>> { - Ok(Either::<(), UnpackAsWrap>::parse(parser)? - .map_right(UnpackAsWrap::into_inner) + Ok(Either::<(), AsWrap>::parse(parser)? + .map_right(AsWrap::into_inner) .right()) } } +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl<'de, T, As> CellDeserializeAs<'de, Option> for Option where As: CellDeserializeAs<'de, T>, { #[inline] fn parse_as(parser: &mut CellParser<'de>) -> Result, CellParserError<'de>> { - Ok(Option::>::parse(parser)?.map(UnpackAsWrap::into_inner)) + Ok(Option::>::parse(parser)?.map(AsWrap::into_inner)) } } diff --git a/crates/tlb/src/de/mod.rs b/crates/tlb/src/de/mod.rs index ba36628..4031fb2 100644 --- a/crates/tlb/src/de/mod.rs +++ b/crates/tlb/src/de/mod.rs @@ -1,3 +1,4 @@ +//! **De**serialization for [TL-B](https://docs.ton.org/develop/data-formats/tl-b-language) pub mod args; pub mod r#as; mod parser; @@ -14,10 +15,13 @@ use crate::{ Cell, ResultExt, }; +/// A type that can be **de**serialized from [`CellParser`]. pub trait CellDeserialize<'de>: Sized { + /// Parse value fn parse(parser: &mut CellParser<'de>) -> Result>; } +/// Owned version of [`CellDeserialize`] pub trait CellDeserializeOwned: for<'de> CellDeserialize<'de> {} impl CellDeserializeOwned for T where T: for<'de> CellDeserialize<'de> {} @@ -100,6 +104,11 @@ where } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl<'de, Left, Right> CellDeserialize<'de> for Either where Left: CellDeserialize<'de>, @@ -114,7 +123,11 @@ where } } -/// [Maybe](https://docs.ton.org/develop/data-formats/tl-b-types#maybe) +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl<'de, T> CellDeserialize<'de> for Option where T: CellDeserialize<'de>, diff --git a/crates/tlb/src/de/parser.rs b/crates/tlb/src/de/parser.rs index 6ea4e70..fccafda 100644 --- a/crates/tlb/src/de/parser.rs +++ b/crates/tlb/src/de/parser.rs @@ -17,8 +17,10 @@ use super::{ CellDeserialize, }; +/// [`Error`] for [`CellParser`] pub type CellParserError<'de> = as BitReader>::Error; +/// Cell parser created with [`Cell::parser()`]. pub struct CellParser<'de> { pub(super) data: &'de BitSlice, pub(super) references: &'de [Arc], @@ -30,6 +32,7 @@ impl<'de> CellParser<'de> { Self { data, references } } + /// Parse the value using its [`CellDeserialize`] implementation #[inline] pub fn parse(&mut self) -> Result> where @@ -38,6 +41,8 @@ impl<'de> CellParser<'de> { T::parse(self) } + /// Return iterator that parses values using [`CellDeserialize`] + /// implementation. #[inline] pub fn parse_iter(&mut self) -> impl Iterator>> + '_ where @@ -48,6 +53,8 @@ impl<'de> CellParser<'de> { .map(|(i, v)| v.with_context(|| format!("[{i}]"))) } + /// Parse the value with args using its [`CellDeserializeWithArgs`] + /// implementation. #[inline] pub fn parse_with(&mut self, args: T::Args) -> Result> where @@ -56,6 +63,8 @@ impl<'de> CellParser<'de> { T::parse_with(self, args) } + /// Return iterator that parses values with args using + /// [`CellDeserializeWithArgs`] implementation. #[inline] pub fn parse_iter_with<'a: 'de, T>( &mut self, @@ -70,6 +79,8 @@ impl<'de> CellParser<'de> { .map(|(i, v)| v.with_context(|| format!("[{i}]"))) } + /// Parse the value using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] pub fn parse_as(&mut self) -> Result> where @@ -78,6 +89,8 @@ impl<'de> CellParser<'de> { As::parse_as(self) } + /// Returns iterator that parses values using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] pub fn parse_iter_as( &mut self, @@ -90,6 +103,8 @@ impl<'de> CellParser<'de> { .map(|(i, v)| v.with_context(|| format!("[{i}]"))) } + /// Parse value with args using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] pub fn parse_as_with(&mut self, args: As::Args) -> Result> where @@ -98,6 +113,8 @@ impl<'de> CellParser<'de> { As::parse_as_with(self, args) } + /// Returns iterator that parses values with args using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] pub fn parse_iter_as_with<'a: 'de, T, As>( &mut self, @@ -141,11 +158,13 @@ impl<'de> CellParser<'de> { self.pop_reference()?.parse_fully_as_with::(args) } + /// Returns whether this parser has no more data and references. #[inline] pub fn is_empty(&self) -> bool { self.data.is_empty() && self.references.is_empty() } + /// Returns an error if this parser has more data or references. #[inline] pub fn ensure_empty(&self) -> Result<(), CellParserError<'de>> { if !self.is_empty() { diff --git a/crates/tlb/src/lib.rs b/crates/tlb/src/lib.rs index 29adbd8..e9f843a 100644 --- a/crates/tlb/src/lib.rs +++ b/crates/tlb/src/lib.rs @@ -1,3 +1,143 @@ +#![doc = include_str!("../README.md")] +//! ## Example +//! +//! Consider the following TL-B schema: +//! +//! ```tlb +//! tag$10 query_id:uint64 amount:(VarUInteger 16) payload:(Maybe ^Cell) = Hello; +//! ``` +//! +//! Let's first define a struct `Hello` that holds these parameters: +//! +//! ```rust +//! # use num_bigint::BigUint; +//! # use tlb::Cell; +//! struct Hello { +//! pub query_id: u64, +//! pub amount: BigUint, +//! pub payload: Option, +//! } +//! ``` +//! +//! ### **Ser**ialization +//! +//! To be able to **ser**ialize a type to [`Cell`], we should implement +//! [`CellSerialize`](crate::ser::CellSerialize) on it: +//! +//! ``` +//! # use num_bigint::BigUint; +//! # use tlb::{ +//! # r#as::Ref, +//! # bits::{r#as::{NBits, VarInt}, ser::BitWriterExt}, +//! # Cell, +//! # ser::{CellSerialize, CellBuilder, CellBuilderError}, +//! # StringError, +//! # }; +//! # +//! # struct Hello { +//! # pub query_id: u64, +//! # pub amount: BigUint, +//! # pub payload: Option, +//! # } +//! impl CellSerialize for Hello { +//! fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { +//! builder +//! // tag$10 +//! .pack_as::<_, NBits<2>>(0b10)? +//! // query_id:uint64 +//! .pack(self.query_id)? +//! // amount:(VarUInteger 16) +//! .pack_as::<_, &VarInt<4>>(&self.amount)? +//! // payload:(Maybe ^Cell) +//! .store_as::<_, Option>(self.payload.as_ref())?; +//! Ok(()) +//! } +//! } +//! +//! # fn main() -> Result<(), StringError> { +//! // create a builder +//! let mut builder = Cell::builder(); +//! // serialize value into builder +//! builder.store(Hello { +//! query_id: 0, +//! amount: 1_000u64.into(), +//! payload: None, +//! })?; +//! // convert builder into cell +//! let cell = builder.into_cell(); +//! # Ok(()) +//! # } +//! ``` +//! +//! ### **De**serialization +//! +//! To be able to **de**serialize a type from [`Cell`], we should implement +//! [`CellDeserialize`](crate::de::CellDeserialize) on it: +//! +//! ```rust +//! # use num_bigint::BigUint; +//! # use tlb::{ +//! # r#as::{Ref, ParseFully}, +//! # bits::{r#as::{NBits, VarInt}, de::BitReaderExt, ser::BitWriterExt}, +//! # Cell, +//! # de::{CellDeserialize, CellParser, CellParserError}, +//! # Error, +//! # ser::{CellSerialize, CellBuilder, CellBuilderError}, +//! # StringError, +//! # }; +//! # #[derive(Debug, PartialEq)] +//! # struct Hello { +//! # pub query_id: u64, +//! # pub amount: BigUint, +//! # pub payload: Option, +//! # } +//! # impl CellSerialize for Hello { +//! # fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { +//! # builder +//! # // tag$10 +//! # .pack_as::<_, NBits<2>>(0b10)? +//! # // query_id:uint64 +//! # .pack(self.query_id)? +//! # // amount:(VarUInteger 16) +//! # .pack_as::<_, &VarInt<4>>(&self.amount)? +//! # // payload:(Maybe ^Cell) +//! # .store_as::<_, Option>(self.payload.as_ref())?; +//! # Ok(()) +//! # } +//! # } +//! impl<'de> CellDeserialize<'de> for Hello { +//! fn parse(parser: &mut CellParser<'de>) -> Result> { +//! // tag$10 +//! let tag: u8 = parser.unpack_as::<_, NBits<2>>()?; +//! if tag != 0b10 { +//! return Err(Error::custom(format!("unknown tag: {tag:#b}"))); +//! } +//! Ok(Self { +//! // query_id:uint64 +//! query_id: parser.unpack()?, +//! // amount:(VarUInteger 16) +//! amount: parser.unpack_as::<_, VarInt<4>>()?, +//! // payload:(Maybe ^Cell) +//! payload: parser.parse_as::<_, Option>>()?, +//! }) +//! } +//! } +//! +//! # fn main() -> Result<(), StringError> { +//! # let orig = Hello { +//! # query_id: 0, +//! # amount: 1_000u64.into(), +//! # payload: None, +//! # }; +//! # let mut builder = Cell::builder(); +//! # builder.store(&orig)?; +//! # let cell = builder.into_cell(); +//! let mut parser = cell.parser(); +//! let hello: Hello = parser.parse()?; +//! # assert_eq!(hello, orig); +//! # Ok(()) +//! # } +//! ``` pub mod r#as; mod cell; pub mod de; diff --git a/crates/tlb/src/ser/args/as.rs b/crates/tlb/src/ser/args/as.rs index 2f0cd5e..ae84463 100644 --- a/crates/tlb/src/ser/args/as.rs +++ b/crates/tlb/src/ser/args/as.rs @@ -1,15 +1,23 @@ use std::{rc::Rc, sync::Arc}; -use crate::{bits::ser::r#as::PackAsWrap, either::Either, r#as::NoArgs}; +use crate::{ + either::Either, + r#as::{AsWrap, NoArgs}, +}; use super::{ super::{CellBuilder, CellBuilderError}, CellSerializeWithArgs, }; +/// Adapter to **ser**ialize `T` with args. +/// See [`as`](crate::as) module-level documentation for more. +/// +/// For version without arguments, see [`CellSerializeAs`](super::super::as::CellSerializeAs). pub trait CellSerializeAsWithArgs { type Args; + /// Stores the value with args using an adapter fn store_as_with( source: &T, builder: &mut CellBuilder, @@ -17,23 +25,6 @@ pub trait CellSerializeAsWithArgs { ) -> Result<(), CellBuilderError>; } -impl<'a, T, As> CellSerializeWithArgs for PackAsWrap<'a, T, As> -where - T: ?Sized, - As: CellSerializeAsWithArgs + ?Sized, -{ - type Args = As::Args; - - #[inline] - fn store_with( - &self, - builder: &mut CellBuilder, - args: Self::Args, - ) -> Result<(), CellBuilderError> { - As::store_as_with(self.into_inner(), builder, args) - } -} - impl<'a, T, As> CellSerializeAsWithArgs<&'a T> for &'a As where T: ?Sized, @@ -47,7 +38,7 @@ where builder: &mut CellBuilder, args: Self::Args, ) -> Result<(), CellBuilderError> { - PackAsWrap::::new(source).store_with(builder, args) + AsWrap::<&T, As>::new(source).store_with(builder, args) } } @@ -64,7 +55,7 @@ where builder: &mut CellBuilder, args: Self::Args, ) -> Result<(), CellBuilderError> { - PackAsWrap::::new(source).store_with(builder, args) + AsWrap::<&T, As>::new(source).store_with(builder, args) } } @@ -144,7 +135,7 @@ where builder: &mut CellBuilder, args: Self::Args, ) -> Result<(), CellBuilderError> { - PackAsWrap::::new(source).store_with(builder, args) + AsWrap::<&T, As>::new(source).store_with(builder, args) } } @@ -160,7 +151,7 @@ where builder: &mut CellBuilder, args: Self::Args, ) -> Result<(), CellBuilderError> { - PackAsWrap::::new(source).store_with(builder, args) + AsWrap::<&T, As>::new(source).store_with(builder, args) } } @@ -176,10 +167,15 @@ where builder: &mut CellBuilder, args: Self::Args, ) -> Result<(), CellBuilderError> { - PackAsWrap::::new(source).store_with(builder, args) + AsWrap::<&T, As>::new(source).store_with(builder, args) } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl CellSerializeAsWithArgs> for Either where @@ -196,10 +192,7 @@ where ) -> Result<(), CellBuilderError> { source .as_ref() - .map_either( - PackAsWrap::::new, - PackAsWrap::::new, - ) + .map_either(AsWrap::<&Left, AsLeft>::new, AsWrap::<&Right, AsRight>::new) .store_with(builder, args) } } @@ -217,13 +210,18 @@ where args: Self::Args, ) -> Result<(), CellBuilderError> { match source.as_ref() { - None => Either::Left(PackAsWrap::<_, NoArgs<_>>::new(&())), - Some(v) => Either::Right(PackAsWrap::::new(v)), + None => Either::Left(AsWrap::<_, NoArgs<_>>::new(&())), + Some(v) => Either::Right(AsWrap::<&T, As>::new(v)), } .store_with(builder, args) } } +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl CellSerializeAsWithArgs> for Option where As: CellSerializeAsWithArgs, @@ -238,7 +236,7 @@ where ) -> Result<(), CellBuilderError> { source .as_ref() - .map(PackAsWrap::<_, As>::new) + .map(AsWrap::<_, As>::new) .store_with(builder, args) } } diff --git a/crates/tlb/src/ser/args/mod.rs b/crates/tlb/src/ser/args/mod.rs index d0cbeab..bb792a0 100644 --- a/crates/tlb/src/ser/args/mod.rs +++ b/crates/tlb/src/ser/args/mod.rs @@ -8,10 +8,15 @@ use crate::{bits::ser::BitWriterExt, either::Either, r#as::Same, ResultExt}; use super::{CellBuilder, CellBuilderError}; +/// A type that can be **ser**ialized. +/// In contrast with [`CellSerialize`](super::CellSerialize) it allows to pass +/// [`Args`](CellSerializeWithArgs::Args) and these arguments can be +/// calculated dynamically in runtime. #[autoimpl(for &T, &mut T, Box, Rc, Arc)] pub trait CellSerializeWithArgs { type Args; + /// Stores the value with args fn store_with( &self, builder: &mut CellBuilder, @@ -37,6 +42,11 @@ where } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl CellSerializeWithArgs for Either where L: CellSerializeWithArgs, @@ -66,7 +76,11 @@ where } } -/// [Maybe](https://docs.ton.org/develop/data-formats/tl-b-types#maybe) +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl CellSerializeWithArgs for Option where T: CellSerializeWithArgs, diff --git a/crates/tlb/src/ser/as.rs b/crates/tlb/src/ser/as.rs index b26d985..dc99a56 100644 --- a/crates/tlb/src/ser/as.rs +++ b/crates/tlb/src/ser/as.rs @@ -1,26 +1,19 @@ use std::{rc::Rc, sync::Arc}; -pub use crate::bits::ser::r#as::PackAsWrap; -use crate::{either::Either, ResultExt}; +use crate::{either::Either, r#as::AsWrap, ResultExt}; use super::{CellBuilder, CellBuilderError, CellSerialize}; +/// Adapter to **ser**ialize `T`. +/// See [`as`](crate::as) module-level documentation for more. +/// +/// For dynamic arguments, see +/// [`CellSerializeAsWithArgs`](super::args::as::CellSerializeAsWithArgs). pub trait CellSerializeAs { + /// Store given value using an adapter fn store_as(source: &T, builder: &mut CellBuilder) -> Result<(), CellBuilderError>; } -impl<'a, T, As> CellSerialize for PackAsWrap<'a, T, As> -where - T: ?Sized, - As: ?Sized, - As: CellSerializeAs, -{ - #[inline] - fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { - As::store_as(self.into_inner(), builder) - } -} - impl<'a, T, As> CellSerializeAs<&'a T> for &'a As where As: CellSerializeAs + ?Sized, @@ -28,7 +21,7 @@ where { #[inline] fn store_as(source: &&T, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { - PackAsWrap::::new(source).store(builder) + AsWrap::<&T, As>::new(source).store(builder) } } @@ -39,7 +32,7 @@ where { #[inline] fn store_as(source: &&mut T, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { - PackAsWrap::::new(source).store(builder) + AsWrap::<&T, As>::new(source).store(builder) } } @@ -102,7 +95,7 @@ where { #[inline] fn store_as(source: &Box, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { - PackAsWrap::::new(source).store(builder) + AsWrap::<&T, As>::new(source).store(builder) } } @@ -112,7 +105,7 @@ where { #[inline] fn store_as(source: &Rc, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { - PackAsWrap::::new(source).store(builder) + AsWrap::<&T, As>::new(source).store(builder) } } @@ -122,10 +115,15 @@ where { #[inline] fn store_as(source: &Arc, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { - PackAsWrap::::new(source).store(builder) + AsWrap::<&T, As>::new(source).store(builder) } } +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl CellSerializeAs> for Either where AsLeft: CellSerializeAs, @@ -138,10 +136,7 @@ where ) -> Result<(), CellBuilderError> { source .as_ref() - .map_either( - PackAsWrap::::new, - PackAsWrap::::new, - ) + .map_either(AsWrap::<&Left, AsLeft>::new, AsWrap::<&Right, AsRight>::new) .store(builder) } } @@ -154,29 +149,34 @@ where fn store_as(source: &Option, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { match source.as_ref() { None => Either::Left(()), - Some(v) => Either::Right(PackAsWrap::::new(v)), + Some(v) => Either::Right(AsWrap::<&T, As>::new(v)), } .store(builder) } } +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl CellSerializeAs> for Option where As: CellSerializeAs, { #[inline] fn store_as(source: &Option, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { - source.as_ref().map(PackAsWrap::<_, As>::new).store(builder) + source.as_ref().map(AsWrap::<_, As>::new).store(builder) } } pub trait CellSerializeWrapAsExt { #[inline] - fn wrap_as(&self) -> PackAsWrap<'_, Self, As> + fn wrap_as(&self) -> AsWrap<&'_ Self, As> where As: CellSerializeAs + ?Sized, { - PackAsWrap::new(self) + AsWrap::new(self) } } impl CellSerializeWrapAsExt for T {} diff --git a/crates/tlb/src/ser/builder.rs b/crates/tlb/src/ser/builder.rs index cd8fd2b..19ce2de 100644 --- a/crates/tlb/src/ser/builder.rs +++ b/crates/tlb/src/ser/builder.rs @@ -15,8 +15,14 @@ use super::{ }; type CellBitWriter = LimitWriter>; + +/// [`Error`] for [`CellBuilder`] pub type CellBuilderError = ::Error; +/// Cell builder created with [`Cell::builder()`]. +/// +/// [`CellBuilder`] can then be converted to constructed [`Cell`] by using +/// [`.into_cell()`](CellBuilder::into_cell). pub struct CellBuilder { data: CellBitWriter, references: Vec>, @@ -35,6 +41,7 @@ impl CellBuilder { } } + /// Store the value using its [`CellSerialize`] implementation #[inline] pub fn store(&mut self, value: T) -> Result<&mut Self, CellBuilderError> where @@ -44,6 +51,8 @@ impl CellBuilder { Ok(self) } + /// Store the value with args using its [`CellSerializeWithArgs`] + /// implementation #[inline] pub fn store_with(&mut self, value: T, args: T::Args) -> Result<&mut Self, CellBuilderError> where @@ -53,6 +62,8 @@ impl CellBuilder { Ok(self) } + /// Store all values from given iterator using [`CellSerialize`] + /// implementation of its item type. #[inline] pub fn store_many( &mut self, @@ -67,6 +78,8 @@ impl CellBuilder { Ok(self) } + /// Store all values from given iterator with args using + /// [`CellSerializeWithArgs`] implementation of its item type. #[inline] pub fn store_many_with( &mut self, @@ -84,6 +97,8 @@ impl CellBuilder { Ok(self) } + /// Store given value using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] pub fn store_as(&mut self, value: T) -> Result<&mut Self, CellBuilderError> where @@ -93,6 +108,8 @@ impl CellBuilder { Ok(self) } + /// Store given value with args using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] pub fn store_as_with( &mut self, @@ -106,6 +123,8 @@ impl CellBuilder { Ok(self) } + /// Store all values from iterator using an adapter. s + /// See [`as`](crate::as) module-level documentation for more. #[inline] pub fn store_many_as( &mut self, @@ -121,6 +140,8 @@ impl CellBuilder { Ok(self) } + /// Store all values from iterator with args using an adapter. + /// See [`as`](crate::as) module-level documentation for more. #[inline] pub fn store_many_as_with( &mut self, @@ -177,6 +198,7 @@ impl CellBuilder { Ok(self) } + /// Convert builder to [`Cell`] #[inline] #[must_use] pub fn into_cell(self) -> Cell { diff --git a/crates/tlb/src/ser/mod.rs b/crates/tlb/src/ser/mod.rs index e7cc5ce..b3e1806 100644 --- a/crates/tlb/src/ser/mod.rs +++ b/crates/tlb/src/ser/mod.rs @@ -1,3 +1,4 @@ +//! **Ser**ialization for [TL-B](https://docs.ton.org/develop/data-formats/tl-b-language) pub mod args; pub mod r#as; mod builder; @@ -15,8 +16,10 @@ use crate::{ Cell, ResultExt, }; +/// A type that can be **ser**ilalized into [`CellBuilder`]. #[autoimpl(for &T, &mut T, Box, Rc, Arc)] pub trait CellSerialize { + /// Store the value into [`CellBuilder`] fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError>; } @@ -75,6 +78,11 @@ impl_cell_serialize_for_tuple!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7); impl_cell_serialize_for_tuple!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8); impl_cell_serialize_for_tuple!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8,9:T9); +/// Implementation of [`Either X Y`](https://docs.ton.org/develop/data-formats/tl-b-types#either): +/// ```tlb +/// left$0 {X:Type} {Y:Type} value:X = Either X Y; +/// right$1 {X:Type} {Y:Type} value:Y = Either X Y; +/// ``` impl CellSerialize for Either where L: CellSerialize, @@ -98,7 +106,11 @@ where } } -/// [Maybe](https://docs.ton.org/develop/data-formats/tl-b-types#maybe) +/// Implementation of [`Maybe X`](https://docs.ton.org/develop/data-formats/tl-b-types#maybe): +/// ```tlb +/// nothing$0 {X:Type} = Maybe X; +/// just$1 {X:Type} value:X = Maybe X; +/// ``` impl CellSerialize for Option where T: CellSerialize, diff --git a/crates/toner/src/lib.rs b/crates/toner/src/lib.rs index 9cdae42..6ae3ffe 100644 --- a/crates/toner/src/lib.rs +++ b/crates/toner/src/lib.rs @@ -1,7 +1,4 @@ -pub mod tlb { - pub use tlb::*; - - pub use tlb_ton as ton; -} - -pub use ton_contracts as contracts; +#![doc = include_str!("../../../README.md")] +pub use tlb; +pub use tlb_ton as ton; +pub use ton_contracts as contracts; \ No newline at end of file From 7a6f3167227c106606af0086ca76c8a014cd50f3 Mon Sep 17 00:00:00 2001 From: Arseny Mitin Date: Tue, 11 Jun 2024 21:16:29 +0400 Subject: [PATCH 2/2] chore: bump version --- Cargo.lock | 10 +++++----- Cargo.toml | 10 +++++----- crates/bits/Cargo.toml | 2 +- crates/contracts/Cargo.toml | 2 +- crates/tlb-ton/Cargo.toml | 2 +- crates/tlb/Cargo.toml | 2 +- crates/toner/Cargo.toml | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00ef6e4..0ada2e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -692,7 +692,7 @@ dependencies = [ [[package]] name = "tlb" -version = "0.2.20" +version = "0.2.21" dependencies = [ "bitvec", "hex", @@ -705,7 +705,7 @@ dependencies = [ [[package]] name = "tlb-ton" -version = "0.2.20" +version = "0.2.21" dependencies = [ "base64 0.21.7", "chrono", @@ -723,7 +723,7 @@ dependencies = [ [[package]] name = "tlbits" -version = "0.2.20" +version = "0.2.21" dependencies = [ "bitvec", "either", @@ -735,7 +735,7 @@ dependencies = [ [[package]] name = "ton-contracts" -version = "0.2.20" +version = "0.2.21" dependencies = [ "anyhow", "bitvec", @@ -753,7 +753,7 @@ dependencies = [ [[package]] name = "toner" -version = "0.2.20" +version = "0.2.21" dependencies = [ "tlb", "tlb-ton", diff --git a/Cargo.toml b/Cargo.toml index c1c10a6..963675d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,11 @@ categories = ["encoding"] license-file = "LICENSE.txt" [workspace.dependencies] -tlb = { path = "./crates/tlb", version = "0.2.20" } -tlbits = { path = "./crates/bits", version = "0.2.20" } -tlb-ton = { path = "./crates/tlb-ton", version = "0.2.20" } -ton-contracts = { path = "./crates/contracts", version = "0.2.20" } -toner = { path = "./crates/toner", version = "0.2.20" } +tlb = { path = "./crates/tlb", version = "0.2.21" } +tlbits = { path = "./crates/bits", version = "0.2.21" } +tlb-ton = { path = "./crates/tlb-ton", version = "0.2.21" } +ton-contracts = { path = "./crates/contracts", version = "0.2.21" } +toner = { path = "./crates/toner", version = "0.2.21" } anyhow = "1" base64 = "0.21" diff --git a/crates/bits/Cargo.toml b/crates/bits/Cargo.toml index f75dc48..194fbab 100644 --- a/crates/bits/Cargo.toml +++ b/crates/bits/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tlbits" -version = "0.2.20" +version = "0.2.21" edition.workspace = true repository.workspace = true license-file.workspace = true diff --git a/crates/contracts/Cargo.toml b/crates/contracts/Cargo.toml index 8e7adad..d03a561 100644 --- a/crates/contracts/Cargo.toml +++ b/crates/contracts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ton-contracts" -version = "0.2.20" +version = "0.2.21" edition.workspace = true repository.workspace = true license-file.workspace = true diff --git a/crates/tlb-ton/Cargo.toml b/crates/tlb-ton/Cargo.toml index c688037..d13a7eb 100644 --- a/crates/tlb-ton/Cargo.toml +++ b/crates/tlb-ton/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tlb-ton" -version = "0.2.20" +version = "0.2.21" edition.workspace = true repository.workspace = true license-file.workspace = true diff --git a/crates/tlb/Cargo.toml b/crates/tlb/Cargo.toml index 9c569d2..ab6c92f 100644 --- a/crates/tlb/Cargo.toml +++ b/crates/tlb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tlb" -version = "0.2.20" +version = "0.2.21" edition.workspace = true repository.workspace = true license-file.workspace = true diff --git a/crates/toner/Cargo.toml b/crates/toner/Cargo.toml index 831db06..7e2ebe1 100644 --- a/crates/toner/Cargo.toml +++ b/crates/toner/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "toner" -version = "0.2.20" +version = "0.2.21" edition.workspace = true repository.workspace = true license-file.workspace = true