Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c3632eb
make contract spec lazy-loaded on type usage
leighmcculloch Jan 9, 2026
23993bd
implement lazy spec loading with markers
leighmcculloch Jan 9, 2026
1b2a428
replace xdr encoding with magic prefix in spec markers
leighmcculloch Jan 9, 2026
63bae73
simplify spec marker encoding to use sha256 hash
leighmcculloch Jan 9, 2026
99ff52e
rename __include_spec to __include_spec_marker
leighmcculloch Jan 9, 2026
a4d4c7b
refactor spec marker generation and usage
leighmcculloch Jan 9, 2026
3b7ef81
Simplify IncludeSpecMarker trait and rename from IncludeSpec
leighmcculloch Jan 9, 2026
1028afc
simplify spec marker hash computation
leighmcculloch Jan 9, 2026
437eb77
simplify spec marker generation and remove unused constants
leighmcculloch Jan 9, 2026
11ef622
extract link_section_attr into separate variable
leighmcculloch Jan 9, 2026
c708308
rename link_section_attr to export_gen
leighmcculloch Jan 9, 2026
9e1d20f
inline spec export attribute and simplify spec_xdr call
leighmcculloch Jan 9, 2026
5b41e20
add conditional spec marker attribute for events
leighmcculloch Jan 9, 2026
1a15573
remove soroban-token-sdk dependency from empty test
leighmcculloch Jan 9, 2026
09b83d0
token test
leighmcculloch Jan 9, 2026
652e56f
spec test
leighmcculloch Jan 9, 2026
0d5d52a
remove token test directory
leighmcculloch Jan 9, 2026
bd1698e
remove test_token package from lock file
leighmcculloch Jan 9, 2026
cb50f07
add check_c function and import ErrorC
leighmcculloch Jan 9, 2026
ca04d3b
use #[used] instead of volatile read
leighmcculloch Jan 9, 2026
1d38b9e
update contract wasm and spec markers
leighmcculloch Jan 9, 2026
3e7bbc2
replace used attribute with volatile read for markers
leighmcculloch Jan 9, 2026
4788682
collect field types for spec marker inclusion
leighmcculloch Jan 13, 2026
3320fa3
Merge main into spec-markers
leighmcculloch Jan 13, 2026
41ece64
update contract spec marker hashes
leighmcculloch Jan 13, 2026
de9e494
add bn254 crypto type markers
leighmcculloch Jan 13, 2026
dfe60be
typo
leighmcculloch Jan 13, 2026
265daec
deduplicate variant field types in spec marker generation
leighmcculloch Jan 13, 2026
751e0a0
Merge branch 'main' into spec-markers
leighmcculloch Jan 30, 2026
2775493
add warning when building wasm without spec optimization
leighmcculloch Jan 30, 2026
3c57623
move spec marker to soroban-spec crate
leighmcculloch Jan 30, 2026
e0ba97d
add SpecMarker type alias and shorten const name
leighmcculloch Jan 30, 2026
7e6c3db
rename generate to generate_for_xdr and add marker functions
leighmcculloch Jan 30, 2026
326099e
extract spec marker impl generation to shared module
leighmcculloch Jan 30, 2026
15c0d29
gate spec markers behind experimental_spec_resolver_v2 feature
leighmcculloch Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions soroban-sdk-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ heck = "0.5.0"

