1use crate::metadata::ContractMetadataKind;
2use ab_contracts_io_type::metadata::{IoTypeDetails, IoTypeMetadataKind};
3
4#[derive(Debug, thiserror::Error)]
6pub enum MetadataDecodingError<'metadata> {
7 #[error("Not enough metadata to decode")]
9 NotEnoughMetadata,
10 #[error("Invalid first metadata byte")]
12 InvalidFirstMetadataByte { byte: u8 },
13 #[error("Multiple contracts found")]
15 MultipleContractsFound,
16 #[error("Expected contract or trait kind, found something else: {metadata_kind:?}")]
18 ExpectedContractOrTrait { metadata_kind: ContractMetadataKind },
19 #[error("Failed to decode state type name")]
21 FailedToDecodeStateTypeName,
22 #[error("Invalid state I/O type")]
24 InvalidStateIoType,
25 #[error("Unexpected method kind {method_kind:?} for container kind {container_kind:?}")]
27 UnexpectedMethodKind {
28 method_kind: MethodKind,
29 container_kind: MethodsContainerKind,
30 },
31 #[error("Expected method kind, found something else: {metadata_kind:?}")]
33 ExpectedMethodKind { metadata_kind: ContractMetadataKind },
34 #[error("Expected argument kind, found something else: {metadata_kind:?}")]
36 ExpectedArgumentKind { metadata_kind: ContractMetadataKind },
37 #[error("Unexpected argument kind {argument_kind:?} for method kind {method_kind:?}")]
39 UnexpectedArgumentKind {
40 argument_kind: ArgumentKind,
41 method_kind: MethodKind,
42 },
43 #[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: &'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: &'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 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 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 let state_type_name = IoTypeMetadataKind::type_name(self.metadata)
169 .ok_or(MetadataDecodingError::FailedToDecodeStateTypeName)?;
170
171 let state_type_details;
173 (state_type_details, self.metadata) = IoTypeMetadataKind::type_details(self.metadata)
174 .ok_or(MetadataDecodingError::InvalidStateIoType)?;
175
176 let slot_type_details;
178 (slot_type_details, self.metadata) = IoTypeMetadataKind::type_details(self.metadata)
179 .ok_or(MetadataDecodingError::InvalidStateIoType)?;
180
181 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 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 let trait_name_length = usize::from(self.metadata[0]);
219 self.metadata = &self.metadata[1..];
220
221 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 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 Init,
294 UpdateStateless,
296 UpdateStatefulRo,
298 UpdateStatefulRw,
300 ViewStateless,
302 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 pub method_name: &'metadata [u8],
325 pub method_kind: MethodKind,
326 pub num_arguments: u8,
327}
328
329#[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 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 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 let method_name_length = usize::from(self.metadata[0]);
422 *self.metadata = &self.metadata[1..];
423
424 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 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 EnvRo,
455 EnvRw,
457 TmpRo,
459 TmpRw,
461 SlotRo,
463 SlotRw,
465 Input,
467 Output,
469}
470
471#[derive(Debug)]
472pub struct ArgumentMetadataItem<'metadata> {
473 pub argument_name: &'metadata [u8],
477 pub argument_kind: ArgumentKind,
478 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 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 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 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 let argument_name_length = usize::from(self.metadata[0]);
594 *self.metadata = &self.metadata[1..];
595
596 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 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}