Skip to main content

ab_contracts_macros_impl/contract/
method.rs

1use crate::contract::common::{derive_ident_metadata, extract_ident_from_type};
2use ident_case::RenameRule;
3use proc_macro2::{Ident, Literal, Span, TokenStream};
4use quote::{format_ident, quote, quote_spanned};
5use syn::spanned::Spanned;
6use syn::{
7    Attribute, Error, GenericArgument, Meta, Pat, PatType, PathArguments, ReturnType, Signature,
8    Token, Type, TypeTuple,
9};
10
11#[derive(Copy, Clone)]
12pub(super) enum MethodType {
13    Init,
14    Update,
15    View,
16}
17
18impl MethodType {
19    fn attr_str(self) -> &'static str {
20        match self {
21            MethodType::Init => "init",
22            MethodType::Update => "update",
23            MethodType::View => "view",
24        }
25    }
26}
27
28#[derive(Clone)]
29struct Env {
30    arg_name: Ident,
31    mutability: Option<Token![mut]>,
32}
33
34#[derive(Clone)]
35struct Tmp {
36    type_name: Type,
37    arg_name: Ident,
38    mutability: Option<Token![mut]>,
39}
40
41#[derive(Clone)]
42struct Slot {
43    with_address_arg: bool,
44    type_name: Type,
45    arg_name: Ident,
46    mutability: Option<Token![mut]>,
47}
48
49#[derive(Clone)]
50struct Input {
51    type_name: Type,
52    arg_name: Ident,
53}
54
55#[derive(Clone)]
56struct Output {
57    type_name: Type,
58    arg_name: Ident,
59    has_self: bool,
60}
61
62enum MethodReturnType {
63    /// The function doesn't have any return type defined
64    Unit(Type),
65    /// Returns a type without [`Result`]
66    Regular(Type),
67    /// Returns [`Result`], but [`Ok`] variant is `()`
68    ResultUnit(Type),
69    /// Returns [`Result`], but [`Ok`] variant is not `()`
70    Result(Type),
71}
72
73impl MethodReturnType {
74    fn unit_type() -> Type {
75        Type::Tuple(TypeTuple {
76            paren_token: Default::default(),
77            elems: Default::default(),
78        })
79    }
80
81    fn unit_return_type(&self) -> bool {
82        match self {
83            Self::Unit(_) | Self::ResultUnit(_) => true,
84            Self::Regular(_) | Self::Result(_) => false,
85        }
86    }
87
88    fn return_type(&self) -> &Type {
89        match self {
90            Self::Unit(ty) | Self::Regular(ty) | Self::ResultUnit(ty) | Self::Result(ty) => ty,
91        }
92    }
93}
94
95#[derive(Default)]
96pub(super) struct ExtTraitComponents {
97    pub(super) definition: TokenStream,
98    pub(super) r#impl: TokenStream,
99}
100
101pub(super) struct MethodDetails {
102    method_type: MethodType,
103    self_type: Type,
104    state: Option<Option<Token![mut]>>,
105    env: Option<Env>,
106    tmp: Option<Tmp>,
107    slots: Vec<Slot>,
108    inputs: Vec<Input>,
109    outputs: Vec<Output>,
110    return_type: MethodReturnType,
111}
112
113impl MethodDetails {
114    pub(super) fn new(method_type: MethodType, self_type: Type) -> Self {
115        Self {
116            method_type,
117            self_type,
118            state: None,
119            env: None,
120            tmp: None,
121            slots: Vec::new(),
122            inputs: Vec::new(),
123            outputs: Vec::new(),
124            return_type: MethodReturnType::Unit(MethodReturnType::unit_type()),
125        }
126    }
127
128    /// Returns `#[tmp]` type (or `()` if it is not used) if all methods have the same slots type
129    pub(super) fn tmp_type<'a, I>(iter: I) -> Option<Type>
130    where
131        I: Iterator<Item = &'a Self> + 'a,
132    {
133        let mut tmp_type = None;
134        for slot in iter.flat_map(|method_details| &method_details.tmp) {
135            match &tmp_type {
136                Some(tmp_type) => {
137                    if tmp_type != &slot.type_name {
138                        return None;
139                    }
140                }
141                None => {
142                    tmp_type.replace(slot.type_name.clone());
143                }
144            }
145        }
146
147        Some(tmp_type.unwrap_or_else(MethodReturnType::unit_type))
148    }
149
150    /// Returns `#[slot]` type (or `()` if it is not used) if all methods have the same slots type
151    pub(super) fn slot_type<'a, I>(iter: I) -> Option<Type>
152    where
153        I: Iterator<Item = &'a Self> + 'a,
154    {
155        let mut slot_type = None;
156        for slot in iter.flat_map(|method_details| &method_details.slots) {
157            match &slot_type {
158                Some(slot_type) => {
159                    if slot_type != &slot.type_name {
160                        return None;
161                    }
162                }
163                None => {
164                    slot_type.replace(slot.type_name.clone());
165                }
166            }
167        }
168
169        Some(slot_type.unwrap_or_else(MethodReturnType::unit_type))
170    }
171
172    pub(super) fn process_env_arg_ro(
173        &mut self,
174        input_span: Span,
175        pat_type: &PatType,
176    ) -> Result<(), Error> {
177        self.process_env_arg(input_span, pat_type, false)
178    }
179
180    pub(super) fn process_env_arg_rw(
181        &mut self,
182        input_span: Span,
183        pat_type: &PatType,
184    ) -> Result<(), Error> {
185        self.process_env_arg(input_span, pat_type, true)
186    }
187
188    fn process_env_arg(
189        &mut self,
190        input_span: Span,
191        pat_type: &PatType,
192        allow_mut: bool,
193    ) -> Result<(), Error> {
194        if self.env.is_some()
195            || self.tmp.is_some()
196            || !(self.inputs.is_empty() && self.outputs.is_empty())
197        {
198            return Err(Error::new(
199                input_span,
200                "`#[env]` must be the first non-Self argument and only appear once",
201            ));
202        }
203
204        if let Type::Reference(type_reference) = &*pat_type.ty
205            && let Type::Path(_type_path) = &*type_reference.elem
206            && let Pat::Ident(pat_ident) = &*pat_type.pat
207        {
208            if type_reference.mutability.is_some() && !allow_mut {
209                return Err(Error::new(
210                    input_span,
211                    "`#[env]` is not allowed to mutate data here",
212                ));
213            }
214
215            self.env.replace(Env {
216                arg_name: pat_ident.ident.clone(),
217                mutability: type_reference.mutability,
218            });
219            Ok(())
220        } else {
221            Err(Error::new(
222                pat_type.span(),
223                "`#[env]` must be a reference to `Env` type (can be shared or exclusive)",
224            ))
225        }
226    }
227
228    pub(super) fn process_state_arg_ro(
229        &mut self,
230        input_span: Span,
231        ty: &Type,
232    ) -> Result<(), Error> {
233        self.process_state_arg(input_span, ty, false)
234    }
235
236    pub(super) fn process_state_arg_rw(
237        &mut self,
238        input_span: Span,
239        ty: &Type,
240    ) -> Result<(), Error> {
241        self.process_state_arg(input_span, ty, true)
242    }
243
244    fn process_state_arg(
245        &mut self,
246        input_span: Span,
247        ty: &Type,
248        allow_mut: bool,
249    ) -> Result<(), Error> {
250        // Only accept `&self` or `&mut self`
251        if let Type::Reference(type_reference) = ty
252            && let Type::Path(type_path) = &*type_reference.elem
253            && type_path.path.is_ident("Self")
254        {
255            if type_reference.mutability.is_some() && !allow_mut {
256                return Err(Error::new(
257                    input_span,
258                    "`#[arg]` is not allowed to mutate data here",
259                ));
260            }
261
262            self.state.replace(type_reference.mutability);
263            Ok(())
264        } else {
265            Err(Error::new(
266                ty.span(),
267                "Can't consume `Self`, use `&self` or `&mut self` instead",
268            ))
269        }
270    }
271
272    pub(super) fn process_tmp_arg(
273        &mut self,
274        input_span: Span,
275        pat_type: &PatType,
276    ) -> Result<(), Error> {
277        if self.tmp.is_some() || !(self.inputs.is_empty() && self.outputs.is_empty()) {
278            return Err(Error::new(
279                input_span,
280                "`#[tmp]` must appear only once before any `#[input]` or `#[output]`",
281            ));
282        }
283
284        // Check if input looks like `&Type` or `&mut Type`
285        if let Type::Reference(type_reference) = &*pat_type.ty {
286            let Some(arg_name) = extract_arg_name(&pat_type.pat) else {
287                return Err(Error::new(
288                    pat_type.span(),
289                    "`#[tmp]` argument name must be either a simple variable or a reference",
290                ));
291            };
292
293            self.tmp.replace(Tmp {
294                type_name: type_reference.elem.as_ref().clone(),
295                arg_name,
296                mutability: type_reference.mutability,
297            });
298
299            return Ok(());
300        }
301
302        Err(Error::new(
303            pat_type.span(),
304            "`#[tmp]` must be a reference to a type implementing `IoTypeOptional` (can be \
305            shared or exclusive) like `&MaybeData<Slot>` or `&mut VariableBytes<1024>`",
306        ))
307    }
308
309    pub(super) fn process_slot_arg_ro(
310        &mut self,
311        input_span: Span,
312        pat_type: &PatType,
313    ) -> Result<(), Error> {
314        self.process_slot_arg(input_span, pat_type, false)
315    }
316
317    pub(super) fn process_slot_arg_rw(
318        &mut self,
319        input_span: Span,
320        pat_type: &PatType,
321    ) -> Result<(), Error> {
322        self.process_slot_arg(input_span, pat_type, true)
323    }
324
325    fn process_slot_arg(
326        &mut self,
327        input_span: Span,
328        pat_type: &PatType,
329        allow_mut: bool,
330    ) -> Result<(), Error> {
331        if !(self.inputs.is_empty() && self.outputs.is_empty()) {
332            return Err(Error::new(
333                input_span,
334                "`#[slot]` must appear before any `#[input]` or `#[output]`",
335            ));
336        }
337
338        match &*pat_type.ty {
339            // Check if input looks like `&Type` or `&mut Type`
340            Type::Reference(type_reference) => {
341                if type_reference.mutability.is_some() && !allow_mut {
342                    return Err(Error::new(
343                        input_span,
344                        "`#[slot]` is not allowed to mutate data here",
345                    ));
346                }
347
348                let Some(arg_name) = extract_arg_name(&pat_type.pat) else {
349                    return Err(Error::new(
350                        pat_type.span(),
351                        "`#[slot]` argument name must be either a simple variable or a reference",
352                    ));
353                };
354
355                self.slots.push(Slot {
356                    with_address_arg: false,
357                    type_name: type_reference.elem.as_ref().clone(),
358                    arg_name,
359                    mutability: type_reference.mutability,
360                });
361                return Ok(());
362            }
363            // Check if input looks like `(&Address, &Type)` or `(&Address, &mut Type)`
364            Type::Tuple(type_tuple) => {
365                if type_tuple.elems.len() == 2
366                    && let Type::Reference(address_type) =
367                        type_tuple.elems.first().expect("Checked above; qed")
368                    && address_type.mutability.is_none()
369                    && let Type::Reference(outer_slot_type) =
370                        type_tuple.elems.last().expect("Checked above; qed")
371                    && let Pat::Tuple(pat_tuple) = &*pat_type.pat
372                    && pat_tuple.elems.len() == 2
373                    && let Some(slot_arg) = extract_arg_name(&pat_tuple.elems[1])
374                {
375                    if outer_slot_type.mutability.is_some() && !allow_mut {
376                        return Err(Error::new(
377                            input_span,
378                            "`#[slot]` is not allowed to mutate data here",
379                        ));
380                    }
381
382                    self.slots.push(Slot {
383                        with_address_arg: true,
384                        type_name: outer_slot_type.elem.as_ref().clone(),
385                        arg_name: slot_arg,
386                        mutability: outer_slot_type.mutability,
387                    });
388                    return Ok(());
389                }
390
391                return Err(Error::new(
392                    pat_type.span(),
393                    "`#[slot]` with address must be a tuple of arguments, each of which is \
394                        either a simple variable or a reference",
395                ));
396            }
397            _ => {
398                // Ignore
399            }
400        }
401
402        Err(Error::new(
403            pat_type.span(),
404            "`#[slot]` must be a reference to a type implementing `IoTypeOptional` (can be \
405            shared or exclusive) like `&MaybeData<Slot>` or a tuple of references to address and \
406            to slot type like `(&Address, &mut VariableBytes<1024>)`",
407        ))
408    }
409
410    pub(super) fn process_input_arg(
411        &mut self,
412        input_span: Span,
413        pat_type: &PatType,
414    ) -> Result<(), Error> {
415        if !self.outputs.is_empty() {
416            return Err(Error::new(
417                input_span,
418                "`#[input]` must appear before any `#[output]`",
419            ));
420        }
421
422        // Ensure input looks like `&Type` or `&mut Type`, but not `Type`
423        if let Type::Reference(type_reference) = &*pat_type.ty {
424            let Some(arg_name) = extract_arg_name(&pat_type.pat) else {
425                return Err(Error::new(
426                    pat_type.span(),
427                    "`#[input]` argument name must be either a simple variable or a reference",
428                ));
429            };
430            if type_reference.mutability.is_some() {
431                return Err(Error::new(
432                    input_span,
433                    "`#[input]` must be a shared reference",
434                ));
435            }
436
437            self.inputs.push(Input {
438                type_name: type_reference.elem.as_ref().clone(),
439                arg_name,
440            });
441
442            Ok(())
443        } else {
444            Err(Error::new(
445                pat_type.span(),
446                "`#[input]` must be a shared reference to a type",
447            ))
448        }
449    }
450
451    pub(super) fn process_output_arg(
452        &mut self,
453        _input_span: Span,
454        pat_type: &PatType,
455    ) -> Result<(), Error> {
456        // Ensure input looks like `&mut Type`
457        if let Type::Reference(type_reference) = &*pat_type.ty
458            && type_reference.mutability.is_some()
459        {
460            let Pat::Ident(pat_ident) = &*pat_type.pat else {
461                return Err(Error::new(
462                    pat_type.span(),
463                    "`#[output]` argument name must be an exclusive reference",
464                ));
465            };
466
467            let mut type_name = type_reference.elem.as_ref().clone();
468            let mut has_self = false;
469
470            // Replace things like `MaybeData<Self>` with `MaybeData<#self_type>`
471            if let Type::Path(type_path) = &mut type_name
472                && let Some(path_segment) = type_path.path.segments.first_mut()
473                && let PathArguments::AngleBracketed(generic_arguments) =
474                    &mut path_segment.arguments
475                && let Some(GenericArgument::Type(first_generic_argument)) =
476                    generic_arguments.args.first_mut()
477                && let Type::Path(type_path) = &first_generic_argument
478                && type_path.path.is_ident("Self")
479            {
480                *first_generic_argument = self.self_type.clone();
481                has_self = true;
482            }
483
484            self.outputs.push(Output {
485                type_name,
486                arg_name: pat_ident.ident.clone(),
487                has_self,
488            });
489            Ok(())
490        } else {
491            Err(Error::new(
492                pat_type.span(),
493                "`#[output]` must be an exclusive reference to a type implementing \
494                `IoTypeOptional`, likely `MaybeData` container",
495            ))
496        }
497    }
498
499    pub(super) fn process_return(&mut self, output: &ReturnType) -> Result<(), Error> {
500        // Check if return type is `T` or `Result<T, ContractError>`
501        let error_message = format!(
502            "`#[{}]` must return `()` or `T` or `Result<T, ContractError>",
503            self.method_type.attr_str()
504        );
505        match output {
506            ReturnType::Default => {
507                self.set_return_type(MethodReturnType::Unit(MethodReturnType::unit_type()));
508            }
509            ReturnType::Type(_r_arrow, return_type) => match return_type.as_ref() {
510                Type::Array(_type_array) => {
511                    self.set_return_type(MethodReturnType::Regular(return_type.as_ref().clone()));
512                }
513                Type::Path(type_path) => {
514                    // Check something with generic rather than a simple type
515                    let Some(last_path_segment) = type_path.path.segments.last() else {
516                        self.set_return_type(MethodReturnType::Regular(
517                            return_type.as_ref().clone(),
518                        ));
519                        return Ok(());
520                    };
521
522                    // Check for `-> Result<T, ContractError>`
523                    if last_path_segment.ident == "Result" {
524                        if let PathArguments::AngleBracketed(result_arguments) =
525                            &last_path_segment.arguments
526                            && result_arguments.args.len() == 2
527                            && let GenericArgument::Type(ok_type) = &result_arguments.args[0]
528                            && let GenericArgument::Type(error_type) = &result_arguments.args[1]
529                            && let Type::Path(error_path) = error_type
530                            && error_path
531                                .path
532                                .segments
533                                .last()
534                                .is_some_and(|s| s.ident == "ContractError")
535                        {
536                            if let Type::Path(ok_path) = ok_type
537                                && ok_path
538                                    .path
539                                    .segments
540                                    .first()
541                                    .is_some_and(|s| s.ident == "Self")
542                            {
543                                // Swap `Self` for an actual struct name
544                                self.set_return_type(MethodReturnType::Result(
545                                    self.self_type.clone(),
546                                ));
547                            } else {
548                                self.set_return_type(MethodReturnType::Result(ok_type.clone()));
549                            }
550                        } else {
551                            return Err(Error::new(return_type.span(), error_message));
552                        }
553                    } else if last_path_segment.ident == "Self" {
554                        // Swap `Self` for an actual struct name
555                        self.set_return_type(MethodReturnType::Regular(self.self_type.clone()));
556                    } else {
557                        self.set_return_type(MethodReturnType::Regular(
558                            return_type.as_ref().clone(),
559                        ));
560                    }
561                }
562                return_type => {
563                    return Err(Error::new(return_type.span(), error_message));
564                }
565            },
566        }
567
568        Ok(())
569    }
570
571    fn set_return_type(&mut self, return_type: MethodReturnType) {
572        let unit_type = MethodReturnType::unit_type();
573        self.return_type = match return_type {
574            MethodReturnType::Unit(ty) => MethodReturnType::Unit(ty),
575            MethodReturnType::Regular(ty) => {
576                if ty == unit_type {
577                    MethodReturnType::Unit(ty)
578                } else {
579                    MethodReturnType::Regular(ty)
580                }
581            }
582            MethodReturnType::ResultUnit(ty) => MethodReturnType::ResultUnit(ty),
583            MethodReturnType::Result(ty) => {
584                if ty == unit_type {
585                    MethodReturnType::ResultUnit(ty)
586                } else {
587                    MethodReturnType::Result(ty)
588                }
589            }
590        };
591    }
592
593    pub(super) fn generate_guest_ffi(
594        &self,
595        fn_sig: &Signature,
596        trait_name: Option<&Ident>,
597    ) -> Result<TokenStream, Error> {
598        let self_type = &self.self_type;
599        if matches!(self.method_type, MethodType::Init) {
600            let self_return_type = self.return_type.return_type() == self_type;
601            let self_last_output_type = self.outputs.last().is_some_and(|output| output.has_self);
602
603            if !(self_return_type || self_last_output_type) {
604                return Err(Error::new(
605                    fn_sig.span(),
606                    "`#[init]` must have `Self` as either return type or last `#[output]` \
607                    argument",
608                ));
609            }
610        }
611
612        let original_method_name = &fn_sig.ident;
613
614        let guest_fn = self.generate_guest_fn(fn_sig, trait_name)?;
615        let external_args_struct = self.generate_external_args_struct(fn_sig, trait_name)?;
616        let metadata = self.generate_metadata(fn_sig, trait_name)?;
617
618        Ok(quote_spanned! {fn_sig.span() =>
619            pub mod #original_method_name {
620                use super::*;
621
622                #guest_fn
623                #external_args_struct
624                #metadata
625            }
626        })
627    }
628
629    pub(super) fn generate_guest_trait_ffi(
630        &self,
631        fn_sig: &Signature,
632        trait_name: Option<&Ident>,
633    ) -> Result<TokenStream, Error> {
634        let original_method_name = &fn_sig.ident;
635
636        let external_args_struct = self.generate_external_args_struct(fn_sig, trait_name)?;
637        let metadata = self.generate_metadata(fn_sig, trait_name)?;
638
639        Ok(quote_spanned! {fn_sig.span() =>
640            pub mod #original_method_name {
641                use super::*;
642
643                #external_args_struct
644                #metadata
645            }
646        })
647    }
648
649    pub(super) fn generate_guest_fn(
650        &self,
651        fn_sig: &Signature,
652        trait_name: Option<&Ident>,
653    ) -> Result<TokenStream, Error> {
654        let self_type = &self.self_type;
655
656        // `internal_args_pointers` will generate pointers in `InternalArgs` fields
657        let mut internal_args_pointers = Vec::new();
658        // `preparation` will generate code used before calling the original function
659        let mut preparation = Vec::new();
660        // `original_fn_args` will generate arguments for calling original method implementation
661        let mut original_fn_args = Vec::new();
662
663        // Optional state argument
664        if let Some(mutability) = self.state {
665            internal_args_pointers.push(quote! {
666                pub self_ptr: ::core::ptr::NonNull<
667                    <#self_type as ::ab_contracts_macros::__private::IoType>::PointerType,
668                >,
669                /// Size of the contents `self_ptr` points to
670                pub self_size: ::core::primitive::u32,
671                /// Capacity of the allocated memory `self_ptr` points to
672                pub self_capacity: ::core::primitive::u32,
673            });
674
675            if mutability.is_some() {
676                original_fn_args.push(quote! {&mut *{
677                    // Ensure the state type implements `IoType`, which is required for crossing the
678                    // host/guest boundary
679                    const {
680                        const fn assert_impl_io_type<T>()
681                        where
682                            T: ::ab_contracts_macros::__private::IoType,
683                        {}
684                        assert_impl_io_type::<#self_type>();
685                    }
686
687                    <#self_type as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
688                        &mut args.self_ptr,
689                        &mut args.self_size,
690                        args.self_capacity,
691                    )
692                }});
693            } else {
694                original_fn_args.push(quote! {&*{
695                    // Ensure the state type implements `IoType`, which is required for crossing the
696                    // host/guest boundary
697                    const {
698                        const fn assert_impl_io_type<T>()
699                        where
700                            T: ::ab_contracts_macros::__private::IoType,
701                        {}
702                        assert_impl_io_type::<#self_type>();
703                    }
704
705                    <#self_type as ::ab_contracts_macros::__private::IoType>::from_ptr(
706                        &args.self_ptr,
707                        &args.self_size,
708                        args.self_capacity,
709                    )
710                }});
711            }
712        }
713
714        // Optional environment argument
715        if let Some(env) = &self.env {
716            let env_field = &env.arg_name;
717            let mutability = env.mutability;
718
719            internal_args_pointers.push(quote! {
720                // Use `Env` to check if the method argument had the correct type at compile time
721                pub #env_field: &'internal_args #mutability ::ab_contracts_macros::__private::Env<'internal_args>,
722            });
723
724            original_fn_args.push(quote! { args.#env_field });
725        }
726
727        // Optional tmp argument
728        if let Some(tmp) = &self.tmp {
729            let type_name = &tmp.type_name;
730            let mutability = tmp.mutability;
731            let ptr_field = format_ident!("{}_ptr", tmp.arg_name);
732            let size_field = format_ident!("{}_size", tmp.arg_name);
733            let size_doc = format!("Size of the contents `{ptr_field}` points to");
734            let capacity_field = format_ident!("{}_capacity", tmp.arg_name);
735            let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
736
737            internal_args_pointers.push(quote! {
738                pub #ptr_field: ::core::ptr::NonNull<
739                    <
740                        // Make sure `#[tmp]` type matches expected type
741                        <#self_type as ::ab_contracts_macros::__private::Contract>::Tmp as ::ab_contracts_macros::__private::IoType
742                    >::PointerType,
743                >,
744                #[doc = #size_doc]
745                pub #size_field: ::core::primitive::u32,
746                #[doc = #capacity_doc]
747                pub #capacity_field: ::core::primitive::u32,
748            });
749
750            if mutability.is_some() {
751                original_fn_args.push(quote! {&mut *{
752                    // Ensure tmp type implements `IoTypeOptional`, which is required for handling
753                    // of tmp that might be removed or not present and implies implementation of
754                    // `IoType`, which is required for crossing the host/guest boundary
755                    const {
756                        const fn assert_impl_io_type_optional<T>()
757                        where
758                            T: ::ab_contracts_macros::__private::IoTypeOptional,
759                        {}
760                        assert_impl_io_type_optional::<#type_name>();
761                    }
762
763                    <#type_name as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
764                        &mut args.#ptr_field,
765                        &mut args.#size_field,
766                        args.#capacity_field,
767                    )
768                }});
769            } else {
770                original_fn_args.push(quote! {&*{
771                    // Ensure tmp type implements `IoTypeOptional`, which is required for handling
772                    // of tmp that might be removed or not present and implies implementation of
773                    // `IoType`, which is required for crossing the host/guest boundary
774                    const {
775                        const fn assert_impl_io_type_optional<T>()
776                        where
777                            T: ::ab_contracts_macros::__private::IoTypeOptional,
778                        {}
779                        assert_impl_io_type_optional::<#type_name>();
780                    }
781
782                    <#type_name as ::ab_contracts_macros::__private::IoType>::from_ptr(
783                        &args.#ptr_field,
784                        &args.#size_field,
785                        args.#capacity_field,
786                    )
787                }});
788            }
789        }
790
791        // Slot arguments with:
792        // * in case address is used: a pointer to address, a pointer to slot and size + capacity
793        // * in case address is not used: a pointer to slot and size + capacity
794        for slot in &self.slots {
795            let type_name = &slot.type_name;
796            let mutability = slot.mutability;
797            let address_field = format_ident!("{}_address", slot.arg_name);
798            let ptr_field = format_ident!("{}_ptr", slot.arg_name);
799            let size_field = format_ident!("{}_size", slot.arg_name);
800            let size_doc = format!("Size of the contents `{ptr_field}` points to");
801            let capacity_field = format_ident!("{}_capacity", slot.arg_name);
802            let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
803
804            internal_args_pointers.push(quote! {
805                // Use `Address` to check if the method argument had the correct type at compile
806                // time
807                pub #address_field: &'internal_args ::ab_contracts_macros::__private::Address,
808                pub #ptr_field: ::core::ptr::NonNull<
809                    <
810                        // Make sure the `#[slot]` type matches the expected type
811                        <#self_type as ::ab_contracts_macros::__private::Contract>::Slot as ::ab_contracts_macros::__private::IoType
812                    >::PointerType,
813                >,
814                #[doc = #size_doc]
815                pub #size_field: ::core::primitive::u32,
816                #[doc = #capacity_doc]
817                pub #capacity_field: ::core::primitive::u32,
818            });
819
820            let arg_extraction = if mutability.is_some() {
821                quote! {&mut *{
822                    // Ensure the slot type implements `IoTypeOptional`, which is required for
823                    // handling of slot that might be removed or not present and implies
824                    // implementation of `IoType`, which is required for crossing the host/guest
825                    // boundary
826                    const {
827                        const fn assert_impl_io_type_optional<T>()
828                        where
829                            T: ::ab_contracts_macros::__private::IoTypeOptional,
830                        {}
831                        assert_impl_io_type_optional::<#type_name>();
832                    }
833
834                    <#type_name as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
835                        &mut args.#ptr_field,
836                        &mut args.#size_field,
837                        args.#capacity_field,
838                    )
839                }}
840            } else {
841                quote! {&*{
842                    // Ensure the slot type implements `IoTypeOptional`, which is required for
843                    // handling of slot that might be removed or not present and implies
844                    // implementation of `IoType`, which is required for crossing the host/guest
845                    // boundary
846                    const {
847                        const fn assert_impl_io_type_optional<T>()
848                        where
849                            T: ::ab_contracts_macros::__private::IoTypeOptional,
850                        {}
851                        assert_impl_io_type_optional::<#type_name>();
852                    }
853
854                    <#type_name as ::ab_contracts_macros::__private::IoType>::from_ptr(
855                        &args.#ptr_field,
856                        &args.#size_field,
857                        args.#capacity_field,
858                    )
859                }}
860            };
861
862            if slot.with_address_arg {
863                original_fn_args.push(quote! {
864                    (
865                        args.#address_field,
866                        #arg_extraction,
867                    )
868                });
869            } else {
870                original_fn_args.push(arg_extraction);
871            }
872        }
873
874        // Inputs
875        for input in &self.inputs {
876            let type_name = &input.type_name;
877            let arg_name = &input.arg_name;
878            let ptr_field = format_ident!("{arg_name}_ptr");
879            let size_field = format_ident!("{arg_name}_size");
880            let size_doc = format!("Size of the contents `{ptr_field}` points to");
881            let capacity_field = format_ident!("{arg_name}_capacity");
882            let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
883
884            internal_args_pointers.push(quote! {
885                pub #ptr_field: ::core::ptr::NonNull<
886                    <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
887                >,
888                #[doc = #size_doc]
889                pub #size_field: ::core::primitive::u32,
890                #[doc = #capacity_doc]
891                pub #capacity_field: ::core::primitive::u32,
892            });
893
894            original_fn_args.push(quote! {&*{
895                // Ensure the input type implements `IoType`, which is required for crossing the
896                // host/guest boundary
897                const {
898                    const fn assert_impl_io_type<T>()
899                    where
900                        T: ::ab_contracts_macros::__private::IoType,
901                    {}
902                    assert_impl_io_type::<#type_name>();
903                }
904
905                <#type_name as ::ab_contracts_macros::__private::IoType>::from_ptr(
906                    &args.#ptr_field,
907                    &args.#size_field,
908                    args.#capacity_field,
909                )
910            }});
911        }
912
913        // Outputs
914        for output in &self.outputs {
915            let type_name = &output.type_name;
916            let arg_name = &output.arg_name;
917            let ptr_field = format_ident!("{arg_name}_ptr");
918            let size_field = format_ident!("{arg_name}_size");
919            let size_doc = format!("Size of the contents `{ptr_field}` points to");
920            let capacity_field = format_ident!("{arg_name}_capacity");
921            let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
922
923            internal_args_pointers.push(quote! {
924                pub #ptr_field: ::core::ptr::NonNull<
925                    <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
926                >,
927                #[doc = #size_doc]
928                pub #size_field: ::core::primitive::u32,
929                #[doc = #capacity_doc]
930                pub #capacity_field: ::core::primitive::u32,
931            });
932
933            original_fn_args.push(quote! {&mut *{
934                // Ensure the output type implements `IoType`, which is required for crossing the
935                // host/guest boundary
936                const {
937                    const fn assert_impl_io_type<T>()
938                    where
939                        T: ::ab_contracts_macros::__private::IoType,
940                    {}
941                    assert_impl_io_type::<#type_name>();
942                }
943
944                <#type_name as ::ab_contracts_macros::__private::IoType>::from_mut_ptr(
945                    &mut args.#ptr_field,
946                    &mut args.#size_field,
947                    args.#capacity_field,
948                )
949            }});
950        }
951
952        let original_method_name = &fn_sig.ident;
953        let ffi_fn_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
954        let return_type = self.return_type.return_type();
955
956        let internal_args_struct = {
957            // No special handling of the return type is needed for a unit return type
958            if !self.return_type.unit_return_type() {
959                internal_args_pointers.push(quote! {
960                    pub ok_result: &'internal_args mut ::core::mem::MaybeUninit<#return_type>,
961                });
962
963                preparation.push(quote! {
964                    // Ensure the return type implements not only `IoType`, which is required for
965                    // crossing host/guest boundary, but also `TrivialType` and result handling is
966                    // trivial without the need to worry about size and capacity.
967                    // `#[output]` must be used for a variable size result.
968                    const {
969                        const fn assert_impl_trivial_type<T>()
970                        where
971                            T: ::ab_contracts_macros::__private::IoType,
972                        {}
973                        assert_impl_trivial_type::<#return_type>();
974                    }
975                });
976            }
977            let args_struct_doc = format!(
978                "Data structure containing expected input to [`{ffi_fn_name}()`], it is used \
979                internally by the contract, there should be no need to construct it explicitly \
980                except maybe in contract's own tests"
981            );
982            quote_spanned! {fn_sig.span() =>
983                #[doc = #args_struct_doc]
984                #[derive(::core::fmt::Debug)]
985                #[repr(C)]
986                pub struct InternalArgs<'internal_args>
987                {
988                    #( #internal_args_pointers )*
989                    _phantom: ::core::marker::PhantomData<&'internal_args ()>,
990                }
991            }
992        };
993
994        let result_var_name = format_ident!("result");
995        let guest_fn = {
996            // Depending on whether `T` or `Result<T, ContractError>` is used as a return type,
997            // generate different code for result handling
998            let result_handling = match &self.return_type {
999                MethodReturnType::Unit(_) => {
1000                    quote! {
1001                        // Return exit code
1002                        ::ab_contracts_macros::__private::ExitCode::ok()
1003                    }
1004                }
1005                MethodReturnType::Regular(_) => {
1006                    quote! {
1007                        args.ok_result.write(#result_var_name);
1008                        // Return exit code
1009                        ::ab_contracts_macros::__private::ExitCode::ok()
1010                    }
1011                }
1012                MethodReturnType::ResultUnit(_) => {
1013                    quote! {
1014                        // Return exit code
1015                        match #result_var_name {
1016                            Ok(()) => ::ab_contracts_macros::__private::ExitCode::ok(),
1017                            Err(error) => error.exit_code(),
1018                        }
1019                    }
1020                }
1021                MethodReturnType::Result(_) => {
1022                    quote! {
1023                        // Write a result into `InternalArgs` if there is any, return exit code
1024                        match #result_var_name {
1025                            Ok(result) => {
1026                                args.ok_result.write(result);
1027                                // Return exit code
1028                                ::ab_contracts_macros::__private::ExitCode::ok()
1029                            }
1030                            Err(error) => error.exit_code(),
1031                        }
1032                    }
1033                }
1034            };
1035
1036            let full_struct_name = if let Some(trait_name) = trait_name {
1037                quote! { <#self_type as #trait_name> }
1038            } else {
1039                quote! { #self_type }
1040            };
1041
1042            // Generate FFI function with the original name
1043            quote_spanned! {fn_sig.span() =>
1044                /// FFI interface into a method, called by the host.
1045                ///
1046                /// NOTE: Calling this function directly shouldn't be necessary except maybe in
1047                /// contract's own tests.
1048                ///
1049                /// # Safety
1050                ///
1051                /// Caller must ensure the provided pointer corresponds to the expected ABI.
1052                #[cfg_attr(feature = "guest", unsafe(no_mangle))]
1053                #[allow(clippy::new_ret_no_self, reason = "Method was re-written for FFI purposes without `Self`")]
1054                #[allow(clippy::absurd_extreme_comparisons, reason = "Macro-generated code doesn't know the size upfront")]
1055                pub unsafe extern "C" fn #ffi_fn_name(
1056                    args: &mut InternalArgs<'_>,
1057                ) -> ::ab_contracts_macros::__private::ExitCode {
1058                    #( #preparation )*
1059
1060                    // Call inner function via normal Rust API
1061                    #[allow(
1062                        unused_variables,
1063                        reason = "Sometimes result is `()`"
1064                    )]
1065                    #[allow(
1066                        clippy::let_unit_value,
1067                        reason = "Sometimes result is `()`"
1068                    )]
1069                    let #result_var_name = #full_struct_name::#original_method_name(
1070                        #( #original_fn_args, )*
1071                    );
1072
1073                    #result_handling
1074                }
1075            }
1076        };
1077
1078        let fn_pointer_static = {
1079            let adapter_ffi_fn_name = format_ident!("{ffi_fn_name}_adapter");
1080            let args_struct_name =
1081                derive_external_args_struct_name(self_type, trait_name, original_method_name)?;
1082
1083            quote! {
1084                #[doc(hidden)]
1085                pub mod fn_pointer {
1086                    use super::*;
1087
1088                    unsafe extern "C" fn #adapter_ffi_fn_name(
1089                        args_ptr: ::core::ptr::NonNull<::core::ffi::c_void>,
1090                    ) -> ::ab_contracts_macros::__private::ExitCode {
1091                        // SAFETY: Caller must ensure correct ABI of the void pointer, little can be
1092                        // done here
1093                        unsafe {
1094                            let mut args = args_ptr.cast::<InternalArgs<'_>>();
1095                            debug_assert!(args.is_aligned(), "`args` pointer is misaligned");
1096                            let args = args.as_mut();
1097                            #ffi_fn_name(args)
1098                        }
1099                    }
1100
1101                    pub const METHOD_FN_POINTER: ::ab_contracts_macros::__private::NativeExecutorContactMethod =
1102                        ::ab_contracts_macros::__private::NativeExecutorContactMethod {
1103                            method_fingerprint: &<#args_struct_name<'_> as ::ab_contracts_macros::__private::ExternalArgs>::FINGERPRINT,
1104                            method_metadata: METADATA,
1105                            ffi_fn: #adapter_ffi_fn_name,
1106                        };
1107                }
1108            }
1109        };
1110
1111        Ok(quote! {
1112            #internal_args_struct
1113            #guest_fn
1114            #fn_pointer_static
1115        })
1116    }
1117
1118    fn generate_external_args_struct(
1119        &self,
1120        fn_sig: &Signature,
1121        trait_name: Option<&Ident>,
1122    ) -> Result<TokenStream, Error> {
1123        let self_type = &self.self_type;
1124        let original_method_name = &fn_sig.ident;
1125
1126        let args_struct_name =
1127            derive_external_args_struct_name(self_type, trait_name, original_method_name)?;
1128        // `external_args_pointers` will generate pointers in `ExternalArgs` fields
1129        let mut external_args_fields = Vec::new();
1130        // Arguments of `::new()` method
1131        let mut method_args = Vec::new();
1132        // Fields set on `Self` in `::new()` method
1133        let mut method_args_fields = Vec::new();
1134
1135        // For slots in external args only the address pointer is needed
1136        for slot in &self.slots {
1137            let arg_name = &slot.arg_name;
1138            let ptr_field = format_ident!("{arg_name}_ptr");
1139
1140            external_args_fields.push(quote! {
1141                pub #ptr_field: ::core::ptr::NonNull<::ab_contracts_macros::__private::Address>,
1142            });
1143
1144            method_args.push(quote! {
1145                #arg_name: &'external_args ::ab_contracts_macros::__private::Address,
1146            });
1147            method_args_fields.push(quote! {
1148                #ptr_field: ::core::ptr::NonNull::from_ref(#arg_name),
1149            });
1150        }
1151
1152        // Inputs with pointers to data and size
1153        for input in &self.inputs {
1154            let type_name = &input.type_name;
1155            let arg_name = &input.arg_name;
1156            let ptr_field = format_ident!("{arg_name}_ptr");
1157            let size_field = format_ident!("{arg_name}_size");
1158            let size_doc = format!("Size of the contents `{ptr_field}` points to");
1159            let capacity_field = format_ident!("{arg_name}_capacity");
1160            let capacity_doc = format!("Capacity of the contents `{ptr_field}` points to");
1161
1162            external_args_fields.push(quote! {
1163                pub #ptr_field: ::core::ptr::NonNull<
1164                    <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
1165                >,
1166                #[doc = #size_doc]
1167                pub #size_field: ::core::primitive::u32,
1168                #[doc = #capacity_doc]
1169                pub #capacity_field: ::core::primitive::u32,
1170            });
1171
1172            method_args.push(quote! {
1173                #arg_name: &'external_args #type_name,
1174            });
1175            method_args_fields.push(quote! {
1176                // SAFETY: This pointer is used as input to FFI call, and underlying data
1177                // will not be modified, also the pointer will not outlive the reference
1178                // from which it was created despite copying
1179                #ptr_field: unsafe {
1180                    *::ab_contracts_macros::__private::IoType::as_ptr(#arg_name)
1181                },
1182                #size_field: ::ab_contracts_macros::__private::IoType::size(#arg_name),
1183                #capacity_field: ::ab_contracts_macros::__private::IoType::capacity(#arg_name),
1184            });
1185        }
1186
1187        // Outputs with pointers to data, size and capacity
1188        let mut outputs_iter = self.outputs.iter().peekable();
1189        while let Some(output) = outputs_iter.next() {
1190            let type_name = &output.type_name;
1191            let arg_name = &output.arg_name;
1192            let ptr_field = format_ident!("{arg_name}_ptr");
1193            let size_field = format_ident!("{arg_name}_size");
1194            let size_doc = format!("Size of the contents `{ptr_field}` points to");
1195            let capacity_field = format_ident!("{arg_name}_capacity");
1196            let capacity_doc = format!("Capacity of the allocated memory `{ptr_field}` points to");
1197
1198            // Initializer's return type will be `()` for caller of `#[init]`, state is stored by
1199            // the host and not returned to the caller, hence no explicit argument is needed
1200            if outputs_iter.is_empty()
1201                && self.return_type.unit_return_type()
1202                && matches!(self.method_type, MethodType::Init)
1203            {
1204                continue;
1205            }
1206
1207            external_args_fields.push(quote! {
1208                pub #ptr_field: ::core::ptr::NonNull<
1209                    <#type_name as ::ab_contracts_macros::__private::IoType>::PointerType,
1210                >,
1211                #[doc = #size_doc]
1212                pub #size_field: ::core::primitive::u32,
1213                #[doc = #capacity_doc]
1214                pub #capacity_field: ::core::primitive::u32,
1215            });
1216
1217            method_args.push(quote! {
1218                #arg_name: &'external_args mut #type_name,
1219            });
1220            method_args_fields.push(quote! {
1221                // SAFETY: This pointer is used as input to FFI call, and underlying data will only
1222                // be modified there, also the pointer will not outlive the reference from which it
1223                // was created despite copying
1224                #ptr_field: unsafe {
1225                    *::ab_contracts_macros::__private::IoType::as_mut_ptr(#arg_name)
1226                },
1227                #size_field: ::ab_contracts_macros::__private::IoType::size(#arg_name),
1228                #capacity_field: ::ab_contracts_macros::__private::IoType::capacity(#arg_name),
1229            });
1230        }
1231
1232        let ffi_fn_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
1233
1234        // Initializer's return type will be `()` for the caller of `#[init]` since the state is
1235        // stored by the host and not returned to the caller and explicit argument is not needed in
1236        // the `ExternalArgs` struct. Similarly, it is skipped for a unit return type.
1237        if !(matches!(self.method_type, MethodType::Init) || self.return_type.unit_return_type()) {
1238            let return_type = &self.return_type.return_type();
1239
1240            external_args_fields.push(quote! {
1241                pub ok_result: &'external_args mut ::core::mem::MaybeUninit<#return_type>,
1242            });
1243
1244            method_args.push(quote! {
1245                ok_result: &'external_args mut ::core::mem::MaybeUninit<#return_type>,
1246            });
1247            method_args_fields.push(quote! {
1248                ok_result,
1249            });
1250        }
1251        let args_struct_doc = format!(
1252            "Data structure containing expected input for external method invocation, eventually \
1253            calling `{ffi_fn_name}()` on the other side by the host.\n\n\
1254            This can be used with [`Env`](::ab_contracts_macros::__private::Env), though there are \
1255            helper methods on this provided by extension trait that allow not dealing with this \
1256            struct directly in simpler cases."
1257        );
1258
1259        Ok(quote_spanned! {fn_sig.span() =>
1260            #[doc = #args_struct_doc]
1261            #[derive(::core::fmt::Debug)]
1262            #[repr(C)]
1263            pub struct #args_struct_name<'external_args> {
1264                #( #external_args_fields )*
1265                lifetime: ::core::marker::PhantomData<&'external_args ()>,
1266            }
1267
1268            #[automatically_derived]
1269            unsafe impl ::ab_contracts_macros::__private::ExternalArgs for #args_struct_name<'_> {
1270                const FINGERPRINT: ::ab_contracts_macros::__private::MethodFingerprint =
1271                    ::ab_contracts_macros::__private::MethodFingerprint::new(METADATA)
1272                        .expect("Metadata is statically correct; qed");
1273                const METADATA: &'static [::core::primitive::u8] = METADATA;
1274            }
1275
1276            impl<'external_args> #args_struct_name<'external_args> {
1277                /// Create a new instance.
1278                ///
1279                /// NOTE: Make sure to query updated sizes of arguments after calling the contract.
1280                #[allow(
1281                    clippy::new_without_default,
1282                    reason = "Do not want `Default` in auto-generated code"
1283                )]
1284                pub fn new(
1285                    #( #method_args )*
1286                ) -> Self {
1287                    Self {
1288                        #( #method_args_fields )*
1289                        lifetime: ::core::marker::PhantomData,
1290                    }
1291                }
1292            }
1293        })
1294    }
1295
1296    fn generate_metadata(
1297        &self,
1298        fn_sig: &Signature,
1299        trait_name: Option<&Ident>,
1300    ) -> Result<TokenStream, Error> {
1301        let self_type = &self.self_type;
1302        // `method_metadata` will generate metadata about method arguments, each element in this
1303        // vector corresponds to one argument
1304        let mut method_metadata = Vec::new();
1305
1306        if let Some(env) = &self.env {
1307            let env_metadata_type = if env.mutability.is_some() {
1308                "EnvRw"
1309            } else {
1310                "EnvRo"
1311            };
1312
1313            let env_metadata_type = format_ident!("{env_metadata_type}");
1314            method_metadata.push(quote! {
1315                &[::ab_contracts_macros::__private::ContractMetadataKind::#env_metadata_type as ::core::primitive::u8],
1316            });
1317        }
1318
1319        if let Some(tmp) = &self.tmp {
1320            let tmp_metadata_type = if tmp.mutability.is_some() {
1321                "TmpRw"
1322            } else {
1323                "TmpRo"
1324            };
1325
1326            let tmp_metadata_type = format_ident!("{tmp_metadata_type}");
1327            let arg_name_metadata = derive_ident_metadata(&tmp.arg_name)?;
1328            method_metadata.push(quote! {
1329                &[::ab_contracts_macros::__private::ContractMetadataKind::#tmp_metadata_type as ::core::primitive::u8],
1330                #arg_name_metadata,
1331            });
1332        }
1333
1334        for slot in &self.slots {
1335            let slot_metadata_type = if slot.mutability.is_some() {
1336                "SlotRw"
1337            } else {
1338                "SlotRo"
1339            };
1340
1341            let slot_metadata_type = format_ident!("{slot_metadata_type}");
1342            let arg_name_metadata = derive_ident_metadata(&slot.arg_name)?;
1343            method_metadata.push(quote! {
1344                &[::ab_contracts_macros::__private::ContractMetadataKind::#slot_metadata_type as ::core::primitive::u8],
1345                #arg_name_metadata,
1346            });
1347        }
1348
1349        for input in &self.inputs {
1350            let io_metadata_type = format_ident!("Input");
1351            let arg_name_metadata = derive_ident_metadata(&input.arg_name)?;
1352            let type_name = &input.type_name;
1353
1354            method_metadata.push(quote! {
1355                &[::ab_contracts_macros::__private::ContractMetadataKind::#io_metadata_type as ::core::primitive::u8],
1356                #arg_name_metadata,
1357                <#type_name as ::ab_contracts_macros::__private::IoType>::METADATA,
1358            });
1359        }
1360
1361        let mut outputs_iter = self.outputs.iter().peekable();
1362        while let Some(output) = outputs_iter.next() {
1363            let io_metadata_type = "Output";
1364
1365            let io_metadata_type = format_ident!("{io_metadata_type}");
1366            let arg_name_metadata = derive_ident_metadata(&output.arg_name)?;
1367            // Skip type metadata for `#[init]`'s last output since it is known statically
1368            let with_type_metadata = if outputs_iter.is_empty()
1369                && self.return_type.unit_return_type()
1370                && matches!(self.method_type, MethodType::Init)
1371            {
1372                None
1373            } else {
1374                let type_name = &output.type_name;
1375                Some(quote! {
1376                    <#type_name as ::ab_contracts_macros::__private::IoType>::METADATA,
1377                })
1378            };
1379            method_metadata.push(quote! {
1380                &[::ab_contracts_macros::__private::ContractMetadataKind::#io_metadata_type as ::core::primitive::u8],
1381                #arg_name_metadata,
1382                #with_type_metadata
1383            });
1384        }
1385
1386        // Skipped if return type is unit
1387        if !self.return_type.unit_return_type() {
1388            // There isn't an explicit name in case of the return type
1389            let arg_name_metadata = Literal::u8_unsuffixed(0);
1390            // Skip type metadata for `#[init]`'s result since it is known statically
1391            let with_type_metadata = if matches!(self.method_type, MethodType::Init) {
1392                None
1393            } else {
1394                let return_type = self.return_type.return_type();
1395                Some(quote! {
1396                    <#return_type as ::ab_contracts_macros::__private::IoType>::METADATA,
1397                })
1398            };
1399            method_metadata.push(quote! {
1400                &[
1401                    ::ab_contracts_macros::__private::ContractMetadataKind::Return as ::core::primitive::u8,
1402                    #arg_name_metadata,
1403                ],
1404                #with_type_metadata
1405            });
1406        }
1407
1408        let method_type = match self.method_type {
1409            MethodType::Init => "Init",
1410            MethodType::Update => {
1411                if let Some(mutable) = &self.state {
1412                    if mutable.is_some() {
1413                        "UpdateStatefulRw"
1414                    } else {
1415                        "UpdateStatefulRo"
1416                    }
1417                } else {
1418                    "UpdateStateless"
1419                }
1420            }
1421            MethodType::View => {
1422                if let Some(mutable) = &self.state {
1423                    if mutable.is_some() {
1424                        return Err(Error::new(
1425                            fn_sig.span(),
1426                            "Stateful view methods are not supported",
1427                        ));
1428                    }
1429
1430                    "ViewStateful"
1431                } else {
1432                    "ViewStateless"
1433                }
1434            }
1435        };
1436
1437        let method_type = format_ident!("{method_type}");
1438        let number_of_arguments = u8::try_from(method_metadata.len()).map_err(|_error| {
1439            Error::new(
1440                fn_sig.span(),
1441                format!("Number of arguments must not be more than {}", u8::MAX),
1442            )
1443        })?;
1444        let total_number_of_arguments =
1445            Literal::u8_unsuffixed(number_of_arguments.saturating_add(1));
1446        let number_of_arguments = Literal::u8_unsuffixed(number_of_arguments);
1447
1448        let original_method_name = &fn_sig.ident;
1449        let ffi_fn_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
1450        let method_name_metadata = derive_ident_metadata(&ffi_fn_name)?;
1451        Ok(quote_spanned! {fn_sig.span() =>
1452            #[expect(
1453                clippy::assertions_on_constants,
1454                reason = "Auto-generated compile-time check"
1455            )]
1456            const fn metadata()
1457                -> ([::core::primitive::u8; ::ab_contracts_macros::__private::MAX_METADATA_CAPACITY], usize)
1458            {
1459                assert!(
1460                    #total_number_of_arguments <= ::ab_contracts_macros::__private::MAX_TOTAL_METHOD_ARGS,
1461                    "Too many arguments"
1462                );
1463                ::ab_contracts_macros::__private::concat_metadata_sources(&[
1464                    &[::ab_contracts_macros::__private::ContractMetadataKind::#method_type as ::core::primitive::u8],
1465                    #method_name_metadata,
1466                    &[#number_of_arguments],
1467                    #( #method_metadata )*
1468                ])
1469            }
1470
1471            /// Method metadata, see [`ContractMetadataKind`] for encoding details
1472            ///
1473            /// [`ContractMetadataKind`]: ::ab_contracts_macros::__private::ContractMetadataKind
1474            // Strange syntax to allow Rust to extend the lifetime of metadata scratch automatically
1475            pub const METADATA: &[::core::primitive::u8] =
1476                metadata()
1477                    .0
1478                    .split_at(metadata().1)
1479                    .0;
1480        })
1481    }
1482
1483    pub(super) fn generate_trait_ext_components(
1484        &self,
1485        fn_sig: &Signature,
1486        fn_attrs: &[Attribute],
1487        trait_name: Option<&Ident>,
1488    ) -> Result<ExtTraitComponents, Error> {
1489        let self_type = &self.self_type;
1490
1491        let mut preparation = Vec::new();
1492        let mut method_args = Vec::new();
1493        let mut external_args_args = Vec::new();
1494        let mut result_processing_before = Vec::new();
1495        let mut result_processing_after = Vec::new();
1496
1497        // Address of the contract
1498        method_args.push(quote_spanned! {fn_sig.span() =>
1499            contract: ::ab_contracts_macros::__private::Address,
1500        });
1501
1502        // For each slot argument generate an address argument
1503        for slot in &self.slots {
1504            let arg_name = &slot.arg_name;
1505
1506            method_args.push(quote_spanned! {fn_sig.span() =>
1507                #arg_name: &::ab_contracts_macros::__private::Address,
1508            });
1509            external_args_args.push(quote_spanned! {fn_sig.span() => #arg_name });
1510        }
1511
1512        // For each input argument, generate a corresponding read-only argument
1513        for input in &self.inputs {
1514            let type_name = &input.type_name;
1515            let arg_name = &input.arg_name;
1516
1517            method_args.push(quote_spanned! {fn_sig.span() =>
1518                #arg_name: &#type_name,
1519            });
1520            external_args_args.push(quote_spanned! {fn_sig.span() => #arg_name });
1521        }
1522
1523        // For each output argument, generate a corresponding write-only argument
1524        let mut outputs_iter = self.outputs.iter().peekable();
1525        while let Some(output) = outputs_iter.next() {
1526            let type_name = &output.type_name;
1527            let arg_name = &output.arg_name;
1528            let size_var = format_ident!("macro_{arg_name}_size");
1529            let size_field = format_ident!("{arg_name}_size");
1530
1531            // Initializer's return type will be `()` for the caller of `#[init]`, state is stored
1532            // by the host and not returned to the caller
1533            if outputs_iter.is_empty()
1534                && self.return_type.unit_return_type()
1535                && matches!(self.method_type, MethodType::Init)
1536            {
1537                continue;
1538            }
1539
1540            method_args.push(quote_spanned! {fn_sig.span() =>
1541                #arg_name: &mut #type_name,
1542            });
1543            external_args_args.push(quote_spanned! {fn_sig.span() => #arg_name });
1544            result_processing_before.push(quote_spanned! {fn_sig.span() =>
1545                let #size_var = args.#size_field;
1546            });
1547            result_processing_after.push(quote_spanned! {fn_sig.span() =>
1548                // SAFETY: The host updates size correctly
1549                unsafe {
1550                    ::ab_contracts_macros::__private::IoType::set_size(#arg_name, #size_var);
1551                }
1552            });
1553        }
1554
1555        let original_method_name = &fn_sig.ident;
1556        let ext_method_name = derive_ffi_fn_name(self_type, trait_name, original_method_name)?;
1557        // Non-`#[view]` methods can only be called on `&mut Env`
1558        let env_self = if matches!(self.method_type, MethodType::View) {
1559            quote_spanned! {fn_sig.span() => &self }
1560        } else {
1561            quote_spanned! {fn_sig.span() => &mut self }
1562        };
1563        // `#[view]` methods do not require explicit method context
1564        let method_context_arg = (!matches!(self.method_type, MethodType::View)).then(|| {
1565            quote_spanned! {fn_sig.span() =>
1566                method_context: ::ab_contracts_macros::__private::MethodContext,
1567            }
1568        });
1569        // Initializer's return type will be `()` for the caller of `#[init]` since the state is
1570        // stored by the host and not returned to the caller. Similarly, it is skipped for a unit
1571        // return type.
1572        let method_signature = if matches!(self.method_type, MethodType::Init)
1573            || self.return_type.unit_return_type()
1574        {
1575            quote_spanned! {fn_sig.span() =>
1576                #[allow(dead_code, reason = "Macro-generated")]
1577                fn #ext_method_name(
1578                    #env_self,
1579                    #method_context_arg
1580                    #( #method_args )*
1581                ) -> ::core::result::Result<(), ::ab_contracts_macros::__private::ContractError>
1582            }
1583        } else {
1584            let return_type = self.return_type.return_type();
1585
1586            preparation.push(quote_spanned! {fn_sig.span() =>
1587                // Ensure the return type implements not only `IoType`, which is required for
1588                // crossing host/guest boundary, but also `TrivialType` and result handling is
1589                // trivial without the need to worry about size and capacity.
1590                // `#[output]` must be used for a variable size result.
1591                const {
1592                    const fn assert_impl_trivial_type<T>()
1593                    where
1594                        T: ::ab_contracts_macros::__private::IoType,
1595                    {}
1596                    assert_impl_trivial_type::<#return_type>();
1597                }
1598
1599                let mut ok_result = ::core::mem::MaybeUninit::uninit();
1600            });
1601            external_args_args.push(quote_spanned! {fn_sig.span() =>
1602                &mut ok_result
1603            });
1604            result_processing_after.push(quote_spanned! {fn_sig.span() =>
1605                // SAFETY: The non-error result indicates successful storing of the result
1606                unsafe {
1607                    ok_result.assume_init()
1608                }
1609            });
1610
1611            quote_spanned! {fn_sig.span() =>
1612                #[allow(dead_code, reason = "Macro-generated")]
1613                fn #ext_method_name(
1614                    #env_self,
1615                    #method_context_arg
1616                    #( #method_args )*
1617                ) -> ::core::result::Result<
1618                    #return_type,
1619                    ::ab_contracts_macros::__private::ContractError,
1620                >
1621            }
1622        };
1623
1624        let attrs = fn_attrs.iter().filter(|attr| {
1625            let path = match &attr.meta {
1626                Meta::Path(path) => path,
1627                Meta::List(list) => &list.path,
1628                Meta::NameValue(name_value) => &name_value.path,
1629            };
1630
1631            if let Some(ident) = path.get_ident() {
1632                ident == "doc" || ident == "allow" || ident == "expect"
1633            } else {
1634                false
1635            }
1636        });
1637        let definition = quote_spanned! {fn_sig.span() =>
1638            #[allow(
1639                clippy::too_many_arguments,
1640                reason = "Generated code may have more arguments that source code"
1641            )]
1642            #( #attrs )*
1643            #method_signature;
1644        };
1645
1646        let args_struct_name =
1647            derive_external_args_struct_name(self_type, trait_name, original_method_name)?;
1648        // `#[view]` methods do not require explicit method context
1649        let method_context_value = if matches!(self.method_type, MethodType::View) {
1650            quote_spanned! {fn_sig.span() =>
1651                ::ab_contracts_macros::__private::MethodContext::Reset
1652            }
1653        } else {
1654            quote_spanned! {fn_sig.span() =>
1655                method_context
1656            }
1657        };
1658        let r#impl = quote_spanned! {fn_sig.span() =>
1659            #[inline]
1660            #method_signature {
1661                #( #preparation )*
1662
1663                let mut args = #original_method_name::#args_struct_name::new(
1664                    #( #external_args_args, )*
1665                );
1666
1667                self.call(contract, &mut args, #method_context_value)?;
1668
1669                #( #result_processing_before )*
1670
1671                #[allow(
1672                    clippy::let_unit_value,
1673                    reason = "Sometimes there is no result to process and block is empty"
1674                )]
1675                let result = {
1676                    #( #result_processing_after )*
1677                };
1678
1679                Ok(result)
1680            }
1681        };
1682
1683        Ok(ExtTraitComponents { definition, r#impl })
1684    }
1685}
1686
1687fn extract_arg_name(mut pat: &Pat) -> Option<Ident> {
1688    loop {
1689        match pat {
1690            Pat::Ident(pat_ident) => {
1691                return Some(pat_ident.ident.clone());
1692            }
1693            Pat::Reference(pat_reference) => {
1694                pat = &pat_reference.pat;
1695            }
1696            _ => {
1697                return None;
1698            }
1699        }
1700    }
1701}
1702
1703fn derive_ffi_fn_name(
1704    type_name: &Type,
1705    trait_name: Option<&Ident>,
1706    method_name: &Ident,
1707) -> Result<Ident, Error> {
1708    let type_name = extract_ident_from_type(type_name).ok_or_else(|| {
1709        Error::new(
1710            type_name.span(),
1711            "`#[contract]` must be applied to a simple struct without generics",
1712        )
1713    })?;
1714    let ffi_fn_prefix =
1715        RenameRule::SnakeCase.apply_to_variant(trait_name.unwrap_or(type_name).to_string());
1716
1717    Ok(format_ident!("{ffi_fn_prefix}_{method_name}"))
1718}
1719
1720fn derive_external_args_struct_name(
1721    type_name: &Type,
1722    trait_name: Option<&Ident>,
1723    method_name: &Ident,
1724) -> Result<Ident, Error> {
1725    let type_name = extract_ident_from_type(type_name).ok_or_else(|| {
1726        Error::new(
1727            type_name.span(),
1728            "`#[contract]` must be applied to a simple struct without generics",
1729        )
1730    })?;
1731    Ok(format_ident!(
1732        "{}{}Args",
1733        trait_name.unwrap_or(type_name),
1734        RenameRule::PascalCase.apply_to_field(method_name.to_string())
1735    ))
1736}