diff --git a/Cargo.lock b/Cargo.lock index bca247577..67e183e4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1686,13 +1686,12 @@ dependencies = [ [[package]] name = "stellar-xdr" version = "27.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ff843326969bdf1ef673dcdba94c08f4a3c8f1e58d6e6ef39b1bd4f749179a" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=0c7bcaa9145d40de52d6592a1df785c2a4d0f1dc#0c7bcaa9145d40de52d6592a1df785c2a4d0f1dc" dependencies = [ "arbitrary", "base64", "cfg_eval", - "crate-git-revision 0.0.6", + "crate-git-revision 0.0.9", "escape-bytes", "ethnum", "hex", diff --git a/Cargo.toml b/Cargo.toml index b661703c3..b74430a7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,11 @@ version = "=27.0.0" #rev = "a749b69b3471aec0c20ec431b0297f3414d66421" default-features = false -#[patch.crates-io] +[patch.crates-io] +# Pending https://github.com/stellar/rs-stellar-xdr/pull/562, which adds the +# borrowing `*Ref` types and their const XDR serializers that the contract spec +# generation in soroban-sdk-macros builds on. +stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "0c7bcaa9145d40de52d6592a1df785c2a4d0f1dc" } #soroban-env-common = { path = "../rs-soroban-env/soroban-env-common" } #soroban-env-guest = { path = "../rs-soroban-env/soroban-env-guest" } #soroban-env-host = { path = "../rs-soroban-env/soroban-env-host" } diff --git a/soroban-sdk-macros/src/derive_contractimpl_trait_default_fns_not_overridden.rs b/soroban-sdk-macros/src/derive_contractimpl_trait_default_fns_not_overridden.rs index ee45862a2..a54b35d03 100644 --- a/soroban-sdk-macros/src/derive_contractimpl_trait_default_fns_not_overridden.rs +++ b/soroban-sdk-macros/src/derive_contractimpl_trait_default_fns_not_overridden.rs @@ -89,7 +89,12 @@ fn derive(args: &Args) -> Result { Some(trait_ident), &args.client_name, )); - output.extend(derive_fns_spec(&args.spec_name, &fns, spec_export)); + output.extend(derive_fns_spec( + &args.crate_path, + &args.spec_name, + &fns, + spec_export, + )); output.extend(derive_client_impl( &args.crate_path, &args.client_name, diff --git a/soroban-sdk-macros/src/derive_enum.rs b/soroban-sdk-macros/src/derive_enum.rs index 32e2f749b..b272bc014 100644 --- a/soroban-sdk-macros/src/derive_enum.rs +++ b/soroban-sdk-macros/src/derive_enum.rs @@ -145,28 +145,27 @@ pub fn derive_type_enum( return quote! { #(#compile_errors)* }; } - // Compute spec XDR once if spec is enabled. - let spec_xdr = if spec { - let spec_entry = ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { + // Build the spec entry once if spec is enabled. + let spec_entry = if spec { + Some(ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc: docs_from_attrs(attrs), lib: lib.as_deref().unwrap_or_default().try_into().unwrap(), name: enum_ident.unraw().to_string().try_into().unwrap(), cases: spec_cases.try_into().unwrap(), - }); - Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap()) + })) } else { None }; // Generated code spec. - let spec_gen = spec_xdr + let spec_gen = spec_entry .as_ref() - .map(|spec_xdr| spec::type_spec(enum_ident, spec_xdr)); + .map(|spec_entry| spec::type_spec(path, enum_ident, spec_entry)); // SpecShakingMarker impl - only generated when spec is true and the // experimental_spec_shaking_v2 feature is enabled. let spec_shaking_impl = if cfg!(feature = "experimental_spec_shaking_v2") { - spec_xdr.as_ref().map(|spec_xdr| { + spec_entry.as_ref().map(|spec_entry| { // Flatten all variant field types for shaking calls, deduplicating // to avoid redundant calls for types that appear in multiple variants. let all_field_types = @@ -176,7 +175,7 @@ pub fn derive_type_enum( shaking::generate_marker_impl( path, quote!(#enum_ident), - spec_xdr, + &spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap(), all_field_types.cloned(), None, None, diff --git a/soroban-sdk-macros/src/derive_enum_int.rs b/soroban-sdk-macros/src/derive_enum_int.rs index 89c08131b..5454517d2 100644 --- a/soroban-sdk-macros/src/derive_enum_int.rs +++ b/soroban-sdk-macros/src/derive_enum_int.rs @@ -67,32 +67,31 @@ pub fn derive_type_enum_int( return quote! { #(#compile_errors)* }; } - // Compute spec XDR once if spec is enabled. - let spec_xdr = if spec { - let spec_entry = ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { + // Build the spec entry once if spec is enabled. + let spec_entry = if spec { + Some(ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { doc: docs_from_attrs(attrs), lib: lib.as_deref().unwrap_or_default().try_into().unwrap(), name: enum_ident.unraw().to_string().try_into().unwrap(), cases: spec_cases.try_into().unwrap(), - }); - Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap()) + })) } else { None }; // Generated code spec. - let spec_gen = spec_xdr + let spec_gen = spec_entry .as_ref() - .map(|spec_xdr| spec::type_spec(enum_ident, spec_xdr)); + .map(|spec_entry| spec::type_spec(path, enum_ident, spec_entry)); // SpecShakingMarker impl - only generated when spec is true and the // experimental_spec_shaking_v2 feature is enabled. let spec_shaking_impl = if cfg!(feature = "experimental_spec_shaking_v2") { - spec_xdr.as_ref().map(|spec_xdr| { + spec_entry.as_ref().map(|spec_entry| { shaking::generate_marker_impl( path, quote!(#enum_ident), - spec_xdr, + &spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap(), std::iter::empty(), None, None, diff --git a/soroban-sdk-macros/src/derive_error_enum_int.rs b/soroban-sdk-macros/src/derive_error_enum_int.rs index 0e6dfafb1..ae1f423bb 100644 --- a/soroban-sdk-macros/src/derive_error_enum_int.rs +++ b/soroban-sdk-macros/src/derive_error_enum_int.rs @@ -64,32 +64,31 @@ pub fn derive_type_error_enum_int( return quote! { #(#compile_errors)* }; } - // Compute spec XDR once if spec is enabled. - let spec_xdr = if spec { - let spec_entry = ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { + // Build the spec entry once if spec is enabled. + let spec_entry = if spec { + Some(ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { doc: docs_from_attrs(attrs), lib: lib.as_deref().unwrap_or_default().try_into().unwrap(), name: enum_ident.unraw().to_string().try_into().unwrap(), cases: spec_cases.try_into().unwrap(), - }); - Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap()) + })) } else { None }; // Generated code spec. - let spec_gen = spec_xdr + let spec_gen = spec_entry .as_ref() - .map(|spec_xdr| spec::type_spec(enum_ident, spec_xdr)); + .map(|spec_entry| spec::type_spec(path, enum_ident, spec_entry)); // SpecShakingMarker impl - only generated when spec is true and the // experimental_spec_shaking_v2 feature is enabled. let spec_shaking_impl = if cfg!(feature = "experimental_spec_shaking_v2") { - spec_xdr.as_ref().map(|spec_xdr| { + spec_entry.as_ref().map(|spec_entry| { shaking::generate_marker_impl( path, quote!(#enum_ident), - spec_xdr, + &spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap(), std::iter::empty(), None, None, diff --git a/soroban-sdk-macros/src/derive_event.rs b/soroban-sdk-macros/src/derive_event.rs index 6bae986fd..2ba2eeabb 100644 --- a/soroban-sdk-macros/src/derive_event.rs +++ b/soroban-sdk-macros/src/derive_event.rs @@ -1,6 +1,7 @@ use crate::{ attribute::remove_attributes_from_item, default_crate_path, doc::docs_from_attrs, - export_arg_v2_deprecation, map_type::map_type, shaking, symbol, DEFAULT_XDR_RW_LIMITS, + export_arg_v2_deprecation, map_type::map_type, shaking, spec_ref, symbol, + DEFAULT_XDR_RW_LIMITS, }; use darling::{ast::NestedMeta, Error, FromMeta}; use heck::ToSnakeCase; @@ -197,9 +198,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result Result [u8; #spec_xdr_len] { - *#spec_xdr_lit + #[doc(hidden)] + pub const __SPEC_XDR_REF: #path::xdr::ScSpecEntryRef<'static> = #spec_ref; + + pub const fn spec_xdr() -> [u8; #ident::__SPEC_XDR_REF.const_xdr_len()] { + #ident::__SPEC_XDR_REF.const_to_xdr() } } }; @@ -228,7 +230,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result( + path: &Path, ty: &Type, fns: impl IntoIterator, export: bool, ) -> Result { fns.into_iter() - .map(|f| derive_fn_spec(ty, &f.ident, &f.attrs, &f.inputs, &f.output, export)) + .map(|f| derive_fn_spec(path, ty, &f.ident, &f.attrs, &f.inputs, &f.output, export)) .collect() } #[allow(clippy::too_many_arguments)] pub fn derive_fn_spec( + path: &Path, ty: &Type, ident: &Ident, attrs: &[Attribute], @@ -158,10 +160,12 @@ pub fn derive_fn_spec( inputs: spec_args.try_into().unwrap(), outputs: spec_result.try_into().unwrap(), }); - let spec_xdr = spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap(); - let spec_xdr_lit = proc_macro2::Literal::byte_string(spec_xdr.as_slice()); - let spec_xdr_len = spec_xdr.len(); + let spec_ref = spec_ref::spec_entry(path, &spec_entry); let spec_ident = format_ident!("__SPEC_XDR_FN_{}", ident.unraw().to_string().to_uppercase()); + // Keeps the fn name's case, because unlike #spec_ident this const shares a + // scope with every other fn's, and fn names differing only in case must not + // collide. + let spec_ref_ident = format_ident!("__SPEC_XDR_REF_{}", ident); let spec_fn_ident = format_ident!("spec_xdr_{}", ident); // If errors have occurred, render them instead. @@ -189,7 +193,7 @@ pub fn derive_fn_spec( #[allow(non_upper_case_globals)] #(#attrs)* #[cfg_attr(target_family = "wasm", link_section = "contractspecv0")] - pub static #spec_ident: [u8; #spec_xdr_len] = super::#ty::#spec_fn_ident(); + pub static #spec_ident: [u8; super::#ty::#spec_ref_ident.const_xdr_len()] = super::#ty::#spec_fn_ident(); } }) } else { @@ -201,10 +205,15 @@ pub fn derive_fn_spec( #exported impl #ty { + #[doc(hidden)] + #[allow(non_upper_case_globals)] + #(#attrs)* + pub const #spec_ref_ident: #path::xdr::ScSpecEntryRef<'static> = #spec_ref; + #[allow(non_snake_case)] #(#attrs)* - pub const fn #spec_fn_ident() -> [u8; #spec_xdr_len] { - *#spec_xdr_lit + pub const fn #spec_fn_ident() -> [u8; #ty::#spec_ref_ident.const_xdr_len()] { + #ty::#spec_ref_ident.const_to_xdr() } } }) diff --git a/soroban-sdk-macros/src/derive_struct.rs b/soroban-sdk-macros/src/derive_struct.rs index 6a7229d2f..2599eedc7 100644 --- a/soroban-sdk-macros/src/derive_struct.rs +++ b/soroban-sdk-macros/src/derive_struct.rs @@ -74,32 +74,31 @@ pub fn derive_type_struct( return quote! { #(#compile_errors)* }; } - // Compute spec XDR once if spec is enabled. - let spec_xdr = if spec { - let spec_entry = ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { + // Build the spec entry once if spec is enabled. + let spec_entry = if spec { + Some(ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { doc: docs_from_attrs(attrs), lib: lib.as_deref().unwrap_or_default().try_into().unwrap(), name: ident.unraw().to_string().try_into().unwrap(), fields: spec_fields.try_into().unwrap(), - }); - Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap()) + })) } else { None }; // Generated code spec. - let spec_gen = spec_xdr + let spec_gen = spec_entry .as_ref() - .map(|spec_xdr| spec::type_spec(ident, spec_xdr)); + .map(|spec_entry| spec::type_spec(path, ident, spec_entry)); // SpecShakingMarker impl - only generated when spec is true and the // experimental_spec_shaking_v2 feature is enabled. let spec_shaking_impl = if cfg!(feature = "experimental_spec_shaking_v2") { - spec_xdr.as_ref().map(|spec_xdr| { + spec_entry.as_ref().map(|spec_entry| { shaking::generate_marker_impl( path, quote!(#ident), - spec_xdr, + &spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap(), field_types.iter().cloned(), None, None, diff --git a/soroban-sdk-macros/src/derive_struct_tuple.rs b/soroban-sdk-macros/src/derive_struct_tuple.rs index 0056bd079..f0233797e 100644 --- a/soroban-sdk-macros/src/derive_struct_tuple.rs +++ b/soroban-sdk-macros/src/derive_struct_tuple.rs @@ -63,32 +63,31 @@ pub fn derive_type_struct_tuple( return quote! { #(#compile_errors)* }; } - // Compute spec XDR once if spec is enabled. - let spec_xdr = if spec { - let spec_entry = ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { + // Build the spec entry once if spec is enabled. + let spec_entry = if spec { + Some(ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { doc: docs_from_attrs(attrs), lib: lib.as_deref().unwrap_or_default().try_into().unwrap(), name: ident.unraw().to_string().try_into().unwrap(), fields: field_specs.try_into().unwrap(), - }); - Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap()) + })) } else { None }; // Generated code spec. - let spec_gen = spec_xdr + let spec_gen = spec_entry .as_ref() - .map(|spec_xdr| spec::type_spec(ident, spec_xdr)); + .map(|spec_entry| spec::type_spec(path, ident, spec_entry)); // SpecShakingMarker impl - only generated when spec is true and the // experimental_spec_shaking_v2 feature is enabled. let spec_shaking_impl = if cfg!(feature = "experimental_spec_shaking_v2") { - spec_xdr.as_ref().map(|spec_xdr| { + spec_entry.as_ref().map(|spec_entry| { shaking::generate_marker_impl( path, quote!(#ident), - spec_xdr, + &spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap(), field_types.iter().cloned(), None, None, diff --git a/soroban-sdk-macros/src/derive_trait.rs b/soroban-sdk-macros/src/derive_trait.rs index 8eef6e5c8..6bb819fb4 100644 --- a/soroban-sdk-macros/src/derive_trait.rs +++ b/soroban-sdk-macros/src/derive_trait.rs @@ -52,7 +52,7 @@ fn derive_or_err(metadata: TokenStream2, input: TokenStream2) -> Result Path { #[derive(Debug, FromMeta)] struct ContractSpecArgs { + #[darling(default = "default_crate_path")] + crate_path: Path, name: Type, export: Option, } @@ -123,7 +126,7 @@ pub fn contractspecfn(metadata: TokenStream, input: TokenStream) -> TokenStream let methods: Vec<_> = item.fns(); let export = args.export.unwrap_or(true); - let derived = derive_fns_spec(&args.name, &methods, export); + let derived = derive_fns_spec(&args.crate_path, &args.name, &methods, export); match derived { Ok(derived_ok) => quote! { @@ -283,7 +286,7 @@ pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream { let mut output = quote! { #[#crate_path::contractargs(name = #args_ident, impl_only = true)] #[#crate_path::contractclient(crate_path = #crate_path_str, name = #client_ident, impl_only = true)] - #[#crate_path::contractspecfn(name = #ty_str)] + #[#crate_path::contractspecfn(crate_path = #crate_path_str, name = #ty_str)] #imp #derived_ok }; diff --git a/soroban-sdk-macros/src/spec.rs b/soroban-sdk-macros/src/spec.rs index 61c910b3e..bb22a98d6 100644 --- a/soroban-sdk-macros/src/spec.rs +++ b/soroban-sdk-macros/src/spec.rs @@ -1,26 +1,31 @@ //! Generates the contract spec for a contract type. -use proc_macro2::{Literal, TokenStream as TokenStream2}; +use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; -use syn::{ext::IdentExt as _, Ident}; +use stellar_xdr::ScSpecEntry; +use syn::{ext::IdentExt as _, Ident, Path}; + +use crate::spec_ref; /// Emits the contract spec for a contract type: the `spec_xdr` const fn holding /// the spec entry's XDR, and the static that places its bytes in the contract's /// spec section. -pub fn type_spec(ident: &Ident, spec_xdr: &[u8]) -> TokenStream2 { - let spec_xdr_lit = Literal::byte_string(spec_xdr); - let spec_xdr_len = spec_xdr.len(); +pub fn type_spec(path: &Path, ident: &Ident, entry: &ScSpecEntry) -> TokenStream2 { + let spec_ref = spec_ref::spec_entry(path, entry); let spec_ident = format_ident!( "__SPEC_XDR_TYPE_{}", ident.unraw().to_string().to_uppercase() ); quote! { #[cfg_attr(target_family = "wasm", link_section = "contractspecv0")] - pub static #spec_ident: [u8; #spec_xdr_len] = #ident::spec_xdr(); + pub static #spec_ident: [u8; #ident::__SPEC_XDR_REF.const_xdr_len()] = #ident::spec_xdr(); impl #ident { - pub const fn spec_xdr() -> [u8; #spec_xdr_len] { - *#spec_xdr_lit + #[doc(hidden)] + pub const __SPEC_XDR_REF: #path::xdr::ScSpecEntryRef<'static> = #spec_ref; + + pub const fn spec_xdr() -> [u8; #ident::__SPEC_XDR_REF.const_xdr_len()] { + #ident::__SPEC_XDR_REF.const_to_xdr() } } } diff --git a/soroban-sdk-macros/src/spec_ref.rs b/soroban-sdk-macros/src/spec_ref.rs new file mode 100644 index 000000000..b04fe0777 --- /dev/null +++ b/soroban-sdk-macros/src/spec_ref.rs @@ -0,0 +1,215 @@ +//! Renders a contract spec entry as a const `ScSpecEntryRef` expression. +//! +//! The derives build owned `ScSpecEntry` values as before, but instead of +//! encoding them to XDR here and emitting the bytes as a literal, they emit the +//! equivalent borrowing `ScSpecEntryRef` value. Contract crates then encode it +//! with `const_xdr_len`/`const_to_xdr`, so the XDR is written at compile time +//! from a value that is visible in the generated code. + +use proc_macro2::{Literal, TokenStream as TokenStream2}; +use quote::{format_ident, quote}; +use stellar_xdr::{ + ScSpecEntry, ScSpecEventV0, ScSpecFunctionV0, ScSpecTypeDef, ScSpecUdtEnumV0, + ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionCaseV0, ScSpecUdtUnionV0, ScSymbol, + StringM, +}; +use syn::Path; + +/// Renders `entry` as a const expression of type `#path::xdr::ScSpecEntryRef`. +pub fn spec_entry(path: &Path, entry: &ScSpecEntry) -> TokenStream2 { + let xdr = "e!(#path::xdr); + let variant = format_ident!("{}", entry.name()); + let value = match entry { + ScSpecEntry::FunctionV0(ScSpecFunctionV0 { + doc, + name, + inputs, + outputs, + }) => { + let (doc, name) = (string(xdr, doc), symbol(xdr, name)); + let inputs = inputs.iter().map(|i| { + let (doc, name, type_) = ( + string(xdr, &i.doc), + string(xdr, &i.name), + type_def(xdr, &i.type_), + ); + quote!(#xdr::ScSpecFunctionInputV0Ref { doc: #doc, name: #name, type_: #type_ }) + }); + let outputs = outputs.iter().map(|o| type_def(xdr, o)); + quote!(#xdr::ScSpecFunctionV0Ref { + doc: #doc, + name: #name, + inputs: #xdr::VecMRef::new(&[#(#inputs),*]), + outputs: #xdr::VecMRef::new(&[#(#outputs),*]), + }) + } + ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { + doc, + lib, + name, + fields, + }) => { + let (doc, lib, name) = (string(xdr, doc), string(xdr, lib), string(xdr, name)); + let fields = fields.iter().map(|f| { + let (doc, name, type_) = ( + string(xdr, &f.doc), + string(xdr, &f.name), + type_def(xdr, &f.type_), + ); + quote!(#xdr::ScSpecUdtStructFieldV0Ref { doc: #doc, name: #name, type_: #type_ }) + }); + quote!(#xdr::ScSpecUdtStructV0Ref { + doc: #doc, + lib: #lib, + name: #name, + fields: #xdr::VecMRef::new(&[#(#fields),*]), + }) + } + ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { + doc, + lib, + name, + cases, + }) => { + let (doc, lib, name) = (string(xdr, doc), string(xdr, lib), string(xdr, name)); + let cases = cases.iter().map(|c| match c { + ScSpecUdtUnionCaseV0::VoidV0(c) => { + let (doc, name) = (string(xdr, &c.doc), string(xdr, &c.name)); + quote!(#xdr::ScSpecUdtUnionCaseV0Ref::VoidV0(#xdr::ScSpecUdtUnionCaseVoidV0Ref { doc: #doc, name: #name })) + } + ScSpecUdtUnionCaseV0::TupleV0(c) => { + let (doc, name) = (string(xdr, &c.doc), string(xdr, &c.name)); + let type_ = c.type_.iter().map(|t| type_def(xdr, t)); + quote!(#xdr::ScSpecUdtUnionCaseV0Ref::TupleV0(#xdr::ScSpecUdtUnionCaseTupleV0Ref { doc: #doc, name: #name, type_: #xdr::VecMRef::new(&[#(#type_),*]) })) + } + }); + quote!(#xdr::ScSpecUdtUnionV0Ref { + doc: #doc, + lib: #lib, + name: #name, + cases: #xdr::VecMRef::new(&[#(#cases),*]), + }) + } + ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { + doc, + lib, + name, + cases, + }) => { + let (doc, lib, name) = (string(xdr, doc), string(xdr, lib), string(xdr, name)); + let cases = cases.iter().map(|c| { + let (doc, name, value) = (string(xdr, &c.doc), string(xdr, &c.name), c.value); + quote!(#xdr::ScSpecUdtEnumCaseV0Ref { doc: #doc, name: #name, value: #value }) + }); + quote!(#xdr::ScSpecUdtEnumV0Ref { + doc: #doc, + lib: #lib, + name: #name, + cases: #xdr::VecMRef::new(&[#(#cases),*]), + }) + } + ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { + doc, + lib, + name, + cases, + }) => { + let (doc, lib, name) = (string(xdr, doc), string(xdr, lib), string(xdr, name)); + let cases = cases.iter().map(|c| { + let (doc, name, value) = (string(xdr, &c.doc), string(xdr, &c.name), c.value); + quote!(#xdr::ScSpecUdtErrorEnumCaseV0Ref { doc: #doc, name: #name, value: #value }) + }); + quote!(#xdr::ScSpecUdtErrorEnumV0Ref { + doc: #doc, + lib: #lib, + name: #name, + cases: #xdr::VecMRef::new(&[#(#cases),*]), + }) + } + ScSpecEntry::EventV0(ScSpecEventV0 { + doc, + lib, + name, + prefix_topics, + params, + data_format, + }) => { + let (doc, lib, name) = (string(xdr, doc), string(xdr, lib), symbol(xdr, name)); + let prefix_topics = prefix_topics.iter().map(|t| symbol(xdr, t)); + let params = params.iter().map(|p| { + let (doc, name, type_) = (string(xdr, &p.doc), string(xdr, &p.name), type_def(xdr, &p.type_)); + let location = format_ident!("{}", p.location.name()); + quote!(#xdr::ScSpecEventParamV0Ref { doc: #doc, name: #name, type_: #type_, location: #xdr::ScSpecEventParamLocationV0::#location }) + }); + let data_format = format_ident!("{}", data_format.name()); + quote!(#xdr::ScSpecEventV0Ref { + doc: #doc, + lib: #lib, + name: #name, + prefix_topics: #xdr::VecMRef::new(&[#(#prefix_topics),*]), + params: #xdr::VecMRef::new(&[#(#params),*]), + data_format: #xdr::ScSpecEventDataFormat::#data_format, + }) + } + }; + quote!(#xdr::ScSpecEntryRef::#variant(#value)) +} + +/// Renders a `StringM` as a const `StringMRef` expression. The `MAX` of the +/// `StringMRef` is inferred from the field it is assigned to. +fn string(xdr: &TokenStream2, s: &StringM) -> TokenStream2 { + let lit = Literal::byte_string(s.as_vec()); + quote!(#xdr::StringMRef::new(#lit)) +} + +/// Renders a `ScSymbol` as a const `ScSymbolRef` expression. +fn symbol(xdr: &TokenStream2, s: &ScSymbol) -> TokenStream2 { + let s = string(xdr, &s.0); + quote!(#xdr::ScSymbolRef(#s)) +} + +/// Renders a `ScSpecTypeDef` as a const `ScSpecTypeDefRef` expression. +fn type_def(xdr: &TokenStream2, t: &ScSpecTypeDef) -> TokenStream2 { + let variant = format_ident!("{}", t.name()); + // Variants that hold a value. The recursive ones are behind a reference in + // the Ref type, matching the Box in the owned type. + let value = match t { + ScSpecTypeDef::Option(o) => { + let value_type = type_def(xdr, &o.value_type); + Some(quote!(( r::ScSpecTypeOptionRef { value_type: &#value_type }))) + } + ScSpecTypeDef::Result(r) => { + let (ok_type, error_type) = (type_def(xdr, &r.ok_type), type_def(xdr, &r.error_type)); + Some( + quote!(( r::ScSpecTypeResultRef { ok_type: &#ok_type, error_type: &#error_type })), + ) + } + ScSpecTypeDef::Vec(v) => { + let element_type = type_def(xdr, &v.element_type); + Some(quote!(( r::ScSpecTypeVecRef { element_type: &#element_type }))) + } + ScSpecTypeDef::Map(m) => { + let (key_type, value_type) = (type_def(xdr, &m.key_type), type_def(xdr, &m.value_type)); + Some( + quote!(( r::ScSpecTypeMapRef { key_type: &#key_type, value_type: &#value_type })), + ) + } + ScSpecTypeDef::Tuple(t) => { + let value_types = t.value_types.iter().map(|t| type_def(xdr, t)); + Some( + quote!(( r::ScSpecTypeTupleRef { value_types: #xdr::VecMRef::new(&[#(#value_types),*]) })), + ) + } + ScSpecTypeDef::BytesN(b) => { + let n = b.n; + Some(quote!((#xdr::ScSpecTypeBytesN { n: #n }))) + } + ScSpecTypeDef::Udt(u) => { + let name = string(xdr, &u.name); + Some(quote!((#xdr::ScSpecTypeUdtRef { name: #name }))) + } + // All remaining variants are void. + _ => None, + }; + quote!(#xdr::ScSpecTypeDefRef::#variant #value) +} diff --git a/soroban-sdk/Cargo.toml b/soroban-sdk/Cargo.toml index 293b816fa..60cb6da32 100644 --- a/soroban-sdk/Cargo.toml +++ b/soroban-sdk/Cargo.toml @@ -21,6 +21,10 @@ crate-git-revision = "0.0.9" [dependencies] soroban-sdk-macros = { workspace = true } +# Not used directly. Enables the const XDR serializers on the `*Ref` types that +# macro-generated contract specs use, on the stellar-xdr re-exported by +# soroban-env-common as soroban_sdk::xdr. +stellar-xdr = { workspace = true, features = ["const"] } stellar-strkey = { workspace = true } bytes-lit = "0.0.6" visibility = "0.1.1"