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