Skip to main content

ab_contracts_common/metadata/
decode.rs

1use crate::MAX_TOTAL_METHOD_ARGS;
2use crate::metadata::{ContractMetadataKind, MAX_METADATA_DEPTH};
3use ab_io_type::metadata::{IoTypeDetails, IoTypeMetadataKind};
4use core::mem::ManuallyDrop;
5use derive_destructure2::destructure;
6
7/// Metadata decoding error
8#[derive(Debug, thiserror::Error)]
9pub enum MetadataDecodingError<'metadata> {
10    /// Not enough metadata to decode
11    #[error("Not enough metadata to decode")]
12    NotEnoughMetadata,
13    /// Invalid first metadata byte
14    #[error("Invalid first metadata byte")]
15    InvalidFirstMetadataByte { byte: u8 },
16    /// Multiple contracts found
17    #[error("Multiple contracts found")]
18    MultipleContractsFound,
19    /// Expected contract or trait kind, found something else
20    #[error("Expected contract or trait kind, found something else: {metadata_kind:?}")]
21    ExpectedContractOrTrait { metadata_kind: ContractMetadataKind },
22    /// Failed to decode state type name
23    #[error("Failed to decode state type name")]
24    FailedToDecodeStateTypeName,
25    /// Invalid state I/O type
26    #[error("Invalid state I/O type")]
27    InvalidStateIoType,
28    /// Unexpected method kind
29    #[error("Unexpected method kind {method_kind:?} for container kind {container_kind:?}")]
30    UnexpectedMethodKind {
31        method_kind: MethodKind,
32        container_kind: MethodsContainerKind,
33    },
34    /// Expected method kind, found something else
35    #[error("Expected method kind, found something else: {metadata_kind:?}")]
36    ExpectedMethodKind { metadata_kind: ContractMetadataKind },
37    /// More arguments than `#[contract]` allows, see [`MAX_TOTAL_METHOD_ARGS`]
38    #[error("Too many arguments: {number_of_arguments}")]
39    TooManyArguments {
40        /// Number of arguments in metadata, together with `self` in methods that have it
41        number_of_arguments: u8,
42    },
43    /// `#[init]` method that doesn't end with the state, see [`ContractMetadataKind::Init`]
44    #[error("`#[init]` method doesn't end with the state")]
45    InitWithoutState,
46    /// Expected argument kind, found something else
47    #[error("Expected argument kind, found something else: {metadata_kind:?}")]
48    ExpectedArgumentKind { metadata_kind: ContractMetadataKind },
49    /// Unexpected argument kind
50    #[error(
51        "Unexpected argument kind {argument_kind:?} for method kind {method_kind:?} in container \
52        kind {container_kind:?}"
53    )]
54    UnexpectedArgumentKind {
55        argument_kind: ArgumentKind,
56        method_kind: MethodKind,
57        container_kind: MethodsContainerKind,
58    },
59    /// An argument out of the order that `#[contract]` generates, see
60    /// [`ContractMetadataKind::Init`]
61    #[error("Argument of kind {argument_kind:?} is out of order")]
62    ArgumentOutOfOrder { argument_kind: ArgumentKind },
63    /// Invalid argument I/O type
64    #[error("Invalid argument I/O type of kind {argument_kind:?} for {argument_name:?}")]
65    InvalidArgumentIoType {
66        argument_name: &'metadata [u8],
67        argument_kind: ArgumentKind,
68    },
69}
70
71#[derive(Debug)]
72pub enum MetadataItem<'a, 'metadata> {
73    Contract {
74        /// State type name as bytes.
75        ///
76        /// Expected to be UTF-8, but must be parsed before printed as text, which is somewhat
77        /// costly.
78        state_type_name: &'metadata [u8],
79        state_type_details: IoTypeDetails,
80        slot_type_details: IoTypeDetails,
81        tmp_type_details: IoTypeDetails,
82        num_methods: u8,
83        decoder: MethodsMetadataDecoder<'a, 'metadata>,
84    },
85    Trait {
86        /// Trait name as bytes.
87        ///
88        /// Expected to be UTF-8, but must be parsed before printed as text, which is somewhat
89        /// costly.
90        trait_name: &'metadata [u8],
91        num_methods: u8,
92        decoder: MethodsMetadataDecoder<'a, 'metadata>,
93    },
94}
95
96impl<'a, 'metadata> MetadataItem<'a, 'metadata> {
97    #[inline(always)]
98    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
99    #[expect(
100        clippy::rest_pattern_accessible_field,
101        reason = "Do not need other fields"
102    )]
103    pub fn num_methods(&self) -> u8 {
104        match self {
105            MetadataItem::Contract { num_methods, .. }
106            | MetadataItem::Trait { num_methods, .. } => *num_methods,
107        }
108    }
109
110    #[inline(always)]
111    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
112    #[expect(
113        clippy::rest_pattern_accessible_field,
114        reason = "Do not need other fields"
115    )]
116    pub fn into_decoder(self) -> MethodsMetadataDecoder<'a, 'metadata> {
117        match self {
118            MetadataItem::Contract { decoder, .. } | MetadataItem::Trait { decoder, .. } => decoder,
119        }
120    }
121}
122
123#[derive(Debug)]
124pub struct MetadataDecoder<'metadata> {
125    metadata: &'metadata [u8],
126    found_contract: bool,
127    found_something: bool,
128}
129
130impl<'metadata> MetadataDecoder<'metadata> {
131    #[inline(always)]
132    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
133    pub fn new(metadata: &'metadata [u8]) -> Self {
134        Self {
135            metadata,
136            found_contract: false,
137            found_something: false,
138        }
139    }
140
141    /// The number of bytes left in the metadata that were not processed yet
142    #[inline(always)]
143    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
144    pub fn remaining_metadata_bytes(&self) -> usize {
145        self.metadata.len()
146    }
147
148    // TODO: Helper method to decode all methods, ideally as a single iterator
149    #[inline]
150    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
151    pub fn decode_next<'a>(
152        &'a mut self,
153    ) -> Option<Result<MetadataItem<'a, 'metadata>, MetadataDecodingError<'metadata>>> {
154        // Decode method kind
155        let metadata_kind = *self.metadata.split_off_first()?;
156        let Ok(metadata_kind) = ContractMetadataKind::try_from(metadata_kind) else {
157            return Some(Err(MetadataDecodingError::InvalidFirstMetadataByte {
158                byte: metadata_kind,
159            }));
160        };
161
162        self.found_something = true;
163
164        match metadata_kind {
165            ContractMetadataKind::Contract => {
166                if self.found_contract {
167                    return Some(Err(MetadataDecodingError::MultipleContractsFound));
168                }
169                self.found_contract = true;
170
171                Some(self.decode_contract())
172            }
173            ContractMetadataKind::Trait => Some(self.decode_trait()),
174            // The rest are methods or arguments and can't appear here
175            ContractMetadataKind::Init
176            | ContractMetadataKind::UpdateStateless
177            | ContractMetadataKind::UpdateStatefulRo
178            | ContractMetadataKind::UpdateStatefulRw
179            | ContractMetadataKind::ViewStateless
180            | ContractMetadataKind::ViewStateful
181            | ContractMetadataKind::EnvRo
182            | ContractMetadataKind::EnvRw
183            | ContractMetadataKind::TmpRo
184            | ContractMetadataKind::TmpRw
185            | ContractMetadataKind::SlotRo
186            | ContractMetadataKind::SlotRw
187            | ContractMetadataKind::Input
188            | ContractMetadataKind::Output
189            | ContractMetadataKind::Return => {
190                Some(Err(MetadataDecodingError::ExpectedContractOrTrait {
191                    metadata_kind,
192                }))
193            }
194        }
195    }
196
197    #[inline(always)]
198    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
199    fn decode_contract<'a>(
200        &'a mut self,
201    ) -> Result<MetadataItem<'a, 'metadata>, MetadataDecodingError<'metadata>> {
202        // Decode state type name without moving the metadata cursor
203        let state_type_name = IoTypeMetadataKind::type_name(self.metadata)
204            .ok_or(MetadataDecodingError::FailedToDecodeStateTypeName)?;
205
206        // Decode the recommended capacity of the state type
207        let state_type_details;
208        (state_type_details, self.metadata) =
209            IoTypeMetadataKind::type_details::<MAX_METADATA_DEPTH>(self.metadata)
210                .ok_or(MetadataDecodingError::InvalidStateIoType)?;
211
212        // Decode the recommended capacity of the `#[slot]` type
213        let slot_type_details;
214        (slot_type_details, self.metadata) =
215            IoTypeMetadataKind::type_details::<MAX_METADATA_DEPTH>(self.metadata)
216                .ok_or(MetadataDecodingError::InvalidStateIoType)?;
217
218        // Decode the recommended capacity of the `#[tmp]` type
219        let tmp_type_details;
220        (tmp_type_details, self.metadata) =
221            IoTypeMetadataKind::type_details::<MAX_METADATA_DEPTH>(self.metadata)
222                .ok_or(MetadataDecodingError::InvalidStateIoType)?;
223
224        // Decode the number of methods
225        let num_methods = *self
226            .metadata
227            .split_off_first()
228            .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
229
230        Ok(MetadataItem::Contract {
231            state_type_name,
232            state_type_details,
233            slot_type_details,
234            tmp_type_details,
235            num_methods,
236            decoder: MethodsMetadataDecoder::new(
237                &mut self.metadata,
238                MethodsContainerKind::Contract,
239                num_methods,
240            ),
241        })
242    }
243
244    #[inline(always)]
245    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
246    fn decode_trait<'a>(
247        &'a mut self,
248    ) -> Result<MetadataItem<'a, 'metadata>, MetadataDecodingError<'metadata>> {
249        // Decode trait name
250        let trait_name_length = usize::from(
251            *self
252                .metadata
253                .split_off_first()
254                .ok_or(MetadataDecodingError::NotEnoughMetadata)?,
255        );
256
257        let trait_name = self
258            .metadata
259            .split_off(..trait_name_length)
260            .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
261
262        // Decode the number of methods
263        let num_methods = *self
264            .metadata
265            .split_off_first()
266            .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
267
268        Ok(MetadataItem::Trait {
269            trait_name,
270            num_methods,
271            decoder: MethodsMetadataDecoder::new(
272                &mut self.metadata,
273                MethodsContainerKind::Trait,
274                num_methods,
275            ),
276        })
277    }
278}
279
280#[derive(Debug, Copy, Clone)]
281pub enum MethodsContainerKind {
282    Contract,
283    Trait,
284    Unknown,
285}
286
287#[derive(Debug)]
288pub struct MethodsMetadataDecoder<'a, 'metadata> {
289    metadata: &'a mut &'metadata [u8],
290    container_kind: MethodsContainerKind,
291    remaining: u8,
292}
293
294impl Drop for MethodsMetadataDecoder<'_, '_> {
295    fn drop(&mut self) {
296        while let Some(_method_metadata_decoder) = self.decode_next() {
297            // Drain remaining methods if dropped early
298        }
299    }
300}
301
302impl<'a, 'metadata> MethodsMetadataDecoder<'a, 'metadata> {
303    #[inline(always)]
304    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
305    fn new(
306        metadata: &'a mut &'metadata [u8],
307        container_kind: MethodsContainerKind,
308        num_methods: u8,
309    ) -> Self {
310        Self {
311            metadata,
312            container_kind,
313            remaining: num_methods,
314        }
315    }
316
317    /// Remaining methods in the decoder
318    #[inline(always)]
319    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
320    pub fn remaining_methods(&self) -> u8 {
321        self.remaining
322    }
323
324    /// The number of bytes left in the metadata that were not processed yet
325    #[inline(always)]
326    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
327    pub fn remaining_metadata_bytes(&self) -> usize {
328        self.metadata.len()
329    }
330
331    #[inline(always)]
332    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
333    pub fn decode_next<'b>(&'b mut self) -> Option<MethodMetadataDecoder<'b, 'metadata>> {
334        if self.remaining == 0 {
335            return None;
336        }
337
338        self.remaining -= 1;
339
340        Some(MethodMetadataDecoder::new(
341            self.metadata,
342            self.container_kind,
343        ))
344    }
345}
346
347#[derive(Debug, Copy, Clone)]
348pub enum MethodKind {
349    /// Corresponds to [`ContractMetadataKind::Init`]
350    Init,
351    /// Corresponds to [`ContractMetadataKind::UpdateStateless`]
352    UpdateStateless,
353    /// Corresponds to [`ContractMetadataKind::UpdateStatefulRo`]
354    UpdateStatefulRo,
355    /// Corresponds to [`ContractMetadataKind::UpdateStatefulRw`]
356    UpdateStatefulRw,
357    /// Corresponds to [`ContractMetadataKind::ViewStateless`]
358    ViewStateless,
359    /// Corresponds to [`ContractMetadataKind::ViewStateful`]
360    ViewStateful,
361}
362
363impl MethodKind {
364    #[inline(always)]
365    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
366    pub fn has_self(&self) -> bool {
367        match self {
368            MethodKind::Init | MethodKind::UpdateStateless | MethodKind::ViewStateless => false,
369            MethodKind::UpdateStatefulRo
370            | MethodKind::UpdateStatefulRw
371            | MethodKind::ViewStateful => true,
372        }
373    }
374}
375
376#[derive(Debug, Copy, Clone)]
377pub struct MethodMetadataItem<'metadata> {
378    /// Method name as bytes.
379    ///
380    /// Expected to be UTF-8, but must be parsed before printed as text, which is somewhat costly.
381    pub method_name: &'metadata [u8],
382    pub method_kind: MethodKind,
383    /// Number of arguments in the metadata of the method, which is the number of items that
384    /// [`ArgumentsMetadataDecoder::decode_next()`] returns. `self` is not one of them, since it is
385    /// part of the method kind (see [`MethodKind::has_self()`]).
386    pub num_arguments: u8,
387}
388
389// TODO: Would be nice to also collect fingerprint at the end
390#[derive(Debug, destructure)]
391pub struct MethodMetadataDecoder<'a, 'metadata> {
392    metadata: &'a mut &'metadata [u8],
393    container_kind: MethodsContainerKind,
394}
395
396impl Drop for MethodMetadataDecoder<'_, '_> {
397    fn drop(&mut self) {
398        let metadata_before = *self.metadata;
399
400        // Decode implicitly
401        if MethodMetadataDecoder::new(self.metadata, self.container_kind)
402            .decode_next()
403            .is_err()
404        {
405            // Restore original metadata if decoding failed so it fails decoding later, this is
406            // a compromise to avoid panics on `drop`
407            *self.metadata = metadata_before;
408        }
409    }
410}
411
412impl<'a, 'metadata> MethodMetadataDecoder<'a, 'metadata> {
413    #[inline(always)]
414    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
415    pub fn new(metadata: &'a mut &'metadata [u8], container_kind: MethodsContainerKind) -> Self {
416        Self {
417            metadata,
418            container_kind,
419        }
420    }
421
422    /// The number of bytes left in the metadata that were not processed yet
423    #[inline(always)]
424    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
425    pub fn remaining_metadata_bytes(&self) -> usize {
426        self.metadata.len()
427    }
428
429    #[inline]
430    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
431    pub fn decode_next(
432        self,
433    ) -> Result<
434        (
435            ArgumentsMetadataDecoder<'a, 'metadata>,
436            MethodMetadataItem<'metadata>,
437        ),
438        MetadataDecodingError<'metadata>,
439    > {
440        let (metadata, container_kind) = self.destructure();
441
442        // Decode method kind
443        let metadata_kind = *metadata
444            .split_off_first()
445            .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
446        let metadata_kind = ContractMetadataKind::try_from(metadata_kind).map_err(|()| {
447            MetadataDecodingError::InvalidFirstMetadataByte {
448                byte: metadata_kind,
449            }
450        })?;
451
452        let method_kind = match metadata_kind {
453            ContractMetadataKind::Init => MethodKind::Init,
454            ContractMetadataKind::UpdateStateless => MethodKind::UpdateStateless,
455            ContractMetadataKind::UpdateStatefulRo => MethodKind::UpdateStatefulRo,
456            ContractMetadataKind::UpdateStatefulRw => MethodKind::UpdateStatefulRw,
457            ContractMetadataKind::ViewStateless => MethodKind::ViewStateless,
458            ContractMetadataKind::ViewStateful => MethodKind::ViewStateful,
459            // The rest are not methods and can't appear here
460            ContractMetadataKind::Contract
461            | ContractMetadataKind::Trait
462            | ContractMetadataKind::EnvRo
463            | ContractMetadataKind::EnvRw
464            | ContractMetadataKind::TmpRo
465            | ContractMetadataKind::TmpRw
466            | ContractMetadataKind::SlotRo
467            | ContractMetadataKind::SlotRw
468            | ContractMetadataKind::Input
469            | ContractMetadataKind::Output
470            | ContractMetadataKind::Return => {
471                return Err(MetadataDecodingError::ExpectedMethodKind { metadata_kind });
472            }
473        };
474
475        let method_allowed = match container_kind {
476            MethodsContainerKind::Contract | MethodsContainerKind::Unknown => match method_kind {
477                MethodKind::Init
478                | MethodKind::UpdateStateless
479                | MethodKind::UpdateStatefulRo
480                | MethodKind::UpdateStatefulRw
481                | MethodKind::ViewStateless
482                | MethodKind::ViewStateful => true,
483            },
484            MethodsContainerKind::Trait => match method_kind {
485                MethodKind::Init
486                | MethodKind::UpdateStatefulRo
487                | MethodKind::UpdateStatefulRw
488                | MethodKind::ViewStateful => false,
489                MethodKind::UpdateStateless | MethodKind::ViewStateless => true,
490            },
491        };
492
493        if !method_allowed {
494            return Err(MetadataDecodingError::UnexpectedMethodKind {
495                method_kind,
496                container_kind,
497            });
498        }
499
500        // Decode method name
501        let method_name_length = usize::from(
502            *metadata
503                .split_off_first()
504                .ok_or(MetadataDecodingError::NotEnoughMetadata)?,
505        );
506
507        let method_name = metadata
508            .split_off(..method_name_length)
509            .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
510
511        // Decode the number of arguments
512        let num_arguments = *metadata
513            .split_off_first()
514            .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
515
516        // `self` is not in metadata, but counts towards `MAX_TOTAL_METHOD_ARGS` in methods with it
517        let number_of_arguments = num_arguments.saturating_add(u8::from(method_kind.has_self()));
518        if number_of_arguments > MAX_TOTAL_METHOD_ARGS {
519            return Err(MetadataDecodingError::TooManyArguments {
520                number_of_arguments,
521            });
522        }
523
524        // The state is the last argument of `#[init]`, the arguments decoder checks its kind
525        if matches!(method_kind, MethodKind::Init) && num_arguments == 0 {
526            return Err(MetadataDecodingError::InitWithoutState);
527        }
528
529        let decoder = ArgumentsMetadataDecoder {
530            metadata,
531            method_kind,
532            container_kind,
533            remaining: num_arguments,
534            next_position: ArgumentPosition::Env,
535        };
536        let item = MethodMetadataItem {
537            method_name,
538            method_kind,
539            num_arguments,
540        };
541
542        Ok((decoder, item))
543    }
544}
545
546#[derive(Debug, Copy, Clone)]
547pub enum ArgumentKind {
548    /// Corresponds to [`ContractMetadataKind::EnvRo`]
549    EnvRo,
550    /// Corresponds to [`ContractMetadataKind::EnvRw`]
551    EnvRw,
552    /// Corresponds to [`ContractMetadataKind::TmpRo`]
553    TmpRo,
554    /// Corresponds to [`ContractMetadataKind::TmpRw`]
555    TmpRw,
556    /// Corresponds to [`ContractMetadataKind::SlotRo`]
557    SlotRo,
558    /// Corresponds to [`ContractMetadataKind::SlotRw`]
559    SlotRw,
560    /// Corresponds to [`ContractMetadataKind::Input`]
561    Input,
562    /// Corresponds to [`ContractMetadataKind::Output`]
563    Output,
564    /// Corresponds to [`ContractMetadataKind::Return`]
565    Return,
566}
567
568/// Position of an argument in the order that `#[contract]` generates, see
569/// [`ContractMetadataKind::Init`]
570#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
571enum ArgumentPosition {
572    Env,
573    Tmp,
574    Slots,
575    Inputs,
576    Outputs,
577    Return,
578    /// After the return value, where no argument can be
579    End,
580}
581
582#[derive(Debug)]
583pub struct ArgumentMetadataItem<'metadata> {
584    /// Argument name as bytes.
585    ///
586    /// Expected to be UTF-8, but must be parsed before printed as text, which is somewhat costly.
587    pub argument_name: &'metadata [u8],
588    pub argument_kind: ArgumentKind,
589    /// Exceptions:
590    /// * `None` for `#[env]`, `#[tmp]` and `#[slot]`
591    /// * `None` for the last `#[output]` or return type otherwise in `#[init]` (see
592    ///   [`ContractMetadataKind::Init`] for details)
593    pub type_details: Option<IoTypeDetails>,
594}
595
596#[derive(Debug)]
597pub struct ArgumentsMetadataDecoder<'a, 'metadata> {
598    metadata: &'a mut &'metadata [u8],
599    method_kind: MethodKind,
600    container_kind: MethodsContainerKind,
601    remaining: u8,
602    /// The earliest position that the next argument can have
603    next_position: ArgumentPosition,
604}
605
606impl Drop for ArgumentsMetadataDecoder<'_, '_> {
607    fn drop(&mut self) {
608        let metadata_before = *self.metadata;
609        while let Some(maybe_argument_metadata_item) = self.decode_next() {
610            if maybe_argument_metadata_item.is_err() {
611                // Restore original metadata if decoding failed so it fails decoding later, this is
612                // a compromise to avoid panics on `drop`
613                *self.metadata = metadata_before;
614                break;
615            }
616        }
617    }
618}
619
620impl<'metadata> ArgumentsMetadataDecoder<'_, 'metadata> {
621    /// Get a wrapped value that does not automatically drain the metadata on `drop`.
622    ///
623    /// The default behavior is to automatically drain the metadata on `drop` such that
624    /// contract/trait or method metadata decoding progresses successfully, even when the caller
625    /// doesn't care about arguments. This, however, generates more code and causes difficulties for
626    /// LLVM when it tries to optimize the code and especially when trying to prove the lack of
627    /// panics. Usually this method is not needed, but if you are having difficulties with
628    /// `no-panic` and either decoding a single method or drain arguments explicitly, you can use
629    /// this helper method to work around compiler limitations.
630    pub fn without_auto_drain(self) -> ManuallyDrop<Self> {
631        ManuallyDrop::new(self)
632    }
633
634    /// The number of bytes left in the metadata that were not processed yet
635    #[inline(always)]
636    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
637    pub fn remaining_metadata_bytes(&self) -> usize {
638        self.metadata.len()
639    }
640
641    /// Decode the next argument, returning `None` after [`MethodMetadataItem::num_arguments`]
642    /// items, errors included.
643    ///
644    /// Arguments are returned in the order that `#[contract]` generates them in, which
645    /// [`ContractMetadataKind::Init`] describes. An argument out of that order is rejected with
646    /// [`MetadataDecodingError::ArgumentOutOfOrder`]. The last argument of `#[init]` is always its
647    /// state, otherwise [`MetadataDecodingError::InitWithoutState`] is returned.
648    #[inline]
649    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
650    pub fn decode_next<'a>(
651        &'a mut self,
652    ) -> Option<Result<ArgumentMetadataItem<'metadata>, MetadataDecodingError<'metadata>>> {
653        if self.remaining == 0 {
654            return None;
655        }
656
657        self.remaining -= 1;
658
659        Some(self.decode_argument())
660    }
661
662    #[inline(always)]
663    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
664    fn decode_argument<'a>(
665        &'a mut self,
666    ) -> Result<ArgumentMetadataItem<'metadata>, MetadataDecodingError<'metadata>> {
667        // Decode method kind
668        let metadata_kind = *self
669            .metadata
670            .split_off_first()
671            .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
672        let metadata_kind = ContractMetadataKind::try_from(metadata_kind).map_err(|()| {
673            MetadataDecodingError::InvalidFirstMetadataByte {
674                byte: metadata_kind,
675            }
676        })?;
677
678        let argument_kind = match metadata_kind {
679            ContractMetadataKind::EnvRo => ArgumentKind::EnvRo,
680            ContractMetadataKind::EnvRw => ArgumentKind::EnvRw,
681            ContractMetadataKind::TmpRo => ArgumentKind::TmpRo,
682            ContractMetadataKind::TmpRw => ArgumentKind::TmpRw,
683            ContractMetadataKind::SlotRo => ArgumentKind::SlotRo,
684            ContractMetadataKind::SlotRw => ArgumentKind::SlotRw,
685            ContractMetadataKind::Input => ArgumentKind::Input,
686            ContractMetadataKind::Output => ArgumentKind::Output,
687            ContractMetadataKind::Return => ArgumentKind::Return,
688            // The rest are not arguments and can't appear here
689            ContractMetadataKind::Contract
690            | ContractMetadataKind::Trait
691            | ContractMetadataKind::Init
692            | ContractMetadataKind::UpdateStateless
693            | ContractMetadataKind::UpdateStatefulRo
694            | ContractMetadataKind::UpdateStatefulRw
695            | ContractMetadataKind::ViewStateless
696            | ContractMetadataKind::ViewStateful => {
697                return Err(MetadataDecodingError::ExpectedArgumentKind { metadata_kind });
698            }
699        };
700
701        let argument_allowed = match self.method_kind {
702            MethodKind::Init
703            | MethodKind::UpdateStateless
704            | MethodKind::UpdateStatefulRo
705            | MethodKind::UpdateStatefulRw => match argument_kind {
706                ArgumentKind::EnvRo
707                | ArgumentKind::EnvRw
708                | ArgumentKind::TmpRo
709                | ArgumentKind::TmpRw
710                | ArgumentKind::SlotRo
711                | ArgumentKind::SlotRw
712                | ArgumentKind::Input
713                | ArgumentKind::Output
714                | ArgumentKind::Return => true,
715            },
716            MethodKind::ViewStateless | MethodKind::ViewStateful => match argument_kind {
717                ArgumentKind::EnvRo
718                | ArgumentKind::SlotRo
719                | ArgumentKind::Input
720                | ArgumentKind::Output
721                | ArgumentKind::Return => true,
722                ArgumentKind::EnvRw
723                | ArgumentKind::TmpRo
724                | ArgumentKind::TmpRw
725                | ArgumentKind::SlotRw => false,
726            },
727        };
728
729        // `#[tmp]` and `#[slot]` types are defined by a contract, so methods of traits have none
730        let argument_allowed_in_container = match self.container_kind {
731            MethodsContainerKind::Contract | MethodsContainerKind::Unknown => true,
732            MethodsContainerKind::Trait => match argument_kind {
733                ArgumentKind::EnvRo
734                | ArgumentKind::EnvRw
735                | ArgumentKind::Input
736                | ArgumentKind::Output
737                | ArgumentKind::Return => true,
738                ArgumentKind::TmpRo
739                | ArgumentKind::TmpRw
740                | ArgumentKind::SlotRo
741                | ArgumentKind::SlotRw => false,
742            },
743        };
744
745        if !argument_allowed || !argument_allowed_in_container {
746            return Err(MetadataDecodingError::UnexpectedArgumentKind {
747                argument_kind,
748                method_kind: self.method_kind,
749                container_kind: self.container_kind,
750            });
751        }
752
753        // The position of the argument and the earliest position of the next one. `#[env]`,
754        // `#[tmp]` and the return value appear at most once, so the next argument must come after
755        // them.
756        let (position, next_position) = match argument_kind {
757            ArgumentKind::EnvRo | ArgumentKind::EnvRw => {
758                (ArgumentPosition::Env, ArgumentPosition::Tmp)
759            }
760            ArgumentKind::TmpRo | ArgumentKind::TmpRw => {
761                (ArgumentPosition::Tmp, ArgumentPosition::Slots)
762            }
763            ArgumentKind::SlotRo | ArgumentKind::SlotRw => {
764                (ArgumentPosition::Slots, ArgumentPosition::Slots)
765            }
766            ArgumentKind::Input => (ArgumentPosition::Inputs, ArgumentPosition::Inputs),
767            ArgumentKind::Output => (ArgumentPosition::Outputs, ArgumentPosition::Outputs),
768            ArgumentKind::Return => (ArgumentPosition::Return, ArgumentPosition::End),
769        };
770        if position < self.next_position {
771            return Err(MetadataDecodingError::ArgumentOutOfOrder { argument_kind });
772        }
773        self.next_position = next_position;
774
775        let last_argument = self.remaining == 0;
776        // The last argument of `#[init]` is the state, see `ContractMetadataKind::Init` for details
777        if matches!(self.method_kind, MethodKind::Init)
778            && last_argument
779            && !matches!(argument_kind, ArgumentKind::Output | ArgumentKind::Return)
780        {
781            return Err(MetadataDecodingError::InitWithoutState);
782        }
783
784        let (argument_name, type_details) = match argument_kind {
785            ArgumentKind::EnvRo | ArgumentKind::EnvRw => ("env".as_bytes(), None),
786            ArgumentKind::TmpRo
787            | ArgumentKind::TmpRw
788            | ArgumentKind::SlotRo
789            | ArgumentKind::SlotRw
790            | ArgumentKind::Input
791            | ArgumentKind::Output
792            | ArgumentKind::Return => {
793                // Decode argument name
794                let argument_name_length = usize::from(
795                    *self
796                        .metadata
797                        .split_off_first()
798                        .ok_or(MetadataDecodingError::NotEnoughMetadata)?,
799                );
800                let argument_name = self
801                    .metadata
802                    .split_off(..argument_name_length)
803                    .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
804
805                let recommended_capacity = match argument_kind {
806                    ArgumentKind::EnvRo
807                    | ArgumentKind::EnvRw
808                    | ArgumentKind::TmpRo
809                    | ArgumentKind::TmpRw
810                    | ArgumentKind::SlotRo
811                    | ArgumentKind::SlotRw => None,
812                    ArgumentKind::Input => {
813                        let decoded =
814                            IoTypeMetadataKind::type_details::<MAX_METADATA_DEPTH>(self.metadata);
815                        let recommended_capacity;
816                        (recommended_capacity, *self.metadata) =
817                            decoded.ok_or(MetadataDecodingError::InvalidArgumentIoType {
818                                argument_name,
819                                argument_kind,
820                            })?;
821
822                        Some(recommended_capacity)
823                    }
824                    ArgumentKind::Output | ArgumentKind::Return => {
825                        // May be skipped for `#[init]`, see `ContractMetadataKind::Init` for
826                        // details
827                        if matches!((self.method_kind, last_argument), (MethodKind::Init, true)) {
828                            None
829                        } else {
830                            let decoded = IoTypeMetadataKind::type_details::<MAX_METADATA_DEPTH>(
831                                self.metadata,
832                            );
833                            let recommended_capacity;
834                            (recommended_capacity, *self.metadata) =
835                                decoded.ok_or(MetadataDecodingError::InvalidArgumentIoType {
836                                    argument_name,
837                                    argument_kind,
838                                })?;
839
840                            Some(recommended_capacity)
841                        }
842                    }
843                };
844
845                (argument_name, recommended_capacity)
846            }
847        };
848
849        Ok(ArgumentMetadataItem {
850            argument_name,
851            argument_kind,
852            type_details,
853        })
854    }
855}