From cd541d059e14ca7e12b1309aa2e4f705f69c8ce5 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:31:24 +1000 Subject: [PATCH] Keep testutils on/off state internal to the sdk (#1301) ### What Change how testutils code is generated to make it rely only on the state of the SDK testutils feature, and not influenced at all by the contract crate's feature set or test cfg. Today the SDK macros generate both non-testutils and testutils code and it is up to the contract crate to enable or disable it. With this change the SDK macros only generate testutils code if the SDK is compiled with testutils. ### Why Today both the SDK and the contract crate must be configured with the testutils feature (or the test cfg) to enable the testutils that are generated by the SDK macros. The SDK has a testutils feature that enables the testutils functions and types in the SDK. The contract crate generated code also has code behind a testutils feature, and the contract crate testutils feature (or test cfg) must be enabled for them to function as well. This largely works, but it results in an oddity in more complex workspace setups that you can be using a testutils SDK with a non-testutils compiled contract crate meaning that none of the types or functions in that crate can be used with testutils functions. This largely goes unnoticed because it is a bit of an edge case and definitely not apparent in simpler setups. Even larger setups may never notice this as an oddity if they always happen to configure their crates appropriately. This also goes unnoticed because when the test cfg is enabled (when building for tests) we also enable all that testutils generated code. This issue has become more apparent to me lately because I'm experimenting with Soroban contracts inside Jupyter notebooks, as one way that folks can experiment and learn Soroban. The Rust runtime kernel for Jupyter notebooks doesn't currently support enable features for code in the notebook so I end up with an odd situation where testutils are enabled in the SDK, but not in the contract. After looking at how to solve this problem it occurred to me that the way we implemented the generated testutils code is not ideal, even independent of this problem. Instead of implementing the generated code so it contains a new testutils feature for the contract crate, we should have made all the generated testutils code enabled/disabled based on the SDKs feature flag. The advantage of this is that in any workspace whenever you have testutils enabled on the SDK, all contracts will also have testutils enabled. Instead of having many levers to pull to get testutils enabled, there is just one. ### Reviewing I recommend reviewing with whitespace changes disabled, because a large portion of the diff is the change to indents when some code was moved inside if..else statements. ### Merging This change _should_ be practically non-breaking. While technically it is removing code from being generated when compiled with an SDK without testutils enabled, most of the testutils code in generated code would be unusable without it anyway. It might be warranted to hold this change until v22, just for the purpose of shipping it in a preview / rc and getting feedback before committing to it. But I think the change is also pretty straightforward and low risk. I'd like to merge this in a minor release, but it warrants some discussion. --- soroban-sdk-macros/src/arbitrary.rs | 4 - soroban-sdk-macros/src/derive_client.rs | 353 ++++++++++-------- soroban-sdk-macros/src/derive_enum.rs | 121 +++--- soroban-sdk-macros/src/derive_enum_int.rs | 65 ++-- soroban-sdk-macros/src/derive_fn.rs | 1 - soroban-sdk-macros/src/derive_struct.rs | 121 +++--- soroban-sdk-macros/src/derive_struct_tuple.rs | 120 +++--- soroban-sdk-macros/src/lib.rs | 70 ++-- 8 files changed, 440 insertions(+), 415 deletions(-) diff --git a/soroban-sdk-macros/src/arbitrary.rs b/soroban-sdk-macros/src/arbitrary.rs index 5369f0d7d..07044aba7 100644 --- a/soroban-sdk-macros/src/arbitrary.rs +++ b/soroban-sdk-macros/src/arbitrary.rs @@ -312,15 +312,11 @@ fn quote_arbitrary( arbitrary_type_decl: TokenStream2, arbitrary_ctor: TokenStream2, ) -> TokenStream2 { - if !cfg!(any(test, feature = "testutils")) { - return quote!(); - } quote! { // This allows us to create a scope to import std and arbitrary, while // also keeping everything from the current scope. This is better than a // module because: modules inside functions have surprisingly // inconsistent scoping rules and visibility management is harder. - #[cfg(any(test, feature = "testutils"))] const _: () = { // derive(Arbitrary) expects these two to be in scope use #path::testutils::arbitrary::std; diff --git a/soroban-sdk-macros/src/derive_client.rs b/soroban-sdk-macros/src/derive_client.rs index 74c14fab8..07a0504e1 100644 --- a/soroban-sdk-macros/src/derive_client.rs +++ b/soroban-sdk-macros/src/derive_client.rs @@ -9,119 +9,124 @@ pub fn derive_client_type(crate_path: &Path, ty: &str, name: &str) -> TokenStrea // Render the Client. let client_doc = format!("{name} is a client for calling the contract defined in {ty_str}."); let client_ident = format_ident!("{}", name); - quote! { - #[doc = #client_doc] - pub struct #client_ident<'a> { - pub env: #crate_path::Env, - pub address: #crate_path::Address, - #[doc(hidden)] - #[cfg(not(any(test, feature = "testutils")))] - _phantom: core::marker::PhantomData<&'a ()>, - #[doc(hidden)] - #[cfg(any(test, feature = "testutils"))] - set_auths: Option<&'a [#crate_path::xdr::SorobanAuthorizationEntry]>, - #[doc(hidden)] - #[cfg(any(test, feature = "testutils"))] - mock_auths: Option<&'a [#crate_path::testutils::MockAuth<'a>]>, - #[doc(hidden)] - #[cfg(any(test, feature = "testutils"))] - mock_all_auths: bool, - #[doc(hidden)] - #[cfg(any(test, feature = "testutils"))] - allow_non_root_auth: bool, - } + if cfg!(not(feature = "testutils")) { + quote! { + #[doc = #client_doc] + pub struct #client_ident<'a> { + pub env: #crate_path::Env, + pub address: #crate_path::Address, + #[doc(hidden)] + _phantom: core::marker::PhantomData<&'a ()>, + } - impl<'a> #client_ident<'a> { - pub fn new(env: &#crate_path::Env, address: &#crate_path::Address) -> Self { - Self { - env: env.clone(), - address: address.clone(), - #[cfg(not(any(test, feature = "testutils")))] - _phantom: core::marker::PhantomData, - #[cfg(any(test, feature = "testutils"))] - set_auths: None, - #[cfg(any(test, feature = "testutils"))] - mock_auths: None, - #[cfg(any(test, feature = "testutils"))] - mock_all_auths: false, - #[cfg(any(test, feature = "testutils"))] - allow_non_root_auth: false, + impl<'a> #client_ident<'a> { + pub fn new(env: &#crate_path::Env, address: &#crate_path::Address) -> Self { + Self { + env: env.clone(), + address: address.clone(), + _phantom: core::marker::PhantomData, + } } } + } + } else { + quote! { + #[doc = #client_doc] + pub struct #client_ident<'a> { + pub env: #crate_path::Env, + pub address: #crate_path::Address, + #[doc(hidden)] + set_auths: Option<&'a [#crate_path::xdr::SorobanAuthorizationEntry]>, + #[doc(hidden)] + mock_auths: Option<&'a [#crate_path::testutils::MockAuth<'a>]>, + #[doc(hidden)] + mock_all_auths: bool, + #[doc(hidden)] + allow_non_root_auth: bool, + } - /// Set authorizations in the environment which will be consumed by - /// contracts when they invoke `Address::require_auth` or - /// `Address::require_auth_for_args` functions. - /// - /// Requires valid signatures for the authorization to be successful. - /// To mock auth without requiring valid signatures, use `mock_auths`. - /// - /// See `soroban_sdk::Env::set_auths` for more details and examples. - #[cfg(any(test, feature = "testutils"))] - pub fn set_auths(&self, auths: &'a [#crate_path::xdr::SorobanAuthorizationEntry]) -> Self { - Self { - env: self.env.clone(), - address: self.address.clone(), - set_auths: Some(auths), - mock_auths: self.mock_auths.clone(), - mock_all_auths: false, - allow_non_root_auth: false, + impl<'a> #client_ident<'a> { + pub fn new(env: &#crate_path::Env, address: &#crate_path::Address) -> Self { + Self { + env: env.clone(), + address: address.clone(), + set_auths: None, + mock_auths: None, + mock_all_auths: false, + allow_non_root_auth: false, + } } - } - /// Mock authorizations in the environment which will cause matching invokes - /// of `Address::require_auth` and `Address::require_auth_for_args` to - /// pass. - /// - /// See `soroban_sdk::Env::set_auths` for more details and examples. - #[cfg(any(test, feature = "testutils"))] - pub fn mock_auths(&self, mock_auths: &'a [#crate_path::testutils::MockAuth<'a>]) -> Self { - Self { - env: self.env.clone(), - address: self.address.clone(), - set_auths: self.set_auths.clone(), - mock_auths: Some(mock_auths), - mock_all_auths: false, - allow_non_root_auth: false, + /// Set authorizations in the environment which will be consumed by + /// contracts when they invoke `Address::require_auth` or + /// `Address::require_auth_for_args` functions. + /// + /// Requires valid signatures for the authorization to be successful. + /// To mock auth without requiring valid signatures, use `mock_auths`. + /// + /// See `soroban_sdk::Env::set_auths` for more details and examples. + pub fn set_auths(&self, auths: &'a [#crate_path::xdr::SorobanAuthorizationEntry]) -> Self { + Self { + env: self.env.clone(), + address: self.address.clone(), + set_auths: Some(auths), + mock_auths: self.mock_auths.clone(), + mock_all_auths: false, + allow_non_root_auth: false, + } } - } - /// Mock all calls to the `Address::require_auth` and - /// `Address::require_auth_for_args` functions in invoked contracts, - /// having them succeed as if authorization was provided. - /// - /// See `soroban_sdk::Env::mock_all_auths` for more details and - /// examples. - #[cfg(any(test, feature = "testutils"))] - pub fn mock_all_auths(&self) -> Self { - Self { - env: self.env.clone(), - address: self.address.clone(), - set_auths: None, - mock_auths: None, - mock_all_auths: true, - allow_non_root_auth: false, + /// Mock authorizations in the environment which will cause matching invokes + /// of `Address::require_auth` and `Address::require_auth_for_args` to + /// pass. + /// + /// See `soroban_sdk::Env::set_auths` for more details and examples. + pub fn mock_auths(&self, mock_auths: &'a [#crate_path::testutils::MockAuth<'a>]) -> Self { + Self { + env: self.env.clone(), + address: self.address.clone(), + set_auths: self.set_auths.clone(), + mock_auths: Some(mock_auths), + mock_all_auths: false, + allow_non_root_auth: false, + } } - } - /// A version of `mock_all_auths` that allows authorizations that - /// are not present in the root invocation. - /// - /// Refer to `mock_all_auths` documentation for details and - /// prefer using `mock_all_auths` unless non-root authorization is - /// required. - /// - /// See `soroban_sdk::Env::mock_all_auths_allowing_non_root_auth` - /// for more details and examples. - #[cfg(any(test, feature = "testutils"))] - pub fn mock_all_auths_allowing_non_root_auth(&self) -> Self { - Self { - env: self.env.clone(), - address: self.address.clone(), - set_auths: None, - mock_auths: None, - mock_all_auths: true, - allow_non_root_auth: true, + /// Mock all calls to the `Address::require_auth` and + /// `Address::require_auth_for_args` functions in invoked contracts, + /// having them succeed as if authorization was provided. + /// + /// See `soroban_sdk::Env::mock_all_auths` for more details and + /// examples. + pub fn mock_all_auths(&self) -> Self { + Self { + env: self.env.clone(), + address: self.address.clone(), + set_auths: None, + mock_auths: None, + mock_all_auths: true, + allow_non_root_auth: false, + } + } + + /// A version of `mock_all_auths` that allows authorizations that + /// are not present in the root invocation. + /// + /// Refer to `mock_all_auths` documentation for details and + /// prefer using `mock_all_auths` unless non-root authorization is + /// required. + /// + /// See `soroban_sdk::Env::mock_all_auths_allowing_non_root_auth` + /// for more details and examples. + pub fn mock_all_auths_allowing_non_root_auth(&self) -> Self { + Self { + env: self.env.clone(), + address: self.address.clone(), + set_auths: None, + mock_auths: None, + mock_all_auths: true, + allow_non_root_auth: true, + } } } } @@ -193,74 +198,94 @@ pub fn derive_client_impl(crate_path: &Path, name: &str, fns: &[syn_ext::Fn]) -> let fn_output = f.output(); let fn_try_output = f.try_output(crate_path); let fn_attrs = f.attrs; - quote! { - #(#fn_attrs)* - pub fn #fn_ident(&self, #(#fn_input_types),*) -> #fn_output { - use core::ops::Not; - #[cfg(any(test, feature = "testutils"))] - let old_auth_manager = self.env.in_contract().not().then(|| - self.env.host().snapshot_auth_manager().unwrap() - ); - #[cfg(any(test, feature = "testutils"))] - { - if let Some(set_auths) = self.set_auths { - self.env.set_auths(set_auths); - } - if let Some(mock_auths) = self.mock_auths { - self.env.mock_auths(mock_auths); - } - if self.mock_all_auths { - if self.allow_non_root_auth { - self.env.mock_all_auths_allowing_non_root_auth(); - } else { - self.env.mock_all_auths(); - } - } + if cfg!(not(feature = "testutils")) { + quote! { + #(#fn_attrs)* + pub fn #fn_ident(&self, #(#fn_input_types),*) -> #fn_output { + use core::ops::Not; + use #crate_path::{IntoVal,FromVal}; + let res = self.env.invoke_contract( + &self.address, + &#fn_name_symbol, + #crate_path::vec![&self.env, #(#fn_input_names.into_val(&self.env)),*], + ); + res } - use #crate_path::{IntoVal,FromVal}; - let res = self.env.invoke_contract( - &self.address, - &#fn_name_symbol, - #crate_path::vec![&self.env, #(#fn_input_names.into_val(&self.env)),*], - ); - #[cfg(any(test, feature = "testutils"))] - if let Some(old_auth_manager) = old_auth_manager { - self.env.host().set_auth_manager(old_auth_manager).unwrap(); + + #(#fn_attrs)* + pub fn #fn_try_ident(&self, #(#fn_input_types),*) -> #fn_try_output { + use #crate_path::{IntoVal,FromVal}; + let res = self.env.try_invoke_contract( + &self.address, + &#fn_name_symbol, + #crate_path::vec![&self.env, #(#fn_input_names.into_val(&self.env)),*], + ); + res } - res } - - #(#fn_attrs)* - pub fn #fn_try_ident(&self, #(#fn_input_types),*) -> #fn_try_output { - #[cfg(any(test, feature = "testutils"))] - use core::ops::Not; - #[cfg(any(test, feature = "testutils"))] - let old_auth_manager = self.env.in_contract().not().then(|| - self.env.host().snapshot_auth_manager().unwrap() - ); - #[cfg(any(test, feature = "testutils"))] - { - if let Some(set_auths) = self.set_auths { - self.env.set_auths(set_auths); - } - if let Some(mock_auths) = self.mock_auths { - self.env.mock_auths(mock_auths); + } else { + quote! { + #(#fn_attrs)* + pub fn #fn_ident(&self, #(#fn_input_types),*) -> #fn_output { + use core::ops::Not; + let old_auth_manager = self.env.in_contract().not().then(|| + self.env.host().snapshot_auth_manager().unwrap() + ); + { + if let Some(set_auths) = self.set_auths { + self.env.set_auths(set_auths); + } + if let Some(mock_auths) = self.mock_auths { + self.env.mock_auths(mock_auths); + } + if self.mock_all_auths { + if self.allow_non_root_auth { + self.env.mock_all_auths_allowing_non_root_auth(); + } else { + self.env.mock_all_auths(); + } + } } - if self.mock_all_auths { - self.env.mock_all_auths(); + use #crate_path::{IntoVal,FromVal}; + let res = self.env.invoke_contract( + &self.address, + &#fn_name_symbol, + #crate_path::vec![&self.env, #(#fn_input_names.into_val(&self.env)),*], + ); + if let Some(old_auth_manager) = old_auth_manager { + self.env.host().set_auth_manager(old_auth_manager).unwrap(); } + res } - use #crate_path::{IntoVal,FromVal}; - let res = self.env.try_invoke_contract( - &self.address, - &#fn_name_symbol, - #crate_path::vec![&self.env, #(#fn_input_names.into_val(&self.env)),*], - ); - #[cfg(any(test, feature = "testutils"))] - if let Some(old_auth_manager) = old_auth_manager { - self.env.host().set_auth_manager(old_auth_manager).unwrap(); + + #(#fn_attrs)* + pub fn #fn_try_ident(&self, #(#fn_input_types),*) -> #fn_try_output { + use core::ops::Not; + let old_auth_manager = self.env.in_contract().not().then(|| + self.env.host().snapshot_auth_manager().unwrap() + ); + { + if let Some(set_auths) = self.set_auths { + self.env.set_auths(set_auths); + } + if let Some(mock_auths) = self.mock_auths { + self.env.mock_auths(mock_auths); + } + if self.mock_all_auths { + self.env.mock_all_auths(); + } + } + use #crate_path::{IntoVal,FromVal}; + let res = self.env.try_invoke_contract( + &self.address, + &#fn_name_symbol, + #crate_path::vec![&self.env, #(#fn_input_names.into_val(&self.env)),*], + ); + if let Some(old_auth_manager) = old_auth_manager { + self.env.host().set_auth_manager(old_auth_manager).unwrap(); + } + res } - res } } }) diff --git a/soroban-sdk-macros/src/derive_enum.rs b/soroban-sdk-macros/src/derive_enum.rs index 5222a43a4..5f4b8f50e 100644 --- a/soroban-sdk-macros/src/derive_enum.rs +++ b/soroban-sdk-macros/src/derive_enum.rs @@ -162,10 +162,8 @@ pub fn derive_type_enum( None }; - let arbitrary_tokens = crate::arbitrary::derive_arbitrary_enum(path, vis, enum_ident, data); - // Output. - quote! { + let mut output = quote! { #spec_gen impl #path::TryFromVal<#path::Env, #path::Val> for #enum_ident { @@ -194,81 +192,82 @@ pub fn derive_type_enum( } } } + }; - #[cfg(any(test, feature = "testutils"))] - impl #path::TryFromVal<#path::Env, #path::xdr::ScVec> for #enum_ident { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVec) -> Result { - use #path::xdr::Validate; - use #path::TryIntoVal; + // Additional output when testutils are enabled. + if cfg!(feature = "testutils") { + let arbitrary_tokens = crate::arbitrary::derive_arbitrary_enum(path, vis, enum_ident, data); + output.extend(quote! { + impl #path::TryFromVal<#path::Env, #path::xdr::ScVec> for #enum_ident { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVec) -> Result { + use #path::xdr::Validate; + use #path::TryIntoVal; - let vec = val; - let mut iter = vec.iter(); - let discriminant: #path::xdr::ScSymbol = iter.next().ok_or(#path::xdr::Error::Invalid)?.clone().try_into().map_err(|_| #path::xdr::Error::Invalid)?; - let discriminant_name: &str = &discriminant.to_utf8_string()?; + let vec = val; + let mut iter = vec.iter(); + let discriminant: #path::xdr::ScSymbol = iter.next().ok_or(#path::xdr::Error::Invalid)?.clone().try_into().map_err(|_| #path::xdr::Error::Invalid)?; + let discriminant_name: &str = &discriminant.to_utf8_string()?; - Ok(match discriminant_name { - #(#try_from_xdrs,)* - _ => Err(#path::xdr::Error::Invalid)?, - }) + Ok(match discriminant_name { + #(#try_from_xdrs,)* + _ => Err(#path::xdr::Error::Invalid)?, + }) + } } - } - #[cfg(any(test, feature = "testutils"))] - impl #path::TryFromVal<#path::Env, #path::xdr::ScVal> for #enum_ident { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVal) -> Result { - if let #path::xdr::ScVal::Vec(Some(vec)) = val { - <_ as #path::TryFromVal<_, _>>::try_from_val(env, vec) - } else { - Err(#path::xdr::Error::Invalid) + impl #path::TryFromVal<#path::Env, #path::xdr::ScVal> for #enum_ident { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVal) -> Result { + if let #path::xdr::ScVal::Vec(Some(vec)) = val { + <_ as #path::TryFromVal<_, _>>::try_from_val(env, vec) + } else { + Err(#path::xdr::Error::Invalid) + } } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<&#enum_ident> for #path::xdr::ScVec { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: &#enum_ident) -> Result { - extern crate alloc; - Ok(match val { - #(#into_xdrs,)* - }) + impl TryFrom<&#enum_ident> for #path::xdr::ScVec { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: &#enum_ident) -> Result { + extern crate alloc; + Ok(match val { + #(#into_xdrs,)* + }) + } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<#enum_ident> for #path::xdr::ScVec { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: #enum_ident) -> Result { - (&val).try_into() + impl TryFrom<#enum_ident> for #path::xdr::ScVec { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: #enum_ident) -> Result { + (&val).try_into() + } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<&#enum_ident> for #path::xdr::ScVal { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: &#enum_ident) -> Result { - Ok(#path::xdr::ScVal::Vec(Some(val.try_into()?))) + impl TryFrom<&#enum_ident> for #path::xdr::ScVal { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: &#enum_ident) -> Result { + Ok(#path::xdr::ScVal::Vec(Some(val.try_into()?))) + } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<#enum_ident> for #path::xdr::ScVal { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: #enum_ident) -> Result { - (&val).try_into() + impl TryFrom<#enum_ident> for #path::xdr::ScVal { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: #enum_ident) -> Result { + (&val).try_into() + } } - } - #arbitrary_tokens + #arbitrary_tokens + }); } + output } struct VariantTokens { diff --git a/soroban-sdk-macros/src/derive_enum_int.rs b/soroban-sdk-macros/src/derive_enum_int.rs index 6eaf4ec27..6cc326c16 100644 --- a/soroban-sdk-macros/src/derive_enum_int.rs +++ b/soroban-sdk-macros/src/derive_enum_int.rs @@ -91,10 +91,8 @@ pub fn derive_type_enum_int( None }; - let arbitrary_tokens = crate::arbitrary::derive_arbitrary_enum_int(path, vis, enum_ident, data); - // Output. - quote! { + let mut output = quote! { #spec_gen const _: () = { fn assert_copy(v: #enum_ident) -> impl Copy { v } }; @@ -120,41 +118,46 @@ pub fn derive_type_enum_int( }) } } + }; - #[cfg(any(test, feature = "testutils"))] - impl #path::TryFromVal<#path::Env, #path::xdr::ScVal> for #enum_ident { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVal) -> Result { - if let #path::xdr::ScVal::U32(discriminant) = val { - Ok(match *discriminant { - #(#try_froms,)* - _ => Err(#path::xdr::Error::Invalid)?, - }) - } else { - Err(#path::xdr::Error::Invalid) + // Additional output when testutils are enabled. + if cfg!(feature = "testutils") { + let arbitrary_tokens = + crate::arbitrary::derive_arbitrary_enum_int(path, vis, enum_ident, data); + output.extend(quote! { + impl #path::TryFromVal<#path::Env, #path::xdr::ScVal> for #enum_ident { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVal) -> Result { + if let #path::xdr::ScVal::U32(discriminant) = val { + Ok(match *discriminant { + #(#try_froms,)* + _ => Err(#path::xdr::Error::Invalid)?, + }) + } else { + Err(#path::xdr::Error::Invalid) + } } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryInto<#path::xdr::ScVal> for &#enum_ident { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_into(self) -> Result<#path::xdr::ScVal, #path::xdr::Error> { - Ok((*self as u32).into()) + impl TryInto<#path::xdr::ScVal> for &#enum_ident { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_into(self) -> Result<#path::xdr::ScVal, #path::xdr::Error> { + Ok((*self as u32).into()) + } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryInto<#path::xdr::ScVal> for #enum_ident { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_into(self) -> Result<#path::xdr::ScVal, #path::xdr::Error> { - Ok((self as u32).into()) + impl TryInto<#path::xdr::ScVal> for #enum_ident { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_into(self) -> Result<#path::xdr::ScVal, #path::xdr::Error> { + Ok((self as u32).into()) + } } - } - #arbitrary_tokens + #arbitrary_tokens + }); } + output } diff --git a/soroban-sdk-macros/src/derive_fn.rs b/soroban-sdk-macros/src/derive_fn.rs index 8b347892f..168d8b0fc 100644 --- a/soroban-sdk-macros/src/derive_fn.rs +++ b/soroban-sdk-macros/src/derive_fn.rs @@ -181,7 +181,6 @@ pub fn derive_contract_function_registration_ctor<'a>( let ctor_ident = format_ident!("__{ty_str}_{trait_str}_{methods_hash}_ctor"); quote! { - #[cfg(any(test, feature = "testutils"))] #[doc(hidden)] #[#crate_path::reexports_for_macros::ctor::ctor] fn #ctor_ident() { diff --git a/soroban-sdk-macros/src/derive_struct.rs b/soroban-sdk-macros/src/derive_struct.rs index c50e9ed76..729a57022 100644 --- a/soroban-sdk-macros/src/derive_struct.rs +++ b/soroban-sdk-macros/src/derive_struct.rs @@ -100,10 +100,8 @@ pub fn derive_type_struct( None }; - let arbitrary_tokens = crate::arbitrary::derive_arbitrary_struct(path, vis, ident, data); - // Output. - quote! { + let mut output = quote! { #spec_gen impl #path::TryFromVal<#path::Env, #path::Val> for #ident { @@ -131,78 +129,79 @@ pub fn derive_type_struct( Ok(env.map_new_from_slices(&KEYS, &vals).map_err(|_| ConversionError)?.into()) } } + }; - #[cfg(any(test, feature = "testutils"))] - impl #path::TryFromVal<#path::Env, #path::xdr::ScMap> for #ident { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from_val(env: &#path::Env, val: &#path::xdr::ScMap) -> Result { - use #path::xdr::Validate; - use #path::TryIntoVal; - let map = val; - if map.len() != #field_count_usize { - return Err(#path::xdr::Error::Invalid); + // Additional output when testutils are enabled. + if cfg!(feature = "testutils") { + let arbitrary_tokens = crate::arbitrary::derive_arbitrary_struct(path, vis, ident, data); + output.extend(quote!{ + impl #path::TryFromVal<#path::Env, #path::xdr::ScMap> for #ident { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from_val(env: &#path::Env, val: &#path::xdr::ScMap) -> Result { + use #path::xdr::Validate; + use #path::TryIntoVal; + let map = val; + if map.len() != #field_count_usize { + return Err(#path::xdr::Error::Invalid); + } + map.validate()?; + Ok(Self{ + #(#try_from_xdrs,)* + }) } - map.validate()?; - Ok(Self{ - #(#try_from_xdrs,)* - }) } - } - #[cfg(any(test, feature = "testutils"))] - impl #path::TryFromVal<#path::Env, #path::xdr::ScVal> for #ident { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVal) -> Result { - if let #path::xdr::ScVal::Map(Some(map)) = val { - <_ as #path::TryFromVal<_, _>>::try_from_val(env, map) - } else { - Err(#path::xdr::Error::Invalid) + impl #path::TryFromVal<#path::Env, #path::xdr::ScVal> for #ident { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVal) -> Result { + if let #path::xdr::ScVal::Map(Some(map)) = val { + <_ as #path::TryFromVal<_, _>>::try_from_val(env, map) + } else { + Err(#path::xdr::Error::Invalid) + } } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<&#ident> for #path::xdr::ScMap { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: &#ident) -> Result { - extern crate alloc; - use #path::TryFromVal; - #path::xdr::ScMap::sorted_from(alloc::vec![ - #(#try_into_xdrs,)* - ]) + impl TryFrom<&#ident> for #path::xdr::ScMap { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: &#ident) -> Result { + extern crate alloc; + use #path::TryFromVal; + #path::xdr::ScMap::sorted_from(alloc::vec![ + #(#try_into_xdrs,)* + ]) + } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<#ident> for #path::xdr::ScMap { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: #ident) -> Result { - (&val).try_into() + impl TryFrom<#ident> for #path::xdr::ScMap { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: #ident) -> Result { + (&val).try_into() + } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<&#ident> for #path::xdr::ScVal { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: &#ident) -> Result { - Ok(#path::xdr::ScVal::Map(Some(val.try_into()?))) + impl TryFrom<&#ident> for #path::xdr::ScVal { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: &#ident) -> Result { + Ok(#path::xdr::ScVal::Map(Some(val.try_into()?))) + } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<#ident> for #path::xdr::ScVal { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: #ident) -> Result { - (&val).try_into() + impl TryFrom<#ident> for #path::xdr::ScVal { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: #ident) -> Result { + (&val).try_into() + } } - } - #arbitrary_tokens + #arbitrary_tokens + }); } + output } diff --git a/soroban-sdk-macros/src/derive_struct_tuple.rs b/soroban-sdk-macros/src/derive_struct_tuple.rs index ee0e5a604..cdfcbc137 100644 --- a/soroban-sdk-macros/src/derive_struct_tuple.rs +++ b/soroban-sdk-macros/src/derive_struct_tuple.rs @@ -89,10 +89,8 @@ pub fn derive_type_struct_tuple( None }; - let arbitrary_tokens = crate::arbitrary::derive_arbitrary_struct_tuple(path, vis, ident, data); - // Output. - quote! { + let mut output = quote! { #spec_gen impl #path::TryFromVal<#path::Env, #path::Val> for #ident { @@ -120,77 +118,79 @@ pub fn derive_type_struct_tuple( Ok(env.vec_new_from_slice(&vals).map_err(|_| ConversionError)?.into()) } } + }; - #[cfg(any(test, feature = "testutils"))] - impl #path::TryFromVal<#path::Env, #path::xdr::ScVec> for #ident { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVec) -> Result { - use #path::xdr::Validate; - use #path::TryIntoVal; - let vec = val; - if vec.len() != #field_count_usize { - return Err(#path::xdr::Error::Invalid); + // Additional output when testutils are enabled. + if cfg!(feature = "testutils") { + let arbitrary_tokens = + crate::arbitrary::derive_arbitrary_struct_tuple(path, vis, ident, data); + output.extend(quote! { + impl #path::TryFromVal<#path::Env, #path::xdr::ScVec> for #ident { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVec) -> Result { + use #path::xdr::Validate; + use #path::TryIntoVal; + let vec = val; + if vec.len() != #field_count_usize { + return Err(#path::xdr::Error::Invalid); + } + Ok(Self{ + #(#try_from_xdrs,)* + }) } - Ok(Self{ - #(#try_from_xdrs,)* - }) } - } - #[cfg(any(test, feature = "testutils"))] - impl #path::TryFromVal<#path::Env, #path::xdr::ScVal> for #ident { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVal) -> Result { - if let #path::xdr::ScVal::Vec(Some(vec)) = val { - <_ as #path::TryFromVal<_, _>>::try_from_val(env, vec) - } else { - Err(#path::xdr::Error::Invalid) + impl #path::TryFromVal<#path::Env, #path::xdr::ScVal> for #ident { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from_val(env: &#path::Env, val: &#path::xdr::ScVal) -> Result { + if let #path::xdr::ScVal::Vec(Some(vec)) = val { + <_ as #path::TryFromVal<_, _>>::try_from_val(env, vec) + } else { + Err(#path::xdr::Error::Invalid) + } } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<&#ident> for #path::xdr::ScVec { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: &#ident) -> Result { - extern crate alloc; - use #path::TryFromVal; - Ok(#path::xdr::ScVec(alloc::vec![ - #(#try_into_xdrs,)* - ].try_into()?)) + impl TryFrom<&#ident> for #path::xdr::ScVec { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: &#ident) -> Result { + extern crate alloc; + use #path::TryFromVal; + Ok(#path::xdr::ScVec(alloc::vec![ + #(#try_into_xdrs,)* + ].try_into()?)) + } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<#ident> for #path::xdr::ScVec { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: #ident) -> Result { - (&val).try_into() + impl TryFrom<#ident> for #path::xdr::ScVec { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: #ident) -> Result { + (&val).try_into() + } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<&#ident> for #path::xdr::ScVal { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: &#ident) -> Result { - Ok(#path::xdr::ScVal::Vec(Some(val.try_into()?))) + impl TryFrom<&#ident> for #path::xdr::ScVal { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: &#ident) -> Result { + Ok(#path::xdr::ScVal::Vec(Some(val.try_into()?))) + } } - } - #[cfg(any(test, feature = "testutils"))] - impl TryFrom<#ident> for #path::xdr::ScVal { - type Error = #path::xdr::Error; - #[inline(always)] - fn try_from(val: #ident) -> Result { - (&val).try_into() + impl TryFrom<#ident> for #path::xdr::ScVal { + type Error = #path::xdr::Error; + #[inline(always)] + fn try_from(val: #ident) -> Result { + (&val).try_into() + } } - } - #arbitrary_tokens + #arbitrary_tokens + }); } + output } diff --git a/soroban-sdk-macros/src/lib.rs b/soroban-sdk-macros/src/lib.rs index 863d42b73..daae39714 100644 --- a/soroban-sdk-macros/src/lib.rs +++ b/soroban-sdk-macros/src/lib.rs @@ -137,40 +137,42 @@ pub fn contract(metadata: TokenStream, input: TokenStream) -> TokenStream { let fn_set_registry_ident = format_ident!("__{ty_str}_fn_set_registry"); let crate_path = &args.crate_path; let client = derive_client_type(&args.crate_path, &ty_str, &client_ident); - quote! { + let mut output = quote! { #input2 #client + }; + if cfg!(feature = "testutils") { + output.extend(quote! { + mod #fn_set_registry_ident { + use super::*; - #[cfg(any(test, feature = "testutils"))] - mod #fn_set_registry_ident { - use super::*; + extern crate std; + use std::sync::Mutex; + use std::collections::BTreeMap; - extern crate std; - use std::sync::Mutex; - use std::collections::BTreeMap; + type F = dyn Send + Sync + Fn(#crate_path::Env, &[#crate_path::Val]) -> #crate_path::Val; - type F = dyn Send + Sync + Fn(#crate_path::Env, &[#crate_path::Val]) -> #crate_path::Val; + static FUNCS: Mutex> = Mutex::new(BTreeMap::new()); - static FUNCS: Mutex> = Mutex::new(BTreeMap::new()); + pub(crate) fn register(name: &'static str, func: &'static F) { + FUNCS.lock().unwrap().insert(name, func); + } - pub(crate) fn register(name: &'static str, func: &'static F) { - FUNCS.lock().unwrap().insert(name, func); + pub(crate) fn call(name: &str, env: #crate_path::Env, args: &[#crate_path::Val]) -> Option<#crate_path::Val> { + let fopt: Option<&'static F> = FUNCS.lock().unwrap().get(name).map(|f| f.clone()); + fopt.map(|f| f(env, args)) + } } - pub(crate) fn call(name: &str, env: #crate_path::Env, args: &[#crate_path::Val]) -> Option<#crate_path::Val> { - let fopt: Option<&'static F> = FUNCS.lock().unwrap().get(name).map(|f| f.clone()); - fopt.map(|f| f(env, args)) - } - } - - #[cfg(any(test, feature = "testutils"))] - #[doc(hidden)] - impl #crate_path::testutils::ContractFunctionSet for #ty { - fn call(&self, func: &str, env: #crate_path::Env, args: &[#crate_path::Val]) -> Option<#crate_path::Val> { - #fn_set_registry_ident::call(func, env, args) + #[doc(hidden)] + impl #crate_path::testutils::ContractFunctionSet for #ty { + fn call(&self, func: &str, env: #crate_path::Env, args: &[#crate_path::Val]) -> Option<#crate_path::Val> { + #fn_set_registry_ident::call(func, env, args) + } } - } - }.into() + }); + } + output.into() } #[derive(Debug, FromMeta)] @@ -231,20 +233,22 @@ pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream { match derived { Ok(derived_ok) => { - let cfs = derive_contract_function_registration_ctor( - crate_path, - ty, - trait_ident, - pub_methods.into_iter(), - ); - quote! { + let mut output = quote! { #[#crate_path::contractclient(crate_path = #crate_path_str, name = #client_ident, impl_only = true)] #[#crate_path::contractspecfn(name = #ty_str)] #imp #derived_ok - #cfs + }; + if cfg!(feature = "testutils") { + let cfs = derive_contract_function_registration_ctor( + crate_path, + ty, + trait_ident, + pub_methods.into_iter(), + ); + output.extend(quote! { #cfs }); } - .into() + output.into() } Err(derived_err) => quote! { #imp