ab_contracts_common/metadata/
decode.rs

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