Skip to main content

ab_contracts_common/metadata/
decode.rs

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