ab_contracts_common/metadata/
decode.rs

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