Skip to main content

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