diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..d6a1cf5 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,65 @@ +on: [push, pull_request] + +name: Continuous integration + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --features=std + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: rustup component add rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: rustup component add clippy + - uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings diff --git a/.gitignore b/.gitignore index 9f434af..4a571a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ target/ Cargo.lock .vscode/ -**/*.rs.bk \ No newline at end of file +**/*.rs.bk diff --git a/README.md b/README.md new file mode 100644 index 0000000..53aaeaa --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# VirtoSDK + +> ⚠️ Work in progress. The Sube repository is now the VirtoSDK where we are gathering all our client side tools and will add higher lever abstractions for applications to easily integrate Web3 services. diff --git a/libwallet/Cargo.toml b/libwallet/Cargo.toml index 439bb25..4f1e9ec 100644 --- a/libwallet/Cargo.toml +++ b/libwallet/Cargo.toml @@ -20,7 +20,7 @@ mnemonic = {package = "bip0039", version = "0.10.1", default-features = false, o rand_core = {version = "0.6.3", optional = true} # substrate related -schnorrkel = {version = "0.10.2", default-features = false, optional = true}# soft derivation in no_std +schnorrkel = {version = "0.11.4", default-features = false, optional = true}# soft derivation in no_std rand_chacha = {version = "0.3.1", default-features = false, optional = true} # vault os @@ -35,9 +35,10 @@ serde_json = {version = "1.0", default-features = false, features = ["alloc"]} dirs = "4.0" [features] -default = ["std", "substrate", "vault_simple", "mnemonic", "rand"] -rand = ["rand_core"] -sr25519 = ["schnorrkel/u64_backend", "schnorrkel/getrandom"] +# The library has no default features but this can be uncommented during development +# default = [ "std", "substrate", "vault_simple", "vault_os", "vault_pass", "mnemonic", "util_pin", "rand", ] +rand = ["rand_core", "schnorrkel?/getrandom"] +sr25519 = ["dep:schnorrkel"] std = [ "rand_core/getrandom", ] @@ -45,7 +46,6 @@ substrate = ["sr25519"] util_pin = ["pbkdf2", "hmac", "sha2"] vault_os = ["keyring"] vault_pass = ["prs-lib"] -vault_simple = [] [workspace] members = [ diff --git a/libwallet/js/Cargo.toml b/libwallet/js/Cargo.toml index bf0dbbe..2bdd9ff 100644 --- a/libwallet/js/Cargo.toml +++ b/libwallet/js/Cargo.toml @@ -25,10 +25,10 @@ wasm-bindgen-test = "0.3.34" features = ["js"] [features] -default = ["wallet", "js", "hex", "util_pin", "vault_simple"] +default = ["wallet", "js", "hex", "util_pin"] hex = ["dep:hex"] js = ["std"] std = [] util_pin = ["libwallet/util_pin"] -vault_simple = ["libwallet/vault_simple", "libwallet/mnemonic", "libwallet/rand"] +vault_simple = ["libwallet/mnemonic", "libwallet/rand"] wallet = ["libwallet/serde", "libwallet/sr25519", "libwallet/substrate"] diff --git a/libwallet/justfile b/libwallet/justfile new file mode 100644 index 0000000..f084758 --- /dev/null +++ b/libwallet/justfile @@ -0,0 +1,6 @@ +default: + just --choose + +check-no-std: + cargo build --features substrate --target wasm32-unknown-unknown + cargo build --features substrate --target riscv32i-unknown-none-elf diff --git a/libwallet/src/key_pair.rs b/libwallet/src/key_pair.rs index 28be8fc..daac3ed 100644 --- a/libwallet/src/key_pair.rs +++ b/libwallet/src/key_pair.rs @@ -63,7 +63,10 @@ pub mod any { fn public(&self) -> Self::Public { match self { + #[cfg(feature = "sr25519")] Pair::Sr25519(p) => AnyPublic::Sr25519(p.public()), + #[cfg(not(feature = "sr25519"))] + Pair::_None => unreachable!(), } } } @@ -76,7 +79,10 @@ pub mod any { Self: Sized, { match self { + #[cfg(feature = "sr25519")] Pair::Sr25519(kp) => Pair::Sr25519(kp.derive(path)), + #[cfg(not(feature = "sr25519"))] + Pair::_None => unreachable!(), } } } @@ -100,7 +106,10 @@ pub mod any { fn verify>(&self, msg: M, sig: &[u8]) -> bool { match self { + #[cfg(feature = "sr25519")] Pair::Sr25519(p) => super::Signer::verify(p, msg, sig), + #[cfg(not(feature = "sr25519"))] + Pair::_None => unreachable!(), } } } @@ -116,7 +125,10 @@ pub mod any { impl AsRef<[u8]> for AnyPublic { fn as_ref(&self) -> &[u8] { match self { + #[cfg(feature = "sr25519")] AnyPublic::Sr25519(p) => p.as_ref(), + #[cfg(not(feature = "sr25519"))] + AnyPublic::_None => unreachable!(), } } } @@ -232,13 +244,13 @@ pub mod sr25519 { #[cfg(not(feature = "rand_chacha"))] fn derive_simple(key: SecretKey, j: Junction) -> SecretKey { - key.derived_key_simple(ChainCode(j), &[]).0 + key.derived_key_simple(ChainCode(j), []).0 } #[cfg(feature = "rand_chacha")] fn derive_simple(key: SecretKey, j: Junction) -> SecretKey { use rand_core::SeedableRng; // As noted in https://docs.rs/schnorrkel/latest/schnorrkel/context/fn.attach_rng.html - // it's not recommended by should be ok for our simple use cases + // it's not recommended but should be ok for our simple use cases let rng = rand_chacha::ChaChaRng::from_seed([0; 32]); key.derived_key_simple_rng(ChainCode(j), &[], rng).0 } @@ -363,6 +375,8 @@ mod derive { #[cfg(test)] mod tests { use super::*; + extern crate alloc; + use alloc::vec::Vec; #[test] fn substrate_junctions() { diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index 2f23c0d..4b3efef 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -1,7 +1,7 @@ #![feature(async_fn_in_trait, impl_trait_projections)] #![feature(trait_alias)] // #![feature(result_option_inspect)] -#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(any(test, feature = "std")), no_std)] //! `libwallet` is the one-stop tool to build easy, slightly opinionated crypto wallets //! that run in all kinds of environments and plattforms including embedded hardware, //! mobile apps or the Web. @@ -97,7 +97,7 @@ where def.unlock(root); }) .await - .map_err(|e| Error::Vault(e))?; + .map_err(Error::Vault)?; self.is_locked = false; } Ok(()) @@ -306,19 +306,6 @@ mod util { phrase } - macro_rules! seed_from_entropy { - ($seed: ident, $pin: expr) => { - #[cfg(feature = "util_pin")] - let protected_seed = $pin.protect::<64>($seed); - #[cfg(feature = "util_pin")] - let $seed = &protected_seed; - #[cfg(not(feature = "util_pin"))] - let _ = &$pin; // use the variable to avoid warning - }; - } - - pub(crate) use seed_from_entropy; - /// A simple pin credential that can be used to add some /// extra level of protection to seeds stored in vaults #[derive(Default, Copy, Clone)] diff --git a/libwallet/src/vault.rs b/libwallet/src/vault.rs index be95885..97f9f92 100644 --- a/libwallet/src/vault.rs +++ b/libwallet/src/vault.rs @@ -3,17 +3,15 @@ mod os; #[cfg(feature = "vault_pass")] mod pass; -#[cfg(feature = "vault_simple")] mod simple; #[cfg(feature = "vault_os")] pub use os::*; #[cfg(feature = "vault_pass")] pub use pass::*; -#[cfg(feature = "vault_simple")] pub use simple::*; -use crate::{any, key_pair, Derive}; +use crate::{any, Derive}; /// Abstration for storage of private keys that are protected by some credentials. pub trait Vault { @@ -36,14 +34,16 @@ pub trait Vault { #[derive(Debug)] pub struct RootAccount { #[cfg(feature = "substrate")] - sub: key_pair::sr25519::Pair, + sub: crate::key_pair::sr25519::Pair, } impl RootAccount { fn from_bytes(seed: &[u8]) -> Self { + #[cfg(not(feature = "substrate"))] + let _ = seed; RootAccount { #[cfg(feature = "substrate")] - sub: ::from_bytes(seed), + sub: ::from_bytes(seed), } } } @@ -61,6 +61,20 @@ impl<'a> Derive for &'a RootAccount { "m/" => unimplemented!(), #[cfg(feature = "substrate")] _ => self.sub.derive("//default").into(), + #[cfg(not(feature = "substrate"))] + _ => unreachable!(), } } } + +macro_rules! seed_from_entropy { + ($seed: ident, $pin: expr) => { + #[cfg(feature = "util_pin")] + let protected_seed = $pin.protect::<64>($seed); + #[cfg(feature = "util_pin")] + let $seed = &protected_seed; + #[cfg(not(feature = "util_pin"))] + let _ = &$pin; // use the variable to avoid warning + }; +} +pub(crate) use seed_from_entropy; diff --git a/libwallet/src/vault/simple.rs b/libwallet/src/vault/simple.rs index 1d5b022..029d94c 100644 --- a/libwallet/src/vault/simple.rs +++ b/libwallet/src/vault/simple.rs @@ -1,6 +1,5 @@ -use core::convert::TryInto; - -use crate::util::{seed_from_entropy, Pin}; +use super::seed_from_entropy; +use crate::util::Pin; use crate::{RootAccount, Vault}; /// A vault that holds secrets in memory @@ -39,20 +38,21 @@ impl Simple { R: rand_core::CryptoRng + rand_core::RngCore, { let phrase = crate::util::gen_phrase(rng, Default::default()); - ( - Self::from_phrase(&phrase), - phrase - ) + (Self::from_phrase(&phrase), phrase) } #[cfg(feature = "mnemonic")] // Provide your own seed pub fn from_phrase(phrase: impl AsRef) -> Self { + use core::convert::TryInto; let phrase = phrase .as_ref() .parse::() .expect("mnemonic"); - let entropy = phrase.entropy().try_into().expect("Size should be 32 bytes"); + let entropy = phrase + .entropy() + .try_into() + .expect("Size should be 32 bytes"); Simple { locked: Some(entropy), diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 07ade69..0000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly \ No newline at end of file diff --git a/scales/Cargo.toml b/scales/Cargo.toml new file mode 100644 index 0000000..321501b --- /dev/null +++ b/scales/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "scale-serialization" +description = "SCALE Serialization" +version = "1.0.0-beta2" +authors = ["Daniel Olano "] +edition = "2018" +repository = "https://github.com/virto-network/scales" +license = "Apache-2.0" + +[dependencies] +bytes = { version = "1.1.0", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["serde"] } +serde = { version = "1.0.137", default-features = false } +serde_json = { version = "1.0.80", default-features = false, optional = true } +codec = { version = "3.1.2", package = "parity-scale-codec", default-features = false, optional = true } +hex = { version = "0.4.3", default-features = false, features = ["alloc"], optional = true } + +[features] +default = ["std", "codec", "json", "hex", "experimental-serializer"] +std = ["scale-info/std", "bytes/std"] +experimental-serializer = [] +json = ["serde_json/preserve_order"] + +[dev-dependencies] +anyhow = "1.0.57" +codec = { version = "3.1.2", package = "parity-scale-codec", features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.137", default-features = false, features = ["derive"] } +serde_json = { version = "1.0.80", default-features = false, features = ["alloc"] } diff --git a/scales/README.md b/scales/README.md new file mode 100644 index 0000000..50d771c --- /dev/null +++ b/scales/README.md @@ -0,0 +1,35 @@ +# scales - SCALE Serialization + +Making use of [type information](https://github.com/paritytech/scale-info) this library allows +conversion to/from [SCALE](https://github.com/paritytech/parity-scale-codec) encoded data, +specially useful when conversion is for dynamic types like JSON. + +### From SCALE + +`scales::Value` wraps the raw SCALE binary data and the type id within type registry +giving you an object that can be serialized to any compatible format. + +```rust +let value = scales::Value::new(scale_binary_data, type_id, &type_registry); +serde_json::to_string(value)?; +``` + +### To SCALE + +Public methods from the `scales::serializer::*` module(feature `experimental-serializer`) +allow for a best effort conversion of dynamic types(e.g. `serde_json::Value`) to SCALE +binary format. The serializer tries to be smart when interpreting the input and convert it +to the desired format dictated by the provided type in the registry. + +```rust +// simple conversion +let scale_data = to_vec(some_serializable_input); // or to_bytes(&mut bytes, input); + +// with type info +let scale_data = to_vec_with_info(input, Some((®istry, type_id))); + +// from an unordered list of properties that make an object +let input = vec![("prop_b", 123), ("prop_a", 456)]; +let scale_data = to_vec_from_iter(input, (®istry, type_id)); +``` + diff --git a/scales/src/lib.rs b/scales/src/lib.rs new file mode 100644 index 0000000..73d9dc6 --- /dev/null +++ b/scales/src/lib.rs @@ -0,0 +1,272 @@ +#![cfg_attr(not(feature = "std"), no_std)] +///! +///! # Scales +///! +///! Dynamic SCALE Serialization using `scale-info` type information. +#[macro_use] +extern crate alloc; + +#[cfg(feature = "experimental-serializer")] +mod serializer; +mod value; + +pub use bytes::Bytes; +#[cfg(feature = "json")] +pub use serde_json::Value as JsonValue; +#[cfg(feature = "experimental-serializer")] +pub use serializer::{to_bytes, to_bytes_with_info, to_vec, to_vec_with_info, Serializer}; +#[cfg(feature = "json")] +pub use serializer::{to_bytes_from_iter, to_vec_from_iter}; +pub use value::Value; + +use prelude::*; +use scale_info::{form::PortableForm as Portable, PortableRegistry}; + +mod prelude { + pub use alloc::{ + collections::BTreeMap, + string::{String, ToString}, + vec::Vec, + }; + pub use core::ops::Deref; +} + +type Type = scale_info::Type; +type Field = scale_info::Field; +type Variant = scale_info::Variant; +type TypeId = u32; + +macro_rules! is_tuple { + ($it:ident) => { + $it.fields().first().and_then(Field::name).is_none() + }; +} + +/// A convenient representation of the scale-info types to a format +/// that matches serde model more closely +#[rustfmt::skip] +#[derive(Debug, Clone, serde::Serialize)] +#[cfg_attr(feature = "codec", derive(codec::Encode))] +pub enum SpecificType { + Bool, + U8, U16, U32, U64, U128, + I8, I16, I32, I64, I128, + Char, + Str, + Bytes(TypeId), + Sequence(TypeId), + Map(TypeId, TypeId), + Tuple(TupleOrArray), + Struct(Vec<(String, TypeId)>), StructUnit, StructNewType(TypeId), StructTuple(Vec), + Variant(String, Vec, Option), + Compact(TypeId), +} + +impl From<(&Type, &PortableRegistry)> for SpecificType { + fn from((ty, registry): (&Type, &PortableRegistry)) -> Self { + use scale_info::{TypeDef, TypeDefComposite, TypeDefPrimitive}; + type Def = TypeDef; + + macro_rules! resolve { + ($ty:expr) => { + registry.resolve($ty.id()).unwrap() + }; + } + let is_map = |ty: &Type| -> bool { ty.path().segments() == ["BTreeMap"] }; + let map_types = |ty: &TypeDefComposite| -> (TypeId, TypeId) { + let field = ty.fields().first().expect("map"); + // Type information of BTreeMap is weirdly packed + if let Def::Sequence(s) = resolve!(field.ty()).type_def() { + if let Def::Tuple(t) = resolve!(s.type_param()).type_def() { + assert_eq!(t.fields().len(), 2); + let key_ty = t.fields().first().expect("key").id(); + let val_ty = t.fields().last().expect("val").id(); + return (key_ty, val_ty); + } + } + unreachable!() + }; + + let name = ty + .path() + .segments() + .last() + .cloned() + .unwrap_or_else(|| "".into()); + + match ty.type_def() { + Def::Composite(c) => { + let fields = c.fields(); + if fields.is_empty() { + Self::StructUnit + } else if is_map(ty) { + let (k, v) = map_types(c); + Self::Map(k, v) + } else if fields.len() == 1 && fields.first().unwrap().name().is_none() { + Self::StructNewType(fields.first().unwrap().ty().id()) + } else if is_tuple!(c) { + Self::StructTuple(fields.iter().map(|f| f.ty().id()).collect()) + } else { + Self::Struct( + fields + .iter() + .map(|f| (f.name().unwrap().deref().into(), f.ty().id())) + .collect(), + ) + } + } + Def::Variant(v) => Self::Variant(name, v.variants().into(), None), + Def::Sequence(s) => { + let ty = s.type_param(); + if matches!( + resolve!(ty).type_def(), + Def::Primitive(TypeDefPrimitive::U8) + ) { + Self::Bytes(ty.id()) + } else { + Self::Sequence(ty.id()) + } + } + Def::Array(a) => Self::Tuple(TupleOrArray::Array(a.type_param().id(), a.len())), + Def::Tuple(t) => Self::Tuple(TupleOrArray::Tuple( + t.fields().iter().map(|ty| ty.id()).collect(), + )), + Def::Primitive(p) => match p { + TypeDefPrimitive::U8 => Self::U8, + TypeDefPrimitive::U16 => Self::U16, + TypeDefPrimitive::U32 => Self::U32, + TypeDefPrimitive::U64 => Self::U64, + TypeDefPrimitive::U128 => Self::U128, + TypeDefPrimitive::I8 => Self::I8, + TypeDefPrimitive::I16 => Self::I16, + TypeDefPrimitive::I32 => Self::I32, + TypeDefPrimitive::I64 => Self::I64, + TypeDefPrimitive::I128 => Self::I128, + TypeDefPrimitive::Bool => Self::Bool, + TypeDefPrimitive::Str => Self::Str, + TypeDefPrimitive::Char => Self::Char, + TypeDefPrimitive::U256 => unimplemented!(), + TypeDefPrimitive::I256 => unimplemented!(), + }, + Def::Compact(c) => Self::Compact(c.type_param().id()), + Def::BitSequence(_b) => todo!(), + } + } +} + +// Utilities for enum variants +impl SpecificType { + fn pick(&self, index: u8) -> Self { + match self { + SpecificType::Variant(name, variant, Some(_)) => { + Self::Variant(name.to_string(), variant.to_vec(), Some(index)) + } + SpecificType::Variant(name, variants, None) => { + let v = variants.iter().find(|v| v.index() == index).unwrap(); + Self::Variant(name.clone(), vec![v.clone()], Some(index)) + } + _ => panic!("Only for enum variants"), + } + } + + #[cfg(feature = "experimental-serializer")] + fn pick_mut(&mut self, selection: A, get_field: F) -> Option<&Self> + where + F: Fn(&Variant) -> B, + A: AsRef<[u8]> + PartialEq + core::fmt::Debug, + B: AsRef<[u8]> + PartialEq + core::fmt::Debug, + { + match self { + SpecificType::Variant(_, _, Some(_)) => Some(self), + SpecificType::Variant(_, ref mut variants, idx @ None) => { + let i = variants + .iter() + .map(get_field) + .position(|f| f.as_ref() == selection.as_ref())? as u8; + variants.retain(|v| v.index() == i); + *idx = Some(i); + Some(self) + } + _ => panic!("Only for enum variants"), + } + } + + #[cfg(feature = "experimental-serializer")] + fn variant_id(&self) -> u8 { + match self { + SpecificType::Variant(_, _, Some(id)) => *id, + _ => panic!("Only for enum variants"), + } + } +} + +#[derive(Debug)] +enum EnumVariant<'a> { + OptionNone, + OptionSome(TypeId), + Unit(u8, &'a str), + NewType(u8, &'a str, TypeId), + Tuple(u8, &'a str, Vec), + Struct(u8, &'a str, Vec<(&'a str, TypeId)>), +} + +impl<'a> From<&'a SpecificType> for EnumVariant<'a> { + fn from(ty: &'a SpecificType) -> Self { + match ty { + SpecificType::Variant(name, variants, Some(idx)) => { + let variant = variants.first().expect("single variant"); + let fields = variant.fields(); + let vname = variant.name().as_ref(); + + if fields.is_empty() { + if name == "Option" && vname == "None" { + Self::OptionNone + } else { + Self::Unit(*idx, vname) + } + } else if is_tuple!(variant) { + if fields.len() == 1 { + let ty = fields.first().map(|f| f.ty().id()).unwrap(); + return if name == "Option" && variant.name() == "Some" { + Self::OptionSome(ty) + } else { + Self::NewType(*idx, vname, ty) + }; + } else { + let fields = fields.iter().map(|f| f.ty().id()).collect(); + Self::Tuple(*idx, vname, fields) + } + } else { + let fields = fields + .iter() + .map(|f| (f.name().unwrap().deref(), f.ty().id())) + .collect(); + Self::Struct(*idx, vname, fields) + } + } + _ => panic!("Only for enum variants"), + } + } +} + +#[derive(Debug, Clone, serde::Serialize)] +#[cfg_attr(feature = "codec", derive(codec::Encode))] +pub enum TupleOrArray { + Array(TypeId, u32), + Tuple(Vec), +} +impl TupleOrArray { + fn len(&self) -> usize { + match self { + Self::Array(_, len) => *len as usize, + Self::Tuple(fields) => fields.len(), + } + } + + fn type_id(&self, i: usize) -> TypeId { + match self { + Self::Array(ty, _) => *ty, + Self::Tuple(fields) => fields[i], + } + } +} diff --git a/scales/src/serializer.rs b/scales/src/serializer.rs new file mode 100644 index 0000000..e805952 --- /dev/null +++ b/scales/src/serializer.rs @@ -0,0 +1,1285 @@ +use crate::prelude::*; +use bytes::BufMut; +use codec::Encode; +use core::fmt::{self, Debug}; +use scale_info::{PortableRegistry, TypeInfo}; +use serde::{ser, Serialize}; + +use crate::{EnumVariant, SpecificType, TupleOrArray}; + +type TypeId = u32; +type Result = core::result::Result; + +#[derive(TypeInfo)] +struct Noop; + +#[inline] +pub fn to_vec(value: &T) -> Result> +where + T: Serialize + ?Sized, +{ + let mut out = vec![]; + to_bytes(&mut out, value)?; + Ok(out) +} + +#[inline] +pub fn to_vec_with_info( + value: &T, + registry_type: Option<(&PortableRegistry, TypeId)>, +) -> Result> +where + T: Serialize + ?Sized, +{ + let mut out = vec![]; + to_bytes_with_info(&mut out, value, registry_type)?; + Ok(out) +} + +pub fn to_bytes(bytes: B, value: &T) -> Result<()> +where + T: Serialize + ?Sized, + B: BufMut + Debug, +{ + to_bytes_with_info(bytes, value, None) +} + +pub fn to_bytes_with_info( + bytes: B, + value: &T, + registry_type: Option<(&PortableRegistry, TypeId)>, +) -> Result<()> +where + T: Serialize + ?Sized, + B: BufMut + Debug, +{ + let mut serializer = Serializer::new(bytes, registry_type); + value.serialize(&mut serializer)?; + Ok(()) +} + +#[cfg(feature = "json")] +pub fn to_bytes_from_iter( + bytes: B, + iter: I, + registry_type: (&PortableRegistry, TypeId), +) -> Result<()> +where + B: BufMut + Debug, + I: IntoIterator, + K: Into, + V: Into, +{ + let ty = registry_type + .0 + .resolve(registry_type.1) + .ok_or_else(|| Error::BadInput("Type not in registry".into()))?; + let obj = iter.into_iter().collect::(); + let val: crate::JsonValue = if let scale_info::TypeDef::Composite(ty) = ty.type_def() { + ty.fields() + .iter() + .map(|f| { + let name = f.name().expect("named field"); + Ok(( + name.deref(), + obj.get(name) + .ok_or_else(|| Error::BadInput(format!("missing field {}", name)))? + .clone(), + )) + }) + .collect::>()? + } else { + return Err(Error::Type(ty.clone())); + }; + + to_bytes_with_info(bytes, &val, Some(registry_type)) +} + +#[cfg(feature = "json")] +pub fn to_vec_from_iter( + iter: I, + registry_type: (&PortableRegistry, TypeId), +) -> Result> +where + I: IntoIterator, + K: Into, + V: Into, +{ + let mut out = vec![]; + to_bytes_from_iter(&mut out, iter, registry_type)?; + Ok(out) +} + +/// A serializer that encodes types to SCALE with the option to coerce +/// the output to an equivalent representation given by some type information. +#[derive(Debug)] +pub struct Serializer<'reg, B> +where + B: Debug, +{ + out: B, + ty: Option, + registry: Option<&'reg PortableRegistry>, +} + +impl<'reg, B> Serializer<'reg, B> +where + B: BufMut + Debug, +{ + pub fn new(out: B, registry_type: Option<(&'reg PortableRegistry, TypeId)>) -> Self { + let (registry, ty) = match registry_type.map(|(reg, ty_id)| { + ( + reg, + (reg.resolve(ty_id).expect("exists in registry"), reg).into(), + ) + }) { + Some((reg, ty)) => (Some(reg), Some(ty)), + None => (None, None), + }; + Serializer { out, ty, registry } + } + + fn serialize_compact(&mut self, ty: u32, v: u128) -> Result<()> { + let type_def = self.resolve(ty); + + use codec::Compact; + let compact_buffer = match type_def { + SpecificType::U32 => Compact(v as u32).encode(), + SpecificType::U64 => Compact(v as u64).encode(), + SpecificType::U128 => Compact(v).encode(), + _ => todo!(), + }; + + self.out.put_slice(&compact_buffer[..]); + + Ok(()) + } +} + +impl<'a, 'reg, B> ser::Serializer for &'a mut Serializer<'reg, B> +where + B: BufMut + Debug, +{ + type Ok = (); + type Error = Error; + + type SerializeSeq = TypedSerializer<'a, 'reg, B>; + type SerializeTuple = TypedSerializer<'a, 'reg, B>; + type SerializeTupleStruct = TypedSerializer<'a, 'reg, B>; + type SerializeTupleVariant = TypedSerializer<'a, 'reg, B>; + type SerializeMap = TypedSerializer<'a, 'reg, B>; + type SerializeStruct = TypedSerializer<'a, 'reg, B>; + type SerializeStructVariant = TypedSerializer<'a, 'reg, B>; + + fn serialize_bool(self, v: bool) -> Result { + self.maybe_some()?; + self.out.put_u8(v.into()); + Ok(()) + } + + fn serialize_i8(self, v: i8) -> Result { + self.maybe_some()?; + self.out.put_i8(v); + Ok(()) + } + + fn serialize_i16(self, v: i16) -> Result { + self.maybe_some()?; + self.out.put_i16_le(v); + Ok(()) + } + + fn serialize_i32(self, v: i32) -> Result { + self.maybe_some()?; + self.out.put_i32_le(v); + Ok(()) + } + + fn serialize_i64(self, v: i64) -> Result { + match self.ty { + Some(SpecificType::I8) => self.serialize_i8(v as i8)?, + Some(SpecificType::I16) => self.serialize_i16(v as i16)?, + Some(SpecificType::I32) => self.serialize_i32(v as i32)?, + _ => { + self.maybe_some()?; + self.out.put_i64_le(v) + } + } + Ok(()) + } + + fn serialize_u8(self, v: u8) -> Result { + self.maybe_some()?; + self.out.put_u8(v); + Ok(()) + } + + fn serialize_u16(self, v: u16) -> Result { + self.maybe_some()?; + self.out.put_u16_le(v); + Ok(()) + } + + fn serialize_u32(self, v: u32) -> Result { + self.maybe_some()?; + self.out.put_u32_le(v); + Ok(()) + } + + fn serialize_u64(self, v: u64) -> Result { + self.maybe_some()?; + // all numbers in serde_json are the same + match self.ty { + Some(SpecificType::I8) => self.serialize_i8(v as i8)?, + Some(SpecificType::I16) => self.serialize_i16(v as i16)?, + Some(SpecificType::I32) => self.serialize_i32(v as i32)?, + Some(SpecificType::U8) => self.serialize_u8(v as u8)?, + Some(SpecificType::U16) => self.serialize_u16(v as u16)?, + Some(SpecificType::U32) => self.serialize_u32(v as u32)?, + Some(SpecificType::Compact(ty)) => self.serialize_compact(ty, v as u128)?, + _ => self.out.put_u64_le(v), + } + Ok(()) + } + + fn serialize_u128(self, v: u128) -> Result { + self.maybe_some()?; + match self.ty { + Some(SpecificType::I8) => self.serialize_i8(v as i8)?, + Some(SpecificType::I16) => self.serialize_i16(v as i16)?, + Some(SpecificType::I32) => self.serialize_i32(v as i32)?, + Some(SpecificType::I64) => self.serialize_i64(v as i64)?, + Some(SpecificType::U8) => self.serialize_u8(v as u8)?, + Some(SpecificType::U16) => self.serialize_u16(v as u16)?, + Some(SpecificType::U32) => self.serialize_u32(v as u32)?, + Some(SpecificType::U64) => self.serialize_u64(v as u64)?, + Some(SpecificType::Compact(ty)) => self.serialize_compact(ty, v)?, + _ => self.out.put_u128_le(v), + } + Ok(()) + } + + fn serialize_f32(self, _v: f32) -> Result { + unimplemented!() + } + + fn serialize_f64(self, _v: f64) -> Result { + unimplemented!() + } + + fn serialize_char(self, _v: char) -> Result { + unimplemented!() + } + + fn serialize_str(self, v: &str) -> Result { + self.maybe_some()?; + if self.maybe_other(v)?.is_some() { + return Ok(()); + } + + compact_number(v.len(), &mut self.out); + self.out.put(v.as_bytes()); + Ok(()) + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + self.maybe_some()?; + + compact_number(v.len(), &mut self.out); + self.out.put(v); + Ok(()) + } + + fn serialize_none(self) -> Result { + self.out.put_u8(0x00); + Ok(()) + } + + fn serialize_some(self, value: &T) -> Result + where + T: Serialize, + { + self.out.put_u8(0x01); + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + self.maybe_some()?; + Ok(()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.maybe_some()?; + Ok(()) + } + + fn serialize_unit_variant( + self, + __name: &'static str, + variant_index: u32, + _variant: &'static str, + ) -> Result { + self.maybe_some()?; + (variant_index as u8).serialize(self) + } + + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result + where + T: Serialize, + { + self.maybe_some()?; + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + __name: &'static str, + variant_index: u32, + _variant: &'static str, + value: &T, + ) -> Result + where + T: Serialize, + { + self.maybe_some()?; + self.out.put_u8(variant_index as u8); + value.serialize(self) + } + + fn serialize_seq(self, len: Option) -> Result { + self.maybe_some()?; + if matches!( + self.ty, + None | Some(SpecificType::Bytes(_)) | Some(SpecificType::Sequence(_)) + ) { + compact_number(len.expect("known length"), &mut self.out); + } + Ok(self.into()) + } + + fn serialize_tuple(self, _len: usize) -> Result { + self.maybe_some()?; + Ok(self.into()) + } + + fn serialize_tuple_struct( + self, + __name: &'static str, + __len: usize, + ) -> Result { + self.maybe_some()?; + Ok(self.into()) + } + + fn serialize_tuple_variant( + self, + __name: &'static str, + variant_index: u32, + _variant: &'static str, + __len: usize, + ) -> Result { + self.maybe_some()?; + self.out.put_u8(variant_index as u8); + Ok(self.into()) + } + + fn serialize_map(self, len: Option) -> Result { + self.maybe_some()?; + if matches!(self.ty, None | Some(SpecificType::Map(_, _))) { + compact_number(len.expect("known length"), &mut self.out); + } + Ok(self.into()) + } + + fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { + self.maybe_some()?; + Ok(self.into()) + } + + fn serialize_struct_variant( + self, + __name: &'static str, + variant_index: u32, + _variant: &'static str, + __len: usize, + ) -> Result { + self.maybe_some()?; + self.out.put_u8(variant_index as u8); + Ok(self.into()) + } +} + +impl<'reg, B> Serializer<'reg, B> +where + B: BufMut + Debug, +{ + // A check to run for every serialize fn since any type could be an Option::Some + // if the type info says its an Option assume its Some and extract the inner type + fn maybe_some(&mut self) -> Result<()> { + match &self.ty { + Some(SpecificType::Variant(ref name, v, _)) if name == "Option" => { + self.ty = v[1].fields().first().map(|f| self.resolve(f.ty().id())); + self.out.put_u8(0x01); + } + _ => (), + } + Ok(()) + } + + fn resolve(&self, ty_id: TypeId) -> SpecificType { + let reg = self.registry.expect("called having type"); + let ty = reg.resolve(ty_id).expect("in registry"); + (ty, reg).into() + } + + #[inline] + fn maybe_other(&mut self, val: &str) -> Result> { + match self.ty { + Some(SpecificType::Str) | None => Ok(None), + // { "foo": "Bar" } => "Bar" might be an enum variant + Some(ref mut var @ SpecificType::Variant(_, _, None)) => { + var.pick_mut(to_vec(val)?, |k| to_vec(k.name()).unwrap()) + .ok_or_else(|| Error::BadInput("Invalid variant".into()))?; + self.out.put_u8(var.variant_id()); + Ok(Some(())) + } + Some(SpecificType::StructNewType(ty)) => match self.resolve(ty) { + // { "foo": "bar" } => "bar" might be a string wrapped in a type + SpecificType::Str => Ok(None), + ref ty => Err(Error::NotSupported( + type_name_of_val(val), + format!("{:?}", ty), + )), + }, + Some(SpecificType::U8) => { + let n = val.parse().map_err(|_| Error::BadInput("u8".into()))?; + self.out.put_u8(n); + Ok(Some(())) + } + Some(SpecificType::U16) => { + let n = val.parse().map_err(|_| Error::BadInput("u16".into()))?; + self.out.put_u16_le(n); + Ok(Some(())) + } + Some(SpecificType::U32) => { + let n = val.parse().map_err(|_| Error::BadInput("u32".into()))?; + self.out.put_u32_le(n); + Ok(Some(())) + } + Some(SpecificType::U64) => { + let n = val.parse().map_err(|_| Error::BadInput("u64".into()))?; + self.out.put_u64_le(n); + Ok(Some(())) + } + Some(SpecificType::U128) => { + let n = val.parse().map_err(|_| Error::BadInput("u128".into()))?; + self.out.put_u128_le(n); + Ok(Some(())) + } + Some(SpecificType::I8) => { + let n = val.parse().map_err(|_| Error::BadInput("i8".into()))?; + self.out.put_i8(n); + Ok(Some(())) + } + Some(SpecificType::I16) => { + let n = val.parse().map_err(|_| Error::BadInput("i16".into()))?; + self.out.put_i16_le(n); + Ok(Some(())) + } + Some(SpecificType::I32) => { + let n = val.parse().map_err(|_| Error::BadInput("i32".into()))?; + self.out.put_i32_le(n); + Ok(Some(())) + } + Some(SpecificType::I64) => { + let n = val.parse().map_err(|_| Error::BadInput("i64".into()))?; + self.out.put_i64_le(n); + Ok(Some(())) + } + Some(SpecificType::I128) => { + let n = val.parse().map_err(|_| Error::BadInput("i128".into()))?; + self.out.put_i128_le(n); + Ok(Some(())) + } + #[cfg(feature = "hex")] + Some(SpecificType::Bytes(_)) => { + if let Some(bytes) = val.strip_prefix("0x") { + let bytes = hex::decode(bytes).map_err(|e| Error::BadInput(e.to_string()))?; + ser::Serializer::serialize_bytes(self, &bytes)?; + Ok(Some(())) + } else { + Err(Error::BadInput("Hex string must start with 0x".into())) + } + } + Some(ref ty) => Err(Error::NotSupported( + type_name_of_val(val), + format!("{:?}", ty), + )), + } + } +} + +/// +#[derive(Debug)] +pub enum TypedSerializer<'a, 'reg, B> +where + B: Debug, +{ + Empty(&'a mut Serializer<'reg, B>), + Composite(&'a mut Serializer<'reg, B>, Vec), + Sequence(&'a mut Serializer<'reg, B>, TypeId), + Enum(&'a mut Serializer<'reg, B>), +} + +impl<'a, 'reg, B: 'a> From<&'a mut Serializer<'reg, B>> for TypedSerializer<'a, 'reg, B> +where + B: Debug, +{ + fn from(ser: &'a mut Serializer<'reg, B>) -> Self { + use SpecificType::*; + match ser.ty.take() { + Some(Struct(fields)) => { + Self::Composite(ser, fields.iter().map(|(_, ty)| *ty).collect()) + } + Some(StructTuple(fields)) => Self::Composite(ser, fields), + Some(Tuple(TupleOrArray::Array(ty, _))) => Self::Sequence(ser, ty), + Some(Tuple(TupleOrArray::Tuple(fields))) => Self::Composite(ser, fields), + Some(Sequence(ty) | Bytes(ty)) => Self::Sequence(ser, ty), + Some(Map(_, _)) => Self::Empty(ser), + Some(var @ Variant(_, _, Some(_))) => match (&var).into() { + EnumVariant::Tuple(_, _, types) => Self::Composite(ser, types), + EnumVariant::Struct(_, _, types) => { + Self::Composite(ser, types.iter().map(|(_, ty)| *ty).collect()) + } + _ => Self::Empty(ser), + }, + Some(var @ Variant(_, _, None)) => { + ser.ty = Some(var); + Self::Enum(ser) + } + _ => Self::Empty(ser), + } + } +} + +impl<'a, 'reg, B> TypedSerializer<'a, 'reg, B> +where + B: Debug, +{ + fn serializer(&mut self) -> &mut Serializer<'reg, B> { + match self { + Self::Empty(ser) + | Self::Composite(ser, _) + | Self::Enum(ser) + | Self::Sequence(ser, _) => ser, + } + } +} + +impl<'a, 'reg, B> ser::SerializeMap for TypedSerializer<'a, 'reg, B> +where + B: BufMut + Debug, +{ + type Ok = (); + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<()> + where + T: Serialize, + { + match self { + TypedSerializer::Enum(ser) => { + if let Some(ref mut var @ SpecificType::Variant(_, _, None)) = ser.ty { + let key_data = to_vec(key)?; + // assume the key is the name of the variant + var.pick_mut(key_data, |v| to_vec(v.name()).unwrap()) + .ok_or_else(|| Error::BadInput("Invalid variant".into()))? + .variant_id() + .serialize(&mut **ser)?; + } + Ok(()) + } + TypedSerializer::Empty(ser) => key.serialize(&mut **ser), + _ => Ok(()), + } + } + + fn serialize_value(&mut self, value: &T) -> Result<()> + where + T: Serialize, + { + match self { + TypedSerializer::Composite(ser, types) => { + let mut ty = ser.resolve(types.remove(0)); + // serde_json unwraps newtypes + if let SpecificType::StructNewType(ty_id) = ty { + ty = ser.resolve(ty_id) + } + ser.ty = Some(ty); + } + TypedSerializer::Enum(ser) => { + if let Some(var @ SpecificType::Variant(_, _, Some(_))) = &ser.ty { + if let EnumVariant::NewType(_, _, ty_id) = var.into() { + let ty = ser.resolve(ty_id); + + ser.ty = Some(if let SpecificType::StructNewType(ty_id) = ty { + ser.resolve(ty_id) + } else { + ty + }); + } + } + } + _ => {} + } + value.serialize(self.serializer()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a, 'reg, B> ser::SerializeSeq for TypedSerializer<'a, 'reg, B> +where + B: BufMut + Debug, +{ + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: Serialize, + { + match self { + TypedSerializer::Composite(ser, types) => { + let mut ty = ser.resolve(types.remove(0)); + if let SpecificType::StructNewType(ty_id) = ty { + ty = ser.resolve(ty_id); + } + ser.ty = Some(ty); + } + TypedSerializer::Sequence(ser, ty_id) => { + let ty = ser.resolve(*ty_id); + ser.ty = Some(match ty { + SpecificType::StructNewType(ty_id) => ser.resolve(ty_id), + _ => ty, + }); + } + _ => {} + }; + value.serialize(self.serializer()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a, 'reg, B> ser::SerializeStruct for TypedSerializer<'a, 'reg, B> +where + B: BufMut + Debug, +{ + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, _key: &'static str, value: &T) -> Result<()> + where + T: Serialize, + { + value.serialize(self.serializer()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a, 'reg, B> ser::SerializeStructVariant for TypedSerializer<'a, 'reg, B> +where + B: BufMut + Debug, +{ + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, _key: &'static str, value: &T) -> Result<()> + where + T: Serialize, + { + value.serialize(self.serializer()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a, 'reg, B> ser::SerializeTuple for TypedSerializer<'a, 'reg, B> +where + B: BufMut + Debug, +{ + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: Serialize, + { + value.serialize(self.serializer()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a, 'reg, B> ser::SerializeTupleStruct for TypedSerializer<'a, 'reg, B> +where + B: BufMut + Debug, +{ + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: Serialize, + { + value.serialize(self.serializer()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a, 'reg, B> ser::SerializeTupleVariant for TypedSerializer<'a, 'reg, B> +where + B: BufMut + Debug, +{ + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: Serialize, + { + value.serialize(self.serializer()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +#[derive(Debug)] +pub enum Error { + Ser(String), + BadInput(String), + Type(scale_info::Type), + NotSupported(&'static str, String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Ser(msg) => write!(f, "{}", msg), + Error::BadInput(msg) => write!(f, "Bad Input: {}", msg), + Error::Type(ty) => write!( + f, + "Unexpected type: {}", + ty.path().ident().unwrap_or_else(|| "Unknown".into()) + ), + Error::NotSupported(from, to) => { + write!(f, "Serializing {} as {} is not supported", from, to) + } + } + } +} + +impl ser::StdError for Error {} + +impl ser::Error for Error { + fn custom(msg: T) -> Self + where + T: fmt::Display, + { + Error::Ser(msg.to_string()) + } +} + +// adapted from https://github.com/paritytech/parity-scale-codec/blob/master/src/compact.rs#L336 +#[allow(clippy::all)] +fn compact_number(n: usize, mut dest: impl BufMut) { + match n { + 0..=0b0011_1111 => dest.put_u8((n as u8) << 2), + 0..=0b0011_1111_1111_1111 => dest.put_u16_le(((n as u16) << 2) | 0b01), + 0..=0b0011_1111_1111_1111_1111_1111_1111_1111 => dest.put_u32_le(((n as u32) << 2) | 0b10), + _ => { + let bytes_needed = 8 - n.leading_zeros() / 8; + assert!( + bytes_needed >= 4, + "Previous match arm matches anyting less than 2^30; qed" + ); + dest.put_u8(0b11 + ((bytes_needed - 4) << 2) as u8); + let mut v = n; + for _ in 0..bytes_needed { + dest.put_u8(v as u8); + v >>= 8; + } + assert_eq!( + v, 0, + "shifted sufficient bits right to lead only leading zeros; qed" + ) + } + } +} + +// nightly only +fn type_name_of_val(_val: &T) -> &'static str { + core::any::type_name::() +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::Encode; + use core::mem::size_of; + use scale_info::{meta_type, Registry, TypeInfo}; + use serde_json::to_value; + + #[test] + fn primitive_u8() -> Result<()> { + let mut out = [0u8]; + to_bytes(&mut out[..], &123u8)?; + + let expected = [123]; + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn primitive_u16() -> Result<()> { + const INPUT: u16 = 0xFF_EE; + let mut out = [0u8; size_of::()]; + let expected = INPUT.encode(); + + to_bytes(out.as_mut(), &INPUT)?; + + assert_eq!(out.as_ref(), expected); + Ok(()) + } + + #[test] + fn primitive_u32() -> Result<()> { + const INPUT: u32 = 0xFF_EE_DD_CC; + let mut out = [0u8; size_of::()]; + let expected = INPUT.encode(); + + to_bytes(out.as_mut(), &INPUT)?; + + assert_eq!(out.as_ref(), expected); + Ok(()) + } + + #[test] + fn primitive_u64() -> Result<()> { + const INPUT: u64 = 0xFF_EE_DD_CC__BB_AA_99_88; + let mut out = [0u8; size_of::()]; + let expected = INPUT.encode(); + + to_bytes(out.as_mut(), &INPUT)?; + + assert_eq!(out.as_mut(), expected); + Ok(()) + } + + #[test] + fn primitive_u128() -> Result<()> { + const INPUT: u128 = 0xFF_EE_DD_CC__BB_AA_99_88__77_66_55_44__33_22_11_00; + let mut out = [0u8; size_of::()]; + let expected = INPUT.encode(); + + to_bytes(out.as_mut(), &INPUT)?; + + assert_eq!(out.as_mut(), expected); + Ok(()) + } + + #[test] + fn primitive_i16() -> Result<()> { + const INPUT: i16 = i16::MIN; + let mut out = [0u8; size_of::()]; + let expected = INPUT.encode(); + + to_bytes(out.as_mut(), &INPUT)?; + + assert_eq!(out.as_mut(), expected); + Ok(()) + } + + #[test] + fn primitive_i32() -> Result<()> { + const INPUT: i32 = i32::MIN; + let mut out = [0u8; size_of::()]; + let expected = INPUT.encode(); + + to_bytes(out.as_mut(), &INPUT)?; + + assert_eq!(out.as_mut(), expected); + Ok(()) + } + + #[test] + fn primitive_i64() -> Result<()> { + const INPUT: i64 = i64::MIN; + let mut out = [0u8; size_of::()]; + let expected = INPUT.encode(); + + to_bytes(out.as_mut(), &INPUT)?; + + assert_eq!(out.as_mut(), expected); + Ok(()) + } + + #[test] + fn primitive_bool() -> Result<()> { + const INPUT: bool = true; + let mut out = [0u8]; + let expected = INPUT.encode(); + + to_bytes(out.as_mut(), &INPUT)?; + + assert_eq!(out.as_mut(), expected); + Ok(()) + } + + #[test] + fn str() -> Result<()> { + const INPUT: &str = "ac orci phasellus egestas tellus rutrum tellus pellentesque"; + let mut out = Vec::::new(); + let expected = INPUT.encode(); + + to_bytes(&mut out, &INPUT)?; + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn bytes() -> Result<()> { + const INPUT: &[u8] = b"dictumst quisque sagittis purus sit amet volutpat consequat"; + let mut out = Vec::::new(); + let expected = INPUT.encode(); + + to_bytes(&mut out, &INPUT)?; + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn tuple_simple() -> Result<()> { + const INPUT: (u8, bool, u64) = (0xD0, false, u64::MAX); + let mut out = Vec::::new(); + let expected = INPUT.encode(); + + to_bytes(&mut out, &INPUT)?; + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn enum_simple() -> Result<()> { + #[derive(Serialize, Encode)] + enum X { + _A, + B, + } + + const INPUT: X = X::B; + let mut out = Vec::::new(); + let expected = INPUT.encode(); + + to_bytes(&mut out, &INPUT)?; + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn tuple_enum_mix() -> Result<()> { + #[derive(Serialize, Encode)] + enum X { + A, + B, + } + + let input: (Option<()>, Option, X, X) = (None, Some("hello".into()), X::A, X::B); + let mut out = Vec::::new(); + let expected = input.encode(); + + to_bytes(&mut out, &input)?; + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn struct_simple() -> Result<()> { + #[derive(Serialize, Encode)] + struct Foo { + a: Bar, + b: Option, + } + #[derive(Serialize, Encode)] + struct Bar(u8); + #[derive(Serialize, Encode)] + struct Baz(String, u16); + + let input = Foo { + a: Bar(0xFF), + b: Some(Baz("lol".into(), u16::MAX)), + }; + let mut out = Vec::::new(); + let expected = input.encode(); + + to_bytes(&mut out, &input)?; + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn vec_simple() -> Result<()> { + let input: Vec = vec!["hello".into(), "beautiful".into(), "people".into()]; + let mut out = Vec::::new(); + let expected = input.encode(); + + to_bytes(&mut out, &input)?; + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn struct_mix() -> Result<()> { + #[derive(Serialize, Encode)] + struct Foo<'a> { + a: Vec, + b: (Bar<'a>, Bar<'a>, Bar<'a>), + } + #[derive(Serialize, Encode)] + enum Bar<'a> { + A { thing: &'a str }, + B(Baz), + C(BTreeMap, i64), + } + #[derive(Serialize, Encode)] + struct Baz; + + let input = Foo { + a: vec!["hello".into(), "beautiful".into(), "people".into()], + b: ( + Bar::A { thing: "barbarbar" }, + Bar::B(Baz), + Bar::C( + { + let mut h = BTreeMap::new(); + h.insert("key".into(), false); + h + }, + i64::MIN, + ), + ), + }; + let mut out = Vec::::new(); + let expected = input.encode(); + + to_bytes(&mut out, &input)?; + + assert_eq!(out, expected); + Ok(()) + } + + fn register(_ty: &T) -> (TypeId, PortableRegistry) + where + T: TypeInfo + 'static, + { + let mut reg = Registry::new(); + let sym = reg.register_type(&meta_type::()); + (sym.id(), reg.into()) + } + + #[test] + fn str_as_u128() -> Result<()> { + const INPUT: &str = "340282366920938463463374607431768211455"; + let mut out = [0u8; size_of::()]; + let expected = u128::MAX.encode(); + + let (id, reg) = register(&0u128); + + to_bytes_with_info(out.as_mut(), &INPUT, Some((®, id)))?; + + assert_eq!(out.as_mut(), expected); + Ok(()) + } + + #[test] + fn json_simple() -> Result<()> { + #[derive(Debug, Serialize, Encode, TypeInfo)] + struct Foo { + a: Bar, + b: Option, + } + #[derive(Debug, Serialize, Encode, TypeInfo)] + struct Bar(u8); + #[derive(Debug, Serialize, Encode, TypeInfo)] + struct Baz(String, i32); + + let input = Foo { + a: Bar(0xFF), + b: Some(Baz("lol".into(), i32::MIN)), + }; + let mut out = Vec::::new(); + let expected = input.encode(); + let (id, reg) = register(&input); + + let json_input = to_value(&input).unwrap(); + to_bytes_with_info(&mut out, &json_input, Some((®, id)))?; + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn json_mix() -> Result<()> { + #[derive(Debug, Serialize, Encode, TypeInfo)] + struct Foo { + a: Vec, + b: (Bar, Bar, Bar), + } + #[derive(Debug, Serialize, Encode, TypeInfo)] + enum Bar { + A { thing: &'static str }, + B(Baz), + C(BTreeMap, i64), + } + #[derive(Debug, Serialize, Encode, TypeInfo)] + struct Baz; + + let input = Foo { + a: vec!["hello".into(), "beautiful".into(), "people".into()], + b: ( + Bar::A { thing: "barbarbar" }, + Bar::B(Baz), + Bar::C( + { + let mut h = BTreeMap::new(); + h.insert("key1".into(), false); + h.insert("key2".into(), true); + h + }, + i64::MIN, + ), + ), + }; + let mut out = Vec::::new(); + let expected = input.encode(); + let (id, reg) = register(&input); + + let json_input = to_value(&input).unwrap(); + to_bytes_with_info(&mut out, &json_input, Some((®, id)))?; + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn json_mix2() -> Result<()> { + #[derive(Debug, Encode, Serialize, TypeInfo)] + enum Bar { + This, + That(i16), + } + #[derive(Debug, Encode, Serialize, TypeInfo)] + struct Baz(String); + #[derive(Debug, Encode, Serialize, TypeInfo)] + struct Foo { + bar: Vec, + baz: Option, + lol: &'static [u8], + } + let input = Foo { + bar: [Bar::That(i16::MAX), Bar::This].into(), + baz: Some(Baz("lorem ipsum".into())), + lol: b"\xFFsome stuff\x00", + }; + let mut out = Vec::::new(); + let expected = input.encode(); + let (id, reg) = register(&input); + + let json_input = to_value(&input).unwrap(); + to_bytes_with_info(&mut out, &json_input, Some((®, id)))?; + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn test_unordered_iter() -> Result<()> { + #[derive(Debug, Encode, TypeInfo, Serialize)] + enum Bar { + _This, + That(i16), + } + #[derive(Debug, Encode, TypeInfo, Serialize)] + struct Foo { + bar: Bar, + baz: Option, + bam: String, + } + let foo = Foo { + bar: Bar::That(i16::MAX), + baz: Some(123), + bam: "lorem ipsum".into(), + }; + let (ty, reg) = register(&foo); + + let input = vec![ + ("bam", crate::JsonValue::String("lol".into())), + ("baz", 123.into()), + ("bam", "lorem ipsum".into()), + ("bar", serde_json::json!({ "That": i16::MAX })), + ]; + + let out = to_vec_from_iter(input, (®, ty))?; + let expected = foo.encode(); + + assert_eq!(out, expected); + Ok(()) + } + + #[test] + fn test_bytes_as_hex_string() -> Result<()> { + #[derive(Debug, Encode, TypeInfo, Serialize)] + struct Foo { + bar: Vec, + } + let foo = Foo { + bar: b"\x00\x12\x34\x56".to_vec(), + }; + let (ty, reg) = register(&foo); + + let hex_string = "0x00123456"; + + let input = vec![("bar", crate::JsonValue::String(hex_string.into()))]; + + let out = to_vec_from_iter(input, (®, ty))?; + let expected = foo.encode(); + + assert_eq!(out, expected); + Ok(()) + } +} diff --git a/scales/src/value.rs b/scales/src/value.rs new file mode 100644 index 0000000..083d6cc --- /dev/null +++ b/scales/src/value.rs @@ -0,0 +1,659 @@ +use crate::{EnumVariant, SpecificType}; +use alloc::{collections::BTreeMap, vec::Vec}; +use bytes::{Buf, Bytes}; +use codec::Encode; +use core::{convert::TryInto, str}; +use scale_info::{prelude::*, PortableRegistry, TypeDefPrimitive as Primitive}; +use serde::ser::{SerializeMap, SerializeSeq, SerializeTuple, SerializeTupleStruct}; +use serde::Serialize; + +type Type = scale_info::Type; +type TypeId = u32; +type TypeDef = scale_info::TypeDef; + +/// A container for SCALE encoded data that can serialize types directly +/// with the help of a type registry and without using an intermediate representation. +pub struct Value<'a> { + data: Bytes, + ty_id: TypeId, + registry: &'a PortableRegistry, +} + +impl<'a> Value<'a> { + pub fn new(data: impl Into, ty_id: u32, registry: &'a PortableRegistry) -> Self { + Value { + data: data.into(), + ty_id, + registry, + } + } + + fn new_value(&self, data: &mut Bytes, ty_id: TypeId) -> Self { + let size = self.ty_len(data.chunk(), ty_id); + Value::new(data.copy_to_bytes(size), ty_id, self.registry) + } + + #[inline] + fn resolve(&self, ty: TypeId) -> &'a Type { + self.registry.resolve(ty).expect("in registry") + } + + fn ty_len(&self, data: &[u8], ty: TypeId) -> usize { + match self.resolve(ty).type_def() { + TypeDef::Primitive(p) => match p { + Primitive::U8 => mem::size_of::(), + Primitive::U16 => mem::size_of::(), + Primitive::U32 => mem::size_of::(), + Primitive::U64 => mem::size_of::(), + Primitive::U128 => mem::size_of::(), + Primitive::I8 => mem::size_of::(), + Primitive::I16 => mem::size_of::(), + Primitive::I32 => mem::size_of::(), + Primitive::I64 => mem::size_of::(), + Primitive::I128 => mem::size_of::(), + Primitive::Bool => mem::size_of::(), + Primitive::Char => mem::size_of::(), + Primitive::Str => { + let (l, p_size) = sequence_len(data); + l + p_size + } + _ => unimplemented!(), + }, + TypeDef::Composite(c) => c + .fields() + .iter() + .fold(0, |c, f| c + self.ty_len(&data[c..], f.ty().id())), + TypeDef::Variant(e) => { + let var = e + .variants() + .iter() + .find(|v| v.index() == data[0]) + .expect("variant"); + + if var.fields().is_empty() { + 1 // unit variant + } else { + var.fields() + .iter() + .fold(1, |c, f| c + self.ty_len(&data[c..], f.ty().id())) + } + } + TypeDef::Sequence(s) => { + let (len, prefix_size) = sequence_len(data); + let ty_id = s.type_param().id(); + (0..len).fold(prefix_size, |c, _| c + self.ty_len(&data[c..], ty_id)) + } + TypeDef::Array(a) => a.len().try_into().unwrap(), + TypeDef::Tuple(t) => t + .fields() + .iter() + .fold(0, |c, f| c + self.ty_len(&data[c..], f.id())), + TypeDef::Compact(_) => compact_len(data), + TypeDef::BitSequence(_) => unimplemented!(), + } + } +} + +impl<'a> Serialize for Value<'a> { + fn serialize(&self, ser: S) -> Result + where + S: serde::Serializer, + { + let mut data = self.data.clone(); + let ty = self.resolve(self.ty_id); + + use SpecificType::*; + match (ty, self.registry).into() { + Bool => ser.serialize_bool(data.get_u8() != 0), + U8 => ser.serialize_u8(data.get_u8()), + U16 => ser.serialize_u16(data.get_u16_le()), + U32 => ser.serialize_u32(data.get_u32_le()), + U64 => ser.serialize_u64(data.get_u64_le()), + U128 => ser.serialize_u128(data.get_u128_le()), + I8 => ser.serialize_i8(data.get_i8()), + I16 => ser.serialize_i16(data.get_i16_le()), + I32 => ser.serialize_i32(data.get_i32_le()), + I64 => ser.serialize_i64(data.get_i64_le()), + I128 => ser.serialize_i128(data.get_i128_le()), + Compact(ty) => { + let type_def = self + .registry + .resolve(ty) + .expect("not found in registry") + .type_def(); + + use codec::Compact; + match type_def { + TypeDef::Primitive(Primitive::U32) => { + ser.serialize_bytes(&Compact(data.get_u32_le()).encode()) + } + TypeDef::Primitive(Primitive::U64) => { + ser.serialize_bytes(&Compact(data.get_u64_le()).encode()) + } + TypeDef::Primitive(Primitive::U128) => { + ser.serialize_bytes(&Compact(data.get_u128_le()).encode()) + } + _ => unimplemented!(), + } + } + Bytes(_) => { + let (_, s) = sequence_len(data.chunk()); + data.advance(s); + ser.serialize_bytes(data.chunk()) + } + Char => ser.serialize_char(char::from_u32(data.get_u32_le()).unwrap()), + Str => { + let (_, s) = sequence_len(data.chunk()); + data.advance(s); + ser.serialize_str(str::from_utf8(data.chunk()).unwrap()) + } + Sequence(ty) => { + let (len, p_size) = sequence_len(data.chunk()); + data.advance(p_size); + + let mut seq = ser.serialize_seq(Some(len))?; + for _ in 0..len { + seq.serialize_element(&self.new_value(&mut data, ty))?; + } + seq.end() + } + Map(ty_k, ty_v) => { + let (len, p_size) = sequence_len(data.chunk()); + data.advance(p_size); + + let mut state = ser.serialize_map(Some(len))?; + for _ in 0..len { + let key = self.new_value(&mut data, ty_k); + let val = self.new_value(&mut data, ty_v); + state.serialize_entry(&key, &val)?; + } + state.end() + } + Tuple(t) => { + let mut state = ser.serialize_tuple(t.len())?; + for i in 0..t.len() { + state.serialize_element(&self.new_value(&mut data, t.type_id(i)))?; + } + state.end() + } + Struct(fields) => { + let mut state = ser.serialize_map(Some(fields.len()))?; + for (name, ty) in fields { + state.serialize_key(&name)?; + state.serialize_value(&self.new_value(&mut data, ty))?; + } + state.end() + } + StructUnit => ser.serialize_unit(), + StructNewType(ty) => ser.serialize_newtype_struct("", &self.new_value(&mut data, ty)), + StructTuple(fields) => { + let mut state = ser.serialize_tuple_struct("", fields.len())?; + for ty in fields { + state.serialize_field(&self.new_value(&mut data, ty))?; + } + state.end() + } + ty @ Variant(_, _, _) => { + let variant = &ty.pick(data.get_u8()); + match variant.into() { + EnumVariant::OptionNone => ser.serialize_none(), + EnumVariant::OptionSome(ty) => { + ser.serialize_some(&self.new_value(&mut data, ty)) + } + EnumVariant::Unit(_idx, name) => ser.serialize_str(name), + EnumVariant::NewType(_idx, name, ty) => { + let mut s = ser.serialize_map(Some(1))?; + s.serialize_key(name)?; + s.serialize_value(&self.new_value(&mut data, ty))?; + s.end() + } + + EnumVariant::Tuple(_idx, name, fields) => { + let mut s = ser.serialize_map(Some(1))?; + s.serialize_key(name)?; + s.serialize_value( + &fields + .iter() + .map(|ty| self.new_value(&mut data, *ty)) + .collect::>(), + )?; + s.end() + } + EnumVariant::Struct(_idx, name, fields) => { + let mut s = ser.serialize_map(Some(1))?; + s.serialize_key(name)?; + s.serialize_value(&fields.iter().fold( + BTreeMap::new(), + |mut m, (name, ty)| { + m.insert(*name, self.new_value(&mut data, *ty)); + m + }, + ))?; + s.end() + } + } + } + } + } +} + +#[inline] +fn compact_len(data: &[u8]) -> usize { + match data[0] % 0b100 { + 0 => 1, + 1 => 2, + 2 => 4, + _ => todo!(), + } +} + +fn sequence_len(data: &[u8]) -> (usize, usize) { + // need to peek at the data to know the length of sequence + // first byte(s) gives us a hint of the(compact encoded) length + // https://substrate.dev/docs/en/knowledgebase/advanced/codec#compactgeneral-integers + let len = compact_len(data); + ( + match len { + 1 => (data[0] >> 2).into(), + 2 => u16::from_le_bytes([(data[0] >> 2), data[1]]).into(), + 4 => u32::from_le_bytes([(data[0] >> 2), data[1], data[2], data[3]]) + .try_into() + .unwrap(), + + _ => todo!(), + }, + len, + ) +} + +impl<'reg> AsRef<[u8]> for Value<'reg> { + fn as_ref(&self) -> &[u8] { + self.data.as_ref() + } +} + +#[cfg(feature = "codec")] +impl<'reg> codec::Encode for Value<'reg> { + fn size_hint(&self) -> usize { + self.data.len() + } + fn using_encoded R>(&self, f: F) -> R { + f(self.data.as_ref()) + } +} + +impl<'reg> core::fmt::Debug for Value<'reg> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Value {{ data: {:?}, type({}): {:?} }}", + self.data, + self.ty_id, + self.registry.resolve(self.ty_id).unwrap().type_def() + ) + } +} + +#[cfg(feature = "json")] +impl<'reg> core::fmt::Display for Value<'reg> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + serde_json::to_string(self).map_err(|_| fmt::Error)? + ) + } +} + +#[cfg(feature = "json")] +impl<'reg> From> for crate::JsonValue { + fn from(val: Value<'reg>) -> Self { + serde_json::value::to_value(val).unwrap() + } +} + +#[cfg(test)] +mod tests { + use alloc::collections::BTreeMap; + + use super::*; + use anyhow::Error; + use codec::Encode; + use scale_info::{ + meta_type, + prelude::{string::String, vec::Vec}, + Registry, TypeInfo, + }; + use serde_json::to_value; + + fn register(_ty: &T) -> (u32, PortableRegistry) + where + T: TypeInfo + 'static, + { + let mut reg = Registry::new(); + let sym = reg.register_type(&meta_type::()); + (sym.id(), reg.into()) + } + + #[cfg(feature = "json")] + #[test] + fn display_as_json() { + #[derive(Encode, TypeInfo)] + struct Foo { + bar: String, + } + let in_value = Foo { bar: "BAZ".into() }; + + let data = in_value.encode(); + let (id, reg) = register(&in_value); + let out_value = Value::new(data, id, ®).to_string(); + + assert_eq!("{\"bar\":\"BAZ\"}", out_value); + } + + #[cfg(feature = "codec")] + #[test] + fn encodable() { + let input = u8::MAX; + let (ty, reg) = register(&input); + let value = Value::new(b"1234".as_ref(), ty, ®); + + let expected: &[u8] = value.as_ref(); + assert_eq!(value.encode(), expected); + } + + #[test] + fn serialize_u8() -> Result<(), Error> { + let in_value = u8::MAX; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_u16() -> Result<(), Error> { + let in_value = u16::MAX; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_u32() -> Result<(), Error> { + let in_value = u32::MAX; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_u64() -> Result<(), Error> { + let in_value = u64::MAX; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_i16() -> Result<(), Error> { + let in_value = i16::MAX; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_i32() -> Result<(), Error> { + let in_value = i32::MAX; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_i64() -> Result<(), Error> { + let in_value = i64::MAX; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_bool() -> Result<(), Error> { + let in_value = true; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + // `char` not supported? + // #[test] + // fn serialize_char() -> Result<(), Error> { + // let extract_value = '⚖'; + // let data = extract_value.encode(); + // let info = char::type_info(); + // let val = Value::new(data, info, reg); + // assert_eq!(to_value(val)?, to_value(extract_value)?); + // Ok(()) + // } + + #[test] + fn serialize_u8array() -> Result<(), Error> { + let in_value: Vec = [2u8; u8::MAX as usize].into(); + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_u16array() -> Result<(), Error> { + let in_value: Vec = [2u16, u16::MAX].into(); + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_u32array() -> Result<(), Error> { + let in_value: Vec = [2u32, u32::MAX].into(); + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_tuple() -> Result<(), Error> { + let in_value: (i64, Vec, bool) = ( + i64::MIN, + vec!["hello".into(), "big".into(), "world".into()], + true, + ); + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_simple_u32struct() -> Result<(), Error> { + #[derive(Encode, Serialize, TypeInfo)] + struct Foo { + bar: u32, + baz: u32, + } + let in_value = Foo { + bar: 123, + baz: u32::MAX, + }; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_simple_u8struct() -> Result<(), Error> { + #[derive(Encode, Serialize, TypeInfo)] + struct Foo { + bar: u8, + baz: u8, + } + let in_value = Foo { + bar: 123, + baz: u8::MAX, + }; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_simple_u64struct() -> Result<(), Error> { + #[derive(Encode, Serialize, TypeInfo)] + struct Foo { + bar: u64, + baz: u64, + } + let in_value = Foo { + bar: 123, + baz: u64::MAX, + }; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_map() -> Result<(), Error> { + let in_value = { + let mut m = BTreeMap::::new(); + m.insert("foo".into(), i32::MAX); + m.insert("bar".into(), i32::MIN); + m + }; + + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_complex_struct_with_enum() -> Result<(), Error> { + #[derive(Encode, Serialize, TypeInfo)] + enum Bar { + This, + That(i16), + } + #[derive(Encode, Serialize, TypeInfo)] + struct Baz(String); + #[derive(Encode, Serialize, TypeInfo)] + struct Foo { + bar: Vec, + baz: Option, + lol: &'static [u8], + } + let in_value = Foo { + bar: [Bar::That(i16::MAX), Bar::This].into(), + baz: Some(Baz("aliquam malesuada bibendum arcu vitae".into())), + lol: b"\0xFFsome stuff\0x00", + }; + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } + + #[test] + fn serialize_tuple_struct() -> Result<(), Error> { + #[derive(Encode, Serialize, TypeInfo)] + struct Foo([u8; 4], (bool, Option<()>), Baz, Baz); + + #[derive(Encode, Serialize, TypeInfo)] + struct Bar; + + #[derive(Encode, Serialize, TypeInfo)] + enum Baz { + A(Bar), + B { bb: &'static str }, + } + + let in_value = Foo( + [1, 2, 3, 4], + (false, None), + Baz::A(Bar), + Baz::B { bb: "lol" }, + ); + let data = in_value.encode(); + let (id, reg) = register(&in_value); + + let out_value = Value::new(data, id, ®); + + assert_eq!(to_value(out_value)?, to_value(in_value)?); + Ok(()) + } +}