1#[cfg(feature = "payload-builder")]
11pub mod builder;
12
13use crate::EXTERNAL_ARGS_BUFFER_SIZE;
14use ab_contracts_common::MAX_TOTAL_METHOD_ARGS;
15use ab_contracts_common::env::{MethodContext, PreparedMethod};
16use ab_contracts_common::method::MethodFingerprint;
17use ab_core_primitives::address::Address;
18use ab_io_type::MAX_ALIGNMENT;
19use ab_io_type::trivial_type::TrivialType;
20use core::ffi::c_void;
21use core::marker::PhantomData;
22use core::mem::{MaybeUninit, offset_of};
23use core::num::{NonZeroU8, NonZeroUsize};
24use core::ops::{Deref, DerefMut};
25use core::ptr::NonNull;
26use core::{ptr, slice};
27
28#[derive(Copy, Clone)]
29#[repr(C)]
30struct FfiDataSizeCapacityRo {
31 data_ptr: NonNull<u8>,
32 size: u32,
33 capacity: u32,
34}
35
36#[derive(Copy, Clone)]
37#[repr(C)]
38struct FfiDataSizeCapacityRw {
39 data_ptr: *mut u8,
40 size: u32,
41 capacity: u32,
42}
43
44#[derive(Debug, Copy, Clone, Eq, PartialEq, TrivialType)]
45#[repr(u8)]
46pub enum TransactionMethodContext {
47 Null,
49 Wallet,
51}
52
53const impl TryFrom<u8> for TransactionMethodContext {
54 type Error = ();
55
56 #[inline(always)]
57 fn try_from(value: u8) -> Result<Self, Self::Error> {
58 Ok(match value {
59 0 => Self::Null,
60 1 => Self::Wallet,
61 _ => return Err(()),
62 })
63 }
64}
65
66#[derive(Debug, Copy, Eq, PartialEq, Clone)]
67pub enum TransactionInputType {
68 Value { alignment_power: u8 },
69 OutputIndex { output_index: u8 },
70}
71
72#[derive(Debug, Copy, Eq, PartialEq, Clone)]
73pub enum TransactionSlotType {
74 Address,
75 OutputIndex { output_index: u8 },
76}
77
78#[derive(Debug, Copy, Clone)]
87pub struct TransactionSlot(TransactionSlotType);
88
89impl TransactionSlot {
90 #[inline(always)]
92 pub const fn new_address() -> Self {
93 Self(TransactionSlotType::Address)
94 }
95
96 #[inline(always)]
100 pub const fn new_output_index(output_index: u8) -> Option<Self> {
101 if output_index > 0b0111_1111 {
102 return None;
103 }
104
105 Some(Self(TransactionSlotType::OutputIndex { output_index }))
106 }
107
108 #[inline(always)]
110 pub const fn from_u8(n: u8) -> Self {
111 if n & 0b1000_0000 == 0 {
113 Self(TransactionSlotType::OutputIndex { output_index: n })
114 } else {
115 Self(TransactionSlotType::Address)
116 }
117 }
118
119 #[inline(always)]
121 pub const fn into_u8(self) -> u8 {
122 match self.0 {
124 TransactionSlotType::Address => 0b1000_0000,
125 TransactionSlotType::OutputIndex { output_index } => output_index,
126 }
127 }
128
129 #[inline(always)]
131 pub const fn slot_type(self) -> TransactionSlotType {
132 self.0
133 }
134}
135
136#[derive(Debug, Copy, Clone)]
145pub struct TransactionInput(TransactionInputType);
146
147impl TransactionInput {
148 #[inline(always)]
152 pub const fn new_value(alignment: NonZeroU8) -> Option<Self> {
153 match alignment.get() {
154 1 | 2 | 4 | 8 | 16 => Some(Self(TransactionInputType::Value {
155 alignment_power: alignment.ilog2() as u8,
156 })),
157 _ => None,
158 }
159 }
160
161 #[inline(always)]
165 pub const fn new_output_index(output_index: u8) -> Option<Self> {
166 if output_index > 0b0111_1111 {
167 return None;
168 }
169
170 Some(Self(TransactionInputType::OutputIndex { output_index }))
171 }
172
173 #[inline(always)]
175 pub const fn from_u8(n: u8) -> Self {
176 if n & 0b1000_0000 == 0 {
178 Self(TransactionInputType::OutputIndex { output_index: n })
179 } else {
180 Self(TransactionInputType::Value {
181 alignment_power: n & 0b0111_1111,
182 })
183 }
184 }
185
186 #[inline(always)]
188 pub const fn into_u8(self) -> u8 {
189 match self.0 {
191 TransactionInputType::Value { alignment_power } => 0b1000_0000 | alignment_power,
192 TransactionInputType::OutputIndex { output_index } => output_index,
193 }
194 }
195
196 #[inline(always)]
198 pub const fn input_type(self) -> TransactionInputType {
199 self.0
200 }
201}
202
203#[derive(Debug, thiserror::Error)]
205pub enum TransactionPayloadDecoderError {
206 #[error("Payload too small")]
208 PayloadTooSmall,
209 #[error("Too many arguments")]
211 TooManyArguments(u8),
212 #[error("`ExternalArgs` buffer too small")]
214 ExternalArgsBufferTooSmall,
215 #[error("Output index not found: {0}")]
217 OutputIndexNotFound(u8),
218 #[error("Invalid output index {output_index} size for slot: {size}")]
220 InvalidSlotOutputIndexSize { output_index: u8, size: u32 },
221 #[error("Invalid output index {output_index} alignment for slot: {alignment}")]
223 InvalidSlotOutputIndexAlign { output_index: u8, alignment: u32 },
224 #[error("Alignment power is too large: {0}")]
226 AlignmentPowerTooLarge(u8),
227 #[error("Output buffer too small")]
229 OutputBufferTooSmall,
230 #[error("Output buffer offsets too small")]
232 OutputBufferOffsetsTooSmall,
233}
234
235#[derive(Debug, Copy, Clone)]
236pub struct OutputBufferDetails {
237 output_offset: u32,
239 size_or_external_args_offset: u32,
245}
246
247#[derive(Debug, Copy, Clone)]
248struct OutputBufferOffsetsCursor {
249 before_last: usize,
252 current: usize,
254}
255
256#[derive(Debug)]
258pub struct TransactionPayloadDecoder<'a> {
259 payload: &'a [u8],
260 external_args_buffer: &'a mut [*mut c_void; EXTERNAL_ARGS_BUFFER_SIZE],
261 output_buffer: &'a mut [MaybeUninit<u128>],
264 output_buffer_cursor: usize,
265 output_buffer_details: &'a mut [MaybeUninit<OutputBufferDetails>],
266 output_buffer_offsets_cursor: OutputBufferOffsetsCursor,
267 map_context: fn(TransactionMethodContext) -> MethodContext,
268}
269
270impl<'a> TransactionPayloadDecoder<'a> {
271 #[inline]
284 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
285 pub fn new(
286 payload: &'a [u128],
287 external_args_buffer: &'a mut [*mut c_void; EXTERNAL_ARGS_BUFFER_SIZE],
288 output_buffer: &'a mut [MaybeUninit<u128>],
289 output_buffer_details: &'a mut [MaybeUninit<OutputBufferDetails>],
290 map_context: fn(TransactionMethodContext) -> MethodContext,
291 ) -> Self {
292 debug_assert_eq!(align_of_val(payload), usize::from(MAX_ALIGNMENT));
293 debug_assert_eq!(align_of_val(output_buffer), usize::from(MAX_ALIGNMENT));
294
295 let payload =
297 unsafe { slice::from_raw_parts(payload.as_ptr().cast::<u8>(), size_of_val(payload)) };
298
299 Self {
300 payload,
301 external_args_buffer,
302 output_buffer,
303 output_buffer_cursor: 0,
304 output_buffer_details,
305 output_buffer_offsets_cursor: OutputBufferOffsetsCursor {
306 before_last: 0,
307 current: 0,
308 },
309 map_context,
310 }
311 }
312
313 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
315 pub fn decode_next_method(
316 &mut self,
317 ) -> Result<Option<PreparedMethod<'_>>, TransactionPayloadDecoderError> {
318 TransactionPayloadDecoderInternal::<true>(self).decode_next_method()
319 }
320
321 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
327 pub unsafe fn decode_next_method_unchecked(&mut self) -> Option<PreparedMethod<'_>> {
328 TransactionPayloadDecoderInternal::<false>(self)
329 .decode_next_method()
330 .expect("No decoding errors are possible with trusted input; qed")
331 }
332}
333
334#[inline(always)]
340unsafe fn write_external_args<T>(external_args: &mut NonNull<c_void>, value: T) {
341 unsafe {
343 external_args.cast::<T>().write(value);
344 *external_args = external_args.byte_add(size_of::<T>());
345 }
346}
347
348struct TransactionPayloadDecoderInternal<'tmp, 'decoder, const VERIFY: bool>(
352 &'tmp mut TransactionPayloadDecoder<'decoder>,
353);
354
355impl<'decoder, const VERIFY: bool> Deref
356 for TransactionPayloadDecoderInternal<'_, 'decoder, VERIFY>
357{
358 type Target = TransactionPayloadDecoder<'decoder>;
359
360 #[inline(always)]
361 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
362 fn deref(&self) -> &Self::Target {
363 self.0
364 }
365}
366
367impl<const VERIFY: bool> DerefMut for TransactionPayloadDecoderInternal<'_, '_, VERIFY> {
368 #[inline(always)]
369 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
370 fn deref_mut(&mut self) -> &mut Self::Target {
371 self.0
372 }
373}
374
375impl<'decoder, const VERIFY: bool> TransactionPayloadDecoderInternal<'_, 'decoder, VERIFY> {
376 #[inline(always)]
377 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
378 fn decode_next_method(
379 mut self,
380 ) -> Result<Option<PreparedMethod<'decoder>>, TransactionPayloadDecoderError> {
381 if self.payload.len() <= usize::from(MAX_ALIGNMENT) {
382 return Ok(None);
383 }
384
385 self.update_output_buffer_details();
386
387 let contract = self.get_trivial_type::<Address>()?;
388 let method_fingerprint = self.get_trivial_type::<MethodFingerprint>()?;
389 let method_context =
390 (self.map_context)(*self.get_trivial_type::<TransactionMethodContext>()?);
391
392 let mut transaction_slots_inputs =
393 [MaybeUninit::<u8>::uninit(); MAX_TOTAL_METHOD_ARGS as usize];
394
395 let num_slot_arguments = self.read_u8()?;
396 for transaction_slot in transaction_slots_inputs
397 .iter_mut()
398 .take(usize::from(num_slot_arguments))
399 {
400 transaction_slot.write(self.read_u8()?);
401 }
402
403 let num_input_arguments = self.read_u8()?;
404 for transaction_input in transaction_slots_inputs
405 .iter_mut()
406 .skip(usize::from(num_slot_arguments))
407 .take(usize::from(num_input_arguments))
408 {
409 transaction_input.write(self.read_u8()?);
410 }
411
412 let num_output_arguments = self.read_u8()?;
413
414 let (transaction_slots, transaction_inputs) = unsafe {
416 let (transaction_slots, transaction_inputs) =
417 transaction_slots_inputs.split_at_unchecked(usize::from(num_slot_arguments));
418 let transaction_inputs =
419 transaction_inputs.get_unchecked(..usize::from(num_input_arguments));
420
421 (
422 transaction_slots.assume_init_ref(),
423 transaction_inputs.assume_init_ref(),
424 )
425 };
426
427 let number_of_arguments = num_slot_arguments
430 .saturating_add(num_input_arguments)
431 .saturating_add(num_output_arguments);
432 if VERIFY && number_of_arguments > MAX_TOTAL_METHOD_ARGS {
433 return Err(TransactionPayloadDecoderError::TooManyArguments(
434 number_of_arguments,
435 ));
436 }
437
438 let external_args = NonNull::new(self.external_args_buffer.as_mut_ptr())
439 .expect("Not null; qed")
440 .cast::<c_void>();
441 {
442 let external_args_cursor = &mut external_args.clone();
443
444 for &transaction_slot in transaction_slots {
445 let address = match TransactionSlot::from_u8(transaction_slot).slot_type() {
446 TransactionSlotType::Address => self.get_trivial_type::<Address>()?,
447 TransactionSlotType::OutputIndex { output_index } => {
448 let (bytes, size) = self.get_from_output_buffer(output_index)?;
449
450 if VERIFY && size != Address::SIZE {
451 return Err(
452 TransactionPayloadDecoderError::InvalidSlotOutputIndexSize {
453 output_index,
454 size,
455 },
456 );
457 }
458
459 let maybe_address = unsafe { Address::from_bytes(bytes) };
461
462 if VERIFY {
463 let Some(address) = maybe_address else {
464 let error =
465 TransactionPayloadDecoderError::InvalidSlotOutputIndexAlign {
466 output_index,
467 alignment: align_of_val(bytes) as u32,
468 };
469 return Err(error);
470 };
471
472 address
473 } else {
474 unsafe { maybe_address.unwrap_unchecked() }
476 }
477 }
478 };
479
480 unsafe {
483 write_external_args(external_args_cursor, ptr::from_ref(address));
484 }
485 }
486
487 for &transaction_input in transaction_inputs {
488 let (bytes, size) = match TransactionInput::from_u8(transaction_input).input_type()
489 {
490 TransactionInputType::Value { alignment_power } => {
491 let alignment = if VERIFY {
494 1_usize.checked_shl(u32::from(alignment_power)).ok_or(
495 TransactionPayloadDecoderError::AlignmentPowerTooLarge(
496 alignment_power,
497 ),
498 )?
499 } else {
500 unsafe { 1_usize.unchecked_shl(u32::from(alignment_power)) }
502 };
503
504 let size = *self.get_trivial_type::<u32>()?;
505 let bytes = self.get_bytes(
506 size,
507 NonZeroUsize::new(alignment).expect("Not zero; qed"),
508 )?;
509
510 (bytes, size)
511 }
512 TransactionInputType::OutputIndex { output_index } => {
513 self.get_from_output_buffer(output_index)?
514 }
515 };
516
517 unsafe {
520 write_external_args(
521 external_args_cursor,
522 FfiDataSizeCapacityRo {
523 data_ptr: NonNull::from_ref(bytes).as_non_null_ptr(),
524 size,
525 capacity: size,
526 },
527 );
528 }
529 }
530
531 for _ in 0..num_output_arguments {
532 let recommended_capacity = *self.get_trivial_type::<u32>()?;
533 let alignment_power = *self.get_trivial_type::<u8>()?;
534 let alignment = if VERIFY {
537 1_usize.checked_shl(u32::from(alignment_power)).ok_or(
538 TransactionPayloadDecoderError::AlignmentPowerTooLarge(alignment_power),
539 )?
540 } else {
541 unsafe { 1_usize.unchecked_shl(u32::from(alignment_power)) }
543 };
544
545 let external_args_size_offset = unsafe {
548 external_args_cursor
549 .byte_add(offset_of!(FfiDataSizeCapacityRw, size))
550 .byte_offset_from_unsigned(external_args)
551 };
552
553 let data = self.allocate_output_buffer(
554 recommended_capacity,
555 NonZeroUsize::new(alignment).expect("Not zero; qed"),
556 external_args_size_offset as u32,
557 )?;
558
559 unsafe {
562 write_external_args(
563 external_args_cursor,
564 FfiDataSizeCapacityRw {
565 data_ptr: data.as_ptr(),
566 size: 0,
567 capacity: recommended_capacity,
568 },
569 );
570 }
571 }
572 }
573
574 Ok(Some(PreparedMethod {
575 contract: *contract,
576 fingerprint: *method_fingerprint,
577 external_args,
578 method_context,
579 phantom: PhantomData,
580 }))
581 }
582
583 #[inline(always)]
585 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
586 fn get_trivial_type<T>(&mut self) -> Result<&'decoder T, TransactionPayloadDecoderError>
587 where
588 T: TrivialType,
589 {
590 self.ensure_alignment(NonZeroUsize::new(align_of::<T>()).expect("Not zero; qed"))?;
591
592 let bytes;
593 if VERIFY {
594 (bytes, self.payload) = self
595 .payload
596 .split_at_checked(size_of::<T>())
597 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
598 } else {
599 (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size_of::<T>()) };
601 }
602
603 let value_ref = unsafe { bytes.as_ptr().cast::<T>().as_ref().expect("Not null; qed") };
605
606 Ok(value_ref)
607 }
608
609 #[inline(always)]
611 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
612 fn get_bytes(
613 &mut self,
614 size: u32,
615 alignment: NonZeroUsize,
616 ) -> Result<&'decoder [u8], TransactionPayloadDecoderError> {
617 self.ensure_alignment(alignment)?;
618
619 let bytes;
620 if VERIFY {
621 (bytes, self.payload) = self
622 .payload
623 .split_at_checked(size as usize)
624 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
625 } else {
626 (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size as usize) };
628 }
629
630 Ok(bytes)
631 }
632
633 #[inline(always)]
634 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
635 fn read_u8(&mut self) -> Result<u8, TransactionPayloadDecoderError> {
636 let value;
637 if VERIFY {
638 (value, self.payload) = self
639 .payload
640 .split_at_checked(1)
641 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
642 } else {
643 (value, self.payload) = unsafe { self.payload.split_at_unchecked(1) };
645 }
646
647 Ok(value[0])
648 }
649
650 #[inline(always)]
651 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
652 fn ensure_alignment(
653 &mut self,
654 alignment: NonZeroUsize,
655 ) -> Result<(), TransactionPayloadDecoderError> {
656 let alignment = alignment.get();
657 debug_assert!(alignment <= usize::from(MAX_ALIGNMENT));
658
659 let unaligned_by = self.payload.as_ptr().addr() & (alignment - 1);
662 if unaligned_by > 0 {
663 let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
665 if VERIFY {
666 self.payload = self
667 .payload
668 .split_off(padding_bytes..)
669 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
670 } else {
671 self.payload = unsafe { self.payload.get_unchecked(padding_bytes..) };
673 }
674 }
675 Ok(())
676 }
677
678 #[inline(always)]
680 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
681 fn allocate_output_buffer(
682 &mut self,
683 capacity: u32,
684 output_alignment: NonZeroUsize,
685 external_args_size_offset: u32,
686 ) -> Result<NonNull<u8>, TransactionPayloadDecoderError> {
687 if VERIFY && self.output_buffer_details.len() == self.output_buffer_offsets_cursor.current {
688 return Err(TransactionPayloadDecoderError::OutputBufferOffsetsTooSmall);
689 }
690
691 let (output_offset, output_ptr) = self
692 .allocate_output_buffer_ptr(output_alignment, capacity as usize)
693 .ok_or(TransactionPayloadDecoderError::OutputBufferTooSmall)?;
694
695 let output_buffer_details = unsafe {
697 let output_buffer_offsets_cursor = self.output_buffer_offsets_cursor.current;
699 self.output_buffer_details
700 .get_unchecked_mut(output_buffer_offsets_cursor)
701 };
702 output_buffer_details.write(OutputBufferDetails {
703 output_offset: output_offset as u32,
704 size_or_external_args_offset: external_args_size_offset,
705 });
706 self.output_buffer_offsets_cursor.current += 1;
707
708 Ok(output_ptr)
709 }
710
711 #[inline(always)]
713 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
714 fn allocate_output_buffer_ptr<T>(
715 &mut self,
716 alignment: NonZeroUsize,
717 size: usize,
718 ) -> Option<(usize, NonNull<T>)> {
719 let alignment = alignment.get();
720 debug_assert!(alignment <= usize::from(MAX_ALIGNMENT));
721
722 let unaligned_by = self.output_buffer_cursor & (alignment - 1);
725 let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
727
728 let new_output_buffer_cursor = if VERIFY {
729 let new_output_buffer_cursor = self
730 .output_buffer_cursor
731 .checked_add(padding_bytes)?
732 .checked_add(size)?;
733
734 if new_output_buffer_cursor > size_of_val(self.output_buffer) {
735 return None;
736 }
737
738 new_output_buffer_cursor
739 } else {
740 unsafe {
742 self.output_buffer_cursor
743 .unchecked_add(padding_bytes)
744 .unchecked_add(size)
745 }
746 };
747
748 let (offset, buffer_ptr) = unsafe {
750 let offset = self.output_buffer_cursor.unchecked_add(padding_bytes);
751 let buffer_ptr = NonNull::new_unchecked(
752 self.output_buffer.as_mut_ptr().byte_add(offset).cast::<T>(),
753 );
754
755 (offset, buffer_ptr)
756 };
757 self.output_buffer_cursor = new_output_buffer_cursor;
758
759 Some((offset, buffer_ptr))
760 }
761
762 #[inline(always)]
765 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
766 fn update_output_buffer_details(&mut self) {
767 #[expect(
768 clippy::rest_pattern_accessible_field,
769 reason = "Do not need other fields"
770 )]
771 let TransactionPayloadDecoder {
772 external_args_buffer,
773 output_buffer_details,
774 output_buffer_offsets_cursor,
775 ..
776 } = &mut **self;
777
778 for output_buffer_details in unsafe {
780 output_buffer_details
781 .get_unchecked_mut(
782 output_buffer_offsets_cursor.before_last..output_buffer_offsets_cursor.current,
783 )
784 .assume_init_mut()
785 } {
786 output_buffer_details.size_or_external_args_offset = unsafe {
788 external_args_buffer
789 .as_ptr()
790 .cast::<u32>()
791 .byte_add(output_buffer_details.size_or_external_args_offset as usize)
792 .read()
793 };
794 }
795
796 output_buffer_offsets_cursor.before_last = output_buffer_offsets_cursor.current;
797 }
798
799 #[inline(always)]
800 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
801 fn get_from_output_buffer(
802 &self,
803 output_index: u8,
804 ) -> Result<(&[u8], u32), TransactionPayloadDecoderError> {
805 let OutputBufferDetails {
806 output_offset,
807 size_or_external_args_offset: size,
808 } = if VERIFY {
809 if usize::from(output_index) < self.output_buffer_offsets_cursor.current {
810 unsafe {
812 self.output_buffer_details
813 .get_unchecked(usize::from(output_index))
814 .assume_init()
815 }
816 } else {
817 return Err(TransactionPayloadDecoderError::OutputIndexNotFound(
818 output_index,
819 ));
820 }
821 } else {
822 unsafe {
824 self.output_buffer_details
825 .get_unchecked(usize::from(output_index))
826 .assume_init()
827 }
828 };
829
830 let bytes = unsafe {
833 let bytes_ptr = self
834 .output_buffer
835 .as_ptr()
836 .cast::<u8>()
837 .add(output_offset as usize);
838
839 slice::from_raw_parts(bytes_ptr, size as usize)
840 };
841
842 Ok((bytes, size))
843 }
844}