Skip to main content

soroban_sdk_macros/
lib.rs

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