diff --git a/src/boc/mod.rs b/src/boc/mod.rs index 446e4223..014bc3a6 100644 --- a/src/boc/mod.rs +++ b/src/boc/mod.rs @@ -2,11 +2,17 @@ use crate::cell::{Cell, CellBuilder, CellContext, CellFamily, DynCell, HashBytes, Load, Store}; +#[cfg(feature = "serde")] +pub use self::serde::SerdeBoc; + /// BOC decoder implementation. pub mod de; /// BOC encoder implementation. pub mod ser; +#[cfg(feature = "serde")] +mod serde; + #[cfg(test)] mod tests; @@ -47,41 +53,6 @@ impl BocTag { } } -/// A serde helper to use [`Boc`] inside [`Option`]. -#[cfg(feature = "serde")] -pub struct OptionBoc; - -#[cfg(feature = "serde")] -impl OptionBoc { - /// Serializes an optional cell into an encoded BOC - /// (as base64 for human readable serializers). - pub fn serialize(cell: &Option, serializer: S) -> Result - where - S: serde::Serializer, - T: AsRef, - { - match cell { - Some(cell) => serializer.serialize_some(cell.as_ref()), - None => serializer.serialize_none(), - } - } - - /// Deserializes an optional cell from an encoded BOC - /// (from base64 for human readable deserializers). - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: serde::Deserializer<'de>, - { - use serde::Deserialize; - - #[derive(Deserialize)] - #[repr(transparent)] - struct Wrapper(#[serde(with = "Boc")] Cell); - - Ok(ok!(Option::::deserialize(deserializer)).map(|Wrapper(cell)| cell)) - } -} - /// BOC (Bag Of Cells) helper. pub struct Boc; @@ -306,40 +277,26 @@ impl Boc { /// Serializes cell into an encoded BOC (as base64 for human readable serializers). #[cfg(feature = "serde")] - pub fn serialize(cell: &T, serializer: S) -> Result + pub fn serialize(value: T, serializer: S) -> Result where - S: serde::Serializer, - T: AsRef + ?Sized, + SerdeBoc: ::serde::Serialize, + S: ::serde::Serializer, { - use serde::Serialize; + use ::serde::Serialize; - cell.as_ref().serialize(serializer) + SerdeBoc::from(value).serialize(serializer) } /// Deserializes cell from an encoded BOC (from base64 for human readable deserializers). #[cfg(feature = "serde")] - pub fn deserialize<'de, D>(deserializer: D) -> Result + pub fn deserialize<'de, T, D>(deserializer: D) -> Result where - D: serde::Deserializer<'de>, + SerdeBoc: ::serde::Deserialize<'de>, + D: ::serde::Deserializer<'de>, { - use serde::de::Error; + use ::serde::Deserialize; - let is_human_readable = deserializer.is_human_readable(); - let mut boc = ok!(borrow_cow_bytes(deserializer)); - - if is_human_readable { - match crate::util::decode_base64(boc) { - Ok(bytes) => { - boc = std::borrow::Cow::Owned(bytes); - } - Err(_) => return Err(Error::custom("invalid base64 string")), - } - } - - match Boc::decode(boc) { - Ok(cell) => Ok(cell), - Err(e) => Err(Error::custom(e)), - } + SerdeBoc::::deserialize(deserializer).map(SerdeBoc::into_inner) } } @@ -534,10 +491,10 @@ impl BocRepr { #[cfg(feature = "serde")] pub fn serialize(data: &T, serializer: S) -> Result where - S: serde::Serializer, + S: ::serde::Serializer, T: Store, { - use serde::ser::{Error, Serialize}; + use ::serde::ser::{Error, Serialize}; let context = &mut Cell::empty_context(); @@ -559,12 +516,12 @@ impl BocRepr { #[cfg(feature = "serde")] pub fn deserialize<'de, D, T>(deserializer: D) -> Result where - D: serde::Deserializer<'de>, + D: ::serde::Deserializer<'de>, for<'a> T: Load<'a>, { - use serde::de::Error; + use ::serde::de::Error; - let cell = ok!(Boc::deserialize(deserializer)); + let cell = ok!(Boc::deserialize::(deserializer)); match cell.as_ref().parse::() { Ok(data) => Ok(data), Err(_) => Err(Error::custom("failed to decode object from cells")), @@ -582,67 +539,3 @@ pub enum BocReprError { #[error("failed to decode object from cells")] InvalidData(#[source] crate::error::Error), } - -#[cfg(feature = "serde")] -fn borrow_cow_bytes<'de: 'a, 'a, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - use std::borrow::Cow; - - use serde::de::{Error, Visitor}; - - struct CowBytesVisitor; - - impl<'a> Visitor<'a> for CowBytesVisitor { - type Value = Cow<'a, [u8]>; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a byte array") - } - - fn visit_str(self, v: &str) -> Result - where - E: Error, - { - Ok(Cow::Owned(v.as_bytes().to_vec())) - } - - fn visit_borrowed_str(self, v: &'a str) -> Result - where - E: Error, - { - Ok(Cow::Borrowed(v.as_bytes())) - } - - fn visit_string(self, v: String) -> Result - where - E: Error, - { - Ok(Cow::Owned(v.into_bytes())) - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: Error, - { - Ok(Cow::Owned(v.to_vec())) - } - - fn visit_borrowed_bytes(self, v: &'a [u8]) -> Result - where - E: Error, - { - Ok(Cow::Borrowed(v)) - } - - fn visit_byte_buf(self, v: Vec) -> Result - where - E: Error, - { - Ok(Cow::Owned(v)) - } - } - - deserializer.deserialize_bytes(CowBytesVisitor) -} diff --git a/src/boc/serde.rs b/src/boc/serde.rs new file mode 100644 index 00000000..9fffb19e --- /dev/null +++ b/src/boc/serde.rs @@ -0,0 +1,397 @@ +use std::borrow::Cow; +use std::collections::{BTreeMap, HashMap}; +use std::hash::{BuildHasher, Hash}; +use std::marker::PhantomData; +use std::rc::Rc; +use std::sync::Arc; + +use serde::de::{MapAccess, Visitor}; +use serde::ser::SerializeTuple; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::cell::{Cell, DynCell}; + +// === SerdeBoc === + +/// A wrapper type which implements `Serialize` and `Deserialize` for +/// types involving `Cell`. +#[repr(transparent)] +pub struct SerdeBoc(T); + +impl SerdeBoc { + /// Creates a wrapper around a reference to the value. + #[inline(always)] + pub const fn wrap(value: &T) -> &Self { + // SAFETY: SerdeBoc is #[repr(transparent)] + unsafe { &*(value as *const T as *const Self) } + } +} + +impl SerdeBoc { + #[inline(always)] + const fn wrap_slice(value: &[T]) -> &[Self] { + // SAFETY: SerdeBoc is #[repr(transparent)] + unsafe { std::slice::from_raw_parts(value.as_ptr() as *const Self, value.len()) } + } + + /// Consumes self, returning the inner value. + #[inline(always)] + pub fn into_inner(self) -> T { + self.0 + } +} + +impl From for SerdeBoc { + #[inline] + fn from(val: T) -> SerdeBoc { + Self(val) + } +} + +impl Serialize for SerdeBoc { + fn serialize(&self, serializer: S) -> Result { + self.0.serialize_as_boc(serializer) + } +} + +impl<'de, T: DeserializeAsBoc<'de>> Deserialize<'de> for SerdeBoc { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + T::deserialize_as_boc(deserializer).map(Self) + } +} + +// === Serializer stuff === + +trait SerializeAsBoc { + fn serialize_as_boc(&self, serializer: S) -> Result; +} + +impl SerializeAsBoc for Cell { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + self.as_ref().serialize(serializer) + } +} + +impl SerializeAsBoc for DynCell { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + self.serialize(serializer) + } +} + +impl SerializeAsBoc for &'_ T { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + T::serialize_as_boc(self, serializer) + } +} + +impl SerializeAsBoc for Option { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + self.as_ref().map(SerdeBoc::::wrap).serialize(serializer) + } +} + +impl SerializeAsBoc for Box { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + T::serialize_as_boc(self.as_ref(), serializer) + } +} + +impl SerializeAsBoc for Rc { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + T::serialize_as_boc(self.as_ref(), serializer) + } +} + +impl SerializeAsBoc for Arc { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + T::serialize_as_boc(self.as_ref(), serializer) + } +} + +impl SerializeAsBoc for [T] { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + SerdeBoc::wrap_slice(self).serialize(serializer) + } +} + +impl SerializeAsBoc for Vec { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + self.as_slice().serialize_as_boc(serializer) + } +} + +impl SerializeAsBoc for [T; 0] { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result { + ok!(serializer.serialize_tuple(0)).end() + } +} + +macro_rules! serialize_as_boc_array_impls { + ($($len:tt)+) => { + $( + #[allow(clippy::zero_prefixed_literal)] + impl SerializeAsBoc for [T; $len] { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = ok!(serializer.serialize_tuple($len)); + for e in self { + ok!(seq.serialize_element(SerdeBoc::::wrap(e))); + } + seq.end() + } + } + )+ + } +} + +serialize_as_boc_array_impls! { + 01 02 03 04 05 06 07 08 09 10 + 11 12 13 14 15 16 17 18 19 20 + 21 22 23 24 25 26 27 28 29 30 + 31 32 +} + +macro_rules! serialize_as_boc_map_impl { + ($ty:ident) => { + impl SerializeAsBoc for $ty + where + K: Serialize $(+ $kbound1 $(+ $kbound2)*)*, + V: SerializeAsBoc, + $($typaram: $bound,)* + { + #[inline] + fn serialize_as_boc(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_map(self.iter().map(|(k, v)| (k, SerdeBoc::wrap(v)))) + } + } + } +} + +serialize_as_boc_map_impl! { BTreeMap } +serialize_as_boc_map_impl! { HashMap } + +// === Deserializer stuff === + +trait DeserializeAsBoc<'de>: Sized { + fn deserialize_as_boc>(deserializer: D) -> Result; +} + +impl<'de> DeserializeAsBoc<'de> for Cell { + fn deserialize_as_boc>(deserializer: D) -> Result { + use serde::de::Error; + + let is_human_readable = deserializer.is_human_readable(); + let mut boc = ok!(borrow_cow_bytes(deserializer)); + + if is_human_readable { + match crate::util::decode_base64(boc) { + Ok(bytes) => { + boc = Cow::Owned(bytes); + } + Err(_) => return Err(Error::custom("invalid base64 string")), + } + } + + match crate::boc::Boc::decode(boc) { + Ok(cell) => Ok(cell), + Err(e) => Err(Error::custom(e)), + } + } +} + +impl<'de, T: DeserializeAsBoc<'de>> DeserializeAsBoc<'de> for Box { + #[inline] + fn deserialize_as_boc>(deserializer: D) -> Result { + let value = ok!(Box::>::deserialize(deserializer)); + // SAFETY: `SerdeBoc` has the same layout as `T`. + Ok(unsafe { Box::from_raw(Box::into_raw(value).cast::()) }) + } +} + +impl<'de, T: DeserializeAsBoc<'de>> DeserializeAsBoc<'de> for Option { + #[inline] + fn deserialize_as_boc>(deserializer: D) -> Result { + Ok(ok!(Option::>::deserialize(deserializer)).map(SerdeBoc::into_inner)) + } +} + +impl<'de, T: DeserializeAsBoc<'de>> DeserializeAsBoc<'de> for Vec { + #[inline] + fn deserialize_as_boc>(deserializer: D) -> Result { + Ok(ok!(Vec::>::deserialize(deserializer)) + .into_iter() + .map(SerdeBoc::into_inner) + .collect()) + } +} + +impl<'de, T: DeserializeAsBoc<'de>, const N: usize> DeserializeAsBoc<'de> for [T; N] +where + [SerdeBoc; N]: Deserialize<'de>, +{ + fn deserialize_as_boc>(deserializer: D) -> Result { + let value = ok!(<[SerdeBoc; N]>::deserialize(deserializer)); + Ok(value.map(SerdeBoc::into_inner)) + } +} + +macro_rules! deserialize_as_boc_map_impl { + ( + $ty:ident , + $access:ident, + $with_capacity:expr, + ) => { + impl<'de, K, V $(, $typaram)*> DeserializeAsBoc<'de> for $ty + where + K: Deserialize<'de> $(+ $kbound1 $(+ $kbound2)*)*, + V: DeserializeAsBoc<'de>, + $($typaram: $bound1 $(+ $bound2)*),* + { + fn deserialize_as_boc(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct MapVisitor { + marker: PhantomData<$ty>, + } + + impl<'de, K, V $(, $typaram)*> Visitor<'de> for MapVisitor + where + K: Deserialize<'de> $(+ $kbound1 $(+ $kbound2)*)*, + V: DeserializeAsBoc<'de>, + $($typaram: $bound1 $(+ $bound2)*),* + { + type Value = $ty; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a map") + } + + #[inline] + fn visit_map(self, mut $access: A) -> Result + where + A: MapAccess<'de>, + { + let mut values = $with_capacity; + + while let Some((key, SerdeBoc(value))) = ok!($access.next_entry()) { + values.insert(key, value); + } + + Ok(values) + } + } + + let visitor = MapVisitor { marker: PhantomData }; + deserializer.deserialize_map(visitor) + } + } + } +} + +deserialize_as_boc_map_impl! { + BTreeMap, + map, + BTreeMap::new(), +} + +deserialize_as_boc_map_impl! { + HashMap, + map, + HashMap::with_capacity_and_hasher(cautious_size_hint::<(K, V)>(map.size_hint()), S::default()), +} + +fn cautious_size_hint(hint: Option) -> usize { + const MAX_PREALLOC_BYTES: usize = 1024 * 1024; + + if std::mem::size_of::() == 0 { + 0 + } else { + std::cmp::min( + hint.unwrap_or(0), + MAX_PREALLOC_BYTES / std::mem::size_of::(), + ) + } +} + +// === Other stuff === + +fn borrow_cow_bytes<'de: 'a, 'a, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + use serde::de::Error; + + struct CowBytesVisitor; + + impl<'a> Visitor<'a> for CowBytesVisitor { + type Value = Cow<'a, [u8]>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a byte array") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + Ok(Cow::Owned(v.as_bytes().to_vec())) + } + + fn visit_borrowed_str(self, v: &'a str) -> Result + where + E: Error, + { + Ok(Cow::Borrowed(v.as_bytes())) + } + + fn visit_string(self, v: String) -> Result + where + E: Error, + { + Ok(Cow::Owned(v.into_bytes())) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: Error, + { + Ok(Cow::Owned(v.to_vec())) + } + + fn visit_borrowed_bytes(self, v: &'a [u8]) -> Result + where + E: Error, + { + Ok(Cow::Borrowed(v)) + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: Error, + { + Ok(Cow::Owned(v)) + } + } + + deserializer.deserialize_bytes(CowBytesVisitor) +} diff --git a/src/models/account/mod.rs b/src/models/account/mod.rs index b0937bb3..e411e8b8 100644 --- a/src/models/account/mod.rs +++ b/src/models/account/mod.rs @@ -374,10 +374,10 @@ pub struct StateInit { /// Optional special contract flags. pub special: Option, /// Optional contract code. - #[cfg_attr(feature = "serde", serde(with = "crate::boc::OptionBoc"))] + #[cfg_attr(feature = "serde", serde(with = "crate::boc::Boc"))] pub code: Option, /// Optional contract data. - #[cfg_attr(feature = "serde", serde(with = "crate::boc::OptionBoc"))] + #[cfg_attr(feature = "serde", serde(with = "crate::boc::Boc"))] pub data: Option, /// Libraries used in smart-contract. pub libraries: Dict, diff --git a/src/models/transaction/mod.rs b/src/models/transaction/mod.rs index b4c248e4..afb0f71a 100644 --- a/src/models/transaction/mod.rs +++ b/src/models/transaction/mod.rs @@ -99,7 +99,7 @@ mod serde_in_msg { None => serializer.serialize_none(), } } else { - crate::boc::OptionBoc::serialize(in_msg, serializer) + crate::boc::Boc::serialize(in_msg, serializer) } } @@ -119,7 +119,7 @@ mod serde_in_msg { None => Ok(None), } } else { - crate::boc::OptionBoc::deserialize(deserializer) + crate::boc::Boc::deserialize(deserializer) } } } diff --git a/src/prelude.rs b/src/prelude.rs index f17f8f0c..d2d29cb5 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,6 +9,3 @@ pub use crate::cell::{ UsageTree, UsageTreeMode, }; pub use crate::dict::{AugDict, Dict, RawDict}; - -#[cfg(feature = "serde")] -pub use crate::boc::OptionBoc;