4 stable releases
1.1.1 | Oct 24, 2024 |
---|---|
1.1.0 | Oct 11, 2024 |
1.0.1 | Sep 14, 2024 |
1.0.0 | Aug 29, 2024 |
#296 in Encoding
21 downloads per month
Used in ckb_ssri_sdk
67KB
2K
SLoC
Introduction
The molecule is a serialization format used on CKB. It has several implementations in different languages (C, Rust, JavaScript). In Rust, the implementation introduces numerous types and functions, making it difficult to remember. However, with the power of the serde framework, this process can be greatly simplified. This project offers an implementation of Molecule using Serde.
How to use
Here is a simple case about how to use:
use serde::{Serialize, Deserialize};
use serde_molecule::{to_vec, from_slice};
#[derive(Serialize, Deserialize)]
pub struct Table1 {
pub f1: u8,
pub f2: u16,
}
fn main() {
let t1 = Table1{f1: 0, f2: 0};
// serialize
let bytes = to_vec(&t1, false).unwrap();
// deserialize
let t2: Table1 = from_slice(&bytes, false).unwrap();
assert_eq!(t1.f1, t2.f1);
}
First step is to add dependency in Cargo.toml:
serde = { version = "???", features = ["derive"] }
serde_molecule = { version = "???" }
Then to annotate the types with #[derive(Serialize, Deserialize)]
. After that,
use to_vec
or from_slice
to serialize/deserialize.
Types mapping
Rust types are mapping to molecule types, according to the RFC:
Rust Type | Molecule Type | Fixed Size |
---|---|---|
i8,u8 | byte | yes |
i16, u16, i32, u32, i64, u64, i128, u128 | array | yes |
f32 | array([byte; 4]) | yes |
f64 | array([byte; 8]) | yes |
[u8; N] | array([byte; N]) | yes |
[T; N] | array([T; N]) | yes |
Vec | fixvec | no |
struct | table | no |
#[serde(with = "struct_serde")] | struct | yes |
#[serde(with = "dynvec_serde")] | dynvec | no |
Option | option | no |
enum | union | no |
String | fixvec | no |
BTreeMap | dynvec | no |
HashMap | dynvec | no |
BinaryHeap | fixvec | no |
LinkedList | fixvec | no |
VecDeque | fixvec | no |
HashSet | fixvec | no |
Tuple Struct | table | no |
Tuple Variant | table | no |
Struct Variant | table | no |
Tuple | not supported | N/A |
By default, Vec
-like containers (such as Vec
, BinaryHeap
, etc.) are
serialized into fixvec
. Every element in the Vec
must have a fixed size
(like a molecule struct, array, or primitive type). If the element is not of a
fixed size, it should be annotated with #[serde(with = "dynvec_serde")]
:
use serde_molecule::dynvec_serde;
#[derive(Serialize, Deserialize)]
struct RawTransaction {
// ...
#[serde(with = "dynvec_serde")]
pub outputs: Vec<CellOutput>,
}
By default, every field is considered as molecule table
. If it is molecule
struct
, we should annotate it explicitly.
use serde_molecule::struct_serde;
#[derive(Serialize, Deserialize)]
pub struct CellInput {
pub since: u64,
#[serde(with = "struct_serde")]
pub previous_output: OutPoint,
}
If the top-level type is a molecule struct
, the second argument to to_vec
or
from_slice
should be true
. If the value is false
, the top-level type is
considered a molecule table
.
For all Molecule struct
s, their inner and descendant fields should be "fixed
size" (see the table above).
Map
The Rust map types (like BTreeMap
and HashMap
) can be mapped to the following Molecule schemas:
table MapEntry {
key: KEY_TYPE,
value: VALUE_TYPE,
}
vector Map <MapEntry>;
It is not recommended to use HashMap because its key-value pairs are stored in arbitrary order.
Union with customized id
For molecule union with customized id, see example.
no_std support
To use this library in a no_std
environment:
- Disable default features for both
serde_molecule
andserde
- Enable the
alloc
feature forserde_molecule
- Enable the
derive
feature forserde
Add the following to your Cargo.toml
:
serde_molecule = { version = "x.x.x", default-features = false, features = ["alloc"] }
serde = { version = "x.x.x", default-features = false, features = ["derive"] }
See the no_std example for more details.
Big Array Support
The Serde framework doesn't support arrays with element sizes greater than 32.
See this solution. This limitation
can be addressed using a new serde with
annotation (big_array_serde
):
use serde::{Deserialize, Serialize};
use serde_molecule::big_array_serde;
#[derive(Serialize, Deserialize)]
struct BigArray {
f1: u8,
#[serde(with = "big_array_serde")]
f2: [u8; 33],
#[serde(with = "big_array_serde")]
f3: [u8; 64],
}
Drawback of Deserialization
Compared to the Rust version of the Molecule
implementation, deserialization with
serde_molecule
consumes at least double the memory. In memory-limited
scenarios, such as on-chain scripts, it's not recommended to use.
Tuple Support
Serde treats tuples (e.g., (u8, String)
) similarly to arrays (e.g., [u8; 2]
), which leads to limited support in the context of Molecule serialization. This is due to Serde's implementation.
However, there are some exceptions:
- The unit tuple
()
is supported and maps to an empty array. - Tuple structs and tuple variants are supported and map to Molecule tables.
For general tuples, it's recommended to use a rust struct instead. For example:
use serde::{Deserialize, Serialize};
// Instead of this:
// type MyTuple = (u8, String);
// Use this:
#[derive(Serialize, Deserialize)]
struct MyStruct {
field1: u8,
field2: String,
}
Example
Here is an example definition of CKB types.
Dependencies
~100–330KB