Skip to content

feat: extract lightweight serialization component from clarity crate #6247

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"stacks-common",
"pox-locking",
"clarity",
"clarity-serialization",
"stx-genesis",
"libstackerdb",
"contrib/tools/relay-server",
Expand Down
43 changes: 43 additions & 0 deletions clarity-serialization/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[package]
name = "clarity-serialization"
version = "0.0.1"
authors = [ "Jude Nelson <jude@stacks.org>",
"Aaron Blankstein <aaron@blockstack.com>",
"Ludo Galabru <ludovic@blockstack.com>" ]
license = "GPLv3"
homepage = "https://github.com/blockstack/stacks-blockchain"
repository = "https://github.com/blockstack/stacks-blockchain"
description = "Lightweight serialization component for Clarity values"
keywords = [ "stacks", "stx", "bitcoin", "crypto", "blockstack", "decentralized", "dapps", "blockchain", "serialization" ]
readme = "README.md"
edition = "2021"
resolver = "2"

[lib]
name = "clarity_serialization"
path = "./src/lib.rs"

[dependencies]
serde = "1"
serde_derive = "1"
serde_stacker = "0.1"
serde_json = { version = "1.0", features = ["arbitrary_precision", "unbounded_depth"] }
regex = "1"
lazy_static = "1.4.0"
integer-sqrt = "0.1.3"
hashbrown = { workspace = true }
stacks_common = { package = "stacks-common", path = "../stacks-common", default-features = false }

[dependencies.time]
version = "0.2.23"
features = ["std"]

[dev-dependencies]
rstest = "0.17.0"
rstest_reuse = "0.5.0"

[features]
default = []
developer-mode = ["stacks_common/developer-mode"]
slog_json = ["stacks_common/slog_json"]
testing = []
71 changes: 71 additions & 0 deletions clarity-serialization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Clarity Serialization

A lightweight serialization component for Clarity values, extracted from the main Clarity VM to provide core serialization/deserialization functionality without heavy dependencies.

## Overview

This crate provides the essential types and functions needed to serialize and deserialize Clarity values, without requiring the full Clarity VM with its database dependencies like rusqlite.

## Features

- **Lightweight**: Only includes serialization-related code and minimal dependencies
- **Core Types**: Includes all essential Clarity value types (Int, UInt, Bool, Sequence, Principal, Tuple, Optional, Response, CallableContract)
- **Serialization Traits**: Provides `ClaritySerializable` and `ClarityDeserializable` traits for custom types
- **Multiple Formats**: Supports both binary and hex string serialization
- **Type Safety**: Maintains the same type safety guarantees as the full Clarity crate

## Usage

```rust
use clarity_serialization::{Value, ClaritySerializable};

// Create a value
let value = Value::UInt(42);

// Serialize to bytes
let bytes = value.serialize_to_vec()?;

// Serialize to hex string
let hex = value.serialize_to_hex()?;

// Deserialize from bytes
let restored = Value::deserialize_from_slice(&bytes)?;

// Deserialize from hex
let restored = Value::deserialize_from_hex(&hex)?;

assert_eq!(value, restored);
```

## Custom Serialization

For custom types, use the `clarity_serializable!` macro:

```rust
use clarity_serialization::{clarity_serializable, ClaritySerializable};

#[derive(Serialize, Deserialize)]
struct MyType {
data: String,
}

clarity_serializable!(MyType);

let my_value = MyType { data: "test".to_string() };
let serialized = my_value.serialize();
```

## Dependencies

This crate has minimal dependencies compared to the full Clarity crate:
- `serde` and `serde_json` for JSON serialization
- `stacks_common` for basic utilities (without rusqlite features)
- Standard library components

## Motivation

The original `clarity` crate includes the full Clarity VM with heavy dependencies like rusqlite, making it difficult for downstream applications that only need serialization functionality. This lightweight crate solves that by providing just the core serialization components.

## Compatibility