[features]
testutils = []
experimental_spec_resolver_v2 = []
65 changes: 52 additions & 13 deletions soroban-sdk-macros/src/derive_enum.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use itertools::MultiUnzip;
use proc_macro2::{Literal, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use quote::{format_ident, quote, ToTokens};
use syn::{spanned::Spanned, Attribute, DataEnum, Error, Fields, Ident, Path, Visibility};

use stellar_xdr::curr as stellar_xdr;
Expand All @@ -9,7 +9,7 @@ use stellar_xdr::{
ScSpecUdtUnionCaseVoidV0, ScSpecUdtUnionV0, StringM, VecM, WriteXdr, SCSYMBOL_LIMIT,
};

use crate::{doc::docs_from_attrs, map_type::map_type, DEFAULT_XDR_RW_LIMITS};
use crate::{doc::docs_from_attrs, map_type::map_type, spec_marker, DEFAULT_XDR_RW_LIMITS};

pub fn derive_type_enum(
path: &Path,
Expand All @@ -30,14 +30,15 @@ pub fn derive_type_enum(
format!("enum {} must have variants", enum_ident),
));
}
let (spec_cases, case_name_str_lits, try_froms, try_intos, try_from_xdrs, into_xdrs): (
Vec<_>,
Vec<_>,
Vec<_>,
Vec<_>,
Vec<_>,
Vec<_>,
) = variants
let (
spec_cases,
case_name_str_lits,
variant_field_types,
try_froms,
try_intos,
try_from_xdrs,
into_xdrs,
): (Vec<_>, Vec<_>, Vec<Vec<_>>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = variants
.iter()
.enumerate()
.map(|(case_num, variant)| {
Expand Down Expand Up @@ -75,6 +76,10 @@ pub fn derive_type_enum(
}
_ => {}
}

// Collect field types for IncludeSpecMarker
let field_types: Vec<_> = variant.fields.iter().map(|f| &f.ty).collect();

let is_unit_variant = variant.fields == Fields::Unit;
if !is_unit_variant {
let VariantTokens {
Expand All @@ -97,6 +102,7 @@ pub fn derive_type_enum(
(
spec_case,
case_name_str_lit,
field_types,
try_from,
try_into,
try_from_xdr,
Expand All @@ -121,6 +127,7 @@ pub fn derive_type_enum(
(
spec_case,
case_name_str_lit,
field_types,
try_from,
try_into,
try_from_xdr,
Expand All @@ -136,15 +143,21 @@ pub fn derive_type_enum(
return quote! { #(#compile_errors)* };
}

// Generated code spec.
let spec_gen = if spec {
// Compute spec XDR once if spec is enabled.
let spec_xdr = if spec {
let spec_entry = ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 {
doc: docs_from_attrs(attrs),
lib: lib.as_deref().unwrap_or_default().try_into().unwrap(),
name: enum_ident.to_string().try_into().unwrap(),
cases: spec_cases.try_into().unwrap(),
});
let spec_xdr = spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap();
Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap())
} else {
None
};

// Generated code spec.
let spec_gen = if let Some(ref spec_xdr) = spec_xdr {
let spec_xdr_lit = proc_macro2::Literal::byte_string(spec_xdr.as_slice());
let spec_xdr_len = spec_xdr.len();
let spec_ident = format_ident!("__SPEC_XDR_TYPE_{}", enum_ident.to_string().to_uppercase());
Expand All @@ -162,10 +175,36 @@ pub fn derive_type_enum(
None
};

// IncludeSpecMarker impl - only generated when spec is true and the
// experimental_spec_resolver_v2 feature is enabled.
let include_spec_impl = if cfg!(feature = "experimental_spec_resolver_v2") {
spec_xdr.as_ref().map(|spec_xdr| {
// Flatten all variant field types for include_spec_marker calls, deduplicating
// to avoid redundant calls for types that appear in multiple variants.
let all_field_types =
itertools::Itertools::unique_by(variant_field_types.iter().flatten(), |t| {
t.to_token_stream().to_string()
});
spec_marker::generate_include_spec_marker_impl(
path,
quote!(#enum_ident),
spec_xdr,
all_field_types.cloned(),
None,
None,
None,
)
})
} else {
None
};

// Output.
let mut output = quote! {
#spec_gen

#include_spec_impl

impl #path::TryFromVal<#path::Env, #path::Val> for #enum_ident {
type Error = #path::ConversionError;
#[inline(always)]
Expand Down
34 changes: 30 additions & 4 deletions soroban-sdk-macros/src/derive_enum_int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use syn::{spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Pat

use stellar_xdr::{ScSpecEntry, ScSpecUdtEnumCaseV0, WriteXdr};

use crate::{doc::docs_from_attrs, DEFAULT_XDR_RW_LIMITS};
use crate::{doc::docs_from_attrs, spec_marker, DEFAULT_XDR_RW_LIMITS};

// TODO: Add conversions to/from ScVal types.

Expand Down Expand Up @@ -65,15 +65,21 @@ pub fn derive_type_enum_int(
return quote! { #(#compile_errors)* };
}

// Generated code spec.
let spec_gen = if spec {
// Compute spec XDR once if spec is enabled.
let spec_xdr = if spec {
let spec_entry = ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 {
doc: docs_from_attrs(attrs),
lib: lib.as_deref().unwrap_or_default().try_into().unwrap(),
name: enum_ident.to_string().try_into().unwrap(),
cases: spec_cases.try_into().unwrap(),
});
let spec_xdr = spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap();
Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap())
} else {
None
};

// Generated code spec.
let spec_gen = if let Some(ref spec_xdr) = spec_xdr {
let spec_xdr_lit = proc_macro2::Literal::byte_string(spec_xdr.as_slice());
let spec_xdr_len = spec_xdr.len();
let spec_ident = format_ident!("__SPEC_XDR_TYPE_{}", enum_ident.to_string().to_uppercase());
Expand All @@ -91,10 +97,30 @@ pub fn derive_type_enum_int(
None
};

// IncludeSpecMarker impl - only generated when spec is true and the
// experimental_spec_resolver_v2 feature is enabled.
let include_spec_impl = if cfg!(feature = "experimental_spec_resolver_v2") {
spec_xdr.as_ref().map(|spec_xdr| {
spec_marker::generate_include_spec_marker_impl(
path,
quote!(#enum_ident),
spec_xdr,
std::iter::empty(),
None,
None,
None,
)
})
} else {
None
};

// Output.
let mut output = quote! {
#spec_gen

#include_spec_impl

impl #path::TryFromVal<#path::Env, #path::Val> for #enum_ident {
type Error = #path::ConversionError;
#[inline(always)]
Expand Down
34 changes: 30 additions & 4 deletions soroban-sdk-macros/src/derive_error_enum_int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use stellar_xdr::curr as stellar_xdr;
use stellar_xdr::{ScSpecEntry, ScSpecUdtErrorEnumCaseV0, ScSpecUdtErrorEnumV0, StringM, WriteXdr};
use syn::{spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Path};

use crate::{doc::docs_from_attrs, DEFAULT_XDR_RW_LIMITS};
use crate::{doc::docs_from_attrs, spec_marker, DEFAULT_XDR_RW_LIMITS};

pub fn derive_type_error_enum_int(
path: &Path,
Expand Down Expand Up @@ -63,15 +63,21 @@ pub fn derive_type_error_enum_int(
return quote! { #(#compile_errors)* };
}

// Generated code spec.
let spec_gen = if spec {
// Compute spec XDR once if spec is enabled.
let spec_xdr = if spec {
let spec_entry = ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 {
doc: docs_from_attrs(attrs),
lib: lib.as_deref().unwrap_or_default().try_into().unwrap(),
name: enum_ident.to_string().try_into().unwrap(),
cases: spec_cases.try_into().unwrap(),
});
let spec_xdr = spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap();
Some(spec_entry.to_xdr(DEFAULT_XDR_RW_LIMITS).unwrap())
} else {
None
};

// Generated code spec.
let spec_gen = if let Some(ref spec_xdr) = spec_xdr {
let spec_xdr_lit = proc_macro2::Literal::byte_string(spec_xdr.as_slice());
let spec_xdr_len = spec_xdr.len();
let spec_ident = format_ident!("__SPEC_XDR_TYPE_{}", enum_ident.to_string().to_uppercase());
Expand All @@ -89,10 +95,30 @@ pub fn derive_type_error_enum_int(
None
};

// IncludeSpecMarker impl - only generated when spec is true and the
// experimental_spec_resolver_v2 feature is enabled.
let include_spec_impl = if cfg!(feature = "experimental_spec_resolver_v2") {
spec_xdr.as_ref().map(|spec_xdr| {
spec_marker::generate_include_spec_marker_impl(
path,
quote!(#enum_ident),
spec_xdr,
std::iter::empty(),
None,
None,
None,
)
})
} else {
None
};

// Output.
quote! {
#spec_gen

#include_spec_impl

impl TryFrom<#path::Error> for #enum_ident {
type Error = #path::Error;
#[inline(always)]
Expand Down
34 changes: 32 additions & 2 deletions soroban-sdk-macros/src/derive_event.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
attribute::remove_attributes_from_item, default_crate_path, doc::docs_from_attrs,
map_type::map_type, symbol, DEFAULT_XDR_RW_LIMITS,
map_type::map_type, spec_marker, symbol, DEFAULT_XDR_RW_LIMITS,
};
use darling::{ast::NestedMeta, Error, FromMeta};
use heck::ToSnakeCase;
Expand Down Expand Up @@ -108,7 +108,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
let fields =
match &input.data {
Data::Struct(struct_) => match &struct_.fields {
Fields::Named(fields) => fields.named.iter(),
Fields::Named(fields) => fields.named.iter().collect::<Vec<_>>(),
Fields::Unnamed(_) => Err(Error::custom(
"structs with unnamed fields are not supported as contract events",
)
Expand All @@ -124,8 +124,12 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
.with_span(&input.span()))?,
};

// Collect field types for IncludeSpecMarker
let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect();

// Map each field of the struct to a spec for a param.
let params = fields
.iter()
.map(|field| {
let ident = field.ident.as_ref().unwrap();
let is_topic = field.attrs.iter().any(|a| a.path().is_ident("topic"));
Expand Down Expand Up @@ -193,6 +197,13 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
"__SPEC_XDR_EVENT_{}",
input.ident.to_string().to_uppercase()
);
let include_spec_call = if export && cfg!(feature = "experimental_spec_resolver_v2") {
Some(quote! { <Self as #path::IncludeSpecMarker>::include_spec_marker(); })
} else {
None
};

// Generated code spec.
let spec_gen = quote! {
#export_gen
pub static #spec_ident: [u8; #spec_xdr_len] = #ident::spec_xdr();
Expand All @@ -204,6 +215,22 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
}
};

// IncludeSpecMarker impl - only generated when export is true and the
// experimental_spec_resolver_v2 feature is enabled.
let include_spec_impl = if export && cfg!(feature = "experimental_spec_resolver_v2") {
Some(spec_marker::generate_include_spec_marker_impl(
path,
quote!(#ident),
&spec_xdr,
field_types.iter().cloned(),
Some(quote!(#gen_impl)),
Some(quote!(#gen_types)),
Some(quote!(#gen_where)),
))
} else {
None
};

// Prepare Topics Conversion to Vec<Val>.
let prefix_topics_symbols = prefix_topics.iter().map(|t| {
symbol::short_or_long(
Expand Down Expand Up @@ -279,6 +306,8 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt
let output = quote! {
#spec_gen

#include_spec_impl

impl #gen_impl #path::Event for #ident #gen_types #gen_where {
fn topics(&self, env: &#path::Env) -> #path::Vec<#path::Val> {
#topics_to_vec_val
Expand All @@ -290,6 +319,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result<TokenSt

impl #gen_impl #ident #gen_types #gen_where {
pub fn publish(&self, env: &#path::Env) {
#include_spec_call
<_ as #path::Event>::publish(self, env);
}
}
Expand Down
Loading