diff --git a/Cargo.lock b/Cargo.lock index f21edb1a..cc093a8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4835,6 +4835,7 @@ dependencies = [ "futures", "gadget-blueprint-proc-macro", "gadget-blueprint-proc-macro-core", + "gadget-blueprint-serde", "gadget-context-derive", "gadget-io", "getrandom", diff --git a/macros/blueprint-proc-macro-core/src/lib.rs b/macros/blueprint-proc-macro-core/src/lib.rs index fcd07d99..1a0eea11 100644 --- a/macros/blueprint-proc-macro-core/src/lib.rs +++ b/macros/blueprint-proc-macro-core/src/lib.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + pub type BlueprintString<'a> = std::borrow::Cow<'a, str>; /// A type that represents an EVM Address. pub type Address = ethereum_types::H160; @@ -52,26 +54,39 @@ pub enum FieldType { AccountId, } -impl AsRef for FieldType { - fn as_ref(&self) -> &str { +impl FieldType { + pub fn as_rust_type(&self) -> Cow<'_, str> { match self { - FieldType::Uint8 => "u8", - FieldType::Uint16 => "u16", - FieldType::Uint32 => "u32", - FieldType::Uint64 => "u64", - FieldType::Int8 => "i8", - FieldType::Int16 => "i16", - FieldType::Int32 => "i32", - FieldType::Int64 => "i64", - FieldType::Uint128 => "u128", - FieldType::U256 => "U256", - FieldType::Int128 => "i128", - FieldType::Float64 => "f64", - FieldType::Bool => "bool", - FieldType::String => "String", - FieldType::Bytes => "Bytes", - FieldType::AccountId => "AccountId", - ty => unimplemented!("Unsupported FieldType {ty:?}"), + FieldType::Uint8 => Cow::Borrowed("u8"), + FieldType::Uint16 => Cow::Borrowed("u16"), + FieldType::Uint32 => Cow::Borrowed("u32"), + FieldType::Uint64 => Cow::Borrowed("u64"), + FieldType::Int8 => Cow::Borrowed("i8"), + FieldType::Int16 => Cow::Borrowed("i16"), + FieldType::Int32 => Cow::Borrowed("i32"), + FieldType::Int64 => Cow::Borrowed("i64"), + FieldType::Uint128 => Cow::Borrowed("u128"), + FieldType::U256 => Cow::Borrowed("U256"), + FieldType::Int128 => Cow::Borrowed("i128"), + FieldType::Float64 => Cow::Borrowed("f64"), + FieldType::Bool => Cow::Borrowed("bool"), + FieldType::String => Cow::Borrowed("String"), + FieldType::Bytes => Cow::Borrowed("Vec"), + FieldType::AccountId => Cow::Borrowed("AccountId"), + + FieldType::Optional(ty) => Cow::Owned(format!("Option<{}>", ty.as_rust_type())), + FieldType::Array(size, ty) => Cow::Owned(format!("[{}; {size}]", ty.as_rust_type())), + FieldType::List(ty) => Cow::Owned(format!("Vec<{}>", ty.as_rust_type())), + FieldType::Struct(..) => unimplemented!("FieldType::Struct encoding"), + FieldType::Tuple(tys) => { + let mut s = String::from("("); + for ty in tys { + s.push_str(&format!("{},", ty.as_rust_type())); + } + s.push(')'); + Cow::Owned(s) + } + FieldType::Void => panic!("Void is not a representable type"), } } } diff --git a/macros/blueprint-proc-macro/src/hooks.rs b/macros/blueprint-proc-macro/src/hooks.rs index 316cd62f..4973dcfe 100644 --- a/macros/blueprint-proc-macro/src/hooks.rs +++ b/macros/blueprint-proc-macro/src/hooks.rs @@ -25,10 +25,11 @@ fn generate_hook_params(input: &ForeignItemFn) -> syn::Result { let param_types = crate::shared::param_types(&input.sig)?; - let params = param_types - .values() - .map(crate::shared::type_to_field_type) - .collect::>>()?; + let mut params = Vec::new(); + for param_type in param_types.values() { + let param_type = crate::shared::type_to_field_type(param_type)?; + params.push(param_type.ty); + } let hook_params = serde_json::to_string(¶ms).map_err(|err| { syn::Error::new_spanned(input, format!("failed to serialize hook: {err}")) diff --git a/macros/blueprint-proc-macro/src/job.rs b/macros/blueprint-proc-macro/src/job.rs index 697dd049..cb401933 100644 --- a/macros/blueprint-proc-macro/src/job.rs +++ b/macros/blueprint-proc-macro/src/job.rs @@ -11,8 +11,9 @@ use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use proc_macro::TokenStream; use proc_macro2::Span; -use quote::{format_ident, quote, ToTokens}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; use std::collections::HashSet; +use std::str::FromStr; use syn::ext::IdentExt; use syn::parse::{Parse, ParseBuffer, ParseStream}; use syn::{Ident, Index, ItemFn, LitInt, Token, Type}; @@ -152,8 +153,8 @@ impl IsResultType for Type { /// Creates Job Definition using input parameters pub fn generate_job_const_block( input: &ItemFn, - params: Vec, - result: Vec, + params: Vec, + result: Vec, job_id: &LitInt, ) -> syn::Result { let (fn_name_string, job_def_name, job_id_name) = get_job_id_field_name(input); @@ -164,8 +165,8 @@ pub fn generate_job_const_block( // filled later on during the rustdoc gen. description: None, }, - params, - result, + params: params.iter().map(ParameterType::field_type).collect(), + result: result.iter().map(ParameterType::field_type).collect(), }; // Serialize Job Definition to JSON string @@ -769,20 +770,39 @@ impl Parse for Params { } } +#[derive(Debug, Clone)] +pub struct ParameterType { + pub ty: FieldType, + pub span: Option, +} + +impl PartialEq for ParameterType { + fn eq(&self, other: &Self) -> bool { + self.ty == other.ty + } +} + +impl Eq for ParameterType {} + +impl ParameterType { + pub(crate) fn field_type(&self) -> FieldType { + self.ty.clone() + } +} + pub(crate) fn declared_params_to_field_types( params: &[Ident], param_types: &IndexMap, -) -> syn::Result> { - let params = params - .iter() - .map(|ident| { - param_types.get(ident).ok_or_else(|| { - syn::Error::new_spanned(ident, "parameter not declared in the function") - }) - }) - .map(|ty| type_to_field_type(ty?)) - .collect::>>()?; - Ok(params) +) -> syn::Result> { + let mut ret = Vec::new(); + for param in params { + let ty = param_types.get(param).ok_or_else(|| { + syn::Error::new_spanned(param, "parameter not declared in the function") + })?; + + ret.push(type_to_field_type(ty)?) + } + Ok(ret) } pub enum ResultsKind { @@ -832,7 +852,7 @@ pub(crate) struct EventListenerArgs { pub(crate) listeners: Vec, } -#[derive(Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ListenerType { Evm, Tangle, @@ -994,17 +1014,28 @@ impl EventListenerArgs { pub fn get_param_name_tokenstream( &self, - params: &[FieldType], + params: &[ParameterType], ) -> Vec { + let listener_type = self.get_event_listener().listener_type; + params .iter() .enumerate() - .map(|(i, t)| { + .map(|(i, param_ty)| { let ident = format_ident!("param{i}"); let index = Index::from(i); - match self.get_event_listener().listener_type { + match listener_type { ListenerType::Tangle => { - crate::special_impls::tangle::field_type_to_param_token(&ident, t) + let ty_token_stream = proc_macro2::TokenStream::from_str(¶m_ty.ty.as_rust_type()).expect("should be valid"); + let ty_tokens = quote_spanned! {param_ty.span.expect("should always be available")=> + #ty_token_stream + }; + quote! { + let __arg = args.next().expect("parameter count checked before"); + let Ok(#ident) = ::gadget_sdk::ext::blueprint_serde::from_field::<#ty_tokens>(__arg) else { + return Err(::gadget_sdk::Error::BadArgumentDecoding(format!("Failed to decode the field `{}` to `{}`", stringify!(#ident), stringify!(#ty_tokens)))); + }; + } } ListenerType::Evm => { quote! { diff --git a/macros/blueprint-proc-macro/src/report.rs b/macros/blueprint-proc-macro/src/report.rs index 49ed00fc..97f70af1 100644 --- a/macros/blueprint-proc-macro/src/report.rs +++ b/macros/blueprint-proc-macro/src/report.rs @@ -1,7 +1,7 @@ use crate::job::{ declared_params_to_field_types, generate_autogen_struct, generate_specialized_logic, get_current_call_id_field_name, get_job_id_field_name, get_return_type, EventListenerArgs, - ResultsKind, + ParameterType, ResultsKind, }; use crate::shared::{pascal_case, MacroExt}; use gadget_blueprint_proc_macro_core::{ @@ -76,8 +76,8 @@ pub(crate) fn report_impl(args: &ReportArgs, input: &ItemFn) -> syn::Result syn::Result { } } -pub fn type_to_field_type(ty: &Type) -> syn::Result { - match ty { +pub fn type_to_field_type(ty: &Type) -> syn::Result { + let field_type = match ty { Type::Array(arr) => { let elem_type = type_to_field_type(&arr.elem)?; // convert arr.len expr to u64 @@ -53,14 +55,16 @@ pub fn type_to_field_type(ty: &Type) -> syn::Result { )) } }; - Ok(FieldType::Array(len, Box::new(elem_type))) + Ok(FieldType::Array(len, Box::new(elem_type.ty))) } Type::Path(inner) => path_to_field_type(&inner.path), - Type::Reference(type_reference) => type_to_field_type(&type_reference.elem), + Type::Reference(type_reference) => { + type_to_field_type(&type_reference.elem).map(|ref_ty| ref_ty.ty) + } Type::Tuple(tuple) => { let mut ret = vec![]; for elem in &tuple.elems { - let elem_type = type_to_field_type(elem)?; + let elem_type = type_to_field_type(elem)?.ty; ret.push(elem_type); } Ok(FieldType::Tuple(ret)) @@ -69,7 +73,12 @@ pub fn type_to_field_type(ty: &Type) -> syn::Result { ty, "unsupported type (type_to_field_type)", )), - } + }; + + Ok(ParameterType { + ty: field_type?, + span: Some(ty.span()), + }) } pub fn path_to_field_type(path: &syn::Path) -> syn::Result { @@ -95,7 +104,7 @@ pub fn path_to_field_type(path: &syn::Path) -> syn::Result { let inner_arg = &inner.args[0]; if let syn::GenericArgument::Type(inner_ty) = inner_arg { let inner_type = type_to_field_type(inner_ty)?; - match inner_type { + match inner_type.ty { FieldType::Uint8 => Ok(FieldType::Bytes), others => Ok(FieldType::List(Box::new(others))), } @@ -111,15 +120,15 @@ pub fn path_to_field_type(path: &syn::Path) -> syn::Result { if ident.eq("Option") && inner.args.len() == 1 => { let inner_arg = &inner.args[0]; - if let syn::GenericArgument::Type(inner_ty) = inner_arg { - let inner_type = type_to_field_type(inner_ty)?; - Ok(FieldType::Optional(Box::new(inner_type))) - } else { - Err(syn::Error::new_spanned( + let syn::GenericArgument::Type(inner_ty) = inner_arg else { + return Err(syn::Error::new_spanned( inner_arg, "unsupported complex type", - )) - } + )); + }; + + let inner_type = type_to_field_type(inner_ty)?; + Ok(FieldType::Optional(Box::new(inner_type.ty))) } // Support for Result where T is a simple type syn::PathArguments::AngleBracketed(inner) @@ -128,7 +137,7 @@ pub fn path_to_field_type(path: &syn::Path) -> syn::Result { let inner_arg = &inner.args[0]; if let syn::GenericArgument::Type(inner_ty) = inner_arg { let inner_type = type_to_field_type(inner_ty)?; - Ok(inner_type) + Ok(inner_type.ty) } else { Err(syn::Error::new_spanned( inner_arg, @@ -146,7 +155,10 @@ pub fn path_to_field_type(path: &syn::Path) -> syn::Result { for inner_arg in &inner.args { if let syn::GenericArgument::Type(inner_ty) = inner_arg { let inner_type = type_to_field_type(inner_ty)?; - ret.push((inner_ty.to_token_stream().to_string(), Box::new(inner_type))) + ret.push(( + inner_ty.to_token_stream().to_string(), + Box::new(inner_type.ty), + )) } else { return Err(syn::Error::new_spanned(inner_arg, "unsupported type param")); } @@ -174,7 +186,7 @@ pub fn get_non_job_arguments( } pub(crate) trait MacroExt { - fn result_to_field_types(&self, result: &Type) -> syn::Result> { + fn result_to_field_types(&self, result: &Type) -> syn::Result> { match self.return_type() { ResultsKind::Infered => type_to_field_type(result).map(|x| vec![x]), ResultsKind::Types(types) => { diff --git a/macros/blueprint-proc-macro/src/special_impls/tangle.rs b/macros/blueprint-proc-macro/src/special_impls/tangle.rs index e86edf9d..469972ae 100644 --- a/macros/blueprint-proc-macro/src/special_impls/tangle.rs +++ b/macros/blueprint-proc-macro/src/special_impls/tangle.rs @@ -1,9 +1,8 @@ use crate::job::{declared_params_to_field_types, EventListenerArgs}; use crate::shared::{get_non_job_arguments, get_return_type_wrapper}; -use gadget_blueprint_proc_macro_core::FieldType; use indexmap::IndexMap; use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; +use quote::quote; use syn::{Ident, Type}; #[allow(clippy::too_many_arguments)] @@ -87,6 +86,11 @@ pub(crate) fn get_tangle_job_processor_wrapper( let params = declared_params_to_field_types(job_params, param_map)?; let params_tokens = event_listeners.get_param_name_tokenstream(¶ms); + let parameter_count = params.len(); + let parameter_count_const = quote! { + const PARAMETER_COUNT: usize = #parameter_count; + }; + let job_processor_call = if params_tokens.is_empty() { let second_param = ordered_inputs .pop() @@ -97,7 +101,15 @@ pub(crate) fn get_tangle_job_processor_wrapper( } } else { quote! { - let mut args_iter = param0.args.clone().into_iter(); + #parameter_count_const + + if param0.args.len() != PARAMETER_COUNT { + return Err( + ::gadget_sdk::Error::BadArgumentDecoding(format!("Parameter count mismatch, got `{}`, expected `{PARAMETER_COUNT}`", param0.args.len())) + ); + } + + let mut args = param0.args.into_iter(); #(#params_tokens)* let res = #fn_name_ident (#(#ordered_inputs)*) #asyncness; } @@ -116,180 +128,3 @@ pub(crate) fn get_tangle_job_processor_wrapper( } }) } - -#[allow(clippy::too_many_lines)] -pub fn field_type_to_param_token(ident: &Ident, t: &FieldType) -> TokenStream { - let ident_str = ident.to_string(); - let field_type_str = format!("{:?}", t); - - let else_block = quote! { - return Err(gadget_sdk::Error::BadArgumentDecoding(format!("Failed to decode the field {:?} to {:?}", #ident_str, #field_type_str))); - }; - - match t { - FieldType::Void => unreachable!("void type should not be in params"), - FieldType::Bool => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Bool(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::Uint8 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Uint8(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::Int8 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Int8(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::Uint16 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Uint16(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::Int16 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Int16(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::Uint32 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Uint32(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::Int32 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Int32(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::Uint64 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Uint64(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::Int64 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Int64(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::Uint128 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Uint128(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::U256 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::U256(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::Int128 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Int128(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::Float64 => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Float64(#ident)) = args_iter.next() else { #else_block }; } - } - FieldType::String => { - let inner_ident = format_ident!("{}_inner", ident); - quote! { - let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::String(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::BoundedString(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(#inner_ident)))) = args_iter.next() else { #else_block }; - // Convert the BoundedVec to a String - let #ident = match String::from_utf8(#inner_ident) { - Ok(s) => s, - Err(e) => { - ::gadget_sdk::warn!("failed to convert bytes to a valid utf8 string: {e}"); - return Err(gadget_sdk::Error::Other(e.to_string())); - } - }; - } - } - FieldType::Bytes => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Bytes(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(#ident))) = args_iter.next() else { #else_block }; } - } - FieldType::Optional(t_x) => { - let inner_ident = format_ident!("{}_inner", ident); - let x_ident = format_ident!("{}_option", ident); - let x_inner = field_type_to_param_token(&x_ident, t_x); - let inner = quote! { - let Some(#inner_ident) = args_iter.next() else { #else_block; }; - }; - quote! { - #inner - let #ident = match #inner_ident { - _ => { - #x_inner - Some(#x_ident) - }, - gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::None => None, - }; - } - } - FieldType::Array(_len, ty) => { - let inner_ident = format_ident!("{}_inner", ident); - let inner = quote! { - let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Array(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(#inner_ident))) = args_iter.next() else { #else_block; }; - }; - - let ty_variant_ident = format_ident!("{ty:?}"); - - quote! { - #inner - let #ident = #inner_ident - .into_iter() - .map(|item| if let gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::<>:: #ty_variant_ident(val) = item { - val.0 - } else { - panic!("Failed to decode the array"); - }) - .collect::>(); - } - } - FieldType::List(ty) => { - let inner_ident = format_ident!("{}_inner", ident); - let inner = quote! { - let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::List(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(#inner_ident))) = args_iter.next() else { #else_block; }; - }; - - let ty_variant_ident = format_ident!("{ty:?}"); - - quote! { - #inner - let #ident = #inner_ident - .into_iter() - .map(|item| if let gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::<>:: #ty_variant_ident(val) = item { - val.0 - } else { - panic!("Failed to decode the list"); - }) - .collect::>(); - } - } - FieldType::Tuple(elements) => { - let inner_tokens: Vec<_> = elements - .iter() - .enumerate() - .map(|(i, ty)| { - let inner_ident = format_ident!("{}_{}", ident, i); - let inner_token = field_type_to_param_token(&inner_ident, ty); - quote! { - #inner_token - #inner_ident, - } - }) - .collect(); - - quote! { - let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Tuple(#ident, ..)) = args_iter.next() else { #else_block }; - let mut #ident = #ident.into_iter(); - #(#inner_tokens)* - let #ident = (#(#inner_tokens)*); - } - } - FieldType::Struct(name, fields) => { - let struct_ident = format_ident!("{}", name); - let field_tokens: Vec<_> = fields - .iter() - .map(|(field_name, field_type)| { - let field_ident = format_ident!("{}", field_name); - let inner_ident = format_ident!("{}_{}", ident, field_name); - let inner_token = field_type_to_param_token(&inner_ident, field_type); - quote! { - #inner_token - #field_ident: #inner_ident, - } - }) - .collect(); - - quote! { - let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::Struct(#ident, ..)) = args_iter.next() else { #else_block }; - let mut #ident = #ident.into_iter(); - #(#field_tokens)* - let #ident = #struct_ident { - #(#field_tokens)* - }; - } - } - - FieldType::AccountId => { - quote! { let Some(gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field::AccountId(#ident)) = args_iter.next() else { #else_block }; } - } - } -} diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index e9178b8e..520ee08c 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -13,6 +13,7 @@ license.workspace = true itertools = { workspace = true } auto_impl = { workspace = true } bollard = { workspace = true } +blueprint-serde.workspace = true elliptic-curve = { workspace = true, features = ["alloc", "sec1"] } getrandom = { workspace = true, optional = true } hex = { workspace = true, features = ["alloc"] } @@ -141,6 +142,7 @@ std = [ "dep:subxt", "dep:tokio", "backon/tokio-sleep", + "blueprint-serde/std", "dep:getrandom", "gadget-io/std", "hex/std", diff --git a/sdk/src/error.rs b/sdk/src/error.rs index ac8b19e1..d5a58a42 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -50,6 +50,9 @@ pub enum Error { #[error("{0}")] Json(#[from] serde_json::Error), + #[error("{0}")] + BlueprintSerde(#[from] blueprint_serde::error::Error), + #[cfg(feature = "std")] #[error("Prometheus error: {err}")] Prometheus { err: String }, diff --git a/sdk/src/event_listener/tangle/jobs.rs b/sdk/src/event_listener/tangle/jobs.rs index 92eab4e8..6cd0ea69 100644 --- a/sdk/src/event_listener/tangle/jobs.rs +++ b/sdk/src/event_listener/tangle/jobs.rs @@ -1,4 +1,4 @@ -use crate::event_listener::tangle::{EventMatcher, TangleEvent, TangleResult, ValueIntoFieldType}; +use crate::event_listener::tangle::{EventMatcher, TangleEvent, TangleResult}; use crate::Error; use std::any::Any; use tangle_subxt::tangle_testnet_runtime::api; @@ -51,20 +51,21 @@ pub async fn services_pre_processor>( } /// By default, the tangle post-processor takes in a job result and submits the result on-chain -pub async fn services_post_processor( +pub async fn services_post_processor( TangleResult { results, service_id, call_id, client, signer, - }: TangleResult, + }: TangleResult, ) -> Result<(), Error> { crate::info!("Submitting result on-chain for service {service_id} call_id {call_id} ..."); - let response = - api::tx() - .services() - .submit_result(service_id, call_id, vec![results.into_field_type()]); + let response = api::tx().services().submit_result( + service_id, + call_id, + vec![blueprint_serde::to_field(results)?], + ); let _ = crate::tx::tangle::send(&client, &signer, &response) .await .map_err(|err| Error::Client(err.to_string()))?; diff --git a/sdk/src/event_listener/tangle/mod.rs b/sdk/src/event_listener/tangle/mod.rs index 03800231..d8ad0990 100644 --- a/sdk/src/event_listener/tangle/mod.rs +++ b/sdk/src/event_listener/tangle/mod.rs @@ -3,15 +3,12 @@ use crate::event_listener::markers::IsTangle; use crate::event_listener::EventListener; use crate::Error; use async_trait::async_trait; -use gadget_blueprint_proc_macro_core::FieldType; pub use subxt_core::utils::AccountId32; use std::collections::VecDeque; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use subxt::backend::StreamOfResults; use subxt_core::events::{EventDetails, StaticEvent}; -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; pub use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field; use tangle_subxt::tangle_testnet_runtime::api::services::calls::types::call::{Job, ServiceId}; use tangle_subxt::tangle_testnet_runtime::api::services::events::job_called; @@ -185,139 +182,8 @@ impl } } -pub trait FieldTypeIntoValue: Sized { - fn convert(field: Field, field_type: FieldType) -> Self; -} - -pub trait ValueIntoFieldType { - fn into_field_type(self) -> Field; -} - -macro_rules! impl_value_to_field_type { - ($($t:ty => $j:path),*) => { - $( - impl ValueIntoFieldType for $t { - fn into_field_type(self) -> Field { - $j(self) - } - } - )* - }; -} - -impl_value_to_field_type!( - u8 => Field::Uint8, - u16 => Field::Uint16, - u32 => Field::Uint32, - u64 => Field::Uint64, - i8 => Field::Int8, - i16 => Field::Int16, - i32 => Field::Int32, - i64 => Field::Int64, - bool => Field::Bool, - AccountId32 => Field::AccountId -); - -impl ValueIntoFieldType for Vec { - fn into_field_type(self) -> Field { - if core::any::TypeId::of::() == core::any::TypeId::of::() { - let (ptr, length, capacity) = { - let mut me = core::mem::ManuallyDrop::new(self); - (me.as_mut_ptr() as *mut u8, me.len(), me.capacity()) - }; - // SAFETY: We are converting a Vec to Vec only when T is u8. - // This is safe because the memory layout of Vec is the same as Vec when T is u8. - // We use ManuallyDrop to prevent double-freeing the memory. - // Vec::from_raw_parts takes ownership of the raw parts, ensuring proper deallocation. - #[allow(unsafe_code)] - Field::Bytes(BoundedVec(unsafe { - Vec::from_raw_parts(ptr, length, capacity) - })) - } else { - Field::List(BoundedVec( - self.into_iter() - .map(ValueIntoFieldType::into_field_type) - .collect(), - )) - } - } -} - -impl ValueIntoFieldType for [T; N] { - fn into_field_type(self) -> Field { - Field::Array(BoundedVec( - self.into_iter() - .map(ValueIntoFieldType::into_field_type) - .collect(), - )) - } -} - -impl ValueIntoFieldType for Option { - fn into_field_type(self) -> Field { - match self { - Some(val) => val.into_field_type(), - None => Field::None, - } - } -} - -impl ValueIntoFieldType for String { - fn into_field_type(self) -> Field { - Field::String(BoundedString(BoundedVec(self.into_bytes()))) - } -} - -macro_rules! impl_field_type_to_value { - ($($t:ty => $f:pat => $j:path),*) => { - $( - impl FieldTypeIntoValue for $t { - fn convert(field: Field, field_type: FieldType) -> Self { - match field_type { - $f => { - let $j (val) = field else { - panic!("Invalid field type!"); - }; - - val - }, - _ => panic!("Invalid field type!"), - } - } - } - )* - }; -} - -impl_field_type_to_value!( - u16 => FieldType::Uint16 => Field::Uint16, - u32 => FieldType::Uint32 => Field::Uint32, - u64 => FieldType::Uint64 => Field::Uint64, - i8 => FieldType::Int8 => Field::Int8, - i16 => FieldType::Int16 => Field::Int16, - i32 => FieldType::Int32 => Field::Int32, - i64 => FieldType::Int64 => Field::Int64, - bool => FieldType::Bool => Field::Bool, - AccountId32 => FieldType::AccountId => Field::AccountId -); - -impl FieldTypeIntoValue for String { - fn convert(field: Field, field_type: FieldType) -> Self { - match field_type { - FieldType::String => { - let Field::String(val) = field else { - panic!("Invalid field type!"); - }; - - String::from_utf8(val.0 .0).expect("Bad String from pallet Field") - } - _ => panic!("Invalid field type!"), - } - } -} - -pub struct TangleResult { - pub results: Res, +pub struct TangleResult { + pub results: R, pub service_id: ServiceId, pub call_id: job_called::CallId, pub client: TangleClient, diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 880d617f..28413672 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -84,6 +84,7 @@ pub use uuid; // External modules usually used in proc-macro codegen. #[doc(hidden)] pub mod ext { + pub use blueprint_serde; pub use lock_api; #[cfg(feature = "std")] pub use parking_lot; diff --git a/sdk/src/runners/mod.rs b/sdk/src/runners/mod.rs index b88da599..3d3be1b8 100644 --- a/sdk/src/runners/mod.rs +++ b/sdk/src/runners/mod.rs @@ -163,14 +163,10 @@ impl BlueprintRunner { while !all_futures.is_empty() { let (result, _index, remaining) = futures::future::select_all(all_futures).await; - match result { - Ok(_) => { - // Job or background service completed successfully - } - Err(e) => { - eprintln!("Job or background service failed: {:?}", e); - } + if let Err(e) = result { + crate::error!("Job or background service failed: {:?}", e); } + all_futures = remaining; }