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 pub fn num_methods(&self) -> u8 {
82 match self {
83 MetadataItem::Contract { num_methods, .. }
84 | MetadataItem::Trait { num_methods, .. } => *num_methods,
85 }
86 }
87
88 #[inline(always)]
89 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
90 pub fn into_decoder(self) -> MethodsMetadataDecoder<'a, 'metadata> {
91 match self {
92 MetadataItem::Contract { decoder, .. } | MetadataItem::Trait { decoder, .. } => decoder,
93 }
94 }
95}
96
97#[derive(Debug)]
98pub struct MetadataDecoder<'metadata> {
99 metadata: &'metadata [u8],
100 found_contract: bool,
101 found_something: bool,
102}
103
104impl<'metadata> MetadataDecoder<'metadata> {
105 #[inline(always)]
106 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
107 pub fn new(metadata: &'metadata [u8]) -> Self {
108 Self {
109 metadata,
110 found_contract: false,
111 found_something: false,
112 }
113 }
114
115 #[inline(always)]
117 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
118 pub fn remaining_metadata_bytes(&self) -> usize {
119 self.metadata.len()
120 }
121
122 #[inline]
124 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
125 pub fn decode_next<'a>(
126 &'a mut self,
127 ) -> Option<Result<MetadataItem<'a, 'metadata>, MetadataDecodingError<'metadata>>> {
128 let metadata_kind = *self.metadata.split_off_first()?;
130 let Some(metadata_kind) = ContractMetadataKind::try_from_u8(metadata_kind) else {
131 return Some(Err(MetadataDecodingError::InvalidFirstMetadataByte {
132 byte: metadata_kind,
133 }));
134 };
135
136 self.found_something = true;
137
138 match metadata_kind {
139 ContractMetadataKind::Contract => {
140 if self.found_contract {
141 return Some(Err(MetadataDecodingError::MultipleContractsFound));
142 }
143 self.found_contract = true;
144
145 Some(self.decode_contract())
146 }
147 ContractMetadataKind::Trait => Some(self.decode_trait()),
148 ContractMetadataKind::Init
150 | ContractMetadataKind::UpdateStateless
151 | ContractMetadataKind::UpdateStatefulRo
152 | ContractMetadataKind::UpdateStatefulRw
153 | ContractMetadataKind::ViewStateless
154 | ContractMetadataKind::ViewStateful
155 | ContractMetadataKind::EnvRo
156 | ContractMetadataKind::EnvRw
157 | ContractMetadataKind::TmpRo
158 | ContractMetadataKind::TmpRw
159 | ContractMetadataKind::SlotRo
160 | ContractMetadataKind::SlotRw
161 | ContractMetadataKind::Input
162 | ContractMetadataKind::Output => {
163 Some(Err(MetadataDecodingError::ExpectedContractOrTrait {
164 metadata_kind,
165 }))
166 }
167 }
168 }
169
170 #[inline(always)]
171 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
172 fn decode_contract<'a>(
173 &'a mut self,
174 ) -> Result<MetadataItem<'a, 'metadata>, MetadataDecodingError<'metadata>> {
175 let state_type_name = IoTypeMetadataKind::type_name(self.metadata)
177 .ok_or(MetadataDecodingError::FailedToDecodeStateTypeName)?;
178
179 let state_type_details;
181 (state_type_details, self.metadata) = IoTypeMetadataKind::type_details(self.metadata)
182 .ok_or(MetadataDecodingError::InvalidStateIoType)?;
183
184 let slot_type_details;
186 (slot_type_details, self.metadata) = IoTypeMetadataKind::type_details(self.metadata)
187 .ok_or(MetadataDecodingError::InvalidStateIoType)?;
188
189 let tmp_type_details;
191 (tmp_type_details, self.metadata) = IoTypeMetadataKind::type_details(self.metadata)
192 .ok_or(MetadataDecodingError::InvalidStateIoType)?;
193
194 let num_methods = *self
196 .metadata
197 .split_off_first()
198 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
199
200 Ok(MetadataItem::Contract {
201 state_type_name,
202 state_type_details,
203 slot_type_details,
204 tmp_type_details,
205 num_methods,
206 decoder: MethodsMetadataDecoder::new(
207 &mut self.metadata,
208 MethodsContainerKind::Contract,
209 num_methods,
210 ),
211 })
212 }
213
214 #[inline(always)]
215 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
216 fn decode_trait<'a>(
217 &'a mut self,
218 ) -> Result<MetadataItem<'a, 'metadata>, MetadataDecodingError<'metadata>> {
219 let trait_name_length = usize::from(
221 *self
222 .metadata
223 .split_off_first()
224 .ok_or(MetadataDecodingError::NotEnoughMetadata)?,
225 );
226
227 let trait_name = self
228 .metadata
229 .split_off(..trait_name_length)
230 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
231
232 let num_methods = *self
234 .metadata
235 .split_off_first()
236 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
237
238 Ok(MetadataItem::Trait {
239 trait_name,
240 num_methods,
241 decoder: MethodsMetadataDecoder::new(
242 &mut self.metadata,
243 MethodsContainerKind::Trait,
244 num_methods,
245 ),
246 })
247 }
248}
249
250#[derive(Debug, Copy, Clone)]
251pub enum MethodsContainerKind {
252 Contract,
253 Trait,
254 Unknown,
255}
256
257#[derive(Debug)]
258pub struct MethodsMetadataDecoder<'a, 'metadata> {
259 metadata: &'a mut &'metadata [u8],
260 container_kind: MethodsContainerKind,
261 remaining: u8,
262}
263
264impl<'a, 'metadata> Drop for MethodsMetadataDecoder<'a, 'metadata> {
265 fn drop(&mut self) {
266 while let Some(_method_metadata_decoder) = self.decode_next() {
267 }
269 }
270}
271
272impl<'a, 'metadata> MethodsMetadataDecoder<'a, 'metadata> {
273 #[inline(always)]
274 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
275 fn new(
276 metadata: &'a mut &'metadata [u8],
277 container_kind: MethodsContainerKind,
278 num_methods: u8,
279 ) -> Self {
280 Self {
281 metadata,
282 container_kind,
283 remaining: num_methods,
284 }
285 }
286
287 #[inline(always)]
289 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
290 pub fn remaining_methods(&self) -> u8 {
291 self.remaining
292 }
293
294 #[inline(always)]
296 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
297 pub fn remaining_metadata_bytes(&self) -> usize {
298 self.metadata.len()
299 }
300
301 #[inline(always)]
302 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
303 pub fn decode_next<'b>(&'b mut self) -> Option<MethodMetadataDecoder<'b, 'metadata>> {
304 if self.remaining == 0 {
305 return None;
306 }
307
308 self.remaining -= 1;
309
310 Some(MethodMetadataDecoder::new(
311 self.metadata,
312 self.container_kind,
313 ))
314 }
315}
316
317#[derive(Debug, Copy, Clone)]
318pub enum MethodKind {
319 Init,
321 UpdateStateless,
323 UpdateStatefulRo,
325 UpdateStatefulRw,
327 ViewStateless,
329 ViewStateful,
331}
332
333impl MethodKind {
334 #[inline(always)]
335 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
336 pub fn has_self(&self) -> bool {
337 match self {
338 MethodKind::Init | MethodKind::UpdateStateless | MethodKind::ViewStateless => false,
339 MethodKind::UpdateStatefulRo
340 | MethodKind::UpdateStatefulRw
341 | MethodKind::ViewStateful => true,
342 }
343 }
344}
345
346#[derive(Debug, Copy, Clone)]
347pub struct MethodMetadataItem<'metadata> {
348 pub method_name: &'metadata [u8],
352 pub method_kind: MethodKind,
353 pub num_arguments: u8,
354}
355
356#[derive(Debug, destructure)]
358pub struct MethodMetadataDecoder<'a, 'metadata> {
359 metadata: &'a mut &'metadata [u8],
360 container_kind: MethodsContainerKind,
361}
362
363impl<'a, 'metadata> Drop for MethodMetadataDecoder<'a, 'metadata> {
364 fn drop(&mut self) {
365 let metadata_before = *self.metadata;
366
367 if MethodMetadataDecoder::new(self.metadata, self.container_kind)
369 .decode_next()
370 .is_err()
371 {
372 *self.metadata = metadata_before;
375 }
376 }
377}
378
379impl<'a, 'metadata> MethodMetadataDecoder<'a, 'metadata> {
380 #[inline(always)]
381 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
382 pub fn new(metadata: &'a mut &'metadata [u8], container_kind: MethodsContainerKind) -> Self {
383 Self {
384 metadata,
385 container_kind,
386 }
387 }
388
389 #[inline(always)]
391 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
392 pub fn remaining_metadata_bytes(&self) -> usize {
393 self.metadata.len()
394 }
395
396 #[inline]
397 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
398 pub fn decode_next(
399 self,
400 ) -> Result<
401 (
402 ArgumentsMetadataDecoder<'a, 'metadata>,
403 MethodMetadataItem<'metadata>,
404 ),
405 MetadataDecodingError<'metadata>,
406 > {
407 let (metadata, container_kind) = self.destructure();
408
409 let metadata_kind = *metadata
411 .split_off_first()
412 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
413 let metadata_kind = ContractMetadataKind::try_from_u8(metadata_kind).ok_or(
414 MetadataDecodingError::InvalidFirstMetadataByte {
415 byte: metadata_kind,
416 },
417 )?;
418
419 let method_kind = match metadata_kind {
420 ContractMetadataKind::Init => MethodKind::Init,
421 ContractMetadataKind::UpdateStateless => MethodKind::UpdateStateless,
422 ContractMetadataKind::UpdateStatefulRo => MethodKind::UpdateStatefulRo,
423 ContractMetadataKind::UpdateStatefulRw => MethodKind::UpdateStatefulRw,
424 ContractMetadataKind::ViewStateless => MethodKind::ViewStateless,
425 ContractMetadataKind::ViewStateful => MethodKind::ViewStateful,
426 ContractMetadataKind::Contract
428 | ContractMetadataKind::Trait
429 | ContractMetadataKind::EnvRo
430 | ContractMetadataKind::EnvRw
431 | ContractMetadataKind::TmpRo
432 | ContractMetadataKind::TmpRw
433 | ContractMetadataKind::SlotRo
434 | ContractMetadataKind::SlotRw
435 | ContractMetadataKind::Input
436 | ContractMetadataKind::Output => {
437 return Err(MetadataDecodingError::ExpectedMethodKind { metadata_kind });
438 }
439 };
440
441 let method_allowed = match container_kind {
442 MethodsContainerKind::Contract | MethodsContainerKind::Unknown => match method_kind {
443 MethodKind::Init
444 | MethodKind::UpdateStateless
445 | MethodKind::UpdateStatefulRo
446 | MethodKind::UpdateStatefulRw
447 | MethodKind::ViewStateless
448 | MethodKind::ViewStateful => true,
449 },
450 MethodsContainerKind::Trait => match method_kind {
451 MethodKind::Init
452 | MethodKind::UpdateStatefulRo
453 | MethodKind::UpdateStatefulRw
454 | MethodKind::ViewStateful => false,
455 MethodKind::UpdateStateless | MethodKind::ViewStateless => true,
456 },
457 };
458
459 if !method_allowed {
460 return Err(MetadataDecodingError::UnexpectedMethodKind {
461 method_kind,
462 container_kind,
463 });
464 }
465
466 let method_name_length = usize::from(
468 *metadata
469 .split_off_first()
470 .ok_or(MetadataDecodingError::NotEnoughMetadata)?,
471 );
472
473 let method_name = metadata
474 .split_off(..method_name_length)
475 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
476
477 let num_arguments = *metadata
479 .split_off_first()
480 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
481
482 let decoder = ArgumentsMetadataDecoder {
483 metadata,
484 method_kind,
485 remaining: num_arguments,
486 };
487 let item = MethodMetadataItem {
488 method_name,
489 method_kind,
490 num_arguments,
491 };
492
493 Ok((decoder, item))
494 }
495}
496
497#[derive(Debug, Copy, Clone)]
498pub enum ArgumentKind {
499 EnvRo,
501 EnvRw,
503 TmpRo,
505 TmpRw,
507 SlotRo,
509 SlotRw,
511 Input,
513 Output,
515}
516
517#[derive(Debug)]
518pub struct ArgumentMetadataItem<'metadata> {
519 pub argument_name: &'metadata [u8],
523 pub argument_kind: ArgumentKind,
524 pub type_details: Option<IoTypeDetails>,
529}
530
531#[derive(Debug)]
532pub struct ArgumentsMetadataDecoder<'a, 'metadata> {
533 metadata: &'a mut &'metadata [u8],
534 method_kind: MethodKind,
535 remaining: u8,
536}
537
538impl<'a, 'metadata> Drop for ArgumentsMetadataDecoder<'a, 'metadata> {
539 fn drop(&mut self) {
540 let metadata_before = *self.metadata;
541 while let Some(maybe_argument_metadata_item) = self.decode_next() {
542 if maybe_argument_metadata_item.is_err() {
543 *self.metadata = metadata_before;
546 break;
547 }
548 }
549 }
550}
551
552impl<'metadata> ArgumentsMetadataDecoder<'_, 'metadata> {
553 pub fn without_auto_drain(self) -> ManuallyDrop<Self> {
563 ManuallyDrop::new(self)
564 }
565
566 #[inline(always)]
568 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
569 pub fn remaining_metadata_bytes(&self) -> usize {
570 self.metadata.len()
571 }
572
573 #[inline]
574 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
575 pub fn decode_next<'a>(
576 &'a mut self,
577 ) -> Option<Result<ArgumentMetadataItem<'metadata>, MetadataDecodingError<'metadata>>> {
578 if self.remaining == 0 {
579 return None;
580 }
581
582 self.remaining -= 1;
583
584 Some(self.decode_argument())
585 }
586
587 #[inline(always)]
588 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
589 fn decode_argument<'a>(
590 &'a mut self,
591 ) -> Result<ArgumentMetadataItem<'metadata>, MetadataDecodingError<'metadata>> {
592 let metadata_kind = *self
594 .metadata
595 .split_off_first()
596 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
597 let metadata_kind = ContractMetadataKind::try_from_u8(metadata_kind).ok_or(
598 MetadataDecodingError::InvalidFirstMetadataByte {
599 byte: metadata_kind,
600 },
601 )?;
602
603 let argument_kind = match metadata_kind {
604 ContractMetadataKind::EnvRo => ArgumentKind::EnvRo,
605 ContractMetadataKind::EnvRw => ArgumentKind::EnvRw,
606 ContractMetadataKind::TmpRo => ArgumentKind::TmpRo,
607 ContractMetadataKind::TmpRw => ArgumentKind::TmpRw,
608 ContractMetadataKind::SlotRo => ArgumentKind::SlotRo,
609 ContractMetadataKind::SlotRw => ArgumentKind::SlotRw,
610 ContractMetadataKind::Input => ArgumentKind::Input,
611 ContractMetadataKind::Output => ArgumentKind::Output,
612 ContractMetadataKind::Contract
614 | ContractMetadataKind::Trait
615 | ContractMetadataKind::Init
616 | ContractMetadataKind::UpdateStateless
617 | ContractMetadataKind::UpdateStatefulRo
618 | ContractMetadataKind::UpdateStatefulRw
619 | ContractMetadataKind::ViewStateless
620 | ContractMetadataKind::ViewStateful => {
621 return Err(MetadataDecodingError::ExpectedArgumentKind { metadata_kind });
622 }
623 };
624
625 let argument_allowed = match self.method_kind {
627 MethodKind::Init
628 | MethodKind::UpdateStateless
629 | MethodKind::UpdateStatefulRo
630 | MethodKind::UpdateStatefulRw => match argument_kind {
631 ArgumentKind::EnvRo
632 | ArgumentKind::EnvRw
633 | ArgumentKind::TmpRo
634 | ArgumentKind::TmpRw
635 | ArgumentKind::SlotRo
636 | ArgumentKind::SlotRw
637 | ArgumentKind::Input
638 | ArgumentKind::Output => true,
639 },
640 MethodKind::ViewStateless | MethodKind::ViewStateful => match argument_kind {
641 ArgumentKind::EnvRo
642 | ArgumentKind::SlotRo
643 | ArgumentKind::Input
644 | ArgumentKind::Output => true,
645 ArgumentKind::EnvRw
646 | ArgumentKind::TmpRo
647 | ArgumentKind::TmpRw
648 | ArgumentKind::SlotRw => false,
649 },
650 };
651
652 if !argument_allowed {
653 return Err(MetadataDecodingError::UnexpectedArgumentKind {
654 argument_kind,
655 method_kind: self.method_kind,
656 });
657 }
658
659 let (argument_name, type_details) = match argument_kind {
660 ArgumentKind::EnvRo | ArgumentKind::EnvRw => ("env".as_bytes(), None),
661 ArgumentKind::TmpRo
662 | ArgumentKind::TmpRw
663 | ArgumentKind::SlotRo
664 | ArgumentKind::SlotRw
665 | ArgumentKind::Input
666 | ArgumentKind::Output => {
667 let argument_name_length = usize::from(
669 *self
670 .metadata
671 .split_off_first()
672 .ok_or(MetadataDecodingError::NotEnoughMetadata)?,
673 );
674 let argument_name = self
675 .metadata
676 .split_off(..argument_name_length)
677 .ok_or(MetadataDecodingError::NotEnoughMetadata)?;
678
679 let recommended_capacity = match argument_kind {
680 ArgumentKind::EnvRo
681 | ArgumentKind::EnvRw
682 | ArgumentKind::TmpRo
683 | ArgumentKind::TmpRw
684 | ArgumentKind::SlotRo
685 | ArgumentKind::SlotRw => None,
686 ArgumentKind::Input => {
687 let recommended_capacity;
688 (recommended_capacity, *self.metadata) =
689 IoTypeMetadataKind::type_details(self.metadata).ok_or(
690 MetadataDecodingError::InvalidArgumentIoType {
691 argument_name,
692 argument_kind,
693 },
694 )?;
695
696 Some(recommended_capacity)
697 }
698 ArgumentKind::Output => {
699 let last_argument = self.remaining == 0;
700 if matches!((self.method_kind, last_argument), (MethodKind::Init, true)) {
703 None
704 } else {
705 let recommended_capacity;
706 (recommended_capacity, *self.metadata) =
707 IoTypeMetadataKind::type_details(self.metadata).ok_or(
708 MetadataDecodingError::InvalidArgumentIoType {
709 argument_name,
710 argument_kind,
711 },
712 )?;
713
714 Some(recommended_capacity)
715 }
716 }
717 };
718
719 (argument_name, recommended_capacity)
720 }
721 };
722
723 Ok(ArgumentMetadataItem {
724 argument_name,
725 argument_kind,
726 type_details,
727 })
728 }
729}