diff --git a/soroban-sdk-macros/src/attribute.rs b/soroban-sdk-macros/src/attribute.rs index c631dcf0b..71d09ff8d 100644 --- a/soroban-sdk-macros/src/attribute.rs +++ b/soroban-sdk-macros/src/attribute.rs @@ -1,4 +1,6 @@ -use syn::{punctuated::Punctuated, Attribute, Data, Fields, FieldsNamed, FieldsUnnamed}; +use syn::{ + ext::IdentExt as _, punctuated::Punctuated, Attribute, Data, Fields, FieldsNamed, FieldsUnnamed, +}; /// Returns true if the attribute is a doc attribute. pub fn is_attr_doc(attr: &Attribute) -> bool { @@ -31,7 +33,7 @@ pub fn remove_attributes_from_item(data: &mut Data, attrs: &[&str]) { !attr .path() .get_ident() - .is_some_and(|ident| attrs.contains(&ident.to_string().as_str())) + .is_some_and(|ident| attrs.contains(&ident.unraw().to_string().as_str())) }); } } diff --git a/soroban-sdk-macros/src/derive_client.rs b/soroban-sdk-macros/src/derive_client.rs index efe98a28b..a9b6e046e 100644 --- a/soroban-sdk-macros/src/derive_client.rs +++ b/soroban-sdk-macros/src/derive_client.rs @@ -3,6 +3,8 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{Error, FnArg, LitStr, Path, Type, TypePath, TypeReference}; +use syn::ext::IdentExt as _; + use crate::{ attribute::pass_through_attr_to_gen_code, map_type::map_type, stellar_xdr::ScSpecTypeDef, symbol, syn_ext, @@ -155,13 +157,15 @@ pub fn derive_client_impl(crate_path: &Path, name: &str, fns: &[syn_ext::Fn]) -> // Skip generating client functions for calling contract functions // that start with '__', because the Soroban Env won't let those // functions be invoked directly as they're reserved for callbacks - // and hooks. - !f.ident.to_string().starts_with("__") + // and hooks. Check the Soroban-facing name so a raw-identifier + // spelling like `r#__check_auth` can't slip past this filter and then + // still export as `__check_auth`. + !f.ident.unraw().to_string().starts_with("__") }) .map(|f| { let fn_ident = &f.ident; - let fn_try_ident = format_ident!("try_{}", &f.ident); - let fn_name = fn_ident.to_string(); + let fn_name = fn_ident.unraw().to_string(); + let fn_try_ident = format_ident!("try_{}", &fn_name); let fn_name_symbol = symbol::short_or_long( crate_path, quote!(&self.env), 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 1c6f316da..db66cc18a 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 @@ -10,7 +10,7 @@ use darling::{ast::NestedMeta, Error, FromMeta}; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::quote; use std::collections::HashSet; -use syn::{LitStr, Path, Type}; +use syn::{ext::IdentExt as _, LitStr, Path, Type}; // See soroban-sdk/docs/contracttrait.md for documentation on how this works. @@ -59,7 +59,7 @@ fn derive(args: &Args) -> Result { // overridden in the input fns. let fns = trait_default_fns .into_iter() - .filter(|f| !impl_fn_idents.contains(&f.ident.to_string())) + .filter(|f| !impl_fn_idents.contains(&f.ident.unraw().to_string())) .collect::>(); let mut output = quote! {}; diff --git a/soroban-sdk-macros/src/derive_contractimpl_trait_macro.rs b/soroban-sdk-macros/src/derive_contractimpl_trait_macro.rs index c81724a03..838571e55 100644 --- a/soroban-sdk-macros/src/derive_contractimpl_trait_macro.rs +++ b/soroban-sdk-macros/src/derive_contractimpl_trait_macro.rs @@ -4,7 +4,7 @@ use heck::ToSnakeCase; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::ToTokens; use quote::{format_ident, quote}; -use syn::{parse2, ImplItemFn, ItemTrait, Path, TraitItem, TraitItemFn, Type}; +use syn::{ext::IdentExt as _, parse2, ImplItemFn, ItemTrait, Path, TraitItem, TraitItemFn, Type}; // See soroban-sdk/docs/contracttrait.md for documentation on how this works. @@ -98,7 +98,7 @@ pub fn generate_call_to_contractimpl_for_trait( args_ident: &str, spec_ident: &str, ) -> TokenStream2 { - let impl_fn_idents = pub_methods.iter().map(|f| f.sig.ident.to_string()); + let impl_fn_idents = pub_methods.iter().map(|f| f.sig.ident.unraw().to_string()); quote! { #trait_ident!( #trait_ident, @@ -112,6 +112,6 @@ pub fn generate_call_to_contractimpl_for_trait( } fn macro_ident(trait_ident: &Ident) -> Ident { - let lower = trait_ident.to_string().to_snake_case(); + let lower = trait_ident.unraw().to_string().to_snake_case(); format_ident!("__contractimpl_for_{lower}") } diff --git a/soroban-sdk-macros/src/derive_enum.rs b/soroban-sdk-macros/src/derive_enum.rs index 982ae2951..6682899da 100644 --- a/soroban-sdk-macros/src/derive_enum.rs +++ b/soroban-sdk-macros/src/derive_enum.rs @@ -1,7 +1,10 @@ use itertools::MultiUnzip; use proc_macro2::{Literal, TokenStream as TokenStream2}; use quote::{format_ident, quote, ToTokens}; -use syn::{spanned::Spanned, Attribute, DataEnum, Error, Fields, Ident, Path, Visibility}; +use syn::{ + ext::IdentExt as _, spanned::Spanned, Attribute, DataEnum, Error, Fields, Ident, Path, + Visibility, +}; use stellar_xdr::curr as stellar_xdr; use stellar_xdr::{ @@ -48,7 +51,7 @@ pub fn derive_type_enum( // TODO: Choose discriminant type based on repr type of enum. // TODO: Use attributes tagged on variant to control whether field is included. let case_ident = &variant.ident; - let case_name = &case_ident.to_string(); + let case_name = &case_ident.unraw().to_string(); let case_name_str_lit = Literal::string(case_name); let case_num_lit = Literal::usize_unsuffixed(case_num); if case_name.len() > SCSYMBOL_LIMIT as usize { @@ -151,7 +154,7 @@ pub fn derive_type_enum( 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(), + 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()) @@ -163,7 +166,10 @@ pub fn derive_type_enum( 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()); + let spec_ident = format_ident!( + "__SPEC_XDR_TYPE_{}", + enum_ident.unraw().to_string().to_uppercase() + ); Some(quote! { #[cfg_attr(target_family = "wasm", link_section = "contractspecv0")] pub static #spec_ident: [u8; #spec_xdr_len] = #enum_ident::spec_xdr(); diff --git a/soroban-sdk-macros/src/derive_enum_int.rs b/soroban-sdk-macros/src/derive_enum_int.rs index add5be85a..e1a5ef421 100644 --- a/soroban-sdk-macros/src/derive_enum_int.rs +++ b/soroban-sdk-macros/src/derive_enum_int.rs @@ -3,7 +3,10 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use stellar_xdr::curr as stellar_xdr; use stellar_xdr::{ScSpecUdtEnumV0, StringM}; -use syn::{spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Path, Visibility}; +use syn::{ + ext::IdentExt as _, spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Path, + Visibility, +}; use stellar_xdr::{ScSpecEntry, ScSpecUdtEnumCaseV0, WriteXdr}; @@ -28,7 +31,7 @@ pub fn derive_type_enum_int( .iter() .map(|v| { let ident = &v.ident; - let name = &ident.to_string(); + let name = &ident.unraw().to_string(); let discriminant: u32 = if let syn::Expr::Lit(ExprLit { lit: Lit::Int(ref lit_int), .. @@ -70,7 +73,7 @@ pub fn derive_type_enum_int( 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(), + 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()) @@ -82,7 +85,10 @@ pub fn derive_type_enum_int( 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()); + let spec_ident = format_ident!( + "__SPEC_XDR_TYPE_{}", + enum_ident.unraw().to_string().to_uppercase() + ); Some(quote! { #[cfg_attr(target_family = "wasm", link_section = "contractspecv0")] pub static #spec_ident: [u8; #spec_xdr_len] = #enum_ident::spec_xdr(); diff --git a/soroban-sdk-macros/src/derive_error_enum_int.rs b/soroban-sdk-macros/src/derive_error_enum_int.rs index f13c28edb..fafdc3f72 100644 --- a/soroban-sdk-macros/src/derive_error_enum_int.rs +++ b/soroban-sdk-macros/src/derive_error_enum_int.rs @@ -3,7 +3,9 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; 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 syn::{ + ext::IdentExt as _, spanned::Spanned, Attribute, DataEnum, Error, ExprLit, Ident, Lit, Path, +}; use crate::{doc::docs_from_attrs, shaking, spec_shaking_v2_enabled, DEFAULT_XDR_RW_LIMITS}; @@ -23,7 +25,7 @@ pub fn derive_type_error_enum_int( .iter() .map(|v| { let ident = &v.ident; - let name = &ident.to_string(); + let name = &ident.unraw().to_string(); let discriminant: u32 = if let syn::Expr::Lit(ExprLit { lit: Lit::Int(ref lit_int), .. @@ -68,7 +70,7 @@ pub fn derive_type_error_enum_int( 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(), + 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()) @@ -80,7 +82,10 @@ pub fn derive_type_error_enum_int( 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()); + let spec_ident = format_ident!( + "__SPEC_XDR_TYPE_{}", + enum_ident.unraw().to_string().to_uppercase() + ); Some(quote! { #[cfg_attr(target_family = "wasm", link_section = "contractspecv0")] pub static #spec_ident: [u8; #spec_xdr_len] = #enum_ident::spec_xdr(); diff --git a/soroban-sdk-macros/src/derive_event.rs b/soroban-sdk-macros/src/derive_event.rs index 19ebf032f..0918f44a0 100644 --- a/soroban-sdk-macros/src/derive_event.rs +++ b/soroban-sdk-macros/src/derive_event.rs @@ -4,7 +4,6 @@ use crate::{ }; use darling::{ast::NestedMeta, Error, FromMeta}; use heck::ToSnakeCase; -use itertools::Itertools as _; use proc_macro2::Span; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; @@ -12,7 +11,7 @@ use stellar_xdr::curr::{ ScSpecEntry, ScSpecEventDataFormat, ScSpecEventParamLocationV0, ScSpecEventParamV0, ScSpecEventV0, ScSymbol, StringM, WriteXdr, }; -use syn::{parse2, spanned::Spanned, Data, DeriveInput, Fields, LitStr, Path}; +use syn::{ext::IdentExt as _, parse2, spanned::Spanned, Data, DeriveInput, Fields, LitStr, Path}; #[derive(Debug, FromMeta)] struct ContractEventArgs { @@ -88,7 +87,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result = errors .handle(event_name.try_into().map_err(|_| { @@ -102,7 +101,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result Result = fields.iter().map(|f| &f.ty).collect(); - // Map each field of the struct to a spec for a param. - let params = fields + // Map each field of the struct to a spec for a param, keeping the original Ident + // alongside so it can still be used for `self.#ident` field access in the generated + // code (raw identifiers like `r#type` need to stay raw for Rust, while the spec name + // is the unraw form). + let params_with_idents = fields .iter() .map(|field| { let ident = field.ident.as_ref().unwrap(); @@ -140,7 +142,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result = errors .handle(name.try_into().map_err(|_| { @@ -153,12 +155,15 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result>(); @@ -183,9 +188,9 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result>() .try_into() .unwrap(), - params: params + params: params_with_idents .iter() - .map(|p| p.clone()) + .map(|(_, p)| p.clone()) .collect::>() .try_into() .unwrap(), @@ -195,7 +200,7 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result::spec_shaking_marker(); }) @@ -239,10 +244,10 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result>(); let topics_to_vec_val = quote! { use #path::IntoVal; @@ -253,14 +258,14 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result>(); let data_params_count = data_params.len(); let data_idents = data_params .iter() - .map(|p| format_ident!("{}", p.name.to_string())) + .map(|(ident, _)| ident.clone()) .collect::>(); let data_to_val = match args.data_format { DataFormat::SingleValue if data_params_count == 0 => quote! { @@ -288,15 +293,18 @@ fn derive_impls(args: &ContractEventArgs, input: &DeriveInput) -> Result { - // Must be sorted for map_new_from_slices - let data_idents_sorted = data_params + // Must be sorted for map_new_from_slices. Sort by the spec name (the + // Soroban-facing Symbol string), and carry the original Ident alongside so + // that `self.#ident` still uses the raw form where needed. + let mut data_params_sorted = data_params.clone(); + data_params_sorted.sort_by_key(|(_, p)| p.name.to_string()); + let data_idents_sorted = data_params_sorted .iter() - .sorted_by_key(|p| p.name.to_string()) - .map(|p| format_ident!("{}", p.name.to_string())) + .map(|(ident, _)| ident.clone()) .collect::>(); - let data_strs_sorted = data_idents_sorted + let data_strs_sorted = data_params_sorted .iter() - .map(|i| i.to_string()) + .map(|(_, p)| p.name.to_string()) .collect::>(); quote! { use #path::{EnvBase,IntoVal,unwrap::UnwrapInfallible}; diff --git a/soroban-sdk-macros/src/derive_fn.rs b/soroban-sdk-macros/src/derive_fn.rs index 5a092b2d3..9fbf1bca9 100644 --- a/soroban-sdk-macros/src/derive_fn.rs +++ b/soroban-sdk-macros/src/derive_fn.rs @@ -8,6 +8,7 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use sha2::{Digest, Sha256}; use syn::{ + ext::IdentExt as _, punctuated::Punctuated, spanned::Spanned, token::{Colon, Comma}, @@ -89,8 +90,11 @@ pub fn derive_pub_fn( .map(|(i, a)| match a { FnArg::Typed(pat_ty) => { // If fn is a __check_auth implementation, allow the first argument, - // signature_payload of type Bytes (32 size), to be a Hash. - let allow_hash = ident == "__check_auth" && i == 0; + // signature_payload of type Bytes (32 size), to be a Hash. Compare on + // the Soroban-facing name so a raw-identifier spelling like + // `r#__check_auth` can't bypass this special-case and then still export + // as `__check_auth`. + let allow_hash = ident.unraw().to_string() == "__check_auth" && i == 0; // Error if the type of the fn arg is mutable. if let Err(e) = fn_arg_type_validate_no_mut(&pat_ty.ty) { @@ -141,14 +145,15 @@ pub fn derive_pub_fn( // Generated code parameters. let impl_ty_safe_str = ty_to_safe_ident_str(impl_ty); - let wrap_export_name = &format!("{}", ident); - let invoke_fn_prefix = format_ident!("__{}__{}", impl_ty_safe_str, ident); + let fn_name = ident.unraw().to_string(); + let wrap_export_name = &fn_name; + let invoke_fn_prefix = format_ident!("__{}__{}", impl_ty_safe_str, fn_name); let invoke_raw = format_ident!("{}__invoke_raw", invoke_fn_prefix); let invoke_raw_slice = format_ident!("{}__invoke_raw_slice", invoke_fn_prefix); let invoke_raw_extern = format_ident!("{}__invoke_raw_extern", invoke_fn_prefix); let deprecated_note = format!( "use `{}::new(&env, &contract_id).{}` instead", - client_ident, &ident + client_ident, &fn_name ); let env_call = if let Some(is_ref) = env_input { if is_ref { @@ -242,7 +247,7 @@ pub fn derive_contract_function_registration_ctor<'a>( let ty_str = ty_to_safe_ident_str(ty); let (idents, wrap_idents): (Vec<_>, Vec<_>) = method_idents .map(|ident| { - let ident_str = format!("{}", ident); + let ident_str = ident.unraw().to_string(); let wrap_ident = format_ident!("__{}__{}__invoke_raw_slice", ty_str, ident_str); (ident_str, wrap_ident) }) @@ -252,7 +257,7 @@ pub fn derive_contract_function_registration_ctor<'a>( .map(|p| { p.segments .iter() - .map(|s| s.ident.to_string()) + .map(|s| s.ident.unraw().to_string()) .collect::>() .join("_") }) diff --git a/soroban-sdk-macros/src/derive_spec_fn.rs b/soroban-sdk-macros/src/derive_spec_fn.rs index fc80e5712..095f3249f 100644 --- a/soroban-sdk-macros/src/derive_spec_fn.rs +++ b/soroban-sdk-macros/src/derive_spec_fn.rs @@ -7,8 +7,8 @@ use stellar_xdr::{ }; use syn::TypeReference; use syn::{ - punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Error, FnArg, Ident, Pat, - ReturnType, Type, TypePath, + ext::IdentExt as _, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Error, + FnArg, Ident, Pat, ReturnType, Type, TypePath, }; use crate::attribute::pass_through_attr_to_gen_code; @@ -69,7 +69,7 @@ pub fn derive_fn_spec( .map(|(i, a)| match a { FnArg::Typed(pat_type) => { let name = if let Pat::Ident(pat_ident) = *pat_type.pat.clone() { - pat_ident.ident.to_string() + pat_ident.ident.unraw().to_string() } else { errors.push(Error::new(a.span(), "argument not supported")); "".to_string() @@ -86,8 +86,11 @@ pub fn derive_fn_spec( let name = name.trim_start_matches("_"); // If fn is a __check_auth implementation, allow the first argument, - // signature_payload of type Bytes (32 size), to be a Hash. - let allow_hash = ident == "__check_auth" && i == 0; + // signature_payload of type Bytes (32 size), to be a Hash. Compare on + // the Soroban-facing name so a raw-identifier spelling like + // `r#__check_auth` can't bypass this special-case and then still export + // as `__check_auth`. + let allow_hash = ident.unraw().to_string() == "__check_auth" && i == 0; match map_type(&pat_type.ty, true, allow_hash) { Ok(type_) => { @@ -139,7 +142,7 @@ pub fn derive_fn_spec( }; // Generated code spec. - let name = &format!("{}", ident); + let name = &ident.unraw().to_string(); let spec_entry = ScSpecEntry::FunctionV0(ScSpecFunctionV0 { doc: docs_from_attrs(attrs), name: name.try_into().unwrap_or_else(|_| { @@ -159,8 +162,8 @@ pub fn derive_fn_spec( 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_ident = format_ident!("__SPEC_XDR_FN_{}", ident.to_string().to_uppercase()); - let spec_fn_ident = format_ident!("spec_xdr_{}", ident.to_string()); + let spec_ident = format_ident!("__SPEC_XDR_FN_{}", ident.unraw().to_string().to_uppercase()); + let spec_fn_ident = format_ident!("spec_xdr_{}", ident); // If errors have occurred, render them instead. if !errors.is_empty() { diff --git a/soroban-sdk-macros/src/derive_struct.rs b/soroban-sdk-macros/src/derive_struct.rs index 750c35cf2..552de88d4 100644 --- a/soroban-sdk-macros/src/derive_struct.rs +++ b/soroban-sdk-macros/src/derive_struct.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use proc_macro2::{Literal, TokenStream as TokenStream2}; use quote::{format_ident, quote}; -use syn::{Attribute, DataStruct, Error, Ident, Path, Visibility}; +use syn::{ext::IdentExt as _, Attribute, DataStruct, Error, Ident, Path, Visibility}; use stellar_xdr::curr as stellar_xdr; use stellar_xdr::{ @@ -32,11 +32,11 @@ pub fn derive_type_struct( let field_count_usize: usize = fields.len(); let (spec_fields, field_idents, field_names, field_idx_lits, field_types, try_from_xdrs, try_into_xdrs): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = fields .iter() - .sorted_by_key(|field| field.ident.as_ref().unwrap().to_string()) + .sorted_by_key(|field| field.ident.as_ref().unwrap().unraw().to_string()) .enumerate() .map(|(field_num, field)| { let field_ident = field.ident.as_ref().unwrap(); - let field_name = field_ident.to_string(); + let field_name = field_ident.unraw().to_string(); let field_idx_lit = Literal::usize_unsuffixed(field_num); let field_type = &field.ty; let spec_field = ScSpecUdtStructFieldV0 { @@ -83,7 +83,7 @@ pub fn derive_type_struct( let spec_entry = ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { doc: docs_from_attrs(attrs), lib: lib.as_deref().unwrap_or_default().try_into().unwrap(), - name: ident.to_string().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()) @@ -95,7 +95,10 @@ pub fn derive_type_struct( 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_{}", ident.to_string().to_uppercase()); + let spec_ident = format_ident!( + "__SPEC_XDR_TYPE_{}", + ident.unraw().to_string().to_uppercase() + ); Some(quote! { #[cfg_attr(target_family = "wasm", link_section = "contractspecv0")] pub static #spec_ident: [u8; #spec_xdr_len] = #ident::spec_xdr(); diff --git a/soroban-sdk-macros/src/derive_struct_tuple.rs b/soroban-sdk-macros/src/derive_struct_tuple.rs index 94a05910b..8faf82d6b 100644 --- a/soroban-sdk-macros/src/derive_struct_tuple.rs +++ b/soroban-sdk-macros/src/derive_struct_tuple.rs @@ -1,7 +1,7 @@ use itertools::MultiUnzip; use proc_macro2::{Literal, TokenStream as TokenStream2}; use quote::{format_ident, quote}; -use syn::{Attribute, DataStruct, Error, Ident, Path, Visibility}; +use syn::{ext::IdentExt as _, Attribute, DataStruct, Error, Ident, Path, Visibility}; use stellar_xdr::curr as stellar_xdr; use stellar_xdr::{ @@ -72,7 +72,7 @@ pub fn derive_type_struct_tuple( let spec_entry = ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { doc: docs_from_attrs(attrs), lib: lib.as_deref().unwrap_or_default().try_into().unwrap(), - name: ident.to_string().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()) @@ -84,7 +84,10 @@ pub fn derive_type_struct_tuple( 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_{}", ident.to_string().to_uppercase()); + let spec_ident = format_ident!( + "__SPEC_XDR_TYPE_{}", + ident.unraw().to_string().to_uppercase() + ); Some(quote! { #[cfg_attr(target_family = "wasm", link_section = "contractspecv0")] pub static #spec_ident: [u8; #spec_xdr_len] = #ident::spec_xdr(); diff --git a/soroban-sdk-macros/src/derive_trait.rs b/soroban-sdk-macros/src/derive_trait.rs index c2f78c139..8eef6e5c8 100644 --- a/soroban-sdk-macros/src/derive_trait.rs +++ b/soroban-sdk-macros/src/derive_trait.rs @@ -2,7 +2,7 @@ use crate::default_crate_path; use darling::{ast::NestedMeta, Error, FromMeta}; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; -use syn::{parse2, ItemTrait, Path}; +use syn::{ext::IdentExt as _, parse2, ItemTrait, Path}; // See soroban-sdk/docs/contracttrait.md for documentation on how this works. @@ -38,11 +38,17 @@ fn derive_or_err(metadata: TokenStream2, input: TokenStream2) -> Result TokenStream { let item = parse_macro_input!(input as ItemStruct); let ty = &item.ident; - let ty_str = quote!(#ty).to_string(); + let ty_str = ty.unraw().to_string(); let client_ident = format!("{ty_str}Client"); let fn_set_registry_ident = format_ident!("__{}_fn_set_registry", ty_str.to_lowercase()); @@ -239,7 +239,7 @@ pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream { path.path .segments .last() - .map(|name| format!("{}Args", name.ident)) + .map(|name| format!("{}Args", name.ident.unraw())) } else { None } @@ -251,7 +251,7 @@ pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream { path.path .segments .last() - .map(|name| format!("{}Client", name.ident)) + .map(|name| format!("{}Client", name.ident.unraw())) } else { None } diff --git a/soroban-sdk-macros/src/map_type.rs b/soroban-sdk-macros/src/map_type.rs index f0acdb17c..101c73dc1 100644 --- a/soroban-sdk-macros/src/map_type.rs +++ b/soroban-sdk-macros/src/map_type.rs @@ -5,11 +5,13 @@ use stellar_xdr::{ ScSpecTypeTuple, ScSpecTypeUdt, ScSpecTypeVec, }; use syn::{ - spanned::Spanned, Error, Expr, ExprLit, GenericArgument, Ident, Lit, Path, PathArguments, - PathSegment, Type, TypePath, TypeTuple, + ext::IdentExt as _, spanned::Spanned, Error, Expr, ExprLit, GenericArgument, Ident, Lit, Path, + PathArguments, PathSegment, Type, TypePath, TypeTuple, }; use syn::{Generics, TypeReference}; +use crate::syn_ext::ident_to_type; + // These constants' values must match the definitions of the constants with the // same names in soroban_sdk::crypto::bls12_381. pub const FP_SERIALIZED_SIZE: u32 = 48; @@ -35,13 +37,10 @@ pub const BN254_G2_SERIALIZED_SIZE: u32 = BN254_G1_SERIALIZED_SIZE * 2; // 128 /// - If the type mapped from `ident` is not a UDT /// - If `generics` has any parameters, as UDTs don't support generics pub fn is_mapped_type_udt(ident: &Ident, generics: &Generics) -> Result<(), Error> { - let name = ident.to_string(); - let ty: Type = syn::parse_str(&name).map_err(|e| { - Error::new( - ident.span(), - format!("type `{}` cannot be used in XDR spec: {}", ident, e), - ) - })?; + // Wrap the Ident directly into a Type rather than stringifying and + // re-parsing — the latter would fail for raw keyword idents like `r#type` + // because the unraw'd form (`type`) isn't a valid Rust Type. + let ty = ident_to_type(ident.clone()); match map_type(&ty, false, false) { Ok(ScSpecTypeDef::Udt(_)) => { // `ty` does not contain the generics, so check manually here @@ -56,6 +55,7 @@ pub fn is_mapped_type_udt(ident: &Ident, generics: &Generics) -> Result<(), Erro } _ => { // Check if the error originated from the UDT-arm of `map_type` + let name = ident.unraw().to_string(); let _ = ScSpecTypeDef::Udt(ScSpecTypeUdt { name: name.try_into().map_err(|e| { Error::new( @@ -90,7 +90,7 @@ pub fn map_type(t: &Type, allow_ref: bool, allow_hash: bool) -> Result match &ident.to_string()[..] { + }) => match &ident.unraw().to_string()[..] { "Val" => Ok(ScSpecTypeDef::Val), "u64" => Ok(ScSpecTypeDef::U64), "i64" => Ok(ScSpecTypeDef::I64), @@ -193,7 +193,7 @@ pub fn map_type(t: &Type, allow_ref: bool, allow_hash: bool) -> Result { let args = angle_bracketed.args.iter().collect::>(); - match &ident.to_string()[..] { + match &ident.unraw().to_string()[..] { "Result" => { let (ok, err) = match args.as_slice() { [GenericArgument::Type(ok), GenericArgument::Type(err)] => (ok, err), diff --git a/soroban-sdk-macros/src/syn_ext.rs b/soroban-sdk-macros/src/syn_ext.rs index 3c6a4a2f7..2a0208914 100644 --- a/soroban-sdk-macros/src/syn_ext.rs +++ b/soroban-sdk-macros/src/syn_ext.rs @@ -1,6 +1,11 @@ use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; use std::collections::HashMap; +use syn::{ + ext::IdentExt as _, spanned::Spanned, token::And, Error, FnArg, Ident, ImplItem, ImplItemFn, + ItemImpl, ItemTrait, Lifetime, Pat, PatIdent, PatType, TraitItem, TraitItemFn, Type, + TypeReference, Visibility, +}; use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, @@ -8,10 +13,6 @@ use syn::{ AngleBracketedGenericArguments, Attribute, GenericArgument, LitStr, Path, PathArguments, PathSegment, ReturnType, Signature, Token, TypePath, }; -use syn::{ - spanned::Spanned, token::And, Error, FnArg, Ident, ImplItem, ImplItemFn, ItemImpl, ItemTrait, - Lifetime, Pat, PatIdent, PatType, TraitItem, TraitItemFn, Type, TypeReference, Visibility, -}; /// Gets methods from the implementation that have public visibility. For /// methods that are inherently implemented this is methods that have a pub @@ -154,7 +155,7 @@ pub enum HasFnsItem { impl HasFnsItem { pub fn name(&'_ self) -> String { match self { - HasFnsItem::Trait(t) => t.ident.to_string(), + HasFnsItem::Trait(t) => t.ident.unraw().to_string(), HasFnsItem::Impl(i) => { let ty = &i.self_ty; quote!(#ty).to_string() @@ -292,7 +293,7 @@ fn unpack_result(typ: &Type) -> Option<(Type, Type)> { }) = path.segments.last() { let args = args.iter().collect::>(); - match (&ident.to_string()[..], args.as_slice()) { + match (&ident.unraw().to_string()[..], args.as_slice()) { ("Result", [GenericArgument::Type(t), GenericArgument::Type(e)]) => { Some((t.clone(), e.clone())) } @@ -423,7 +424,11 @@ fn self_type_ident(ty: &Type) -> Result, Error> { } pub fn ty_to_safe_ident_str(ty: &Type) -> String { - quote!(#ty).to_string().replace(' ', "").replace(':', "_") + quote!(#ty) + .to_string() + .replace(' ', "") + .replace(':', "_") + .replace("r#", "") } pub fn ident_to_type(ident: Ident) -> Type { diff --git a/soroban-sdk/src/tests.rs b/soroban-sdk/src/tests.rs index 1cd8bc81b..678395400 100644 --- a/soroban-sdk/src/tests.rs +++ b/soroban-sdk/src/tests.rs @@ -27,6 +27,7 @@ mod contract_udt_enum_error; mod contract_udt_enum_int; mod contract_udt_enum_option; mod contract_udt_option; +mod contract_udt_raw_identifier; mod contract_udt_struct; mod contract_udt_struct_tuple; mod contractimpl_trait_call_resolution; diff --git a/soroban-sdk/src/tests/contract_udt_raw_identifier.rs b/soroban-sdk/src/tests/contract_udt_raw_identifier.rs new file mode 100644 index 000000000..c4080d44b --- /dev/null +++ b/soroban-sdk/src/tests/contract_udt_raw_identifier.rs @@ -0,0 +1,352 @@ +use crate::{self as soroban_sdk, Event}; +use soroban_sdk::{ + contract, contracterror, contractevent, contractimpl, contracttrait, contracttype, + testutils::Events as _, Env, +}; +use stellar_xdr::curr as stellar_xdr; +use stellar_xdr::{ + Limits, ReadXdr, ScSpecEntry, ScSpecEventDataFormat, ScSpecEventParamLocationV0, + ScSpecEventParamV0, ScSpecEventV0, ScSpecFunctionInputV0, ScSpecFunctionV0, ScSpecTypeDef, + ScSpecTypeResult, ScSpecTypeUdt, ScSpecUdtEnumCaseV0, ScSpecUdtEnumV0, + ScSpecUdtErrorEnumCaseV0, ScSpecUdtErrorEnumV0, ScSpecUdtStructFieldV0, ScSpecUdtStructV0, + ScSpecUdtUnionCaseTupleV0, ScSpecUdtUnionCaseV0, ScSpecUdtUnionV0, ScSymbol, +}; + +#[contract] +pub struct r#Contract; + +#[contracterror] +#[derive(Debug, Eq, PartialEq)] +pub enum r#TestError { + r#Error = 1, +} + +#[contractevent] +pub struct r#TestEvent { + #[topic] + pub r#type: u32, + pub r#fn: u32, +} + +#[contracttype] +#[derive(Debug)] +pub struct r#TestType { + pub r#type: u32, + // `rank` is intentionally non-raw and starts with `r` so that its sort + // position differs between the raw form (`r#type`,`r#value`,`raw`) and + // unraw form (`raw`,`type`,`value`) of the other fields. + pub rank: u32, + pub r#value: u32, +} + +#[contracttype] +#[derive(Clone)] +pub enum r#TestEnum { + r#Enum = 0, + TestError = 1, +} + +#[contracttype] +#[derive(Clone)] +pub enum r#TestTupleEnum { + r#Tuple(u32), + TestTuple(i32), +} + +#[contracttrait] +pub trait r#RawTrait { + fn r#method(env: &Env) -> u32 { + let _ = env; + 42 + } +} + +#[contracttype] +#[allow(non_camel_case_types)] +pub struct r#type { + pub value: u32, +} + +#[contracttype] +pub struct r#TupleStruct(u32, u32); + +#[contractimpl] +impl r#Contract { + pub fn r#type(env: Env, r#fn: TestEnum) -> Result { + TestEvent { + r#type: r#fn.clone() as u32, + r#fn: 1, + } + .publish(&env); + + match r#fn { + TestEnum::r#Enum => Ok(TestType { + rank: 7, + r#type: r#fn as u32, + r#value: 42, + }), + TestEnum::TestError => Err(TestError::r#Error), + } + } +} + +#[contractimpl(contracttrait)] +impl r#RawTrait for r#Contract {} + +#[test] +fn test_functional() { + let env = Env::default(); + let contract_id = env.register(r#Contract, ()); + let client = ContractClient::new(&env, &contract_id); + + let res = client.r#type(&TestEnum::r#Enum); + assert_eq!(res.rank, 7); + assert_eq!(res.r#type, 0); + assert_eq!(res.value, 42); + + assert_eq!( + env.events().all(), + std::vec![TestEvent { r#type: 0, r#fn: 1 }.to_xdr(&env, &contract_id)], + ); + + let err = client.try_type(&TestEnum::TestError); + assert_eq!(err.unwrap_err().unwrap(), TestError::r#Error); + + assert_eq!(client.method(), 42); +} + +#[test] +fn test_spec_contract() { + let fn_entry = ScSpecEntry::from_xdr(r#Contract::spec_xdr_type(), Limits::none()).unwrap(); + assert_eq!( + fn_entry, + ScSpecEntry::FunctionV0(ScSpecFunctionV0 { + doc: "".try_into().unwrap(), + name: "type".try_into().unwrap(), + inputs: [ScSpecFunctionInputV0 { + doc: "".try_into().unwrap(), + name: "fn".try_into().unwrap(), + type_: ScSpecTypeDef::Udt(ScSpecTypeUdt { + name: "TestEnum".try_into().unwrap(), + }), + }] + .try_into() + .unwrap(), + outputs: [ScSpecTypeDef::Result(Box::new(ScSpecTypeResult { + ok_type: Box::new(ScSpecTypeDef::Udt(ScSpecTypeUdt { + name: "TestType".try_into().unwrap(), + })), + error_type: Box::new(ScSpecTypeDef::Udt(ScSpecTypeUdt { + name: "TestError".try_into().unwrap(), + })), + }))] + .try_into() + .unwrap(), + }), + ); + + let trait_fn_entry = + ScSpecEntry::from_xdr(r#Contract::spec_xdr_method(), Limits::none()).unwrap(); + assert_eq!( + trait_fn_entry, + ScSpecEntry::FunctionV0(ScSpecFunctionV0 { + doc: "".try_into().unwrap(), + name: "method".try_into().unwrap(), + inputs: [].try_into().unwrap(), + outputs: [ScSpecTypeDef::U32].try_into().unwrap(), + }), + ); + + let trait_method_spec = + ScSpecEntry::from_xdr(RawTraitSpec::spec_xdr_method(), Limits::none()).unwrap(); + assert_eq!( + trait_method_spec, + ScSpecEntry::FunctionV0(ScSpecFunctionV0 { + doc: "".try_into().unwrap(), + name: "method".try_into().unwrap(), + inputs: [].try_into().unwrap(), + outputs: [ScSpecTypeDef::U32].try_into().unwrap(), + }), + ); +} + +#[test] +fn test_spec_struct() { + // `r#TestType` with raw fields `r#type` and `r#value` must emit with its + // name unraw'd, fields unraw'd, and sorted by the unraw'd field name. + let type_entry = ScSpecEntry::from_xdr(TestType::spec_xdr(), Limits::none()).unwrap(); + assert_eq!( + type_entry, + ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { + doc: "".try_into().unwrap(), + lib: "".try_into().unwrap(), + name: "TestType".try_into().unwrap(), + fields: [ + ScSpecUdtStructFieldV0 { + doc: "".try_into().unwrap(), + name: "rank".try_into().unwrap(), + type_: ScSpecTypeDef::U32, + }, + ScSpecUdtStructFieldV0 { + doc: "".try_into().unwrap(), + name: "type".try_into().unwrap(), + type_: ScSpecTypeDef::U32, + }, + ScSpecUdtStructFieldV0 { + doc: "".try_into().unwrap(), + name: "value".try_into().unwrap(), + type_: ScSpecTypeDef::U32, + }, + ] + .try_into() + .unwrap(), + }), + ); + + let keyword_struct_entry = ScSpecEntry::from_xdr(r#type::spec_xdr(), Limits::none()).unwrap(); + assert_eq!( + keyword_struct_entry, + ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { + doc: "".try_into().unwrap(), + lib: "".try_into().unwrap(), + name: "type".try_into().unwrap(), + fields: [ScSpecUdtStructFieldV0 { + doc: "".try_into().unwrap(), + name: "value".try_into().unwrap(), + type_: ScSpecTypeDef::U32, + }] + .try_into() + .unwrap(), + }), + ); + + let tuple_struct_entry = + ScSpecEntry::from_xdr(r#TupleStruct::spec_xdr(), Limits::none()).unwrap(); + assert_eq!( + tuple_struct_entry, + ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { + doc: "".try_into().unwrap(), + lib: "".try_into().unwrap(), + name: "TupleStruct".try_into().unwrap(), + fields: [ + ScSpecUdtStructFieldV0 { + doc: "".try_into().unwrap(), + name: "0".try_into().unwrap(), + type_: ScSpecTypeDef::U32, + }, + ScSpecUdtStructFieldV0 { + doc: "".try_into().unwrap(), + name: "1".try_into().unwrap(), + type_: ScSpecTypeDef::U32, + }, + ] + .try_into() + .unwrap(), + }), + ); +} + +#[test] +fn test_spec_enum() { + let enum_entry = ScSpecEntry::from_xdr(TestEnum::spec_xdr(), Limits::none()).unwrap(); + assert_eq!( + enum_entry, + ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { + doc: "".try_into().unwrap(), + lib: "".try_into().unwrap(), + name: "TestEnum".try_into().unwrap(), + cases: [ + ScSpecUdtEnumCaseV0 { + doc: "".try_into().unwrap(), + name: "Enum".try_into().unwrap(), + value: 0, + }, + ScSpecUdtEnumCaseV0 { + doc: "".try_into().unwrap(), + name: "TestError".try_into().unwrap(), + value: 1, + }, + ] + .try_into() + .unwrap(), + }), + ); + + let error_entry = ScSpecEntry::from_xdr(TestError::spec_xdr(), Limits::none()).unwrap(); + assert_eq!( + error_entry, + ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { + doc: "".try_into().unwrap(), + lib: "".try_into().unwrap(), + name: "TestError".try_into().unwrap(), + cases: [ScSpecUdtErrorEnumCaseV0 { + doc: "".try_into().unwrap(), + name: "Error".try_into().unwrap(), + value: 1, + }] + .try_into() + .unwrap(), + }), + ); + + let tuple_enum_entry = + ScSpecEntry::from_xdr(TestTupleEnum::spec_xdr(), Limits::none()).unwrap(); + assert_eq!( + tuple_enum_entry, + ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { + doc: "".try_into().unwrap(), + lib: "".try_into().unwrap(), + name: "TestTupleEnum".try_into().unwrap(), + cases: [ + ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { + doc: "".try_into().unwrap(), + name: "Tuple".try_into().unwrap(), + type_: [ScSpecTypeDef::U32].try_into().unwrap(), + }), + ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { + doc: "".try_into().unwrap(), + name: "TestTuple".try_into().unwrap(), + type_: [ScSpecTypeDef::I32].try_into().unwrap(), + }), + ] + .try_into() + .unwrap(), + }), + ); +} + +#[test] +fn test_spec_event() { + // `r#TestEvent` must emit its prefix topic as snake_case of the unraw'd + // struct name ("test_event"), and its fields `r#type` and `r#fn` as "type" + // and "fn" in declaration order. + let event_entry = ScSpecEntry::from_xdr(TestEvent::spec_xdr(), Limits::none()).unwrap(); + assert_eq!( + event_entry, + ScSpecEntry::EventV0(ScSpecEventV0 { + doc: "".try_into().unwrap(), + lib: "".try_into().unwrap(), + name: ScSymbol("TestEvent".try_into().unwrap()), + prefix_topics: [ScSymbol("test_event".try_into().unwrap())] + .try_into() + .unwrap(), + params: [ + ScSpecEventParamV0 { + doc: "".try_into().unwrap(), + name: "type".try_into().unwrap(), + type_: ScSpecTypeDef::U32, + location: ScSpecEventParamLocationV0::TopicList, + }, + ScSpecEventParamV0 { + doc: "".try_into().unwrap(), + name: "fn".try_into().unwrap(), + type_: ScSpecTypeDef::U32, + location: ScSpecEventParamLocationV0::Data, + }, + ] + .try_into() + .unwrap(), + data_format: ScSpecEventDataFormat::Map, + }), + ); +} diff --git a/soroban-sdk/test_snapshots/tests/contract_udt_raw_identifier/test_functional.1.json b/soroban-sdk/test_snapshots/tests/contract_udt_raw_identifier/test_functional.1.json new file mode 100644 index 000000000..2e8d29d5c --- /dev/null +++ b/soroban-sdk/test_snapshots/tests/contract_udt_raw_identifier/test_functional.1.json @@ -0,0 +1,63 @@ +{ + "generators": { + "address": 1, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 26, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file