Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement SCALE encoding and serde deserialization #19

Merged
merged 18 commits into from
Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"]
scale-info-derive = { version = "0.2.0", path = "derive", default-features = false, optional = true }
serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
derive_more = { version = "0.99.1", default-features = false, features = ["from"] }
scale = { package = "parity-scale-codec", version = "1.3.4", default-features = false, features = ["derive"] }

[features]
default = ["std"]
std = [
"serde/std",
"serde/std",
"scale/std"
ascjones marked this conversation as resolved.
Show resolved Hide resolved
]
derive = [
"scale-info-derive"
"scale-info-derive"
]

[workspace]
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,28 @@ pub trait TypeInfo {
Types implementing this trait build up and return a `Type` struct:

```rust
pub struct Type<F: Form = MetaForm> {
pub struct Type<T: Form = MetaForm> {
/// The unique path to the type. Can be empty for built-in types
path: Path<F>,
path: Path<T>,
/// The generic type parameters of the type in use. Empty for non generic types
type_params: Vec<F::TypeId>,
type_params: Vec<T::TypeId>,
/// The actual type definition
type_def: TypeDef<F>,
type_def: TypeDef<T>,
}
```
Types are defined as one of the following variants:
```rust
pub enum TypeDef<F: Form = MetaForm> {
pub enum TypeDef<T: Form = MetaForm> {
/// A composite type (e.g. a struct or a tuple)
Composite(TypeDefComposite<F>),
Composite(TypeDefComposite<T>),
/// A variant type (e.g. an enum)
Variant(TypeDefVariant<F>),
Variant(TypeDefVariant<T>),
/// A sequence type with runtime known length.
Sequence(TypeDefSequence<F>),
Sequence(TypeDefSequence<T>),
/// An array type with compile-time known length.
Array(TypeDefArray<F>),
Array(TypeDefArray<T>),
/// A tuple type.
Tuple(TypeDefTuple<F>),
Tuple(TypeDefTuple<T>),
/// A Rust primitive type.
Primitive(TypeDefPrimitive),
}
Expand Down
6 changes: 6 additions & 0 deletions src/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ use serde::Serialize;
pub trait Form {
/// The type identifier type.
type TypeId: PartialEq + Eq + PartialOrd + Ord + Clone + core::fmt::Debug;
/// The string type.
type String: Serialize + PartialEq + Eq + PartialOrd + Ord + Clone + core::fmt::Debug;
Comment on lines +45 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I like the overall idea I think it is better in this case to opt-into KISS (keep it simple stupid) and instead convert all &'static str simply into String. The &'static str was used in the beginning when we did not need deserialization in ink!. Now that we want to support Substrate we need it. However, if we do not generate metadata in any performance critical code section we don't need to opt-in for maximum performance and can instead opt-into simple code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the OwnedForm now, but kept MetaForm as & 'static str, and changed CompactForm to String. Let me know what you think.

}

/// A meta meta-type.
Expand All @@ -53,6 +55,7 @@ pub enum MetaForm {}

impl Form for MetaForm {
type TypeId = MetaType;
type String = &'static str;
}

/// Compact form that has its lifetime untracked in association to its interner.
Expand All @@ -62,9 +65,12 @@ impl Form for MetaForm {
/// This resolves some lifetime issues with self-referential structs (such as
/// the registry itself) but can no longer be used to resolve to the original
/// underlying data.
///
/// `type String` is owned in order to enable decoding
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Debug)]
pub enum CompactForm {}

impl Form for CompactForm {
type TypeId = UntrackedSymbol<TypeId>;
type String = String;
}
24 changes: 22 additions & 2 deletions src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,40 @@
//! elements and is later used for compact serialization within the registry.

use crate::tm_std::*;
use serde::Serialize;
use serde::{Deserialize, Serialize};

/// A symbol that is not lifetime tracked.
///
/// This can be used by self-referential types but
/// can no longer be used to resolve instances.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct UntrackedSymbol<T> {
id: NonZeroU32,
#[serde(skip)]
marker: PhantomData<fn() -> T>,
}

impl<T> scale::Encode for UntrackedSymbol<T> {
fn encode_to<W: scale::Output>(&self, dest: &mut W) {
self.id.get().encode_to(dest)
}
}

impl<T> scale::Decode for UntrackedSymbol<T> {
fn decode<I: scale::Input>(value: &mut I) -> Result<Self, scale::Error> {
let id = <u32 as scale::Decode>::decode(value)?;
if id < 1 {
return Err("UntrackedSymbol::id should be a non-zero unsigned integer".into());
}
let id = NonZeroU32::new(id).expect("ID is non zero");
Ok(UntrackedSymbol {
id,
marker: Default::default(),
})
}
}

/// A symbol from an interner.
///
/// Can be used to resolve to the associated instance.
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ mod tests;

pub use self::{
meta_type::MetaType,
registry::{IntoCompact, Registry},
registry::{IntoCompact, Registry, RegistryReadOnly},
ty::*,
};

Expand Down
43 changes: 41 additions & 2 deletions src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@

use crate::tm_std::*;
use crate::{
form::CompactForm,
form::{CompactForm, Form},
interner::{Interner, UntrackedSymbol},
meta_type::MetaType,
Type,
};
use serde::Serialize;
use scale::{Decode, Encode};
use serde::{Deserialize, Serialize};

/// Compacts the implementor using a registry.
pub trait IntoCompact {
Expand All @@ -42,6 +43,14 @@ pub trait IntoCompact {
fn into_compact(self, registry: &mut Registry) -> Self::Output;
}

impl IntoCompact for &'static str {
type Output = <CompactForm as Form>::String;

fn into_compact(self, _registry: &mut Registry) -> Self::Output {
self.to_string()
}
}

/// The registry for compaction of type identifiers and definitions.
///
/// The registry consists of a cache for already compactified type identifiers and definitions.
Expand Down Expand Up @@ -88,6 +97,23 @@ impl Default for Registry {
}
}

impl Encode for Registry {
fn size_hint(&self) -> usize {
mem::size_of::<u32>() + mem::size_of::<Type<CompactForm>>() * self.types.len()
}

fn encode_to<W: scale::Output>(&self, dest: &mut W) {
if self.types.len() > u32::max_value() as usize {
panic!("Attempted to encode too many elements.");
}
scale::Compact(self.types.len() as u32).encode_to(dest);

for ty in self.types.values() {
ty.encode_to(dest);
}
}
}

impl Registry {
/// Creates a new empty registry.
pub fn new() -> Self {
Expand Down Expand Up @@ -148,3 +174,16 @@ impl Registry {
iter.into_iter().map(|i| i.into_compact(self)).collect::<Vec<_>>()
}
}

/// A read-only registry, to be used for decoding/deserializing
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Decode)]
pub struct RegistryReadOnly {
types: Vec<Type<CompactForm>>,
}

impl RegistryReadOnly {
/// Returns the type definition for the given identifier, `None` if no type found for that ID.
pub fn resolve(&self, id: NonZeroU32) -> Option<&Type<CompactForm>> {
self.types.get((id.get() - 1) as usize)
}
}
1 change: 1 addition & 0 deletions src/tm_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub use self::core::{
fmt::{Debug, Error as FmtError, Formatter},
hash::{Hash, Hasher},
iter,
mem,
};

mod alloc {
Expand Down
16 changes: 10 additions & 6 deletions src/ty/composite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ use crate::{
Field, IntoCompact, Registry,
};
use derive_more::From;
use serde::Serialize;
use scale::{Decode, Encode};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

/// A composite type, consisting of either named (struct) or unnamed (tuple
/// struct) fields
Expand Down Expand Up @@ -47,12 +48,15 @@ use serde::Serialize;
/// ```
/// struct JustAMarker;
/// ```
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, From)]
#[serde(bound = "F::TypeId: Serialize")]
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, From, Serialize, Deserialize, Encode, Decode)]
#[serde(bound(
serialize = "T::TypeId: Serialize, T::String: Serialize",
deserialize = "T::TypeId: DeserializeOwned, T::String: DeserializeOwned"
))]
#[serde(rename_all = "lowercase")]
pub struct TypeDefComposite<F: Form = MetaForm> {
#[serde(skip_serializing_if = "Vec::is_empty")]
fields: Vec<Field<F>>,
pub struct TypeDefComposite<T: Form = MetaForm> {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
fields: Vec<Field<T>>,
}

impl IntoCompact for TypeDefComposite {
Expand Down
20 changes: 12 additions & 8 deletions src/ty/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,34 @@ use crate::{
form::{CompactForm, Form, MetaForm},
IntoCompact, MetaType, Registry, TypeInfo,
};
use serde::Serialize;
use scale::{Decode, Encode};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

/// A field of a struct like data type.
///
/// Name is optional so it can represent both named and unnamed fields.
///
/// This can be a named field of a struct type or a struct variant.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize)]
#[serde(bound = "F::TypeId: Serialize")]
pub struct Field<F: Form = MetaForm> {
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize, Encode, Decode)]
#[serde(bound(
serialize = "T::TypeId: Serialize, T::String: Serialize",
deserialize = "T::TypeId: DeserializeOwned, T::String: DeserializeOwned"
))]
pub struct Field<T: Form = MetaForm> {
/// The name of the field. None for unnamed fields.
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none", default)]
name: Option<T::String>,
/// The type of the field.
#[serde(rename = "type")]
ty: F::TypeId,
ty: T::TypeId,
}

impl IntoCompact for Field {
type Output = Field<CompactForm>;

fn into_compact(self, registry: &mut Registry) -> Self::Output {
Field {
name: self.name,
name: self.name.map(|name| name.into_compact(registry)),
ty: registry.register_type(&self.ty),
}
}
Expand Down
Loading