diff --git a/Cargo.lock b/Cargo.lock index ff8ae13a9..fe6606ed6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1202,8 +1202,6 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "soroban-builtin-sdk-macros" version = "22.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c45d2492cd44f05cc79eeb857985f153f12a4423ce51b4b746b5925024c473b1" dependencies = [ "itertools", "proc-macro2", @@ -1214,8 +1212,6 @@ dependencies = [ [[package]] name = "soroban-env-common" version = "22.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39b6d2ec8955243394278e1fae88be3b367fcfed9cf74e5044799a90786a8642" dependencies = [ "arbitrary", "crate-git-revision", @@ -1233,8 +1229,6 @@ dependencies = [ [[package]] name = "soroban-env-guest" version = "22.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4002fc582cd20cc9b9fbb73959bc5d6b5b15feda11485cbfab0c28e78ecbab3e" dependencies = [ "soroban-env-common", "static_assertions", @@ -1243,8 +1237,6 @@ dependencies = [ [[package]] name = "soroban-env-host" version = "22.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb9be0260d39a648db0d33e1c6f8f494ec0c4f5be2b8a0a4e15ed4b7c6a92b0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -1279,8 +1271,6 @@ dependencies = [ [[package]] name = "soroban-env-macros" version = "22.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a328297a568ae98999fdb06902e3362dfd8a2bfa9abea40beaeb7dc93a402fe7" dependencies = [ "itertools", "proc-macro2", @@ -1383,8 +1373,7 @@ dependencies = [ [[package]] name = "soroban-wasmi" version = "0.31.1-soroban.20.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710403de32d0e0c35375518cb995d4fc056d0d48966f2e56ea471b8cb8fc9719" +source = "git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0#0ed3f3dee30dc41ebe21972399e0a73a41944aa0" dependencies = [ "smallvec", "spin", @@ -1784,15 +1773,13 @@ checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasmi_arena" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" +version = "0.4.0" +source = "git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0#0ed3f3dee30dc41ebe21972399e0a73a41944aa0" [[package]] name = "wasmi_core" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +source = "git+https://github.com/stellar/wasmi?rev=0ed3f3dee30dc41ebe21972399e0a73a41944aa0#0ed3f3dee30dc41ebe21972399e0a73a41944aa0" dependencies = [ "downcast-rs", "libm", diff --git a/Cargo.toml b/Cargo.toml index 9cde3dd77..0a16670f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,10 +48,10 @@ features = ["curr"] #git = "https://github.com/stellar/rs-stellar-xdr" #rev = "67be5955a15f1d3a4df83fe86e6ae107f687141b" -#[patch."https://github.com/stellar/rs-soroban-env"] -#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" } +[patch.crates-io] +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" } #[patch."https://github.com/stellar/rs-stellar-xdr"] #stellar-xdr = { path = "../rs-stellar-xdr/" } diff --git a/soroban-sdk-macros/src/derive_args.rs b/soroban-sdk-macros/src/derive_args.rs new file mode 100644 index 000000000..6d1763688 --- /dev/null +++ b/soroban-sdk-macros/src/derive_args.rs @@ -0,0 +1,92 @@ +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +use syn::{Error, FnArg, Lifetime, Path, Type, TypePath, TypeReference}; + +use crate::syn_ext; + +pub fn derive_args_type(ty: &str, name: &str) -> TokenStream { + let ty_str = quote!(#ty).to_string(); + // Render the Client. + let args_doc = format!("{name} is a type for buildings function args defined in {ty_str}."); + let args_ident = format_ident!("{}", name); + quote! { + #[doc = #args_doc] + pub struct #args_ident; + } +} + +pub fn derive_args_impl(crate_path: &Path, name: &str, fns: &[syn_ext::Fn]) -> TokenStream { + // Map the traits methods to methods for the Args. + let mut errors = Vec::::new(); + let fns: Vec<_> = fns + .iter() + .map(|f| { + let fn_ident = &f.ident; + + // Check for the Env argument. + let env_input = f.inputs.first().and_then(|a| match a { + FnArg::Typed(pat_type) => { + let mut ty = &*pat_type.ty; + if let Type::Reference(TypeReference { elem, .. }) = ty { + ty = elem; + } + if let Type::Path(TypePath { + path: syn::Path { segments, .. }, + .. + }) = ty + { + if segments.last().map_or(false, |s| s.ident == "Env") { + Some(()) + } else { + None + } + } else { + None + } + } + FnArg::Receiver(_) => None, + }); + + // Map all remaining inputs. + let fn_input_lifetime = Lifetime::new("'i", Span::call_site()); + let (fn_input_names, fn_input_types): (Vec<_>, Vec<_>) = f + .inputs + .iter() + .skip(if env_input.is_some() { 1 } else { 0 }) + .map(|t| { + let ident = match syn_ext::fn_arg_ident(t) { + Ok(ident) => ident, + Err(e) => { + errors.push(e); + format_ident!("_") + } + }; + (ident, syn_ext::fn_arg_make_ref(t, Some(&fn_input_lifetime))) + }) + .unzip(); + + quote! { + // TODO: Make the fn_input_types an impl Borrow + pub fn #fn_ident<#fn_input_lifetime>(#(#fn_input_types),*) + -> impl #crate_path::IntoVal<#crate_path::Env, #crate_path::Vec<#crate_path::Val>> + #fn_input_lifetime + { + (#(#fn_input_names,)*) + } + } + }) + .collect(); + + // If errors have occurred, render them instead. + if !errors.is_empty() { + let compile_errors = errors.iter().map(Error::to_compile_error); + return quote! { #(#compile_errors)* }; + } + + // Render the Client. + let args_ident = format_ident!("{}", name); + quote! { + impl #args_ident { + #(#fns)* + } + } +} diff --git a/soroban-sdk-macros/src/derive_client.rs b/soroban-sdk-macros/src/derive_client.rs index 07a0504e1..aa896c633 100644 --- a/soroban-sdk-macros/src/derive_client.rs +++ b/soroban-sdk-macros/src/derive_client.rs @@ -192,7 +192,7 @@ pub fn derive_client_impl(crate_path: &Path, name: &str, fns: &[syn_ext::Fn]) -> format_ident!("_") } }; - (ident, syn_ext::fn_arg_make_ref(t)) + (ident, syn_ext::fn_arg_make_ref(t, None)) }) .unzip(); let fn_output = f.output(); diff --git a/soroban-sdk-macros/src/lib.rs b/soroban-sdk-macros/src/lib.rs index 69231f92c..60d9b264a 100644 --- a/soroban-sdk-macros/src/lib.rs +++ b/soroban-sdk-macros/src/lib.rs @@ -1,6 +1,7 @@ extern crate proc_macro; mod arbitrary; +mod derive_args; mod derive_client; mod derive_enum; mod derive_enum_int; @@ -15,6 +16,7 @@ mod path; mod symbol; mod syn_ext; +use derive_args::{derive_args_impl, derive_args_type}; use derive_client::{derive_client_impl, derive_client_type}; use derive_enum::derive_type_enum; use derive_enum_int::derive_type_enum_int; @@ -136,8 +138,11 @@ pub fn contract(metadata: TokenStream, input: TokenStream) -> TokenStream { let fn_set_registry_ident = format_ident!("__{}_fn_set_registry", ty_str.to_lowercase()); let crate_path = &args.crate_path; let client = derive_client_type(&args.crate_path, &ty_str, &client_ident); + let args_ident = format!("{ty_str}Args"); + let contract_args = derive_args_type(&ty_str, &args_ident); let mut output = quote! { #input2 + #contract_args #client }; if cfg!(feature = "testutils") { @@ -206,6 +211,18 @@ pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream { let ty = &imp.self_ty; let ty_str = quote!(#ty).to_string(); + // TODO: Use imp.trait_ in generating the args ident, to create a unique + // args for each trait impl for a contract, to avoid conflicts. + let args_ident = if let Type::Path(path) = &**ty { + path.path + .segments + .last() + .map(|name| format!("{}Args", name.ident)) + } else { + None + } + .unwrap_or_else(|| "Args".to_string()); + // TODO: Use imp.trait_ in generating the client ident, to create a unique // client for each trait impl for a contract, to avoid conflicts. let client_ident = if let Type::Path(path) = &**ty { @@ -239,6 +256,7 @@ pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream { match derived { Ok(derived_ok) => { let mut output = quote! { + #[#crate_path::contractargs(crate_path = #crate_path_str, 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)] #imp @@ -533,6 +551,40 @@ pub fn contractfile(metadata: TokenStream) -> TokenStream { quote! { #contents_lit }.into() } +#[derive(Debug, FromMeta)] +struct ContractArgsArgs { + #[darling(default = "default_crate_path")] + crate_path: Path, + name: String, + #[darling(default)] + impl_only: bool, +} + +#[proc_macro_attribute] +pub fn contractargs(metadata: TokenStream, input: TokenStream) -> TokenStream { + let args = match NestedMeta::parse_meta_list(metadata.into()) { + Ok(v) => v, + Err(e) => { + return TokenStream::from(darling::Error::from(e).write_errors()); + } + }; + let args = match ContractArgsArgs::from_list(&args) { + Ok(v) => v, + Err(e) => return e.write_errors().into(), + }; + let input2: TokenStream2 = input.clone().into(); + let item = parse_macro_input!(input as HasFnsItem); + let methods: Vec<_> = item.fns(); + let args_type = (!args.impl_only).then(|| derive_args_type(&item.name(), &args.name)); + let args_impl = derive_args_impl(&args.crate_path, &args.name, &methods); + quote! { + #input2 + #args_type + #args_impl + } + .into() +} + #[derive(Debug, FromMeta)] struct ContractClientArgs { #[darling(default = "default_crate_path")] diff --git a/soroban-sdk-macros/src/syn_ext.rs b/soroban-sdk-macros/src/syn_ext.rs index 71f91216c..fd515d5cd 100644 --- a/soroban-sdk-macros/src/syn_ext.rs +++ b/soroban-sdk-macros/src/syn_ext.rs @@ -9,7 +9,7 @@ use syn::{ }; use syn::{ spanned::Spanned, token::And, Error, FnArg, Ident, ImplItem, ImplItemFn, ItemImpl, ItemTrait, - Pat, PatType, TraitItem, TraitItemFn, Type, TypeReference, Visibility, + Lifetime, Pat, PatType, TraitItem, TraitItemFn, Type, TypeReference, Visibility, }; /// Gets methods from the implementation that have public visibility. For @@ -49,7 +49,7 @@ pub fn fn_arg_ident(arg: &FnArg) -> Result { /// Returns a clone of FnArg with the type as a reference if the arg is a typed /// arg and its type is not already a reference. -pub fn fn_arg_make_ref(arg: &FnArg) -> FnArg { +pub fn fn_arg_make_ref(arg: &FnArg, lifetime: Option<&Lifetime>) -> FnArg { if let FnArg::Typed(pat_type) = arg { if !matches!(*pat_type.ty, Type::Reference(_)) { return FnArg::Typed(PatType { @@ -58,7 +58,7 @@ pub fn fn_arg_make_ref(arg: &FnArg) -> FnArg { colon_token: pat_type.colon_token, ty: Box::new(Type::Reference(TypeReference { and_token: And::default(), - lifetime: None, + lifetime: lifetime.cloned(), mutability: None, elem: pat_type.ty.clone(), })), diff --git a/soroban-sdk/src/lib.rs b/soroban-sdk/src/lib.rs index 95207d7a9..a605a26df 100644 --- a/soroban-sdk/src/lib.rs +++ b/soroban-sdk/src/lib.rs @@ -593,6 +593,11 @@ pub use soroban_sdk_macros::contractmeta; /// ``` pub use soroban_sdk_macros::contracttype; +/// Generates a type that helps build function args for a contract trait. +/// +/// TODO: Docs and examples. +pub use soroban_sdk_macros::contractargs; + /// Generates a client for a contract trait. /// /// Can be used to create clients for contracts that live outside the current diff --git a/soroban-spec-rust/src/lib.rs b/soroban-spec-rust/src/lib.rs index 5e62c4172..83cc5d292 100644 --- a/soroban-spec-rust/src/lib.rs +++ b/soroban-spec-rust/src/lib.rs @@ -89,6 +89,7 @@ pub fn generate_without_file(specs: &[ScSpecEntry]) -> TokenStream { let error_enums = spec_error_enums.iter().map(|s| generate_error_enum(s)); quote! { + #[soroban_sdk::contractargs(name = "Args")] #[soroban_sdk::contractclient(name = "Client")] #trait_ diff --git a/tests/constructor/src/lib.rs b/tests/constructor/src/lib.rs index 3868bf933..e5e0230e7 100644 --- a/tests/constructor/src/lib.rs +++ b/tests/constructor/src/lib.rs @@ -37,7 +37,7 @@ impl Contract { #[test] fn test_constructor() { let env = Env::default(); - let contract_id = env.register(Contract, (100_u32, 1000_i64)); + let contract_id = env.register(Contract, ContractArgs::__constructor(100_u32, 1000_i64)); let client = ContractClient::new(&env, &contract_id); assert_eq!(client.get_data(&DataKey::Persistent(100)), Some(1000)); assert_eq!(client.get_data(&DataKey::Temp(200)), Some(2000));