1extern crate proc_macro;
2
3mod arbitrary;
4mod attribute;
5mod derive_args;
6mod derive_client;
7mod derive_contractimpl_trait_default_fns_not_overridden;
8mod derive_contractimpl_trait_macro;
9mod derive_enum;
10mod derive_enum_int;
11mod derive_error_enum_int;
12mod derive_event;
13mod derive_fn;
14mod derive_spec_fn;
15mod derive_struct;
16mod derive_struct_tuple;
17mod derive_trait;
18mod doc;
19mod map_type;
20mod path;
21mod symbol;
22mod syn_ext;
23
24use derive_args::{derive_args_impl, derive_args_type};
25use derive_client::{derive_client_impl, derive_client_type};
26use derive_contractimpl_trait_default_fns_not_overridden::derive_contractimpl_trait_default_fns_not_overridden;
27use derive_contractimpl_trait_macro::{
28 derive_contractimpl_trait_macro, generate_call_to_contractimpl_for_trait,
29};
30use derive_enum::derive_type_enum;
31use derive_enum_int::derive_type_enum_int;
32use derive_error_enum_int::derive_type_error_enum_int;
33use derive_event::derive_event;
34use derive_fn::{derive_contract_function_registration_ctor, derive_pub_fns};
35use derive_spec_fn::derive_fns_spec;
36use derive_struct::derive_type_struct;
37use derive_struct_tuple::derive_type_struct_tuple;
38use derive_trait::derive_trait;
39
40use darling::{ast::NestedMeta, FromMeta};
41use macro_string::MacroString;
42use proc_macro::TokenStream;
43use proc_macro2::{Literal, Span, TokenStream as TokenStream2};
44use quote::{format_ident, quote, ToTokens};
45use sha2::{Digest, Sha256};
46use std::{fmt::Write, fs};
47use syn::{
48 parse_macro_input, parse_str, spanned::Spanned, Data, DeriveInput, Error, Expr, Fields,
49 ItemImpl, ItemStruct, LitStr, Path, Type, Visibility,
50};
51use syn_ext::HasFnsItem;
52
53use soroban_spec_rust::{generate_from_wasm, GenerateFromFileError};
54
55use stellar_xdr::curr as stellar_xdr;
56use stellar_xdr::{Limits, ScMetaEntry, ScMetaV0, StringM, WriteXdr};
57
58pub(crate) const DEFAULT_XDR_RW_LIMITS: Limits = Limits {
59 depth: 500,
60 len: 0x1000000,
61};
62
63#[proc_macro]
64pub fn internal_symbol_short(input: TokenStream) -> TokenStream {
65 let input = parse_macro_input!(input as LitStr);
66 let crate_path: Path = syn::parse_str("crate").unwrap();
67 symbol::short(&crate_path, &input).into()
68}
69
70#[proc_macro]
71pub fn symbol_short(input: TokenStream) -> TokenStream {
72 let input = parse_macro_input!(input as LitStr);
73 let crate_path: Path = syn::parse_str("soroban_sdk").unwrap();
74 symbol::short(&crate_path, &input).into()
75}
76
77pub(crate) fn default_crate_path() -> Path {
78 parse_str("soroban_sdk").unwrap()
79}
80
81#[derive(Debug, FromMeta)]
82struct ContractSpecArgs {
83 name: Type,
84 export: Option<bool>,
85}
86
87#[proc_macro_attribute]
88pub fn contractspecfn(metadata: TokenStream, input: TokenStream) -> TokenStream {
89 let args = match NestedMeta::parse_meta_list(metadata.into()) {
90 Ok(v) => v,
91 Err(e) => {
92 return TokenStream::from(darling::Error::from(e).write_errors());
93 }
94 };
95 let args = match ContractSpecArgs::from_list(&args) {
96 Ok(v) => v,
97 Err(e) => return e.write_errors().into(),
98 };
99 let input2: TokenStream2 = input.clone().into();
100 let item = parse_macro_input!(input as HasFnsItem);
101 let methods: Vec<_> = item.fns();
102 let export = args.export.unwrap_or(true);
103
104 let derived = derive_fns_spec(&args.name, &methods, export);
105
106 match derived {
107 Ok(derived_ok) => quote! {
108 #input2
109 #derived_ok
110 }
111 .into(),
112 Err(derived_err) => quote! {
113 #input2
114 #derived_err
115 }
116 .into(),
117 }
118}
119
120#[derive(Debug, FromMeta)]
121struct ContractArgs {
122 #[darling(default = "default_crate_path")]
123 crate_path: Path,
124}
125
126#[proc_macro_attribute]
127pub fn contract(metadata: TokenStream, input: TokenStream) -> TokenStream {
128 let args = match NestedMeta::parse_meta_list(metadata.into()) {
129 Ok(v) => v,
130 Err(e) => {
131 return TokenStream::from(darling::Error::from(e).write_errors());
132 }
133 };
134 let args = match ContractArgs::from_list(&args) {
135 Ok(v) => v,
136 Err(e) => return e.write_errors().into(),
137 };
138
139 let input2: TokenStream2 = input.clone().into();
140
141 let item = parse_macro_input!(input as ItemStruct);
142
143 let ty = &item.ident;
144 let ty_str = quote!(#ty).to_string();
145
146 let client_ident = format!("{ty_str}Client");
147 let fn_set_registry_ident = format_ident!("__{}_fn_set_registry", ty_str.to_lowercase());
148 let crate_path = &args.crate_path;
149 let client = derive_client_type(&args.crate_path, &ty_str, &client_ident);
150 let args_ident = format!("{ty_str}Args");
151 let contract_args = derive_args_type(&ty_str, &args_ident);
152 let mut output = quote! {
153 #input2
154 #contract_args
155 #client
156 };
157 if cfg!(feature = "testutils") {
158 output.extend(quote! {
159 mod #fn_set_registry_ident {
160 use super::*;
161
162 extern crate std;
163 use std::sync::Mutex;
164 use std::collections::BTreeMap;
165
166 pub type F = #crate_path::testutils::ContractFunctionF;
167
168 static FUNCS: Mutex<BTreeMap<&'static str, &'static F>> = Mutex::new(BTreeMap::new());
169
170 pub fn register(name: &'static str, func: &'static F) {
171 FUNCS.lock().unwrap().insert(name, func);
172 }
173
174 pub fn call(name: &str, env: #crate_path::Env, args: &[#crate_path::Val]) -> Option<#crate_path::Val> {
175 let fopt: Option<&'static F> = FUNCS.lock().unwrap().get(name).map(|f| f.clone());
176 fopt.map(|f| f(env, args))
177 }
178 }
179
180 impl #crate_path::testutils::ContractFunctionRegister for #ty {
181 fn register(name: &'static str, func: &'static #fn_set_registry_ident::F) {
182 #fn_set_registry_ident::register(name, func);
183 }
184 }
185
186 #[doc(hidden)]
187 impl #crate_path::testutils::ContractFunctionSet for #ty {
188 fn call(&self, func: &str, env: #crate_path::Env, args: &[#crate_path::Val]) -> Option<#crate_path::Val> {
189 #fn_set_registry_ident::call(func, env, args)
190 }
191 }
192 });
193 }
194 output.into()
195}
196
197#[derive(Debug, FromMeta)]
198struct ContractImplArgs {
199 #[darling(default = "default_crate_path")]
200 crate_path: Path,
201 #[darling(default)]
202 contracttrait: bool,
203}
204
205#[proc_macro_attribute]
206pub fn contractimpl(metadata: TokenStream, input: TokenStream) -> TokenStream {
207 let args = match NestedMeta::parse_meta_list(metadata.into()) {
208 Ok(v) => v,
209 Err(e) => {
210 return TokenStream::from(darling::Error::from(e).write_errors());
211 }
212 };
213 let args = match ContractImplArgs::from_list(&args) {
214 Ok(v) => v,
215 Err(e) => return e.write_errors().into(),
216 };
217 let crate_path = &args.crate_path;
218 let crate_path_str = quote!(#crate_path).to_string();
219
220 let imp = parse_macro_input!(input as ItemImpl);
221 let trait_ident = imp.trait_.as_ref().and_then(|x| x.1.get_ident());
222 let ty = &imp.self_ty;
223 let ty_str = quote!(#ty).to_string();
224
225 let args_ident = if let Type::Path(path) = &**ty {
228 path.path
229 .segments
230 .last()
231 .map(|name| format!("{}Args", name.ident))
232 } else {
233 None
234 }
235 .unwrap_or_else(|| "Args".to_string());
236
237 let client_ident = if let Type::Path(path) = &**ty {
240 path.path
241 .segments
242 .last()
243 .map(|name| format!("{}Client", name.ident))
244 } else {
245 None
246 }
247 .unwrap_or_else(|| "Client".to_string());
248
249 let pub_methods: Vec<_> = syn_ext::impl_pub_methods(&imp);
250 let pub_methods_fns: Vec<syn_ext::Fn> = pub_methods.iter().map(Into::into).collect();
251 let derived = derive_pub_fns(
252 crate_path,
253 &ty,
254 &pub_methods_fns,
255 trait_ident,
256 &client_ident,
257 );
258
259 match derived {
260 Ok(derived_ok) => {
261 let mut output = quote! {
262 #[#crate_path::contractargs(name = #args_ident, impl_only = true)]
263 #[#crate_path::contractclient(crate_path = #crate_path_str, name = #client_ident, impl_only = true)]
264 #[#crate_path::contractspecfn(name = #ty_str)]
265 #imp
266 #derived_ok
267 };
268 let contractimpl_for_trait =
269 trait_ident
270 .filter(|_| args.contracttrait)
271 .map(|trait_ident| {
272 generate_call_to_contractimpl_for_trait(
273 trait_ident.into(),
274 ty,
275 &pub_methods,
276 &client_ident,
277 &args_ident,
278 &ty_str,
279 )
280 });
281 output.extend(quote! { #contractimpl_for_trait });
282 let cfs = derive_contract_function_registration_ctor(
283 crate_path,
284 ty,
285 trait_ident,
286 pub_methods.iter().map(|m| &m.sig.ident),
287 );
288 output.extend(quote! { #cfs });
289 output.into()
290 }
291 Err(derived_err) => quote! {
292 #imp
293 #derived_err
294 }
295 .into(),
296 }
297}
298
299#[proc_macro_attribute]
300pub fn contracttrait(metadata: TokenStream, input: TokenStream) -> TokenStream {
301 derive_trait(metadata.into(), input.into()).into()
302}
303
304#[proc_macro_attribute]
305pub fn contractimpl_trait_macro(metadata: TokenStream, input: TokenStream) -> TokenStream {
306 derive_contractimpl_trait_macro(metadata.into(), input.into()).into()
307}
308
309#[proc_macro]
310pub fn contractimpl_trait_default_fns_not_overridden(input: TokenStream) -> TokenStream {
311 derive_contractimpl_trait_default_fns_not_overridden(input.into()).into()
312}
313
314#[derive(Debug, FromMeta)]
315struct MetadataArgs {
316 key: String,
317 #[darling(with = darling::util::parse_expr::preserve_str_literal)]
318 val: Expr,
319}
320
321#[proc_macro]
322pub fn contractmeta(metadata: TokenStream) -> TokenStream {
323 let args = match NestedMeta::parse_meta_list(metadata.into()) {
324 Ok(v) => v,
325 Err(e) => {
326 return TokenStream::from(darling::Error::from(e).write_errors());
327 }
328 };
329 let args = match MetadataArgs::from_list(&args) {
330 Ok(v) => v,
331 Err(e) => return e.write_errors().into(),
332 };
333
334 let gen = {
335 let key: StringM = match args.key.clone().try_into() {
336 Ok(k) => k,
337 Err(e) => {
338 return Error::new(Span::call_site(), e.to_string())
339 .into_compile_error()
340 .into()
341 }
342 };
343
344 let val = args.val.to_token_stream().into();
345 let MacroString(val) = parse_macro_input!(val);
346 let val: StringM = match val.try_into() {
347 Ok(k) => k,
348 Err(e) => {
349 return Error::new(Span::call_site(), e.to_string())
350 .into_compile_error()
351 .into()
352 }
353 };
354
355 let meta_v0 = ScMetaV0 { key, val };
356 let meta_entry = ScMetaEntry::ScMetaV0(meta_v0);
357 let metadata_xdr: Vec<u8> = match meta_entry.to_xdr(DEFAULT_XDR_RW_LIMITS) {
358 Ok(v) => v,
359 Err(e) => {
360 return Error::new(Span::call_site(), e.to_string())
361 .into_compile_error()
362 .into()
363 }
364 };
365
366 let metadata_xdr_lit = proc_macro2::Literal::byte_string(metadata_xdr.as_slice());
367 let metadata_xdr_len = metadata_xdr.len();
368
369 let ident = format_ident!(
370 "__CONTRACT_KEY_{}",
371 args.key.as_bytes().iter().fold(String::new(), |mut s, b| {
372 let _ = write!(s, "{b:02x}");
373 s
374 })
375 );
376 quote! {
377 #[doc(hidden)]
378 #[cfg_attr(target_family = "wasm", link_section = "contractmetav0")]
379 static #ident: [u8; #metadata_xdr_len] = *#metadata_xdr_lit;
380 }
381 };
382
383 quote! {
384 #gen
385 }
386 .into()
387}
388
389#[proc_macro_attribute]
390pub fn contractevent(metadata: TokenStream, input: TokenStream) -> TokenStream {
391 derive_event(metadata.into(), input.into()).into()
392}
393
394#[derive(Debug, FromMeta)]
395struct ContractTypeArgs {
396 #[darling(default = "default_crate_path")]
397 crate_path: Path,
398 lib: Option<String>,
399 export: Option<bool>,
400}
401
402#[proc_macro_attribute]
403pub fn contracttype(metadata: TokenStream, input: TokenStream) -> TokenStream {
404 let args = match NestedMeta::parse_meta_list(metadata.into()) {
405 Ok(v) => v,
406 Err(e) => {
407 return TokenStream::from(darling::Error::from(e).write_errors());
408 }
409 };
410 let args = match ContractTypeArgs::from_list(&args) {
411 Ok(v) => v,
412 Err(e) => return e.write_errors().into(),
413 };
414 let input = parse_macro_input!(input as DeriveInput);
415 let vis = &input.vis;
416 let ident = &input.ident;
417 let attrs = &input.attrs;
418 let gen_spec = if let Some(export) = args.export {
421 export
422 } else {
423 matches!(input.vis, Visibility::Public(_))
424 };
425 let derived = match &input.data {
426 Data::Struct(s) => match s.fields {
427 Fields::Named(_) => {
428 derive_type_struct(&args.crate_path, vis, ident, attrs, s, gen_spec, &args.lib)
429 }
430 Fields::Unnamed(_) => derive_type_struct_tuple(
431 &args.crate_path,
432 vis,
433 ident,
434 attrs,
435 s,
436 gen_spec,
437 &args.lib,
438 ),
439 Fields::Unit => Error::new(
440 s.fields.span(),
441 "unit structs are not supported as contract types",
442 )
443 .to_compile_error(),
444 },
445 Data::Enum(e) => {
446 let count_of_variants = e.variants.len();
447 let count_of_int_variants = e
448 .variants
449 .iter()
450 .filter(|v| v.discriminant.is_some())
451 .count();
452 if count_of_int_variants == 0 {
453 derive_type_enum(&args.crate_path, vis, ident, attrs, e, gen_spec, &args.lib)
454 } else if count_of_int_variants == count_of_variants {
455 derive_type_enum_int(&args.crate_path, vis, ident, attrs, e, gen_spec, &args.lib)
456 } else {
457 Error::new(input.span(), "enums are supported as contract types only when all variants have an explicit integer literal, or when all variants are unit or single field")
458 .to_compile_error()
459 }
460 }
461 Data::Union(u) => Error::new(
462 u.union_token.span(),
463 "unions are unsupported as contract types",
464 )
465 .to_compile_error(),
466 };
467 quote! {
468 #input
469 #derived
470 }
471 .into()
472}
473
474#[proc_macro_attribute]
475pub fn contracterror(metadata: TokenStream, input: TokenStream) -> TokenStream {
476 let args = match NestedMeta::parse_meta_list(metadata.into()) {
477 Ok(v) => v,
478 Err(e) => {
479 return TokenStream::from(darling::Error::from(e).write_errors());
480 }
481 };
482 let args = match ContractTypeArgs::from_list(&args) {
483 Ok(v) => v,
484 Err(e) => return e.write_errors().into(),
485 };
486 let input = parse_macro_input!(input as DeriveInput);
487 let ident = &input.ident;
488 let attrs = &input.attrs;
489 let gen_spec = if let Some(export) = args.export {
492 export
493 } else {
494 matches!(input.vis, Visibility::Public(_))
495 };
496 let derived = match &input.data {
497 Data::Enum(e) => {
498 if e.variants.iter().all(|v| v.discriminant.is_some()) {
499 derive_type_error_enum_int(&args.crate_path, ident, attrs, e, gen_spec, &args.lib)
500 } else {
501 Error::new(input.span(), "enums are supported as contract errors only when all variants have an explicit integer literal")
502 .to_compile_error()
503 }
504 }
505 Data::Struct(s) => Error::new(
506 s.struct_token.span(),
507 "structs are unsupported as contract errors",
508 )
509 .to_compile_error(),
510 Data::Union(u) => Error::new(
511 u.union_token.span(),
512 "unions are unsupported as contract errors",
513 )
514 .to_compile_error(),
515 };
516 quote! {
517 #input
518 #derived
519 }
520 .into()
521}
522
523#[derive(Debug, FromMeta)]
524struct ContractFileArgs {
525 file: String,
526 sha256: darling::util::SpannedValue<String>,
527}
528
529#[proc_macro]
530pub fn contractfile(metadata: TokenStream) -> TokenStream {
531 let args = match NestedMeta::parse_meta_list(metadata.into()) {
532 Ok(v) => v,
533 Err(e) => {
534 return TokenStream::from(darling::Error::from(e).write_errors());
535 }
536 };
537 let args = match ContractFileArgs::from_list(&args) {
538 Ok(v) => v,
539 Err(e) => return e.write_errors().into(),
540 };
541
542 let file_abs = path::abs_from_rel_to_manifest(&args.file);
544 let wasm = match fs::read(file_abs) {
545 Ok(wasm) => wasm,
546 Err(e) => {
547 return Error::new(Span::call_site(), e.to_string())
548 .into_compile_error()
549 .into()
550 }
551 };
552
553 let sha256 = Sha256::digest(&wasm);
555 let sha256 = format!("{:x}", sha256);
556 if *args.sha256 != sha256 {
557 return Error::new(
558 args.sha256.span(),
559 format!("sha256 does not match, expected: {}", sha256),
560 )
561 .into_compile_error()
562 .into();
563 }
564
565 let contents_lit = Literal::byte_string(&wasm);
567 quote! { #contents_lit }.into()
568}
569
570#[derive(Debug, FromMeta)]
571struct ContractArgsArgs {
572 name: String,
573 #[darling(default)]
574 impl_only: bool,
575}
576
577#[proc_macro_attribute]
578pub fn contractargs(metadata: TokenStream, input: TokenStream) -> TokenStream {
579 let args = match NestedMeta::parse_meta_list(metadata.into()) {
580 Ok(v) => v,
581 Err(e) => {
582 return TokenStream::from(darling::Error::from(e).write_errors());
583 }
584 };
585 let args = match ContractArgsArgs::from_list(&args) {
586 Ok(v) => v,
587 Err(e) => return e.write_errors().into(),
588 };
589 let input2: TokenStream2 = input.clone().into();
590 let item = parse_macro_input!(input as HasFnsItem);
591 let methods: Vec<_> = item.fns();
592 let args_type = (!args.impl_only).then(|| derive_args_type(&item.name(), &args.name));
593 let args_impl = derive_args_impl(&args.name, &methods);
594 quote! {
595 #input2
596 #args_type
597 #args_impl
598 }
599 .into()
600}
601
602#[derive(Debug, FromMeta)]
603struct ContractClientArgs {
604 #[darling(default = "default_crate_path")]
605 crate_path: Path,
606 name: String,
607 #[darling(default)]
608 impl_only: bool,
609}
610
611#[proc_macro_attribute]
612pub fn contractclient(metadata: TokenStream, input: TokenStream) -> TokenStream {
613 let args = match NestedMeta::parse_meta_list(metadata.into()) {
614 Ok(v) => v,
615 Err(e) => {
616 return TokenStream::from(darling::Error::from(e).write_errors());
617 }
618 };
619 let args = match ContractClientArgs::from_list(&args) {
620 Ok(v) => v,
621 Err(e) => return e.write_errors().into(),
622 };
623 let input2: TokenStream2 = input.clone().into();
624 let item = parse_macro_input!(input as HasFnsItem);
625 let methods: Vec<_> = item.fns();
626 let client_type =
627 (!args.impl_only).then(|| derive_client_type(&args.crate_path, &item.name(), &args.name));
628 let client_impl = derive_client_impl(&args.crate_path, &args.name, &methods);
629 quote! {
630 #input2
631 #client_type
632 #client_impl
633 }
634 .into()
635}
636
637#[derive(Debug, FromMeta)]
638struct ContractImportArgs {
639 file: String,
640 #[darling(default)]
641 sha256: darling::util::SpannedValue<Option<String>>,
642}
643#[proc_macro]
644pub fn contractimport(metadata: TokenStream) -> TokenStream {
645 let args = match NestedMeta::parse_meta_list(metadata.into()) {
646 Ok(v) => v,
647 Err(e) => {
648 return TokenStream::from(darling::Error::from(e).write_errors());
649 }
650 };
651 let args = match ContractImportArgs::from_list(&args) {
652 Ok(v) => v,
653 Err(e) => return e.write_errors().into(),
654 };
655
656 let file_abs = path::abs_from_rel_to_manifest(&args.file);
658 let wasm = match fs::read(file_abs) {
659 Ok(wasm) => wasm,
660 Err(e) => {
661 return Error::new(Span::call_site(), e.to_string())
662 .into_compile_error()
663 .into()
664 }
665 };
666
667 match generate_from_wasm(&wasm, &args.file, args.sha256.as_deref()) {
669 Ok(code) => quote! { #code },
670 Err(e @ GenerateFromFileError::VerifySha256 { .. }) => {
671 Error::new(args.sha256.span(), e.to_string()).into_compile_error()
672 }
673 Err(e) => Error::new(Span::call_site(), e.to_string()).into_compile_error(),
674 }
675 .into()
676}