This crate maintains full compatibility with values serialized by the main Clarity crate, ensuring seamless interoperability.
83 changes: 83 additions & 0 deletions clarity-serialization/examples/basic_usage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Example demonstrating basic usage of clarity-serialization

Check warning on line 1 in clarity-serialization/examples/basic_usage.rs

View workflow job for this annotation

GitHub Actions / Rust Format

Diff in /home/runner/work/stacks-core/stacks-core/clarity-serialization/examples/basic_usage.rs

use clarity_serialization::{
Value, ClaritySerializable, ClarityDeserializable,
SerializationError, to_hex, from_hex
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Clarity Serialization Example ===\n");

// Create some basic values
let uint_value = Value::UInt(42);
let bool_value = Value::Bool(true);
let none_value = Value::none();

println!("1. Basic Value Serialization:");
demonstrate_serialization(&uint_value, "UInt(42)")?;
demonstrate_serialization(&bool_value, "Bool(true)")?;
demonstrate_serialization(&none_value, "None")?;

// Test hex serialization
println!("\n2. Hex Serialization:");

Check warning on line 22 in clarity-serialization/examples/basic_usage.rs

View workflow job for this annotation

GitHub Actions / Rust Format

Diff in /home/runner/work/stacks-core/stacks-core/clarity-serialization/examples/basic_usage.rs
let hex = uint_value.serialize_to_hex()?;
println!("UInt(42) as hex: {}", hex);

let restored = Value::deserialize_from_hex(&hex)?;
println!("Restored from hex: {:?}", restored);
assert_eq!(uint_value, restored);
println!("✓ Roundtrip successful\n");

// Test complex values
println!("3. Complex Value Types:");
let ok_value = Value::ok(Value::UInt(100))?;
let error_value = Value::error(Value::Bool(false))?;
let some_value = Value::some(Value::UInt(200))?;

demonstrate_serialization(&ok_value, "Ok(UInt(100))")?;
demonstrate_serialization(&error_value, "Error(Bool(false))")?;
demonstrate_serialization(&some_value, "Some(UInt(200))")?;

// Test custom type serialization using the macro
println!("4. Custom Type Serialization:");
let my_data = MyCustomData {
value: 123,

Check warning on line 44 in clarity-serialization/examples/basic_usage.rs

View workflow job for this annotation

GitHub Actions / Rust Format

Diff in /home/runner/work/stacks-core/stacks-core/clarity-serialization/examples/basic_usage.rs
name: "test".to_string(),
};

let serialized = my_data.serialize();
println!("Custom data serialized: {}", serialized);

let restored = MyCustomData::deserialize(&serialized)?;
println!("Custom data restored: {:?}", restored);
assert_eq!(my_data.value, restored.value);
assert_eq!(my_data.name, restored.name);
println!("✓ Custom type roundtrip successful\n");

println!("All examples completed successfully!");
Ok(())
}

Check warning on line 60 in clarity-serialization/examples/basic_usage.rs

View workflow job for this annotation

GitHub Actions / Rust Format

Diff in /home/runner/work/stacks-core/stacks-core/clarity-serialization/examples/basic_usage.rs
fn demonstrate_serialization(value: &Value, description: &str) -> Result<(), SerializationError> {
println!(" {}: {:?}", description, value);

// Serialize to bytes
let bytes = value.serialize_to_vec()?;
println!(" Serialized bytes: {} bytes", bytes.len());

Check warning on line 67 in clarity-serialization/examples/basic_usage.rs

View workflow job for this annotation

GitHub Actions / Rust Format

Diff in /home/runner/work/stacks-core/stacks-core/clarity-serialization/examples/basic_usage.rs
// Deserialize from bytes
let restored = Value::deserialize_from_slice(&bytes)?;
assert_eq!(*value, restored);
println!(" ✓ Roundtrip successful");

Check warning on line 71 in clarity-serialization/examples/basic_usage.rs

View workflow job for this annotation

GitHub Actions / Rust Format

Diff in /home/runner/work/stacks-core/stacks-core/clarity-serialization/examples/basic_usage.rs

Ok(())
}

