Skip to main content

ab_contracts_common/metadata/
decode.rs

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