From 71ea78a243035c85371f0f1984300fe8fff01e42 Mon Sep 17 00:00:00 2001 From: Donovan Tjemmes <37707055+Tjemmmic@users.noreply.github.com> Date: Mon, 4 Nov 2024 19:23:48 -0600 Subject: [PATCH 1/5] feat(blueprint-build-utils): utilities lib for build scripts (#442) --- Cargo.lock | 6 ++ Cargo.toml | 2 + blueprint-build-utils/Cargo.toml | 13 ++++ blueprint-build-utils/src/lib.rs | 61 +++++++++++++++++++ .../incredible-squaring-eigenlayer/Cargo.toml | 3 + .../incredible-squaring-eigenlayer/build.rs | 52 +--------------- .../incredible-squaring-symbiotic/Cargo.toml | 1 + .../incredible-squaring-symbiotic/build.rs | 52 +--------------- 8 files changed, 88 insertions(+), 102 deletions(-) create mode 100644 blueprint-build-utils/Cargo.toml create mode 100644 blueprint-build-utils/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 330f97ec..e7f7df40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1880,6 +1880,10 @@ dependencies = [ "zeroize", ] +[[package]] +name = "blueprint-build-utils" +version = "0.1.0" + [[package]] name = "blueprint-manager" version = "0.1.1" @@ -5890,6 +5894,7 @@ dependencies = [ "ark-ff 0.4.2", "async-trait", "bip39", + "blueprint-build-utils", "blueprint-test-utils", "clap", "color-eyre", @@ -5941,6 +5946,7 @@ dependencies = [ "ark-ec", "ark-ff 0.4.2", "async-trait", + "blueprint-build-utils", "blueprint-metadata", "color-eyre", "ed25519-zebra 4.0.3", diff --git a/Cargo.toml b/Cargo.toml index 6b4dcea3..9452901c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "blueprint-build-utils", "blueprint-metadata", "blueprints/incredible-squaring", "blueprints/incredible-squaring-eigenlayer", @@ -54,6 +55,7 @@ tangle-raw-event-listener-blueprint = { path = "./blueprints/tangle-raw-event-li gadget-blueprint-proc-macro = { path = "./macros/blueprint-proc-macro", default-features = false, version = "0.2.3" } gadget-blueprint-proc-macro-core = { path = "./macros/blueprint-proc-macro-core", default-features = false, version = "0.1.5" } gadget-context-derive = { path = "./macros/context-derive", default-features = false, version = "0.1.3" } +blueprint-build-utils = { path = "./blueprint-build-utils", default-features = false, version = "0.1.0" } blueprint-metadata = { path = "./blueprint-metadata", default-features = false, version = "0.1.6" } cargo-tangle = { path = "./cli", version = "0.2.1" } cargo_metadata = { version = "0.18.1" } diff --git a/blueprint-build-utils/Cargo.toml b/blueprint-build-utils/Cargo.toml new file mode 100644 index 00000000..939476d1 --- /dev/null +++ b/blueprint-build-utils/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "blueprint-build-utils" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] + +[lints] +workspace = true diff --git a/blueprint-build-utils/src/lib.rs b/blueprint-build-utils/src/lib.rs new file mode 100644 index 00000000..6d493d8c --- /dev/null +++ b/blueprint-build-utils/src/lib.rs @@ -0,0 +1,61 @@ +use std::env; +use std::path::PathBuf; +use std::process::Command; + +/// Build the Smart contracts at the specified directories, automatically rerunning if changes are +/// detected in this crates Smart Contracts (`./contracts/lib`). +/// +/// # Panics +/// - If the Cargo Manifest directory is not found. +/// - If the `forge` executable is not found. +pub fn build_contracts(contract_dirs: Vec<&str>) { + println!("cargo::rerun-if-changed=contracts/lib/*"); + println!("cargo::rerun-if-changed=contracts/src/*"); + + // Get the project root directory + let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + + // Try to find the `forge` executable dynamically + let forge_executable = match Command::new("which").arg("forge").output() { + Ok(output) => { + let path = String::from_utf8_lossy(&output.stdout).trim().to_string(); + assert!( + !path.is_empty(), + "Forge executable not found. Make sure Foundry is installed." + ); + path + } + Err(e) => panic!("Failed to find `forge` executable: {e}"), + }; + + for dir in contract_dirs { + let full_path = root.join(dir).canonicalize().unwrap_or_else(|_| { + println!( + "Directory not found or inaccessible: {}", + root.join(dir).display() + ); + root.join(dir) + }); + + if full_path.exists() { + println!("cargo:rerun-if-changed={}", full_path.display()); + + let status = Command::new(&forge_executable) + .current_dir(&full_path) + .arg("build") + .status() + .expect("Failed to execute Forge build"); + + assert!( + status.success(), + "Forge build failed for directory: {}", + full_path.display() + ); + } else { + println!( + "Directory not found or does not exist: {}", + full_path.display() + ); + } + } +} diff --git a/blueprints/incredible-squaring-eigenlayer/Cargo.toml b/blueprints/incredible-squaring-eigenlayer/Cargo.toml index 97a8ee99..5863f598 100644 --- a/blueprints/incredible-squaring-eigenlayer/Cargo.toml +++ b/blueprints/incredible-squaring-eigenlayer/Cargo.toml @@ -59,6 +59,9 @@ num-bigint = { workspace = true } blueprint-test-utils = { workspace = true } gadget-io = { workspace = true } +[build-dependencies] +blueprint-build-utils = { workspace = true } + [features] default = ["std"] std = [] diff --git a/blueprints/incredible-squaring-eigenlayer/build.rs b/blueprints/incredible-squaring-eigenlayer/build.rs index 122edeef..e3190ee5 100644 --- a/blueprints/incredible-squaring-eigenlayer/build.rs +++ b/blueprints/incredible-squaring-eigenlayer/build.rs @@ -1,12 +1,4 @@ -use std::env; -use std::path::PathBuf; -use std::process::Command; - fn main() { - println!("cargo:rerun-if-changed=src/cli"); - println!("cargo:rerun-if-changed=src/lib.rs"); - println!("cargo:rerun-if-changed=src/main.rs"); - let contract_dirs: Vec<&str> = vec![ "./contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts", "./contracts/lib/eigenlayer-middleware", @@ -14,47 +6,5 @@ fn main() { "./contracts", ]; - // Get the project root directory - let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - - // Try to find the `forge` executable dynamically - let forge_executable = match Command::new("which").arg("forge").output() { - Ok(output) => { - let path = String::from_utf8_lossy(&output.stdout).trim().to_string(); - if path.is_empty() { - panic!("Forge executable not found. Make sure Foundry is installed."); - } - path - } - Err(_) => panic!("Failed to locate `forge` executable. Make sure Foundry is installed."), - }; - - for dir in contract_dirs { - let full_path = root.join(dir).canonicalize().unwrap_or_else(|_| { - println!( - "Directory not found or inaccessible: {}", - root.join(dir).display() - ); - root.join(dir) - }); - - if full_path.exists() { - println!("cargo:rerun-if-changed={}", full_path.display()); - - let status = Command::new(&forge_executable) - .current_dir(&full_path) - .arg("build") - .status() - .expect("Failed to execute Forge build"); - - if !status.success() { - panic!("Forge build failed for directory: {}", full_path.display()); - } - } else { - println!( - "Directory not found or does not exist: {}", - full_path.display() - ); - } - } + blueprint_build_utils::build_contracts(contract_dirs); } diff --git a/blueprints/incredible-squaring-symbiotic/Cargo.toml b/blueprints/incredible-squaring-symbiotic/Cargo.toml index 1738451a..4739088b 100644 --- a/blueprints/incredible-squaring-symbiotic/Cargo.toml +++ b/blueprints/incredible-squaring-symbiotic/Cargo.toml @@ -49,6 +49,7 @@ lazy_static = { workspace = true } [build-dependencies] blueprint-metadata = { workspace = true } +blueprint-build-utils = { workspace = true } [features] default = ["std"] diff --git a/blueprints/incredible-squaring-symbiotic/build.rs b/blueprints/incredible-squaring-symbiotic/build.rs index 0f05d1a0..5a98e538 100644 --- a/blueprints/incredible-squaring-symbiotic/build.rs +++ b/blueprints/incredible-squaring-symbiotic/build.rs @@ -1,59 +1,9 @@ -use std::env; -use std::path::PathBuf; -use std::process::Command; - fn main() { - println!("cargo:rerun-if-changed=src/cli"); - println!("cargo:rerun-if-changed=src/lib.rs"); - println!("cargo:rerun-if-changed=src/main.rs"); - let contract_dirs: Vec<&str> = vec![ "./contracts/lib/core", "./contracts/lib/forge-std", "./contracts", ]; - // Get the project root directory - let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - print!("root: {:?}", root); - // Try to find the `forge` executable dynamically - let forge_executable = match Command::new("which").arg("forge").output() { - Ok(output) => { - let path = String::from_utf8_lossy(&output.stdout).trim().to_string(); - if path.is_empty() { - panic!("Forge executable not found. Make sure Foundry is installed."); - } - path - } - Err(_) => panic!("Failed to locate `forge` executable. Make sure Foundry is installed."), - }; - - for dir in contract_dirs { - let full_path = root.join(dir).canonicalize().unwrap_or_else(|_| { - println!( - "Directory not found or inaccessible: {}", - root.join(dir).display() - ); - root.join(dir) - }); - - if full_path.exists() { - println!("cargo:rerun-if-changed={}", full_path.display()); - - let status = Command::new(&forge_executable) - .current_dir(&full_path) - .arg("build") - .status() - .expect("Failed to execute Forge build"); - - if !status.success() { - panic!("Forge build failed for directory: {}", full_path.display()); - } - } else { - println!( - "Directory not found or does not exist: {}", - full_path.display() - ); - } - } + blueprint_build_utils::build_contracts(contract_dirs); } From c6d9ffe5075ec18438cc9b68c4d68d7d8eb9f743 Mon Sep 17 00:00:00 2001 From: Alex <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 5 Nov 2024 02:41:35 -0500 Subject: [PATCH 2/5] feat: `blueprint-serde` crate (#429) * feat: `blueprint-serde` crate This crates lets us convert to and from the `Field` type easily with `serde`'s `Serialize` and `Deserialize` traits. This will allow for custom types in job parameters, as well as making it easier overall to convert between these types. The crate adds two public functions: * `to_field` - Convert any `Serialize` type into a `Field` * `from_field` - Attempt to convert a `Field` into a `DeserializeOwned` type While mostly useful for `Field::Struct`, one can also do: ```rust let age: u8 = gadget_blueprint_serde::from_field(Field::Uint8(40)).unwrap(); ``` Or conversion for any primitive type, if they wanted. * chore(gadget-blueprint-serde): simplify primitive & struct deserialization * fix(gadget-blueprint-serde): add unit serialization * chore(gadget-blueprint-serde): move `serde-test` to dev dependencies * chore(gadget-blueprint-serde): improve docs and add examples * feat(gadget-blueprint-serde): force `#![no_std]` * feat(gadget-blueprint-serde): add `std` feature * chore(gadget-blueprint-serde): add tests for enums * chore(gadget-blueprint-serde): defer enum variant deserializing * chore: bump `tangle-subxt` --- Cargo.lock | 24 +- Cargo.toml | 8 +- blueprint-serde/Cargo.toml | 23 ++ blueprint-serde/src/de.rs | 435 +++++++++++++++++++++++++++++++++ blueprint-serde/src/error.rs | 88 +++++++ blueprint-serde/src/lib.rs | 104 ++++++++ blueprint-serde/src/ser.rs | 358 +++++++++++++++++++++++++++ blueprint-serde/src/tests.rs | 453 +++++++++++++++++++++++++++++++++++ 8 files changed, 1490 insertions(+), 3 deletions(-) create mode 100644 blueprint-serde/Cargo.toml create mode 100644 blueprint-serde/src/de.rs create mode 100644 blueprint-serde/src/error.rs create mode 100644 blueprint-serde/src/lib.rs create mode 100644 blueprint-serde/src/ser.rs create mode 100644 blueprint-serde/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index e7f7df40..6b2dedff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4655,6 +4655,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "gadget-blueprint-serde" +version = "0.1.0" +dependencies = [ + "paste", + "serde", + "serde_test", + "tangle-subxt", +] + [[package]] name = "gadget-context-derive" version = "0.1.3" @@ -4727,6 +4737,7 @@ dependencies = [ "futures", "gadget-blueprint-proc-macro", "gadget-blueprint-proc-macro-core", + "gadget-blueprint-serde", "gadget-context-derive", "gadget-io", "getrandom", @@ -10421,6 +10432,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_test" +version = "1.0.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -11902,9 +11922,9 @@ dependencies = [ [[package]] name = "tangle-subxt" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20bf7f8d29cfdb72ea840a6d58d9191b3573dc0309f1b94eee25848498f89c1a" +checksum = "5fd92b3c29823ab4db09ed7030222dbcdf94d5edbb384b5fca4eb76f646ead9c" dependencies = [ "parity-scale-codec", "scale-info", diff --git a/Cargo.toml b/Cargo.toml index 9452901c..519bff7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "gadget-io", "blueprint-test-utils", "blueprint-manager", + "blueprint-serde", "sdk", "macros/blueprint-proc-macro", "macros/blueprint-proc-macro-core", @@ -37,6 +38,8 @@ unused_import_braces = "deny" pedantic = { level = "deny", priority = -1 } all = { level = "deny", priority = -1 } single_match_else = "allow" +uninlined_format_args = "allow" +needless_late_init = "allow" [workspace.lints.rustdoc] broken_intra_doc_links = "deny" @@ -44,6 +47,7 @@ broken_intra_doc_links = "deny" [workspace.dependencies] gadget-io = { version = "0.0.4", path = "./gadget-io", default-features = false } blueprint-manager = { version = "0.1.1", path = "./blueprint-manager" } +blueprint-serde = { version = "0.1.0", path = "./blueprint-serde", package = "gadget-blueprint-serde" } blueprint-test-utils = { path = "./blueprint-test-utils" } gadget-sdk = { path = "./sdk", default-features = false, version = "0.2.3" } @@ -61,7 +65,7 @@ cargo-tangle = { path = "./cli", version = "0.2.1" } cargo_metadata = { version = "0.18.1" } # Tangle-related dependencies -tangle-subxt = { version = "0.4.0", default-features = false } +tangle-subxt = { version = "0.5.0", default-features = false } subxt-signer = { version = "0.37.0", default-features = false } subxt = { version = "0.37.0", default-features = false } subxt-core = { version = "0.37.0", default-features = false } @@ -117,6 +121,7 @@ multiaddr = { version = "0.18.1", default-features = false } nix = { version = "0.29.0", features = ["process", "signal"] } num-bigint = "0.4.6" parking_lot = "0.12.3" +paste = "1.0.15" proc-macro2 = "1.0" prometheus = { version = "0.13.4", default-features = false } quote = "1.0" @@ -126,6 +131,7 @@ rustdoc-types = "0.31.0" schnorrkel = { version = "0.11.4", default-features = false, features = ["preaudit_deprecated", "getrandom"] } serde = { version = "1.0.208", default-features = false } serde_json = "1.0" +serde_test = "1.0.177" sha2 = "0.10.8" sqlx = "=0.7.3" syn = "2.0.75" diff --git a/blueprint-serde/Cargo.toml b/blueprint-serde/Cargo.toml new file mode 100644 index 00000000..bbcac65f --- /dev/null +++ b/blueprint-serde/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "gadget-blueprint-serde" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +paste.workspace = true +serde.workspace = true +tangle-subxt.workspace = true + +[dev-dependencies] +serde_test.workspace = true + +[lints] +workspace = true + +[features] +default = ["std"] +std = [] \ No newline at end of file diff --git a/blueprint-serde/src/de.rs b/blueprint-serde/src/de.rs new file mode 100644 index 00000000..87d0b12f --- /dev/null +++ b/blueprint-serde/src/de.rs @@ -0,0 +1,435 @@ +use crate::error::{Error, Result, UnsupportedType}; +use crate::Field; +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::vec::Vec; +use serde::de; +use serde::de::IntoDeserializer; +use tangle_subxt::subxt_core::utils::AccountId32; +use tangle_subxt::tangle_testnet_runtime::api::runtime_types::bounded_collections::bounded_vec::BoundedVec; +use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::BoundedString; + +/// A deserializer for [`Field`] +/// +/// This is simply a wrapper over an owned `Field`, since it's an external type. +/// +/// See [`crate::from_field`]. +pub struct Deserializer(pub(crate) Field); + +macro_rules! deserialize_primitive { + ($($t:ty => $pat:pat),+ $(,)?) => { + $( + paste::paste! { + fn [](self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.0 { + $pat(value) => visitor.[](value), + _ => Err(self.invalid_type(&visitor)) + } + } + } + )+ + } +} + +impl<'de> de::Deserializer<'de> for Deserializer { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.0 { + Field::None => visitor.visit_none(), + Field::Bool(b) => visitor.visit_bool(b), + Field::Uint8(u) => visitor.visit_u8(u), + Field::Int8(i) => visitor.visit_i8(i), + Field::Uint16(u) => visitor.visit_u16(u), + Field::Int16(i) => visitor.visit_i16(i), + Field::Uint32(u) => visitor.visit_u32(u), + Field::Int32(i) => visitor.visit_i32(i), + Field::Uint64(u) => visitor.visit_u64(u), + Field::Int64(i) => visitor.visit_i64(i), + Field::String(s) => { + let s = String::from_utf8(s.0 .0)?; + visitor.visit_string(s) + } + Field::Bytes(b) => visitor.visit_bytes(b.0.as_slice()), + Field::Array(seq) | Field::List(seq) => visit_seq(seq.0, visitor), + Field::Struct(_, fields) => visit_struct(*fields, visitor), + Field::AccountId(a) => visitor.visit_bytes(a.0.as_slice()), + } + } + + deserialize_primitive!( + bool => Field::Bool, + i8 => Field::Int8, + i16 => Field::Int16, + i32 => Field::Int32, + i64 => Field::Int64, + u8 => Field::Uint8, + u16 => Field::Uint16, + u32 => Field::Uint32, + u64 => Field::Uint64, + ); + + fn deserialize_f32(self, _visitor: V) -> Result + where + V: de::Visitor<'de>, + { + Err(Error::UnsupportedType(UnsupportedType::f32)) + } + + fn deserialize_f64(self, _visitor: V) -> Result + where + V: de::Visitor<'de>, + { + Err(Error::UnsupportedType(UnsupportedType::f64)) + } + + fn deserialize_char(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + let string; + match self.0 { + Field::String(bound_string) => string = String::from_utf8(bound_string.0 .0)?, + _ => return Err(self.invalid_type(&visitor)), + }; + + let mut chars = string.chars(); + let Some(ch) = chars.next() else { + return Err(Error::BadCharLength(0)); + }; + + if chars.next().is_some() { + return Err(Error::BadCharLength(chars.count().saturating_add(2))); + } + + visitor.visit_char(ch) + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_string(visitor) + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.0 { + Field::String(bound_string) => { + visitor.visit_string(String::from_utf8(bound_string.0 .0)?) + } + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_byte_buf(visitor) + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.0 { + Field::Bytes(seq) => visitor.visit_byte_buf(seq.0), + Field::String(s) => visitor.visit_string(String::from_utf8(s.0 .0)?), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.0 { + Field::None => visitor.visit_none(), + _ => visitor.visit_some(self), + } + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.0 { + Field::None => visitor.visit_unit(), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + fn deserialize_newtype_struct(self, _name: &'static str, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.0 { + Field::Array(seq) | Field::List(seq) => visit_seq(seq.0, visitor), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + match self.0 { + Field::Struct(_, fields) => { + let mut values = Vec::with_capacity(fields.0.len()); + for (_, field) in fields.0 { + values.push(field); + } + + visit_seq(values, visitor) + } + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_map(self, _visitor: V) -> Result + where + V: de::Visitor<'de>, + { + Err(Error::UnsupportedType(UnsupportedType::Map)) + } + + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + match self.0 { + Field::Struct(_name, fields) => visit_struct(*fields, visitor), + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + // Only accept unit variants + match self.0 { + Field::String(bound_string) => { + visitor.visit_enum(String::from_utf8(bound_string.0 .0)?.into_deserializer()) + } + _ => Err(self.invalid_type(&visitor)), + } + } + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + drop(self); + visitor.visit_unit() + } +} + +impl Deserializer { + #[cold] + fn invalid_type(&self, exp: &dyn de::Expected) -> E + where + E: de::Error, + { + de::Error::invalid_type(self.unexpected(), exp) + } + + #[cold] + fn unexpected(&self) -> de::Unexpected<'_> { + match &self.0 { + Field::None => de::Unexpected::Unit, + Field::Bool(b) => de::Unexpected::Bool(*b), + + Field::Uint8(u) => de::Unexpected::Unsigned(u64::from(*u)), + Field::Uint16(u) => de::Unexpected::Unsigned(u64::from(*u)), + Field::Uint32(u) => de::Unexpected::Unsigned(u64::from(*u)), + Field::Uint64(u) => de::Unexpected::Unsigned(*u), + + Field::Int8(i) => de::Unexpected::Signed(i64::from(*i)), + Field::Int16(i) => de::Unexpected::Signed(i64::from(*i)), + Field::Int32(i) => de::Unexpected::Signed(i64::from(*i)), + Field::Int64(i) => de::Unexpected::Signed(*i), + + Field::String(s) => de::Unexpected::Str(core::str::from_utf8(&s.0 .0).unwrap_or("")), + Field::Bytes(b) => de::Unexpected::Bytes(b.0.as_slice()), + Field::Array(_) | Field::List(_) => de::Unexpected::Seq, + Field::Struct(_, _) => de::Unexpected::Other("Struct"), + Field::AccountId(_) => de::Unexpected::Other("AccountId"), + } + } +} + +struct StructDeserializer { + iter: > as IntoIterator>::IntoIter, + field: Option>, +} + +impl StructDeserializer { + fn new(map: BTreeMap>) -> Self { + StructDeserializer { + iter: map.into_iter(), + field: None, + } + } +} + +impl<'de> de::MapAccess<'de> for StructDeserializer { + type Error = Error; + + fn next_key_seed(&mut self, seed: K) -> Result> + where + K: de::DeserializeSeed<'de>, + { + match self.iter.next() { + Some((key, field)) => { + self.field = Some(field); + seed.deserialize(key.into_deserializer()).map(Some) + } + None => Ok(None), + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: de::DeserializeSeed<'de>, + { + match self.field.take() { + Some(field) => seed.deserialize(Deserializer(field)), + None => Err(serde::de::Error::custom("value is missing")), + } + } + + fn size_hint(&self) -> Option { + match self.iter.size_hint() { + (lower, Some(upper)) if lower == upper => Some(upper), + _ => None, + } + } +} + +fn visit_struct<'de, V>( + serialized_fields: BoundedVec<(BoundedString, Field)>, + visitor: V, +) -> Result +where + V: de::Visitor<'de>, +{ + let mut fields = BTreeMap::new(); + for (name, value) in serialized_fields.0 { + let name = String::from_utf8(name.0 .0)?; + fields.insert(name, value); + } + + let len = fields.len(); + let mut de = StructDeserializer::new(fields); + let seq = visitor.visit_map(&mut de)?; + let remaining = de.iter.len(); + if remaining == 0 { + Ok(seq) + } else { + Err(de::Error::invalid_length(len, &"fewer elements in struct")) + } +} + +struct SeqDeserializer { + iter: alloc::vec::IntoIter>, +} + +impl SeqDeserializer { + fn new(vec: Vec>) -> Self { + SeqDeserializer { + iter: vec.into_iter(), + } + } +} + +impl<'de> de::SeqAccess<'de> for SeqDeserializer { + type Error = Error; + + fn next_element_seed(&mut self, seed: T) -> Result> + where + T: de::DeserializeSeed<'de>, + { + match self.iter.next() { + Some(field) => seed.deserialize(Deserializer(field)).map(Some), + None => Ok(None), + } + } + + fn size_hint(&self) -> Option { + match self.iter.size_hint() { + (lower, Some(upper)) if lower == upper => Some(upper), + _ => None, + } + } +} + +fn visit_seq<'de, V>(seq: Vec>, visitor: V) -> Result +where + V: de::Visitor<'de>, +{ + let len = seq.len(); + let mut de = SeqDeserializer::new(seq); + let seq = visitor.visit_seq(&mut de)?; + let remaining = de.iter.len(); + if remaining == 0 { + Ok(seq) + } else { + Err(de::Error::invalid_length( + len, + &"fewer elements in sequence", + )) + } +} diff --git a/blueprint-serde/src/error.rs b/blueprint-serde/src/error.rs new file mode 100644 index 00000000..01087eac --- /dev/null +++ b/blueprint-serde/src/error.rs @@ -0,0 +1,88 @@ +use alloc::string::{String, ToString}; +use core::fmt::Display; +use core::fmt::Formatter; +use serde::{de, ser}; + +pub type Result = core::result::Result; + +/// Types that cannot be represented as a [`Field`](crate::Field) +/// +/// Attempting to de/serialize any of these types will cause an error. +#[derive(Debug)] +#[allow(non_camel_case_types)] +pub enum UnsupportedType { + f32, + f64, + Map, + /// Any enum that is not a simple unit enum + /// + /// ## Valid + /// + /// ```rust + /// enum Foo { + /// Bar, + /// Baz, + /// } + /// ``` + /// + /// ## Invalid + /// + /// ```rust + /// enum Foo { + /// Bar(String), + /// } + /// ``` + /// + /// Or + /// + /// ```rust + /// enum Foo { + /// Baz { a: String }, + /// } + /// ``` + NonUnitEnum, +} + +#[derive(Debug)] +pub enum Error { + UnsupportedType(UnsupportedType), + /// Attempting to deserialize a [`char`] from a [`Field::String`](crate::Field::String) + BadCharLength(usize), + FromUtf8Error(alloc::string::FromUtf8Error), + Other(String), +} + +impl ser::Error for Error { + fn custom(msg: T) -> Self { + Error::Other(msg.to_string()) + } +} + +impl de::Error for Error { + fn custom(msg: T) -> Self { + Error::Other(msg.to_string()) + } +} + +impl From for Error { + fn from(err: alloc::string::FromUtf8Error) -> Self { + Error::FromUtf8Error(err) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Error::UnsupportedType(unsupported_type) => { + write!(f, "Type `{:?}` unsupported", unsupported_type) + } + Error::BadCharLength(len) => { + write!(f, "String contains {len} characters, expected 1") + } + Error::FromUtf8Error(e) => write!(f, "{e}"), + Error::Other(msg) => write!(f, "{}", msg), + } + } +} + +impl core::error::Error for Error {} diff --git a/blueprint-serde/src/lib.rs b/blueprint-serde/src/lib.rs new file mode 100644 index 00000000..d58b1b96 --- /dev/null +++ b/blueprint-serde/src/lib.rs @@ -0,0 +1,104 @@ +#![cfg_attr(feature = "std", no_std)] + +mod de; +pub mod error; +mod ser; +#[cfg(test)] +mod tests; + +extern crate alloc; + +use serde::Serialize; +use serde::de::DeserializeOwned; +use tangle_subxt::subxt_core::utils::AccountId32; +pub use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field; +pub use tangle_subxt::tangle_testnet_runtime::api::runtime_types::bounded_collections::bounded_vec::BoundedVec; +pub use ser::new_bounded_string; +use error::Result; + +/// Derive a [`Field`] from an instance of type `S` +/// +/// # Errors +/// +/// * Attempting to serialize an [`UnsupportedType`](error::UnsupportedType) +/// +/// # Examples +/// +/// ```rust +/// use gadget_blueprint_serde::{new_bounded_string, BoundedVec, Field}; +/// use serde::Serialize; +/// +/// #[derive(Serialize)] +/// struct Person { +/// name: String, +/// age: u8, +/// } +/// +/// let person = Person { +/// name: String::from("John"), +/// age: 40, +/// }; +/// +/// let expected = Field::Struct( +/// new_bounded_string("Person"), +/// Box::new(BoundedVec(vec![ +/// ( +/// new_bounded_string("name"), +/// Field::String(new_bounded_string("John")), +/// ), +/// (new_bounded_string("age"), Field::Uint8(40)), +/// ])), +/// ); +/// +/// let field = gadget_blueprint_serde::to_field(person).unwrap(); +/// assert_eq!(expected, field); +/// ``` +pub fn to_field(value: S) -> Result> +where + S: Serialize, +{ + let mut ser = ser::Serializer; + value.serialize(&mut ser) +} + +/// Derive an instance of type `D` from a [`Field`] +/// +/// # Errors +/// +/// * Attempting to deserialize an [`UnsupportedType`](error::UnsupportedType) +/// * Attempting to deserialize non UTF-8 bytes into a [`String`] +/// * Any type mismatch (e.g. attempting to deserialize [`Field::Int8`] into a [`char`]). +/// +/// # Examples +/// +/// ```rust +/// use gadget_blueprint_serde::{new_bounded_string, BoundedVec, Field}; +/// use serde::Deserialize; +/// +/// #[derive(Deserialize, Debug)] +/// struct Person { +/// name: String, +/// age: u8, +/// } +/// +/// let field = Field::Struct( +/// new_bounded_string("Person"), +/// Box::new(BoundedVec(vec![ +/// ( +/// new_bounded_string("name"), +/// Field::String(new_bounded_string("John")), +/// ), +/// (new_bounded_string("age"), Field::Uint8(40)), +/// ])), +/// ); +/// +/// let person: Person = gadget_blueprint_serde::from_field(field).unwrap(); +/// println!("{:#?}", person); +/// ``` +pub fn from_field(field: Field) -> Result +where + D: DeserializeOwned, +{ + let de = de::Deserializer(field); + D::deserialize(de) +} diff --git a/blueprint-serde/src/ser.rs b/blueprint-serde/src/ser.rs new file mode 100644 index 00000000..9f2d600c --- /dev/null +++ b/blueprint-serde/src/ser.rs @@ -0,0 +1,358 @@ +use crate::error::{Result, UnsupportedType}; +use crate::Field; +use alloc::boxed::Box; +use alloc::format; +use alloc::string::String; +use alloc::vec::Vec; +use serde::ser; +use serde::Serialize; +use tangle_subxt::subxt_core::utils::AccountId32; +use tangle_subxt::tangle_testnet_runtime::api::runtime_types::bounded_collections::bounded_vec::BoundedVec; +use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::BoundedString; + +/// A serializer for [`Field`] +/// +/// See [`crate::into_field`]. +pub struct Serializer; + +impl<'a> serde::Serializer for &'a mut Serializer { + type Ok = Field; + type Error = crate::error::Error; + type SerializeSeq = SerializeSeq<'a>; + type SerializeTuple = Self::SerializeSeq; + type SerializeTupleStruct = SerializeTupleStruct<'a>; + type SerializeTupleVariant = Self; + type SerializeMap = Self; + type SerializeStruct = SerializeStruct<'a>; + type SerializeStructVariant = Self; + + fn serialize_bool(self, v: bool) -> Result { + Ok(Field::Bool(v)) + } + + fn serialize_i8(self, v: i8) -> Result { + Ok(Field::Int8(v)) + } + + fn serialize_i16(self, v: i16) -> Result { + Ok(Field::Int16(v)) + } + + fn serialize_i32(self, v: i32) -> Result { + Ok(Field::Int32(v)) + } + + fn serialize_i64(self, v: i64) -> Result { + Ok(Field::Int64(v)) + } + + fn serialize_u8(self, v: u8) -> Result { + Ok(Field::Uint8(v)) + } + + fn serialize_u16(self, v: u16) -> Result { + Ok(Field::Uint16(v)) + } + + fn serialize_u32(self, v: u32) -> Result { + Ok(Field::Uint32(v)) + } + + fn serialize_u64(self, v: u64) -> Result { + Ok(Field::Uint64(v)) + } + + fn serialize_f32(self, _v: f32) -> Result { + Err(Self::Error::UnsupportedType(UnsupportedType::f32)) + } + + fn serialize_f64(self, _v: f64) -> Result { + Err(Self::Error::UnsupportedType(UnsupportedType::f64)) + } + + fn serialize_char(self, v: char) -> Result { + Ok(Field::String(new_bounded_string(v))) + } + + fn serialize_str(self, v: &str) -> Result { + Ok(Field::String(new_bounded_string(v))) + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + Ok(Field::Bytes(BoundedVec(v.into()))) + } + + fn serialize_none(self) -> Result { + self.serialize_unit() + } + + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + Ok(Field::None) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.serialize_unit() + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + Ok(Field::String(new_bounded_string(variant))) + } + + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + Err(Self::Error::UnsupportedType(UnsupportedType::NonUnitEnum)) + } + + fn serialize_seq(self, len: Option) -> Result { + let vec; + match len { + Some(len) => vec = Vec::with_capacity(len), + None => vec = Vec::new(), + } + + Ok(SerializeSeq { ser: self, vec }) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + let ser = SerializeTupleStruct { + ser: self, + name, + fields: Vec::with_capacity(len), + }; + Ok(ser) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Ok(self) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(Self::Error::UnsupportedType(UnsupportedType::Map)) + } + + fn serialize_struct(self, name: &'static str, len: usize) -> Result { + let ser = SerializeStruct { + ser: self, + name, + fields: Vec::with_capacity(len), + }; + Ok(ser) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Ok(self) + } + + fn is_human_readable(&self) -> bool { + false + } +} + +pub struct SerializeSeq<'a> { + ser: &'a mut Serializer, + vec: Vec>, +} + +impl ser::SerializeSeq for SerializeSeq<'_> { + type Ok = Field; + type Error = crate::error::Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + let value = value.serialize(&mut *self.ser)?; + self.vec.push(value); + Ok(()) + } + + fn end(self) -> Result { + Ok(Field::List(BoundedVec(self.vec))) + } +} + +impl ser::SerializeTuple for SerializeSeq<'_> { + type Ok = Field; + type Error = crate::error::Error; + + #[inline] + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + as ser::SerializeSeq>::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + as ser::SerializeSeq>::end(self) + } +} + +pub struct SerializeTupleStruct<'a> { + ser: &'a mut Serializer, + name: &'a str, + fields: Vec<(BoundedString, Field)>, +} + +impl ser::SerializeTupleStruct for SerializeTupleStruct<'_> { + type Ok = Field; + type Error = crate::error::Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + let field_value = value.serialize(&mut *self.ser)?; + let field_name = format!("field_{}", self.fields.len()); + self.fields + .push((new_bounded_string(field_name), field_value)); + Ok(()) + } + + fn end(self) -> Result { + Ok(Field::Struct( + new_bounded_string(self.name), + Box::new(BoundedVec(self.fields)), + )) + } +} + +pub struct SerializeStruct<'a> { + ser: &'a mut Serializer, + name: &'a str, + fields: Vec<(BoundedString, Field)>, +} + +impl ser::SerializeStruct for SerializeStruct<'_> { + type Ok = Field; + type Error = crate::error::Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + let field_value = value.serialize(&mut *self.ser)?; + self.fields.push((new_bounded_string(key), field_value)); + Ok(()) + } + + fn end(self) -> Result { + Ok(Field::Struct( + new_bounded_string(self.name), + Box::new(BoundedVec(self.fields)), + )) + } +} + +// === UNSUPPORTED TYPES === + +impl ser::SerializeTupleVariant for &mut Serializer { + type Ok = Field; + type Error = crate::error::Error; + + fn serialize_field(&mut self, _value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + Err(Self::Error::UnsupportedType(UnsupportedType::NonUnitEnum)) + } + + fn end(self) -> Result { + Err(Self::Error::UnsupportedType(UnsupportedType::NonUnitEnum)) + } +} + +impl ser::SerializeMap for &mut Serializer { + type Ok = Field; + type Error = crate::error::Error; + + fn serialize_key(&mut self, _key: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + Err(Self::Error::UnsupportedType(UnsupportedType::Map)) + } + + fn serialize_value(&mut self, _value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + Err(Self::Error::UnsupportedType(UnsupportedType::Map)) + } + + fn end(self) -> Result { + Err(Self::Error::UnsupportedType(UnsupportedType::Map)) + } +} + +impl ser::SerializeStructVariant for &mut Serializer { + type Ok = Field; + type Error = crate::error::Error; + + fn serialize_field(&mut self, _key: &'static str, _value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + Err(Self::Error::UnsupportedType(UnsupportedType::NonUnitEnum)) + } + + fn end(self) -> Result { + Err(Self::Error::UnsupportedType(UnsupportedType::NonUnitEnum)) + } +} + +pub fn new_bounded_string(s: S) -> BoundedString +where + S: Into, +{ + let s = s.into(); + BoundedString(BoundedVec(s.into_bytes())) +} diff --git a/blueprint-serde/src/tests.rs b/blueprint-serde/src/tests.rs new file mode 100644 index 00000000..2f300bc2 --- /dev/null +++ b/blueprint-serde/src/tests.rs @@ -0,0 +1,453 @@ +use crate::from_field; +use crate::ser::new_bounded_string; +use crate::to_field; +use crate::Field; +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec; +use serde::{Deserialize, Serialize}; +use serde_test::{assert_de_tokens, assert_ser_tokens, Token}; +use tangle_subxt::subxt_core::utils::AccountId32; +use tangle_subxt::tangle_testnet_runtime::api::runtime_types::bounded_collections::bounded_vec::BoundedVec; + +mod structs { + use super::*; + + #[derive(Serialize, Deserialize, Debug, PartialEq)] + struct Person { + name: String, + age: u8, + } + + impl Default for Person { + fn default() -> Self { + Person { + name: String::from("John"), + age: 40, + } + } + } + + impl Person { + fn as_field(&self) -> Field { + let struct_fields = vec![ + ( + new_bounded_string("name"), + Field::String(new_bounded_string(&self.name)), + ), + (new_bounded_string("age"), Field::Uint8(self.age)), + ]; + + Field::Struct( + new_bounded_string("Person"), + Box::new(BoundedVec(struct_fields)), + ) + } + } + + #[test] + fn test_ser_struct_valid() { + let person = Person::default(); + + assert_ser_tokens( + &person, + &[ + Token::Struct { + name: "Person", + len: 2, + }, + Token::Str("name"), + Token::Str("John"), + Token::Str("age"), + Token::U8(40), + Token::StructEnd, + ], + ); + + let field = to_field(&person).unwrap(); + assert_eq!(field, person.as_field()); + } + + #[test] + fn test_de_struct_valid() { + let person = Person::default(); + + assert_de_tokens( + &person, + &[ + Token::Struct { + name: "Person", + len: 2, + }, + Token::Str("name"), + Token::Str("John"), + Token::Str("age"), + Token::U8(40), + Token::StructEnd, + ], + ); + + let person_de: Person = from_field(person.as_field()).unwrap(); + assert_eq!(person_de, person); + } + + #[derive(Serialize, Deserialize, Debug, PartialEq)] + struct PersonWithFriend { + name: String, + age: u8, + friend: Person, + } + + impl Default for PersonWithFriend { + fn default() -> Self { + PersonWithFriend { + name: String::from("Matthew"), + age: 37, + friend: Person::default(), + } + } + } + + impl PersonWithFriend { + fn as_field(&self) -> Field { + let friend_fields = vec![ + ( + new_bounded_string("name"), + Field::String(new_bounded_string(Person::default().name)), + ), + ( + new_bounded_string("age"), + Field::Uint8(Person::default().age), + ), + ]; + + let person_fields = vec![ + ( + new_bounded_string("name"), + Field::String(new_bounded_string(&self.name)), + ), + (new_bounded_string("age"), Field::Uint8(self.age)), + ( + new_bounded_string("friend"), + Field::Struct( + new_bounded_string("Person"), + Box::new(BoundedVec(friend_fields)), + ), + ), + ]; + + Field::Struct( + new_bounded_string("PersonWithFriend"), + Box::new(BoundedVec(person_fields)), + ) + } + } + + #[test] + fn test_ser_struct_nested() { + let person_with_friend = PersonWithFriend::default(); + + assert_ser_tokens( + &person_with_friend, + &[ + Token::Struct { + name: "PersonWithFriend", + len: 3, + }, + Token::Str("name"), + Token::Str("Matthew"), + Token::Str("age"), + Token::U8(37), + Token::Str("friend"), + Token::Struct { + name: "Person", + len: 2, + }, + Token::Str("name"), + Token::Str("John"), + Token::Str("age"), + Token::U8(40), + Token::StructEnd, + Token::StructEnd, + ], + ); + + let field = to_field(&person_with_friend).unwrap(); + assert_eq!(field, person_with_friend.as_field()); + } + + #[test] + fn test_de_struct_nested() { + let person_with_friend = PersonWithFriend::default(); + + assert_de_tokens( + &person_with_friend, + &[ + Token::Struct { + name: "PersonWithFriend", + len: 3, + }, + Token::Str("name"), + Token::Str("Matthew"), + Token::Str("age"), + Token::U8(37), + Token::Str("friend"), + Token::Struct { + name: "Person", + len: 2, + }, + Token::Str("name"), + Token::Str("John"), + Token::Str("age"), + Token::U8(40), + Token::StructEnd, + Token::StructEnd, + ], + ); + + let person_with_friend_de: PersonWithFriend = + from_field(person_with_friend.as_field()).unwrap(); + assert_eq!(person_with_friend_de, person_with_friend); + } + + #[derive(Serialize, Deserialize, Debug, PartialEq)] + struct PersonTuple(String, u8); + + impl Default for PersonTuple { + fn default() -> Self { + let person = Person::default(); + PersonTuple(person.name, person.age) + } + } + + impl PersonTuple { + fn as_field(&self) -> Field { + let fields = vec![ + ( + new_bounded_string("field_0"), + Field::String(new_bounded_string(self.0.clone())), + ), + (new_bounded_string("field_1"), Field::Uint8(self.1)), + ]; + + Field::Struct( + new_bounded_string("PersonTuple"), + Box::new(BoundedVec(fields)), + ) + } + } + + #[test] + fn test_ser_struct_tuple() { + let person_tuple = PersonTuple::default(); + + assert_ser_tokens( + &person_tuple, + &[ + Token::TupleStruct { + name: "PersonTuple", + len: 2, + }, + Token::Str("John"), + Token::U8(40), + Token::TupleStructEnd, + ], + ); + + let field = to_field(&person_tuple).unwrap(); + assert_eq!(field, person_tuple.as_field()); + } + + #[test] + fn test_de_struct_tuple() { + let person_tuple = PersonTuple::default(); + + assert_de_tokens( + &person_tuple, + &[ + Token::TupleStruct { + name: "PersonTuple", + len: 2, + }, + Token::Str("John"), + Token::U8(40), + Token::TupleStructEnd, + ], + ); + + let person_tuple_de: PersonTuple = from_field(person_tuple.as_field()).unwrap(); + assert_eq!(person_tuple_de, person_tuple); + } +} + +mod enums { + use super::*; + use serde::{Deserialize, Serialize}; + use serde_test::{assert_ser_tokens, Token}; + + #[derive(Serialize, Deserialize, Debug, PartialEq, Default)] + enum Availability { + Available, + #[default] + NotAvailable, + } + + impl Availability { + fn as_field(&self) -> Field { + match self { + Availability::Available => Field::String(new_bounded_string("Available")), + Availability::NotAvailable => Field::String(new_bounded_string("NotAvailable")), + } + } + } + + #[test] + fn test_ser_enum() { + let availability = Availability::default(); + + assert_ser_tokens( + &availability, + &[ + Token::Enum { + name: "Availability", + }, + Token::Str("NotAvailable"), + Token::Unit, + ], + ); + + let field = to_field(&availability).unwrap(); + assert_eq!(field, availability.as_field()); + } + + #[test] + fn test_de_enum() { + let availability = Availability::default(); + + assert_de_tokens( + &availability, + &[ + Token::Enum { + name: "Availability", + }, + Token::UnitVariant { + name: "Availability", + variant: "NotAvailable", + }, + ], + ); + + let availability_de: Availability = from_field(availability.as_field()).unwrap(); + assert_eq!(availability_de, availability); + } + + #[derive(Serialize, Deserialize, Debug, PartialEq)] + enum InvalidAvailability { + Available { days: u8 }, + NotAvailable(String), + } + + impl Default for InvalidAvailability { + fn default() -> Self { + Self::Available { days: 5 } + } + } + + #[test] + fn test_ser_invalid_enum() { + let invalid_availability = InvalidAvailability::default(); + + assert_ser_tokens( + &invalid_availability, + &[ + Token::StructVariant { + name: "InvalidAvailability", + variant: "Available", + len: 1, + }, + Token::Str("days"), + Token::U8(5), + Token::StructVariantEnd, + ], + ); + + let err = to_field(&invalid_availability).unwrap_err(); + assert!(matches!(err, crate::error::Error::UnsupportedType(_))); + } + + #[test] + fn test_de_invalid_enum() { + let invalid_availability = InvalidAvailability::default(); + + assert_ser_tokens( + &invalid_availability, + &[ + Token::StructVariant { + name: "InvalidAvailability", + variant: "Available", + len: 1, + }, + Token::Str("days"), + Token::U8(5), + Token::StructVariantEnd, + ], + ); + + let _ = from_field::(Field::String(new_bounded_string("Available"))) + .expect_err("should fail"); + } +} + +mod primitives { + use super::*; + + macro_rules! test_primitive { + ($($t:ty => $val:literal, $token:path, $field:path);+ $(;)?) => { + $( + paste::paste! { + #[test] + fn []() { + let val: $t = $val; + + assert_ser_tokens( + &val, + &[ + $token($val) + ], + ); + + let field = to_field(&val).unwrap(); + assert_eq!(field, $field(val)); + } + + #[test] + fn []() { + let val: $t = $val; + + assert_de_tokens( + &val, + &[ + $token($val) + ], + ); + + let val_de: $t = from_field($field(val)).unwrap(); + assert_eq!(val_de, val); + } + } + )+ + }; + } + + test_primitive!( + bool => true, Token::Bool, Field::Bool; + u8 => 0, Token::U8, Field::Uint8; + i8 => 0, Token::I8, Field::Int8; + u16 => 0, Token::U16, Field::Uint16; + i16 => 0, Token::I16, Field::Int16; + u32 => 0, Token::U32, Field::Uint32; + i32 => 0, Token::I32, Field::Int32; + u64 => 0, Token::U64, Field::Uint64; + i64 => 0, Token::I64, Field::Int64; + ); +} From 3696be0eaf77873dba2a22261087029f962d9d63 Mon Sep 17 00:00:00 2001 From: shekohex Date: Tue, 5 Nov 2024 12:37:25 +0200 Subject: [PATCH 3/5] chore: add description to crates (#444) --- blueprint-build-utils/Cargo.toml | 2 ++ blueprint-serde/Cargo.toml | 3 ++- blueprint-test-utils/Cargo.toml | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/blueprint-build-utils/Cargo.toml b/blueprint-build-utils/Cargo.toml index 939476d1..9ccd554b 100644 --- a/blueprint-build-utils/Cargo.toml +++ b/blueprint-build-utils/Cargo.toml @@ -1,11 +1,13 @@ [package] name = "blueprint-build-utils" version = "0.1.0" +description = "Tangle Blueprint build utils" authors.workspace = true edition.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true +publish = false [dependencies] diff --git a/blueprint-serde/Cargo.toml b/blueprint-serde/Cargo.toml index bbcac65f..70fd610d 100644 --- a/blueprint-serde/Cargo.toml +++ b/blueprint-serde/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "gadget-blueprint-serde" version = "0.1.0" +description = "Tangle Blueprints serde integration" authors.workspace = true edition.workspace = true license.workspace = true @@ -20,4 +21,4 @@ workspace = true [features] default = ["std"] -std = [] \ No newline at end of file +std = [] diff --git a/blueprint-test-utils/Cargo.toml b/blueprint-test-utils/Cargo.toml index 5406cbe9..861e9ce8 100644 --- a/blueprint-test-utils/Cargo.toml +++ b/blueprint-test-utils/Cargo.toml @@ -2,6 +2,7 @@ name = "blueprint-test-utils" version = "0.1.1" edition = "2021" +description = "Tangle Blueprint test utils" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 61b870c0358495cc5eb4885e53168f469c9f8653 Mon Sep 17 00:00:00 2001 From: "webb-spider[bot]" <182531479+webb-spider[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:18:14 +0200 Subject: [PATCH 4/5] chore: release (#410) Co-authored-by: webb-spider[bot] <182531479+webb-spider[bot]@users.noreply.github.com> --- Cargo.lock | 11 +++++----- Cargo.toml | 10 +++++----- blueprint-serde/CHANGELOG.md | 18 +++++++++++++++++ .../tangle-raw-event-listener/Cargo.toml | 2 +- cli/CHANGELOG.md | 14 +++++++++++++ cli/Cargo.toml | 2 +- gadget-io/CHANGELOG.md | 6 ++++++ gadget-io/Cargo.toml | 2 +- macros/blueprint-proc-macro/CHANGELOG.md | 17 ++++++++++++++++ macros/blueprint-proc-macro/Cargo.toml | 2 +- macros/context-derive/CHANGELOG.md | 15 ++++++++++++++ macros/context-derive/Cargo.toml | 2 +- sdk/CHANGELOG.md | 20 +++++++++++++++++++ sdk/Cargo.toml | 2 +- 14 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 blueprint-serde/CHANGELOG.md diff --git a/Cargo.lock b/Cargo.lock index 6b2dedff..315899bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2178,7 +2178,7 @@ dependencies = [ [[package]] name = "cargo-tangle" -version = "0.2.1" +version = "0.2.2" dependencies = [ "alloy-json-abi", "alloy-network", @@ -4617,7 +4617,7 @@ dependencies = [ [[package]] name = "gadget-blueprint-proc-macro" -version = "0.2.3" +version = "0.3.0" dependencies = [ "async-trait", "gadget-blueprint-proc-macro-core", @@ -4667,7 +4667,7 @@ dependencies = [ [[package]] name = "gadget-context-derive" -version = "0.1.3" +version = "0.2.0" dependencies = [ "alloy-network", "alloy-provider", @@ -4681,7 +4681,7 @@ dependencies = [ [[package]] name = "gadget-io" -version = "0.0.4" +version = "0.0.5" dependencies = [ "cfg-if 1.0.0", "clap", @@ -4708,7 +4708,7 @@ dependencies = [ [[package]] name = "gadget-sdk" -version = "0.2.3" +version = "0.3.0" dependencies = [ "alloy-contract", "alloy-network", @@ -4737,7 +4737,6 @@ dependencies = [ "futures", "gadget-blueprint-proc-macro", "gadget-blueprint-proc-macro-core", - "gadget-blueprint-serde", "gadget-context-derive", "gadget-io", "getrandom", diff --git a/Cargo.toml b/Cargo.toml index 519bff7f..19a03955 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,23 +45,23 @@ needless_late_init = "allow" broken_intra_doc_links = "deny" [workspace.dependencies] -gadget-io = { version = "0.0.4", path = "./gadget-io", default-features = false } +gadget-io = { version = "0.0.5", path = "./gadget-io", default-features = false } blueprint-manager = { version = "0.1.1", path = "./blueprint-manager" } blueprint-serde = { version = "0.1.0", path = "./blueprint-serde", package = "gadget-blueprint-serde" } blueprint-test-utils = { path = "./blueprint-test-utils" } -gadget-sdk = { path = "./sdk", default-features = false, version = "0.2.3" } +gadget-sdk = { path = "./sdk", default-features = false, version = "0.3.0" } incredible-squaring-blueprint = { path = "./blueprints/incredible-squaring", default-features = false, version = "0.1.1" } incredible-squaring-blueprint-eigenlayer = { path = "./blueprints/incredible-squaring-eigenlayer", default-features = false, version = "0.1.1" } incredible-squaring-blueprint-symbiotic = { path = "./blueprints/incredible-squaring-symbiotic", default-features = false, version = "0.1.1" } periodic-web-poller-blueprint = { path = "./blueprints/periodic-web-poller", default-features = false, version = "0.1.1" } tangle-raw-event-listener-blueprint = { path = "./blueprints/tangle-raw-event-listener", default-features = false, version = "0.1.1" } -gadget-blueprint-proc-macro = { path = "./macros/blueprint-proc-macro", default-features = false, version = "0.2.3" } +gadget-blueprint-proc-macro = { path = "./macros/blueprint-proc-macro", default-features = false, version = "0.3.0" } gadget-blueprint-proc-macro-core = { path = "./macros/blueprint-proc-macro-core", default-features = false, version = "0.1.5" } -gadget-context-derive = { path = "./macros/context-derive", default-features = false, version = "0.1.3" } +gadget-context-derive = { path = "./macros/context-derive", default-features = false, version = "0.2.0" } blueprint-build-utils = { path = "./blueprint-build-utils", default-features = false, version = "0.1.0" } blueprint-metadata = { path = "./blueprint-metadata", default-features = false, version = "0.1.6" } -cargo-tangle = { path = "./cli", version = "0.2.1" } +cargo-tangle = { path = "./cli", version = "0.2.2" } cargo_metadata = { version = "0.18.1" } # Tangle-related dependencies diff --git a/blueprint-serde/CHANGELOG.md b/blueprint-serde/CHANGELOG.md new file mode 100644 index 00000000..2482ed40 --- /dev/null +++ b/blueprint-serde/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.0](https://github.com/tangle-network/gadget/releases/tag/gadget-blueprint-serde-v0.1.0) - 2024-11-05 + +### Added + +- `blueprint-serde` crate ([#429](https://github.com/tangle-network/gadget/pull/429)) + +### Other + +- add description to crates ([#444](https://github.com/tangle-network/gadget/pull/444)) diff --git a/blueprints/tangle-raw-event-listener/Cargo.toml b/blueprints/tangle-raw-event-listener/Cargo.toml index 60e138ad..f02ba81d 100644 --- a/blueprints/tangle-raw-event-listener/Cargo.toml +++ b/blueprints/tangle-raw-event-listener/Cargo.toml @@ -19,4 +19,4 @@ blueprint-metadata = { workspace = true } [features] default = ["std"] -std = [] \ No newline at end of file +std = [] diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 55895ae3..0ea4da23 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.2](https://github.com/tangle-network/gadget/compare/cargo-tangle-v0.2.1...cargo-tangle-v0.2.2) - 2024-11-05 + +### Added + +- *(gadget-sdk)* add TxProgressExt trait ([#425](https://github.com/tangle-network/gadget/pull/425)) + +### Fixed + +- *(cargo-tangle)* CLI bugs ([#409](https://github.com/tangle-network/gadget/pull/409)) + +### Other + +- Continue Improving Event Flows ([#399](https://github.com/tangle-network/gadget/pull/399)) + ## [0.2.1](https://github.com/tangle-network/gadget/compare/cargo-tangle-v0.2.0...cargo-tangle-v0.2.1) - 2024-10-25 ### Added diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 237ca354..33e8faf7 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-tangle" -version = "0.2.1" +version = "0.2.2" description = "A command-line tool to create and deploy blueprints on Tangle Network" authors.workspace = true edition.workspace = true diff --git a/gadget-io/CHANGELOG.md b/gadget-io/CHANGELOG.md index 3f41f252..40b0cbe9 100644 --- a/gadget-io/CHANGELOG.md +++ b/gadget-io/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.5](https://github.com/tangle-network/gadget/compare/gadget-io-v0.0.4...gadget-io-v0.0.5) - 2024-11-05 + +### Fixed + +- *(cargo-tangle)* CLI bugs ([#409](https://github.com/tangle-network/gadget/pull/409)) + ## [0.0.4](https://github.com/tangle-network/gadget/compare/gadget-io-v0.0.3...gadget-io-v0.0.4) - 2024-10-25 ### Other diff --git a/gadget-io/Cargo.toml b/gadget-io/Cargo.toml index 7328f842..79cb4eb3 100644 --- a/gadget-io/Cargo.toml +++ b/gadget-io/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gadget-io" -version = "0.0.4" +version = "0.0.5" license.workspace = true edition = "2021" description = "Tangle's gadget IO library for writing Tangle blueprints" diff --git a/macros/blueprint-proc-macro/CHANGELOG.md b/macros/blueprint-proc-macro/CHANGELOG.md index fe887070..c442cd5e 100644 --- a/macros/blueprint-proc-macro/CHANGELOG.md +++ b/macros/blueprint-proc-macro/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.0](https://github.com/tangle-network/gadget/compare/gadget-blueprint-proc-macro-v0.2.3...gadget-blueprint-proc-macro-v0.3.0) - 2024-11-05 + +### Added + +- [**breaking**] Refactor EventFlows for EVM and Remove EventWatchers ([#423](https://github.com/tangle-network/gadget/pull/423)) +- symbiotic initial integration ([#411](https://github.com/tangle-network/gadget/pull/411)) + +### Fixed + +- *(gadget-sdk)* update sdk and utilities for tangle avs ([#355](https://github.com/tangle-network/gadget/pull/355)) +- *(blueprint-proc-macro)* resolve dependency cycle with gadget-sdk +- *(cargo-tangle)* CLI bugs ([#409](https://github.com/tangle-network/gadget/pull/409)) + +### Other + +- Continue Improving Event Flows ([#399](https://github.com/tangle-network/gadget/pull/399)) + ## [0.2.3](https://github.com/tangle-network/gadget/compare/gadget-blueprint-proc-macro-v0.2.2...gadget-blueprint-proc-macro-v0.2.3) - 2024-10-25 ### Other diff --git a/macros/blueprint-proc-macro/Cargo.toml b/macros/blueprint-proc-macro/Cargo.toml index a61477fd..b21504e4 100644 --- a/macros/blueprint-proc-macro/Cargo.toml +++ b/macros/blueprint-proc-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gadget-blueprint-proc-macro" -version = "0.2.3" +version = "0.3.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/macros/context-derive/CHANGELOG.md b/macros/context-derive/CHANGELOG.md index f84783c0..04cfb1ee 100644 --- a/macros/context-derive/CHANGELOG.md +++ b/macros/context-derive/CHANGELOG.md @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.0](https://github.com/tangle-network/gadget/compare/gadget-context-derive-v0.1.3...gadget-context-derive-v0.2.0) - 2024-11-05 + +### Added + +- symbiotic initial integration ([#411](https://github.com/tangle-network/gadget/pull/411)) + +### Fixed + +- *(sdk)* [**breaking**] allow for zero-based `blueprint_id` ([#426](https://github.com/tangle-network/gadget/pull/426)) + +### Other + +- Continue Improving Event Flows ([#399](https://github.com/tangle-network/gadget/pull/399)) +- improve blueprint-manager and blueprint-test-utils ([#421](https://github.com/tangle-network/gadget/pull/421)) + ## [0.1.3](https://github.com/tangle-network/gadget/compare/gadget-context-derive-v0.1.2...gadget-context-derive-v0.1.3) - 2024-10-25 ### Other diff --git a/macros/context-derive/Cargo.toml b/macros/context-derive/Cargo.toml index d4d2435a..1d89d40b 100644 --- a/macros/context-derive/Cargo.toml +++ b/macros/context-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gadget-context-derive" -version = "0.1.3" +version = "0.2.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 7aced082..d74f2aa9 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.0](https://github.com/tangle-network/gadget/compare/gadget-sdk-v0.2.3...gadget-sdk-v0.3.0) - 2024-11-05 + +### Added + +- [**breaking**] Refactor EventFlows for EVM and Remove EventWatchers ([#423](https://github.com/tangle-network/gadget/pull/423)) +- *(gadget-sdk)* add TxProgressExt trait ([#425](https://github.com/tangle-network/gadget/pull/425)) +- feat!(gadget-sdk): add an Error type for executor module ([#420](https://github.com/tangle-network/gadget/pull/420)) +- symbiotic initial integration ([#411](https://github.com/tangle-network/gadget/pull/411)) + +### Fixed + +- *(gadget-sdk)* update sdk and utilities for tangle avs ([#355](https://github.com/tangle-network/gadget/pull/355)) +- *(gadget-sdk)* Return `Bytes` when using `Vec` in params and result ([#428](https://github.com/tangle-network/gadget/pull/428)) +- *(sdk)* [**breaking**] allow for zero-based `blueprint_id` ([#426](https://github.com/tangle-network/gadget/pull/426)) +- *(cargo-tangle)* CLI bugs ([#409](https://github.com/tangle-network/gadget/pull/409)) + +### Other + +- Continue Improving Event Flows ([#399](https://github.com/tangle-network/gadget/pull/399)) + ## [0.2.3](https://github.com/tangle-network/gadget/compare/gadget-sdk-v0.2.2...gadget-sdk-v0.2.3) - 2024-10-25 ### Added diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 2a05e925..6fec8836 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gadget-sdk" -version = "0.2.3" +version = "0.3.0" authors.workspace = true edition.workspace = true homepage.workspace = true From eacf85663acde96ea43316fb487db95a127319b6 Mon Sep 17 00:00:00 2001 From: shekohex Date: Tue, 5 Nov 2024 13:34:26 +0200 Subject: [PATCH 5/5] fix: cyclic dependencies between context-derive and sdk (#446) --- .../incredible-squaring-eigenlayer/contracts/lib/forge-std | 2 +- blueprints/incredible-squaring/contracts/lib/forge-std | 2 +- macros/context-derive/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/blueprints/incredible-squaring-eigenlayer/contracts/lib/forge-std b/blueprints/incredible-squaring-eigenlayer/contracts/lib/forge-std index 1eea5bae..1de6eecf 160000 --- a/blueprints/incredible-squaring-eigenlayer/contracts/lib/forge-std +++ b/blueprints/incredible-squaring-eigenlayer/contracts/lib/forge-std @@ -1 +1 @@ -Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 +Subproject commit 1de6eecf821de7fe2c908cc48d3ab3dced20717f diff --git a/blueprints/incredible-squaring/contracts/lib/forge-std b/blueprints/incredible-squaring/contracts/lib/forge-std index 1eea5bae..1de6eecf 160000 --- a/blueprints/incredible-squaring/contracts/lib/forge-std +++ b/blueprints/incredible-squaring/contracts/lib/forge-std @@ -1 +1 @@ -Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 +Subproject commit 1de6eecf821de7fe2c908cc48d3ab3dced20717f diff --git a/macros/context-derive/Cargo.toml b/macros/context-derive/Cargo.toml index 1d89d40b..59ef91ba 100644 --- a/macros/context-derive/Cargo.toml +++ b/macros/context-derive/Cargo.toml @@ -22,7 +22,7 @@ proc-macro2 = { workspace = true } [dev-dependencies] trybuild = { workspace = true } -gadget-sdk = { workspace = true, features = ["std"] } +gadget-sdk = { path = "../../sdk", features = ["std"] } # EVM Stuff alloy-network = { workspace = true } alloy-provider = { workspace = true }