// Example custom type using the serialization macro
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct MyCustomData {
value: u32,
name: String,
}

Check warning on line 81 in clarity-serialization/examples/basic_usage.rs

View workflow job for this annotation

GitHub Actions / Rust Format

Diff in /home/runner/work/stacks-core/stacks-core/clarity-serialization/examples/basic_usage.rs

clarity_serialization::clarity_serializable!(MyCustomData);
71 changes: 71 additions & 0 deletions clarity-serialization/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
// Copyright (C) 2020 Stacks Open Internet Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

use std::fmt;

/// Errors that may occur in serialization or deserialization
#[derive(Debug, PartialEq)]
pub enum SerializationError {
IOError(String),
DeserializationError(String),
SerializationError(String),
InvalidFormat(String),
UnexpectedType(String),
LeftoverBytesInDeserialization,
UnexpectedSerialization,
}

impl fmt::Display for SerializationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SerializationError::IOError(e) => {
write!(f, "Serialization error caused by IO: {}", e)
}
SerializationError::DeserializationError(e) => {
write!(f, "Deserialization error: {}", e)
}
SerializationError::SerializationError(e) => {
write!(f, "Serialization error: {}", e)
}
SerializationError::InvalidFormat(e) => {
write!(f, "Invalid format error: {}", e)
}
SerializationError::UnexpectedType(e) => {
write!(f, "Unexpected type error: {}", e)
}
SerializationError::UnexpectedSerialization => {
write!(f, "The serializer handled an input in an unexpected way")
}
SerializationError::LeftoverBytesInDeserialization => {
write!(f, "Deserialization error: bytes left over in buffer")
}
}
}
}

impl std::error::Error for SerializationError {}

impl From<std::io::Error> for SerializationError {
fn from(e: std::io::Error) -> Self {
SerializationError::IOError(e.to_string())
}
}

impl From<serde_json::Error> for SerializationError {
fn from(e: serde_json::Error) -> Self {
SerializationError::DeserializationError(e.to_string())

Check warning on line 69 in clarity-serialization/src/errors.rs

View workflow job for this annotation

GitHub Actions / Rust Format

Diff in /home/runner/work/stacks-core/stacks-core/clarity-serialization/src/errors.rs
}
}
68 changes: 68 additions & 0 deletions clarity-serialization/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
// Copyright (C) 2020 Stacks Open Internet Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

//! Lightweight serialization component for Clarity values
//!
//! This crate provides core serialization and deserialization functionality
//! for Clarity Value types without the heavy dependencies of the full Clarity VM.

#![allow(dead_code)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![cfg_attr(test, allow(unused_variables, unused_assignments))]

#[macro_use]
extern crate serde_derive;

extern crate serde_json;

#[cfg(any(test, feature = "testing"))]
#[macro_use]
extern crate rstest;

#[cfg(any(test, feature = "testing"))]
#[macro_use]
pub extern crate rstest_reuse;

extern crate stacks_common;

pub use stacks_common::{
codec, consts, impl_array_hexstring_fmt, impl_array_newtype, impl_byte_array_message_codec,
impl_byte_array_serde, util,
};

/// Core serialization traits and types
pub mod traits;

/// Core Clarity value types for serialization
pub mod types;

/// Serialization and deserialization implementations
pub mod serialization;

/// Basic representations and identifiers
pub mod representations;

/// Error types for serialization operations
pub mod errors;

// Re-export commonly used types
pub use traits::{ClaritySerializable, ClarityDeserializable};
pub use types::{Value, TypeSignature, PrincipalData};
pub use errors::SerializationError;
pub use serialization::{to_hex, from_hex};
pub use representations::{ClarityName, ContractName, QualifiedContractIdentifier};
Loading
Loading