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
53impl const 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<'tmp, 'decoder, const VERIFY: bool> Deref
356 for TransactionPayloadDecoderInternal<'tmp, '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<'tmp, 'decoder, const VERIFY: bool> DerefMut
368 for TransactionPayloadDecoderInternal<'tmp, 'decoder, VERIFY>
369{
370 #[inline(always)]
371 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
372 fn deref_mut(&mut self) -> &mut Self::Target {
373 self.0
374 }
375}
376
377impl<'tmp, 'decoder, const VERIFY: bool> TransactionPayloadDecoderInternal<'tmp, 'decoder, VERIFY> {
378 #[inline(always)]
379 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
380 fn decode_next_method(
381 mut self,
382 ) -> Result<Option<PreparedMethod<'decoder>>, TransactionPayloadDecoderError> {
383 if self.payload.len() <= usize::from(MAX_ALIGNMENT) {
384 return Ok(None);
385 }
386
387 self.update_output_buffer_details();
388
389 let contract = self.get_trivial_type::<Address>()?;
390 let method_fingerprint = self.get_trivial_type::<MethodFingerprint>()?;
391 let method_context =
392 (self.map_context)(*self.get_trivial_type::<TransactionMethodContext>()?);
393
394 let mut transaction_slots_inputs =
395 [MaybeUninit::<u8>::uninit(); MAX_TOTAL_METHOD_ARGS as usize];
396
397 let num_slot_arguments = self.read_u8()?;
398 for transaction_slot in transaction_slots_inputs
399 .iter_mut()
400 .take(usize::from(num_slot_arguments))
401 {
402 transaction_slot.write(self.read_u8()?);
403 }
404
405 let num_input_arguments = self.read_u8()?;
406 for transaction_input in transaction_slots_inputs
407 .iter_mut()
408 .skip(usize::from(num_slot_arguments))
409 .take(usize::from(num_input_arguments))
410 {
411 transaction_input.write(self.read_u8()?);
412 }
413
414 let num_output_arguments = self.read_u8()?;
415
416 let (transaction_slots, transaction_inputs) = unsafe {
418 let (transaction_slots, transaction_inputs) =
419 transaction_slots_inputs.split_at_unchecked(usize::from(num_slot_arguments));
420 let transaction_inputs =
421 transaction_inputs.get_unchecked(..usize::from(num_input_arguments));
422
423 (
424 transaction_slots.assume_init_ref(),
425 transaction_inputs.assume_init_ref(),
426 )
427 };
428
429 let number_of_arguments = num_slot_arguments
432 .saturating_add(num_input_arguments)
433 .saturating_add(num_output_arguments);
434 if VERIFY && number_of_arguments > MAX_TOTAL_METHOD_ARGS {
435 return Err(TransactionPayloadDecoderError::TooManyArguments(
436 number_of_arguments,
437 ));
438 }
439
440 let external_args = NonNull::new(self.external_args_buffer.as_mut_ptr())
441 .expect("Not null; qed")
442 .cast::<c_void>();
443 {
444 let external_args_cursor = &mut external_args.clone();
445
446 for &transaction_slot in transaction_slots {
447 let address = match TransactionSlot::from_u8(transaction_slot).slot_type() {
448 TransactionSlotType::Address => self.get_trivial_type::<Address>()?,
449 TransactionSlotType::OutputIndex { output_index } => {
450 let (bytes, size) = self.get_from_output_buffer(output_index)?;
451
452 if VERIFY && size != Address::SIZE {
453 return Err(
454 TransactionPayloadDecoderError::InvalidSlotOutputIndexSize {
455 output_index,
456 size,
457 },
458 );
459 }
460
461 let maybe_address = unsafe { Address::from_bytes(bytes) };
463
464 if VERIFY {
465 let Some(address) = maybe_address else {
466 let error =
467 TransactionPayloadDecoderError::InvalidSlotOutputIndexAlign {
468 output_index,
469 alignment: align_of_val(bytes) as u32,
470 };
471 return Err(error);
472 };
473
474 address
475 } else {
476 unsafe { maybe_address.unwrap_unchecked() }
478 }
479 }
480 };
481
482 unsafe {
485 write_external_args(external_args_cursor, ptr::from_ref(address));
486 }
487 }
488
489 for &transaction_input in transaction_inputs {
490 let (bytes, size) = match TransactionInput::from_u8(transaction_input).input_type()
491 {
492 TransactionInputType::Value { alignment_power } => {
493 let alignment = if VERIFY {
496 1_usize.checked_shl(u32::from(alignment_power)).ok_or(
497 TransactionPayloadDecoderError::AlignmentPowerTooLarge(
498 alignment_power,
499 ),
500 )?
501 } else {
502 unsafe { 1_usize.unchecked_shl(u32::from(alignment_power)) }
504 };
505
506 let size = *self.get_trivial_type::<u32>()?;
507 let bytes = self.get_bytes(
508 size,
509 NonZeroUsize::new(alignment).expect("Not zero; qed"),
510 )?;
511
512 (bytes, size)
513 }
514 TransactionInputType::OutputIndex { output_index } => {
515 self.get_from_output_buffer(output_index)?
516 }
517 };
518
519 unsafe {
522 write_external_args(
523 external_args_cursor,
524 FfiDataSizeCapacityRo {
525 data_ptr: NonNull::from_ref(bytes).as_non_null_ptr(),
526 size,
527 capacity: size,
528 },
529 );
530 }
531 }
532
533 for _ in 0..num_output_arguments {
534 let recommended_capacity = *self.get_trivial_type::<u32>()?;
535 let alignment_power = *self.get_trivial_type::<u8>()?;
536 let alignment = if VERIFY {
539 1_usize.checked_shl(u32::from(alignment_power)).ok_or(
540 TransactionPayloadDecoderError::AlignmentPowerTooLarge(alignment_power),
541 )?
542 } else {
543 unsafe { 1_usize.unchecked_shl(u32::from(alignment_power)) }
545 };
546
547 let external_args_size_offset = unsafe {
550 external_args_cursor
551 .byte_add(offset_of!(FfiDataSizeCapacityRw, size))
552 .byte_offset_from_unsigned(external_args)
553 };
554
555 let data = self.allocate_output_buffer(
556 recommended_capacity,
557 NonZeroUsize::new(alignment).expect("Not zero; qed"),
558 external_args_size_offset as u32,
559 )?;
560
561 unsafe {
564 write_external_args(
565 external_args_cursor,
566 FfiDataSizeCapacityRw {
567 data_ptr: data.as_ptr(),
568 size: 0,
569 capacity: recommended_capacity,
570 },
571 );
572 }
573 }
574 }
575
576 Ok(Some(PreparedMethod {
577 contract: *contract,
578 fingerprint: *method_fingerprint,
579 external_args,
580 method_context,
581 phantom: PhantomData,
582 }))
583 }
584
585 #[inline(always)]
587 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
588 fn get_trivial_type<T>(&mut self) -> Result<&'decoder T, TransactionPayloadDecoderError>
589 where
590 T: TrivialType,
591 {
592 self.ensure_alignment(NonZeroUsize::new(align_of::<T>()).expect("Not zero; qed"))?;
593
594 let bytes;
595 if VERIFY {
596 (bytes, self.payload) = self
597 .payload
598 .split_at_checked(size_of::<T>())
599 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
600 } else {
601 (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size_of::<T>()) };
603 }
604
605 let value_ref = unsafe { bytes.as_ptr().cast::<T>().as_ref().expect("Not null; qed") };
607
608 Ok(value_ref)
609 }
610
611 #[inline(always)]
613 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
614 fn get_bytes(
615 &mut self,
616 size: u32,
617 alignment: NonZeroUsize,
618 ) -> Result<&'decoder [u8], TransactionPayloadDecoderError> {
619 self.ensure_alignment(alignment)?;
620
621 let bytes;
622 if VERIFY {
623 (bytes, self.payload) = self
624 .payload
625 .split_at_checked(size as usize)
626 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
627 } else {
628 (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size as usize) };
630 }
631
632 Ok(bytes)
633 }
634
635 #[inline(always)]
636 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
637 fn read_u8(&mut self) -> Result<u8, TransactionPayloadDecoderError> {
638 let value;
639 if VERIFY {
640 (value, self.payload) = self
641 .payload
642 .split_at_checked(1)
643 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
644 } else {
645 (value, self.payload) = unsafe { self.payload.split_at_unchecked(1) };
647 }
648
649 Ok(value[0])
650 }
651
652 #[inline(always)]
653 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
654 fn ensure_alignment(
655 &mut self,
656 alignment: NonZeroUsize,
657 ) -> Result<(), TransactionPayloadDecoderError> {
658 let alignment = alignment.get();
659 debug_assert!(alignment <= usize::from(MAX_ALIGNMENT));
660
661 let unaligned_by = self.payload.as_ptr().addr() & (alignment - 1);
664 if unaligned_by > 0 {
665 let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
667 if VERIFY {
668 self.payload = self
669 .payload
670 .split_off(padding_bytes..)
671 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
672 } else {
673 self.payload = unsafe { self.payload.get_unchecked(padding_bytes..) };
675 }
676 }
677 Ok(())
678 }
679
680 #[inline(always)]
682 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
683 fn allocate_output_buffer(
684 &mut self,
685 capacity: u32,
686 output_alignment: NonZeroUsize,
687 external_args_size_offset: u32,
688 ) -> Result<NonNull<u8>, TransactionPayloadDecoderError> {
689 if VERIFY && self.output_buffer_details.len() == self.output_buffer_offsets_cursor.current {
690 return Err(TransactionPayloadDecoderError::OutputBufferOffsetsTooSmall);
691 }
692
693 let (output_offset, output_ptr) = self
694 .allocate_output_buffer_ptr(output_alignment, capacity as usize)
695 .ok_or(TransactionPayloadDecoderError::OutputBufferTooSmall)?;
696
697 let output_buffer_details = unsafe {
699 let output_buffer_offsets_cursor = self.output_buffer_offsets_cursor.current;
701 self.output_buffer_details
702 .get_unchecked_mut(output_buffer_offsets_cursor)
703 };
704 output_buffer_details.write(OutputBufferDetails {
705 output_offset: output_offset as u32,
706 size_or_external_args_offset: external_args_size_offset,
707 });
708 self.output_buffer_offsets_cursor.current += 1;
709
710 Ok(output_ptr)
711 }
712
713 #[inline(always)]
715 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
716 fn allocate_output_buffer_ptr<T>(
717 &mut self,
718 alignment: NonZeroUsize,
719 size: usize,
720 ) -> Option<(usize, NonNull<T>)> {
721 let alignment = alignment.get();
722 debug_assert!(alignment <= usize::from(MAX_ALIGNMENT));
723
724 let unaligned_by = self.output_buffer_cursor & (alignment - 1);
727 let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
729
730 let new_output_buffer_cursor = if VERIFY {
731 let new_output_buffer_cursor = self
732 .output_buffer_cursor
733 .checked_add(padding_bytes)?
734 .checked_add(size)?;
735
736 if new_output_buffer_cursor > size_of_val(self.output_buffer) {
737 return None;
738 }
739
740 new_output_buffer_cursor
741 } else {
742 unsafe {
744 self.output_buffer_cursor
745 .unchecked_add(padding_bytes)
746 .unchecked_add(size)
747 }
748 };
749
750 let (offset, buffer_ptr) = unsafe {
752 let offset = self.output_buffer_cursor.unchecked_add(padding_bytes);
753 let buffer_ptr = NonNull::new_unchecked(
754 self.output_buffer.as_mut_ptr().byte_add(offset).cast::<T>(),
755 );
756
757 (offset, buffer_ptr)
758 };
759 self.output_buffer_cursor = new_output_buffer_cursor;
760
761 Some((offset, buffer_ptr))
762 }
763
764 #[inline(always)]
767 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
768 fn update_output_buffer_details(&mut self) {
769 let TransactionPayloadDecoder {
770 external_args_buffer,
771 output_buffer_details,
772 output_buffer_offsets_cursor,
773 ..
774 } = &mut **self;
775
776 for output_buffer_details in unsafe {
778 output_buffer_details
779 .get_unchecked_mut(
780 output_buffer_offsets_cursor.before_last..output_buffer_offsets_cursor.current,
781 )
782 .assume_init_mut()
783 } {
784 output_buffer_details.size_or_external_args_offset = unsafe {
786 external_args_buffer
787 .as_ptr()
788 .cast::<u32>()
789 .byte_add(output_buffer_details.size_or_external_args_offset as usize)
790 .read()
791 };
792 }
793
794 output_buffer_offsets_cursor.before_last = output_buffer_offsets_cursor.current;
795 }
796
797 #[inline(always)]
798 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
799 fn get_from_output_buffer(
800 &self,
801 output_index: u8,
802 ) -> Result<(&[u8], u32), TransactionPayloadDecoderError> {
803 let OutputBufferDetails {
804 output_offset,
805 size_or_external_args_offset: size,
806 } = if VERIFY {
807 if usize::from(output_index) < self.output_buffer_offsets_cursor.current {
808 unsafe {
810 self.output_buffer_details
811 .get_unchecked(usize::from(output_index))
812 .assume_init()
813 }
814 } else {
815 return Err(TransactionPayloadDecoderError::OutputIndexNotFound(
816 output_index,
817 ));
818 }
819 } else {
820 unsafe {
822 self.output_buffer_details
823 .get_unchecked(usize::from(output_index))
824 .assume_init()
825 }
826 };
827
828 let bytes = unsafe {
831 let bytes_ptr = self
832 .output_buffer
833 .as_ptr()
834 .cast::<u8>()
835 .add(output_offset as usize);
836
837 slice::from_raw_parts(bytes_ptr, size as usize)
838 };
839
840 Ok((bytes, size))
841 }
842}