1use crate::metadata::ContractMetadataKind;
2use ab_io_type::metadata::{IoTypeDetails, IoTypeMetadataKind};
3use core::mem::ManuallyDrop;
4use derive_destructure2::destructure;
5
6#[derive(Debug, thiserror::Error)]
8pub enum MetadataDecodingError<'metadata> {
9 #[error("Not enough metadata to decode")]
11 NotEnoughMetadata,
12 #[error("Invalid first metadata byte")]
14 InvalidFirstMetadataByte { byte: u8 },
15 #[error("Multiple contracts found")]
17 MultipleContractsFound,
18 #[error("Expected contract or trait kind, found something else: {metadata_kind:?}")]
20 ExpectedContractOrTrait { metadata_kind: ContractMetadataKind },
21 #[error("Failed to decode state type name")]
23 FailedToDecodeStateTypeName,
24 #[error("Invalid state I/O type")]
26 InvalidStateIoType,
27 #[error("Unexpected method kind {method_kind:?} for container kind {container_kind:?}")]
29 UnexpectedMethodKind {
30 method_kind: MethodKind,
31 container_kind: MethodsContainerKind,
32 },
33 #[error("Expected method kind, found something else: {metadata_kind:?}")]
35 ExpectedMethodKind { metadata_kind: ContractMetadataKind },
36 #[error("Expected argument kind, found something else: {metadata_kind:?}")]
38 ExpectedArgumentKind { metadata_kind: ContractMetadataKind },
39 #[error("Unexpected argument kind {argument_kind:?} for method kind {method_kind:?}")]
41 UnexpectedArgumentKind {
42 argument_kind: ArgumentKind,
43 method_kind: MethodKind,
44 },
45 #[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: &'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: &'metadata [u8],
73 num_methods: u8,
74 decoder: MethodsMetadataDecoder<'a, 'metadata>,
75 },
76}
77
78impl<'a, 'metadata> MetadataItem<'a, 'metadata> {
79 #[inline(always)]
80 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
81 #[expect(
82 clippy::rest_pattern_accessible_field,
83 reason = "Do not need other fields"
84 )]
85 pub fn num_methods(&self) -> u8 {
86 match self {
87 MetadataItem::Contract { num_methods, .. }
88 | MetadataItem::Trait { num_methods, .. } => *num_methods,
89 }
90 }
91
92 #[inline(always)]
93 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
94 #[expect(
95 clippy::rest_pattern_accessible_field,
96 reason = "Do not need other fields"
97 )]
98 pub fn into_decoder(self) -> MethodsMetadataDecoder<'a, 'metadata> {
99 match self {
100 MetadataItem::Contract { decoder, .. } | MetadataItem::Trait { decoder, .. } => decoder,
101 }
102 }
103}
104
105#[derive(Debug)]
106pub struct MetadataDecoder<'metadata> {
107 metadata: &'metadata [u8],
108 found_contract: bool,
109 found_something: bool,
110}
111
112impl<'metadata> MetadataDecoder<'metadata> {
113 #[inline(always)]
114 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
115 pub fn new(metadata: &'metadata [u8]) -> Self {
116 Self {
117 metadata,
118 found_contract: false,
119 found_something: false,
120 }
121 }
122
123 #[inline(always)]
125 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
126 pub fn remaining_metadata_bytes(&self) -> usize {
127 self.metadata.len()
128 }
129
130 #[inline]
132 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
133 pub fn decode_next<'a>(
134 &'a mut self,
135 ) -> Option<Result<MetadataItem<'a, 'metadata>, MetadataDecodingError<'metadata>>> {
136 let metadata_kind = *self.metadata.split_off_first()?;
138 let Ok(metadata_kind) = ContractMetadataKind::try_from(metadata_kind) else {
139 return Some(Err(MetadataDecodingError::InvalidFirstMetadataByte {
140 byte: metadata_kind,
141 }));
142 };
143
144 self.found_something = true;
145
146 match metadata_kind {
147 ContractMetadataKind::Contract => {
148 if self.found_contract {
149 return Some(Err(MetadataDecodingError::MultipleContractsFound));
150 }
151 self.found_contract = true;
152
153 Some(self.decode_contract())
154 }
155 ContractMetadataKind::Trait => Some(self.decode_trait()),
156 ContractMetadataKind::Init
158 | ContractMetadataKind::UpdateStateless
159 | ContractMetadataKind::UpdateStatefulRo
160 | ContractMetadataKind::UpdateStatefulRw
161 | ContractMetadataKind::ViewStateless
162 | ContractMetadataKind::ViewStateful
163 | ContractMetadataKind::EnvRo
164 | ContractMetadataKind::EnvRw
165 | ContractMetadataKind::TmpRo
166 | ContractMetadataKind::TmpRw
167 | ContractMetadataKind::SlotRo
168 | ContractMetadataKind::SlotRw
169 | ContractMetadataKind::Input
170 | ContractMetadataKind::Output
171 | ContractMetadataKind::Return => {
172 Some(Err(MetadataDecodingError::ExpectedContractOrTrait {
173 metadata_kind,
174 }))
175 }
176 }
177 }
178
179 #[inline(always)]
180 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
181 fn decode_contract<'a>(
182 &'a mut self,
183 ) -> Result<MetadataItem<'a, 'metadata>, MetadataDecodingError<'metadata>> {
184 let state_type_name = IoTypeMetadataKind::type_name(self.metadata)
186 .ok_or(MetadataDecodingError::FailedToDecodeStateTypeName)?;
187
188 let state_type_details;
190 (state_type_details, self.metadata) = IoTypeMetadataKind::type_details(self.metadata)
191 .ok_or(MetadataDecodingError::InvalidStateIoType)?;
192
193 let slot_type_details;
195 (slot_type_details, self.metadata) = IoTypeMetadataKind::type_details(self.metadata)
196 .ok_or(MetadataDecodingError::InvalidStateIoType)?;
197
198 let tmp_type_details;
200 (tmp_type_details, self.metadata) = IoTypeMetadataKind::type_details(self.metadata)
201 .ok_or(MetadataDecodingError::InvalidStateIoType)?;
202
203 let num_methods = *self
205 .metadata
206 .split_off_first()
207 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
208
209 Ok(MetadataItem::Contract {
210 state_type_name,
211 state_type_details,
212 slot_type_details,
213 tmp_type_details,
214 num_methods,
215 decoder: MethodsMetadataDecoder::new(
216 &mut self.metadata,
217 MethodsContainerKind::Contract,
218 num_methods,
219 ),
220 })
221 }
222
223 #[inline(always)]
224 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
225 fn decode_trait<'a>(
226 &'a mut self,
227 ) -> Result<MetadataItem<'a, 'metadata>, MetadataDecodingError<'metadata>> {
228 let trait_name_length = usize::from(
230 *self
231 .metadata
232 .split_off_first()
233 .ok_or(MetadataDecodingError::NotEnoughMetadata)?,
234 );
235
236 let trait_name = self
237 .metadata
238 .split_off(..trait_name_length)
239 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
240
241 let num_methods = *self
243 .metadata
244 .split_off_first()
245 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
246
247 Ok(MetadataItem::Trait {
248 trait_name,
249 num_methods,
250 decoder: MethodsMetadataDecoder::new(
251 &mut self.metadata,
252 MethodsContainerKind::Trait,
253 num_methods,
254 ),
255 })
256 }
257}
258
259#[derive(Debug, Copy, Clone)]
260pub enum MethodsContainerKind {
261 Contract,
262 Trait,
263 Unknown,
264}
265
266#[derive(Debug)]
267pub struct MethodsMetadataDecoder<'a, 'metadata> {
268 metadata: &'a mut &'metadata [u8],
269 container_kind: MethodsContainerKind,
270 remaining: u8,
271}
272
273impl Drop for MethodsMetadataDecoder<'_, '_> {
274 fn drop(&mut self) {
275 while let Some(_method_metadata_decoder) = self.decode_next() {
276 }
278 }
279}
280
281impl<'a, 'metadata> MethodsMetadataDecoder<'a, 'metadata> {
282 #[inline(always)]
283 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
284 fn new(
285 metadata: &'a mut &'metadata [u8],
286 container_kind: MethodsContainerKind,
287 num_methods: u8,
288 ) -> Self {
289 Self {
290 metadata,
291 container_kind,
292 remaining: num_methods,
293 }
294 }
295
296 #[inline(always)]
298 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
299 pub fn remaining_methods(&self) -> u8 {
300 self.remaining
301 }
302
303 #[inline(always)]
305 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
306 pub fn remaining_metadata_bytes(&self) -> usize {
307 self.metadata.len()
308 }
309
310 #[inline(always)]
311 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
312 pub fn decode_next<'b>(&'b mut self) -> Option<MethodMetadataDecoder<'b, 'metadata>> {
313 if self.remaining == 0 {
314 return None;
315 }
316
317 self.remaining -= 1;
318
319 Some(MethodMetadataDecoder::new(
320 self.metadata,
321 self.container_kind,
322 ))
323 }
324}
325
326#[derive(Debug, Copy, Clone)]
327pub enum MethodKind {
328 Init,
330 UpdateStateless,
332 UpdateStatefulRo,
334 UpdateStatefulRw,
336 ViewStateless,
338 ViewStateful,
340}
341
342impl MethodKind {
343 #[inline(always)]
344 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
345 pub fn has_self(&self) -> bool {
346 match self {
347 MethodKind::Init | MethodKind::UpdateStateless | MethodKind::ViewStateless => false,
348 MethodKind::UpdateStatefulRo
349 | MethodKind::UpdateStatefulRw
350 | MethodKind::ViewStateful => true,
351 }
352 }
353}
354
355#[derive(Debug, Copy, Clone)]
356pub struct MethodMetadataItem<'metadata> {
357 pub method_name: &'metadata [u8],
361 pub method_kind: MethodKind,
362 pub num_arguments: u8,
363}
364
365#[derive(Debug, destructure)]
367pub struct MethodMetadataDecoder<'a, 'metadata> {
368 metadata: &'a mut &'metadata [u8],
369 container_kind: MethodsContainerKind,
370}
371
372impl Drop for MethodMetadataDecoder<'_, '_> {
373 fn drop(&mut self) {
374 let metadata_before = *self.metadata;
375
376 if MethodMetadataDecoder::new(self.metadata, self.container_kind)
378 .decode_next()
379 .is_err()
380 {
381 *self.metadata = metadata_before;
384 }
385 }
386}
387
388impl<'a, 'metadata> MethodMetadataDecoder<'a, 'metadata> {
389 #[inline(always)]
390 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
391 pub fn new(metadata: &'a mut &'metadata [u8], container_kind: MethodsContainerKind) -> Self {
392 Self {
393 metadata,
394 container_kind,
395 }
396 }
397
398 #[inline(always)]
400 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
401 pub fn remaining_metadata_bytes(&self) -> usize {
402 self.metadata.len()
403 }
404
405 #[inline]
406 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
407 pub fn decode_next(
408 self,
409 ) -> Result<
410 (
411 ArgumentsMetadataDecoder<'a, 'metadata>,
412 MethodMetadataItem<'metadata>,
413 ),
414 MetadataDecodingError<'metadata>,
415 > {
416 let (metadata, container_kind) = self.destructure();
417
418 let metadata_kind = *metadata
420 .split_off_first()
421 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
422 let metadata_kind = ContractMetadataKind::try_from(metadata_kind).map_err(|()| {
423 MetadataDecodingError::InvalidFirstMetadataByte {
424 byte: metadata_kind,
425 }
426 })?;
427
428 let method_kind = match metadata_kind {
429 ContractMetadataKind::Init => MethodKind::Init,
430 ContractMetadataKind::UpdateStateless => MethodKind::UpdateStateless,
431 ContractMetadataKind::UpdateStatefulRo => MethodKind::UpdateStatefulRo,
432 ContractMetadataKind::UpdateStatefulRw => MethodKind::UpdateStatefulRw,
433 ContractMetadataKind::ViewStateless => MethodKind::ViewStateless,
434 ContractMetadataKind::ViewStateful => MethodKind::ViewStateful,
435 ContractMetadataKind::Contract
437 | ContractMetadataKind::Trait
438 | ContractMetadataKind::EnvRo
439 | ContractMetadataKind::EnvRw
440 | ContractMetadataKind::TmpRo
441 | ContractMetadataKind::TmpRw
442 | ContractMetadataKind::SlotRo
443 | ContractMetadataKind::SlotRw
444 | ContractMetadataKind::Input
445 | ContractMetadataKind::Output
446 | ContractMetadataKind::Return => {
447 return Err(MetadataDecodingError::ExpectedMethodKind { metadata_kind });
448 }
449 };
450
451 let method_allowed = match container_kind {
452 MethodsContainerKind::Contract | MethodsContainerKind::Unknown => match method_kind {
453 MethodKind::Init
454 | MethodKind::UpdateStateless
455 | MethodKind::UpdateStatefulRo
456 | MethodKind::UpdateStatefulRw
457 | MethodKind::ViewStateless
458 | MethodKind::ViewStateful => true,
459 },
460 MethodsContainerKind::Trait => match method_kind {
461 MethodKind::Init
462 | MethodKind::UpdateStatefulRo
463 | MethodKind::UpdateStatefulRw
464 | MethodKind::ViewStateful => false,
465 MethodKind::UpdateStateless | MethodKind::ViewStateless => true,
466 },
467 };
468
469 if !method_allowed {
470 return Err(MetadataDecodingError::UnexpectedMethodKind {
471 method_kind,
472 container_kind,
473 });
474 }
475
476 let method_name_length = usize::from(
478 *metadata
479 .split_off_first()
480 .ok_or(MetadataDecodingError::NotEnoughMetadata)?,
481 );
482
483 let method_name = metadata
484 .split_off(..method_name_length)
485 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
486
487 let num_arguments = *metadata
489 .split_off_first()
490 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
491
492 let decoder = ArgumentsMetadataDecoder {
493 metadata,
494 method_kind,
495 remaining: num_arguments,
496 };
497 let item = MethodMetadataItem {
498 method_name,
499 method_kind,
500 num_arguments,
501 };
502
503 Ok((decoder, item))
504 }
505}
506
507#[derive(Debug, Copy, Clone)]
508pub enum ArgumentKind {
509 EnvRo,
511 EnvRw,
513 TmpRo,
515 TmpRw,
517 SlotRo,
519 SlotRw,
521 Input,
523 Output,
525 Return,
527}
528
529#[derive(Debug)]
530pub struct ArgumentMetadataItem<'metadata> {
531 pub argument_name: &'metadata [u8],
535 pub argument_kind: ArgumentKind,
536 pub type_details: Option<IoTypeDetails>,
541}
542
543#[derive(Debug)]
544pub struct ArgumentsMetadataDecoder<'a, 'metadata> {
545 metadata: &'a mut &'metadata [u8],
546 method_kind: MethodKind,
547 remaining: u8,
548}
549
550impl Drop for ArgumentsMetadataDecoder<'_, '_> {
551 fn drop(&mut self) {
552 let metadata_before = *self.metadata;
553 while let Some(maybe_argument_metadata_item) = self.decode_next() {
554 if maybe_argument_metadata_item.is_err() {
555 *self.metadata = metadata_before;
558 break;
559 }
560 }
561 }
562}
563
564impl<'metadata> ArgumentsMetadataDecoder<'_, 'metadata> {
565 pub fn without_auto_drain(self) -> ManuallyDrop<Self> {
575 ManuallyDrop::new(self)
576 }
577
578 #[inline(always)]
580 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
581 pub fn remaining_metadata_bytes(&self) -> usize {
582 self.metadata.len()
583 }
584
585 #[inline]
586 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
587 pub fn decode_next<'a>(
588 &'a mut self,
589 ) -> Option<Result<ArgumentMetadataItem<'metadata>, MetadataDecodingError<'metadata>>> {
590 if self.remaining == 0 {
591 return None;
592 }
593
594 self.remaining -= 1;
595
596 Some(self.decode_argument())
597 }
598
599 #[inline(always)]
600 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
601 fn decode_argument<'a>(
602 &'a mut self,
603 ) -> Result<ArgumentMetadataItem<'metadata>, MetadataDecodingError<'metadata>> {
604 let metadata_kind = *self
606 .metadata
607 .split_off_first()
608 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
609 let metadata_kind = ContractMetadataKind::try_from(metadata_kind).map_err(|()| {
610 MetadataDecodingError::InvalidFirstMetadataByte {
611 byte: metadata_kind,
612 }
613 })?;
614
615 let argument_kind = match metadata_kind {
616 ContractMetadataKind::EnvRo => ArgumentKind::EnvRo,
617 ContractMetadataKind::EnvRw => ArgumentKind::EnvRw,
618 ContractMetadataKind::TmpRo => ArgumentKind::TmpRo,
619 ContractMetadataKind::TmpRw => ArgumentKind::TmpRw,
620 ContractMetadataKind::SlotRo => ArgumentKind::SlotRo,
621 ContractMetadataKind::SlotRw => ArgumentKind::SlotRw,
622 ContractMetadataKind::Input => ArgumentKind::Input,
623 ContractMetadataKind::Output => ArgumentKind::Output,
624 ContractMetadataKind::Return => ArgumentKind::Return,
625 ContractMetadataKind::Contract
627 | ContractMetadataKind::Trait
628 | ContractMetadataKind::Init
629 | ContractMetadataKind::UpdateStateless
630 | ContractMetadataKind::UpdateStatefulRo
631 | ContractMetadataKind::UpdateStatefulRw
632 | ContractMetadataKind::ViewStateless
633 | ContractMetadataKind::ViewStateful => {
634 return Err(MetadataDecodingError::ExpectedArgumentKind { metadata_kind });
635 }
636 };
637
638 let argument_allowed = match self.method_kind {
640 MethodKind::Init
641 | MethodKind::UpdateStateless
642 | MethodKind::UpdateStatefulRo
643 | MethodKind::UpdateStatefulRw => match argument_kind {
644 ArgumentKind::EnvRo
645 | ArgumentKind::EnvRw
646 | ArgumentKind::TmpRo
647 | ArgumentKind::TmpRw
648 | ArgumentKind::SlotRo
649 | ArgumentKind::SlotRw
650 | ArgumentKind::Input
651 | ArgumentKind::Output
652 | ArgumentKind::Return => true,
653 },
654 MethodKind::ViewStateless | MethodKind::ViewStateful => match argument_kind {
655 ArgumentKind::EnvRo
656 | ArgumentKind::SlotRo
657 | ArgumentKind::Input
658 | ArgumentKind::Output
659 | ArgumentKind::Return => true,
660 ArgumentKind::EnvRw
661 | ArgumentKind::TmpRo
662 | ArgumentKind::TmpRw
663 | ArgumentKind::SlotRw => false,
664 },
665 };
666
667 if !argument_allowed {
668 return Err(MetadataDecodingError::UnexpectedArgumentKind {
669 argument_kind,
670 method_kind: self.method_kind,
671 });
672 }
673
674 let (argument_name, type_details) = match argument_kind {
675 ArgumentKind::EnvRo | ArgumentKind::EnvRw => ("env".as_bytes(), None),
676 ArgumentKind::TmpRo
677 | ArgumentKind::TmpRw
678 | ArgumentKind::SlotRo
679 | ArgumentKind::SlotRw
680 | ArgumentKind::Input
681 | ArgumentKind::Output
682 | ArgumentKind::Return => {
683 let argument_name_length = usize::from(
685 *self
686 .metadata
687 .split_off_first()
688 .ok_or(MetadataDecodingError::NotEnoughMetadata)?,
689 );
690 let argument_name = self
691 .metadata
692 .split_off(..argument_name_length)
693 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
694
695 let recommended_capacity = match argument_kind {
696 ArgumentKind::EnvRo
697 | ArgumentKind::EnvRw
698 | ArgumentKind::TmpRo
699 | ArgumentKind::TmpRw
700 | ArgumentKind::SlotRo
701 | ArgumentKind::SlotRw => None,
702 ArgumentKind::Input => {
703 let recommended_capacity;
704 (recommended_capacity, *self.metadata) =
705 IoTypeMetadataKind::type_details(self.metadata).ok_or(
706 MetadataDecodingError::InvalidArgumentIoType {
707 argument_name,
708 argument_kind,
709 },
710 )?;
711
712 Some(recommended_capacity)
713 }
714 ArgumentKind::Output | ArgumentKind::Return => {
715 let last_argument = self.remaining == 0;
716 if matches!((self.method_kind, last_argument), (MethodKind::Init, true)) {
719 None
720 } else {
721 let recommended_capacity;
722 (recommended_capacity, *self.metadata) =
723 IoTypeMetadataKind::type_details(self.metadata).ok_or(
724 MetadataDecodingError::InvalidArgumentIoType {
725 argument_name,
726 argument_kind,
727 },
728 )?;
729
730 Some(recommended_capacity)
731 }
732 }
733 };
734
735 (argument_name, recommended_capacity)
736 }
737 };
738
739 Ok(ArgumentMetadataItem {
740 argument_name,
741 argument_kind,
742 type_details,
743 })
744 }
745}