1#![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#[derive(Debug, thiserror::Error)]
14pub enum MetadataDecodingError<'metadata> {
15 #[error("Not enough metadata to decode")]
17 NotEnoughMetadata,
18 #[error("Invalid first metadata byte")]
20 InvalidFirstMetadataByte { byte: u8 },
21 #[error("Multiple contracts found")]
23 MultipleContractsFound,
24 #[error("Expected contract or trait kind, found something else: {metadata_kind:?}")]
26 ExpectedContractOrTrait { metadata_kind: ContractMetadataKind },
27 #[error("Failed to decode state type name")]
29 FailedToDecodeStateTypeName,
30 #[error("Invalid state I/O type")]
32 InvalidStateIoType,
33 #[error("Unexpected method kind {method_kind:?} for container kind {container_kind:?}")]
35 UnexpectedMethodKind {
36 method_kind: MethodKind,
37 container_kind: MethodsContainerKind,
38 },
39 #[error("Expected method kind, found something else: {metadata_kind:?}")]
41 ExpectedMethodKind { metadata_kind: ContractMetadataKind },
42 #[error("Expected argument kind, found something else: {metadata_kind:?}")]
44 ExpectedArgumentKind { metadata_kind: ContractMetadataKind },
45 #[error("Unexpected argument kind {argument_kind:?} for method kind {method_kind:?}")]
47 UnexpectedArgumentKind {
48 argument_kind: ArgumentKind,
49 method_kind: MethodKind,
50 },
51 #[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: &'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: &'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 #[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 #[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 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 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 let state_type_name = IoTypeMetadataKind::type_name(self.metadata)
184 .ok_or(MetadataDecodingError::FailedToDecodeStateTypeName)?;
185
186 let state_type_details;
188 (state_type_details, self.metadata) = IoTypeMetadataKind::type_details(self.metadata)
189 .ok_or(MetadataDecodingError::InvalidStateIoType)?;
190
191 let slot_type_details;
193 (slot_type_details, self.metadata) = IoTypeMetadataKind::type_details(self.metadata)
194 .ok_or(MetadataDecodingError::InvalidStateIoType)?;
195
196 let tmp_type_details;
198 (tmp_type_details, self.metadata) = IoTypeMetadataKind::type_details(self.metadata)
199 .ok_or(MetadataDecodingError::InvalidStateIoType)?;
200
201 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 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 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 }
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 #[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 #[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 Init,
328 UpdateStateless,
330 UpdateStatefulRo,
332 UpdateStatefulRw,
334 ViewStateless,
336 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 pub method_name: &'metadata [u8],
359 pub method_kind: MethodKind,
360 pub num_arguments: u8,
361}
362
363#[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 if MethodMetadataDecoder::new(self.metadata, self.container_kind)
376 .decode_next()
377 .is_err()
378 {
379 *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 #[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 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 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 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 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 EnvRo,
509 EnvRw,
511 TmpRo,
513 TmpRw,
515 SlotRo,
517 SlotRw,
519 Input,
521 Output,
523 Return,
525}
526
527#[derive(Debug)]
528pub struct ArgumentMetadataItem<'metadata> {
529 pub argument_name: &'metadata [u8],
533 pub argument_kind: ArgumentKind,
534 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 *self.metadata = metadata_before;
556 break;
557 }
558 }
559 }
560}
561
562impl<'metadata> ArgumentsMetadataDecoder<'_, 'metadata> {
563 pub fn without_auto_drain(self) -> ManuallyDrop<Self> {
573 ManuallyDrop::new(self)
574 }
575
576 #[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 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 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 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 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 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}