ab_contracts_common/metadata/
decode.rs

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