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 TransactionMethodContext {
54 #[inline(always)]
57 pub const fn try_from_u8(n: u8) -> Option<Self> {
58 Some(match n {
59 0 => Self::Null,
60 1 => Self::Wallet,
61 _ => {
62 return None;
63 }
64 })
65 }
66}
67
68#[derive(Debug, Copy, Eq, PartialEq, Clone)]
69pub enum TransactionInputType {
70 Value { alignment_power: u8 },
71 OutputIndex { output_index: u8 },
72}
73
74#[derive(Debug, Copy, Eq, PartialEq, Clone)]
75pub enum TransactionSlotType {
76 Address,
77 OutputIndex { output_index: u8 },
78}
79
80#[derive(Debug, Copy, Clone)]
89pub struct TransactionSlot(TransactionSlotType);
90
91impl TransactionSlot {
92 #[inline(always)]
94 pub const fn new_address() -> Self {
95 Self(TransactionSlotType::Address)
96 }
97
98 #[inline(always)]
102 pub const fn new_output_index(output_index: u8) -> Option<Self> {
103 if output_index > 0b0111_1111 {
104 return None;
105 }
106
107 Some(Self(TransactionSlotType::OutputIndex { output_index }))
108 }
109
110 #[inline(always)]
112 pub const fn from_u8(n: u8) -> Self {
113 if n & 0b1000_0000 == 0 {
115 Self(TransactionSlotType::OutputIndex { output_index: n })
116 } else {
117 Self(TransactionSlotType::Address)
118 }
119 }
120
121 #[inline(always)]
123 pub const fn into_u8(self) -> u8 {
124 match self.0 {
126 TransactionSlotType::Address => 0b1000_0000,
127 TransactionSlotType::OutputIndex { output_index } => output_index,
128 }
129 }
130
131 #[inline(always)]
133 pub const fn slot_type(self) -> TransactionSlotType {
134 self.0
135 }
136}
137
138#[derive(Debug, Copy, Clone)]
147pub struct TransactionInput(TransactionInputType);
148
149impl TransactionInput {
150 #[inline(always)]
154 pub const fn new_value(alignment: NonZeroU8) -> Option<Self> {
155 match alignment.get() {
156 1 | 2 | 4 | 8 | 16 => Some(Self(TransactionInputType::Value {
157 alignment_power: alignment.ilog2() as u8,
158 })),
159 _ => None,
160 }
161 }
162
163 #[inline(always)]
167 pub const fn new_output_index(output_index: u8) -> Option<Self> {
168 if output_index > 0b0111_1111 {
169 return None;
170 }
171
172 Some(Self(TransactionInputType::OutputIndex { output_index }))
173 }
174
175 #[inline(always)]
177 pub const fn from_u8(n: u8) -> Self {
178 if n & 0b1000_0000 == 0 {
180 Self(TransactionInputType::OutputIndex { output_index: n })
181 } else {
182 Self(TransactionInputType::Value {
183 alignment_power: n & 0b0111_1111,
184 })
185 }
186 }
187
188 #[inline(always)]
190 pub const fn into_u8(self) -> u8 {
191 match self.0 {
193 TransactionInputType::Value { alignment_power } => 0b1000_0000 | alignment_power,
194 TransactionInputType::OutputIndex { output_index } => output_index,
195 }
196 }
197
198 #[inline(always)]
200 pub const fn input_type(self) -> TransactionInputType {
201 self.0
202 }
203}
204
205#[derive(Debug, thiserror::Error)]
207pub enum TransactionPayloadDecoderError {
208 #[error("Payload too small")]
210 PayloadTooSmall,
211 #[error("Too many arguments")]
213 TooManyArguments(u8),
214 #[error("`ExternalArgs` buffer too small")]
216 ExternalArgsBufferTooSmall,
217 #[error("Output index not found: {0}")]
219 OutputIndexNotFound(u8),
220 #[error("Invalid output index {output_index} size for slot: {size}")]
222 InvalidSlotOutputIndexSize { output_index: u8, size: u32 },
223 #[error("Invalid output index {output_index} alignment for slot: {alignment}")]
225 InvalidSlotOutputIndexAlign { output_index: u8, alignment: u32 },
226 #[error("Alignment power is too large: {0}")]
228 AlignmentPowerTooLarge(u8),
229 #[error("Output buffer too small")]
231 OutputBufferTooSmall,
232 #[error("Output buffer offsets too small")]
234 OutputBufferOffsetsTooSmall,
235}
236
237#[derive(Debug, Copy, Clone)]
238pub struct OutputBufferDetails {
239 output_offset: u32,
241 size_or_external_args_offset: u32,
247}
248
249#[derive(Debug, Copy, Clone)]
250struct OutputBufferOffsetsCursor {
251 before_last: usize,
254 current: usize,
256}
257
258#[derive(Debug)]
260pub struct TransactionPayloadDecoder<'a> {
261 payload: &'a [u8],
262 external_args_buffer: &'a mut [*mut c_void; EXTERNAL_ARGS_BUFFER_SIZE],
263 output_buffer: &'a mut [MaybeUninit<u128>],
266 output_buffer_cursor: usize,
267 output_buffer_details: &'a mut [MaybeUninit<OutputBufferDetails>],
268 output_buffer_offsets_cursor: OutputBufferOffsetsCursor,
269 map_context: fn(TransactionMethodContext) -> MethodContext,
270}
271
272impl<'a> TransactionPayloadDecoder<'a> {
273 #[inline]
286 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
287 pub fn new(
288 payload: &'a [u128],
289 external_args_buffer: &'a mut [*mut c_void; EXTERNAL_ARGS_BUFFER_SIZE],
290 output_buffer: &'a mut [MaybeUninit<u128>],
291 output_buffer_details: &'a mut [MaybeUninit<OutputBufferDetails>],
292 map_context: fn(TransactionMethodContext) -> MethodContext,
293 ) -> Self {
294 debug_assert_eq!(align_of_val(payload), usize::from(MAX_ALIGNMENT));
295 debug_assert_eq!(align_of_val(output_buffer), usize::from(MAX_ALIGNMENT));
296
297 let payload =
299 unsafe { slice::from_raw_parts(payload.as_ptr().cast::<u8>(), size_of_val(payload)) };
300
301 Self {
302 payload,
303 external_args_buffer,
304 output_buffer,
305 output_buffer_cursor: 0,
306 output_buffer_details,
307 output_buffer_offsets_cursor: OutputBufferOffsetsCursor {
308 before_last: 0,
309 current: 0,
310 },
311 map_context,
312 }
313 }
314}
315
316impl<'a> TransactionPayloadDecoder<'a> {
317 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
319 pub fn decode_next_method(
320 &mut self,
321 ) -> Result<Option<PreparedMethod<'_>>, TransactionPayloadDecoderError> {
322 TransactionPayloadDecoderInternal::<true>(self).decode_next_method()
323 }
324
325 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
331 pub unsafe fn decode_next_method_unchecked(&mut self) -> Option<PreparedMethod<'_>> {
332 TransactionPayloadDecoderInternal::<false>(self)
333 .decode_next_method()
334 .expect("No decoding errors are possible with trusted input; qed")
335 }
336}
337
338#[inline(always)]
344unsafe fn write_external_args<T>(external_args: &mut NonNull<c_void>, value: T) {
345 unsafe {
347 external_args.cast::<T>().write(value);
348 *external_args = external_args.byte_add(size_of::<T>());
349 }
350}
351
352struct TransactionPayloadDecoderInternal<'tmp, 'decoder, const VERIFY: bool>(
356 &'tmp mut TransactionPayloadDecoder<'decoder>,
357);
358
359impl<'tmp, 'decoder, const VERIFY: bool> Deref
360 for TransactionPayloadDecoderInternal<'tmp, 'decoder, VERIFY>
361{
362 type Target = TransactionPayloadDecoder<'decoder>;
363
364 #[inline(always)]
365 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
366 fn deref(&self) -> &Self::Target {
367 self.0
368 }
369}
370
371impl<'tmp, 'decoder, const VERIFY: bool> DerefMut
372 for TransactionPayloadDecoderInternal<'tmp, 'decoder, VERIFY>
373{
374 #[inline(always)]
375 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
376 fn deref_mut(&mut self) -> &mut Self::Target {
377 self.0
378 }
379}
380
381impl<'tmp, 'decoder, const VERIFY: bool> TransactionPayloadDecoderInternal<'tmp, 'decoder, VERIFY> {
382 #[inline(always)]
383 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
384 fn decode_next_method(
385 mut self,
386 ) -> Result<Option<PreparedMethod<'decoder>>, TransactionPayloadDecoderError> {
387 if self.payload.len() <= usize::from(MAX_ALIGNMENT) {
388 return Ok(None);
389 }
390
391 self.update_output_buffer_details();
392
393 let contract = self.get_trivial_type::<Address>()?;
394 let method_fingerprint = self.get_trivial_type::<MethodFingerprint>()?;
395 let method_context =
396 (self.map_context)(*self.get_trivial_type::<TransactionMethodContext>()?);
397
398 let mut transaction_slots_inputs =
399 [MaybeUninit::<u8>::uninit(); MAX_TOTAL_METHOD_ARGS as usize];
400
401 let num_slot_arguments = self.read_u8()?;
402 for transaction_slot in transaction_slots_inputs
403 .iter_mut()
404 .take(usize::from(num_slot_arguments))
405 {
406 transaction_slot.write(self.read_u8()?);
407 }
408
409 let num_input_arguments = self.read_u8()?;
410 for transaction_input in transaction_slots_inputs
411 .iter_mut()
412 .skip(usize::from(num_slot_arguments))
413 .take(usize::from(num_input_arguments))
414 {
415 transaction_input.write(self.read_u8()?);
416 }
417
418 let num_output_arguments = self.read_u8()?;
419
420 let (transaction_slots, transaction_inputs) = unsafe {
422 let (transaction_slots, transaction_inputs) =
423 transaction_slots_inputs.split_at_unchecked(usize::from(num_slot_arguments));
424 let transaction_inputs =
425 transaction_inputs.get_unchecked(..usize::from(num_input_arguments));
426
427 (
428 transaction_slots.assume_init_ref(),
429 transaction_inputs.assume_init_ref(),
430 )
431 };
432
433 let number_of_arguments = num_slot_arguments
436 .saturating_add(num_input_arguments)
437 .saturating_add(num_output_arguments);
438 if VERIFY && number_of_arguments > MAX_TOTAL_METHOD_ARGS {
439 return Err(TransactionPayloadDecoderError::TooManyArguments(
440 number_of_arguments,
441 ));
442 }
443
444 let external_args = NonNull::new(self.external_args_buffer.as_mut_ptr())
445 .expect("Not null; qed")
446 .cast::<c_void>();
447 {
448 let external_args_cursor = &mut external_args.clone();
449
450 for &transaction_slot in transaction_slots {
451 let address = match TransactionSlot::from_u8(transaction_slot).slot_type() {
452 TransactionSlotType::Address => self.get_trivial_type::<Address>()?,
453 TransactionSlotType::OutputIndex { output_index } => {
454 let (bytes, size) = self.get_from_output_buffer(output_index)?;
455
456 if VERIFY && size != Address::SIZE {
457 return Err(
458 TransactionPayloadDecoderError::InvalidSlotOutputIndexSize {
459 output_index,
460 size,
461 },
462 );
463 }
464
465 let maybe_address = unsafe { Address::from_bytes(bytes) };
467
468 if VERIFY {
469 let Some(address) = maybe_address else {
470 let error =
471 TransactionPayloadDecoderError::InvalidSlotOutputIndexAlign {
472 output_index,
473 alignment: align_of_val(bytes) as u32,
474 };
475 return Err(error);
476 };
477
478 address
479 } else {
480 unsafe { maybe_address.unwrap_unchecked() }
482 }
483 }
484 };
485
486 unsafe {
489 write_external_args(external_args_cursor, ptr::from_ref(address));
490 }
491 }
492
493 for &transaction_input in transaction_inputs {
494 let (bytes, size) = match TransactionInput::from_u8(transaction_input).input_type()
495 {
496 TransactionInputType::Value { alignment_power } => {
497 let alignment = if VERIFY {
500 1_usize.checked_shl(u32::from(alignment_power)).ok_or(
501 TransactionPayloadDecoderError::AlignmentPowerTooLarge(
502 alignment_power,
503 ),
504 )?
505 } else {
506 unsafe { 1_usize.unchecked_shl(u32::from(alignment_power)) }
508 };
509
510 let size = *self.get_trivial_type::<u32>()?;
511 let bytes = self.get_bytes(
512 size,
513 NonZeroUsize::new(alignment).expect("Not zero; qed"),
514 )?;
515
516 (bytes, size)
517 }
518 TransactionInputType::OutputIndex { output_index } => {
519 self.get_from_output_buffer(output_index)?
520 }
521 };
522
523 unsafe {
526 write_external_args(
527 external_args_cursor,
528 FfiDataSizeCapacityRo {
529 data_ptr: NonNull::from_ref(bytes).as_non_null_ptr(),
530 size,
531 capacity: size,
532 },
533 );
534 }
535 }
536
537 for _ in 0..num_output_arguments {
538 let recommended_capacity = *self.get_trivial_type::<u32>()?;
539 let alignment_power = *self.get_trivial_type::<u8>()?;
540 let alignment = if VERIFY {
543 1_usize.checked_shl(u32::from(alignment_power)).ok_or(
544 TransactionPayloadDecoderError::AlignmentPowerTooLarge(alignment_power),
545 )?
546 } else {
547 unsafe { 1_usize.unchecked_shl(u32::from(alignment_power)) }
549 };
550
551 let external_args_size_offset = unsafe {
554 external_args_cursor
555 .byte_add(offset_of!(FfiDataSizeCapacityRw, size))
556 .byte_offset_from_unsigned(external_args)
557 };
558
559 let data = self.allocate_output_buffer(
560 recommended_capacity,
561 NonZeroUsize::new(alignment).expect("Not zero; qed"),
562 external_args_size_offset as u32,
563 )?;
564
565 unsafe {
568 write_external_args(
569 external_args_cursor,
570 FfiDataSizeCapacityRw {
571 data_ptr: data.as_ptr(),
572 size: 0,
573 capacity: recommended_capacity,
574 },
575 );
576 }
577 }
578 }
579
580 Ok(Some(PreparedMethod {
581 contract: *contract,
582 fingerprint: *method_fingerprint,
583 external_args,
584 method_context,
585 phantom: PhantomData,
586 }))
587 }
588
589 #[inline(always)]
591 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
592 fn get_trivial_type<T>(&mut self) -> Result<&'decoder T, TransactionPayloadDecoderError>
593 where
594 T: TrivialType,
595 {
596 self.ensure_alignment(NonZeroUsize::new(align_of::<T>()).expect("Not zero; qed"))?;
597
598 let bytes;
599 if VERIFY {
600 (bytes, self.payload) = self
601 .payload
602 .split_at_checked(size_of::<T>())
603 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
604 } else {
605 (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size_of::<T>()) };
607 }
608
609 let value_ref = unsafe { bytes.as_ptr().cast::<T>().as_ref().expect("Not null; qed") };
611
612 Ok(value_ref)
613 }
614
615 #[inline(always)]
617 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
618 fn get_bytes(
619 &mut self,
620 size: u32,
621 alignment: NonZeroUsize,
622 ) -> Result<&'decoder [u8], TransactionPayloadDecoderError> {
623 self.ensure_alignment(alignment)?;
624
625 let bytes;
626 if VERIFY {
627 (bytes, self.payload) = self
628 .payload
629 .split_at_checked(size as usize)
630 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
631 } else {
632 (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size as usize) };
634 }
635
636 Ok(bytes)
637 }
638
639 #[inline(always)]
640 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
641 fn read_u8(&mut self) -> Result<u8, TransactionPayloadDecoderError> {
642 let value;
643 if VERIFY {
644 (value, self.payload) = self
645 .payload
646 .split_at_checked(1)
647 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
648 } else {
649 (value, self.payload) = unsafe { self.payload.split_at_unchecked(1) };
651 }
652
653 Ok(value[0])
654 }
655
656 #[inline(always)]
657 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
658 fn ensure_alignment(
659 &mut self,
660 alignment: NonZeroUsize,
661 ) -> Result<(), TransactionPayloadDecoderError> {
662 let alignment = alignment.get();
663 debug_assert!(alignment <= usize::from(MAX_ALIGNMENT));
664
665 let unaligned_by = self.payload.as_ptr().addr() & (alignment - 1);
668 if unaligned_by > 0 {
669 let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
671 if VERIFY {
672 self.payload = self
673 .payload
674 .split_off(padding_bytes..)
675 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
676 } else {
677 self.payload = unsafe { self.payload.get_unchecked(padding_bytes..) };
679 }
680 }
681 Ok(())
682 }
683
684 #[inline(always)]
686 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
687 fn allocate_output_buffer(
688 &mut self,
689 capacity: u32,
690 output_alignment: NonZeroUsize,
691 external_args_size_offset: u32,
692 ) -> Result<NonNull<u8>, TransactionPayloadDecoderError> {
693 if VERIFY && self.output_buffer_details.len() == self.output_buffer_offsets_cursor.current {
694 return Err(TransactionPayloadDecoderError::OutputBufferOffsetsTooSmall);
695 }
696
697 let (output_offset, output_ptr) = self
698 .allocate_output_buffer_ptr(output_alignment, capacity as usize)
699 .ok_or(TransactionPayloadDecoderError::OutputBufferTooSmall)?;
700
701 let output_buffer_details = unsafe {
703 let output_buffer_offsets_cursor = self.output_buffer_offsets_cursor.current;
705 self.output_buffer_details
706 .get_unchecked_mut(output_buffer_offsets_cursor)
707 };
708 output_buffer_details.write(OutputBufferDetails {
709 output_offset: output_offset as u32,
710 size_or_external_args_offset: external_args_size_offset,
711 });
712 self.output_buffer_offsets_cursor.current += 1;
713
714 Ok(output_ptr)
715 }
716
717 #[inline(always)]
719 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
720 fn allocate_output_buffer_ptr<T>(
721 &mut self,
722 alignment: NonZeroUsize,
723 size: usize,
724 ) -> Option<(usize, NonNull<T>)> {
725 let alignment = alignment.get();
726 debug_assert!(alignment <= usize::from(MAX_ALIGNMENT));
727
728 let unaligned_by = self.output_buffer_cursor & (alignment - 1);
731 let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
733
734 let new_output_buffer_cursor = if VERIFY {
735 let new_output_buffer_cursor = self
736 .output_buffer_cursor
737 .checked_add(padding_bytes)?
738 .checked_add(size)?;
739
740 if new_output_buffer_cursor > size_of_val(self.output_buffer) {
741 return None;
742 }
743
744 new_output_buffer_cursor
745 } else {
746 unsafe {
748 self.output_buffer_cursor
749 .unchecked_add(padding_bytes)
750 .unchecked_add(size)
751 }
752 };
753
754 let (offset, buffer_ptr) = unsafe {
756 let offset = self.output_buffer_cursor.unchecked_add(padding_bytes);
757 let buffer_ptr = NonNull::new_unchecked(
758 self.output_buffer.as_mut_ptr().byte_add(offset).cast::<T>(),
759 );
760
761 (offset, buffer_ptr)
762 };
763 self.output_buffer_cursor = new_output_buffer_cursor;
764
765 Some((offset, buffer_ptr))
766 }
767
768 #[inline(always)]
771 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
772 fn update_output_buffer_details(&mut self) {
773 let TransactionPayloadDecoder {
774 external_args_buffer,
775 output_buffer_details,
776 output_buffer_offsets_cursor,
777 ..
778 } = &mut **self;
779
780 for output_buffer_details in unsafe {
782 output_buffer_details
783 .get_unchecked_mut(
784 output_buffer_offsets_cursor.before_last..output_buffer_offsets_cursor.current,
785 )
786 .assume_init_mut()
787 } {
788 output_buffer_details.size_or_external_args_offset = unsafe {
790 external_args_buffer
791 .as_ptr()
792 .cast::<u32>()
793 .byte_add(output_buffer_details.size_or_external_args_offset as usize)
794 .read()
795 };
796 }
797
798 output_buffer_offsets_cursor.before_last = output_buffer_offsets_cursor.current;
799 }
800
801 #[inline(always)]
802 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
803 fn get_from_output_buffer(
804 &self,
805 output_index: u8,
806 ) -> Result<(&[u8], u32), TransactionPayloadDecoderError> {
807 let OutputBufferDetails {
808 output_offset,
809 size_or_external_args_offset: size,
810 } = if VERIFY {
811 if usize::from(output_index) < self.output_buffer_offsets_cursor.current {
812 unsafe {
814 self.output_buffer_details
815 .get_unchecked(usize::from(output_index))
816 .assume_init()
817 }
818 } else {
819 return Err(TransactionPayloadDecoderError::OutputIndexNotFound(
820 output_index,
821 ));
822 }
823 } else {
824 unsafe {
826 self.output_buffer_details
827 .get_unchecked(usize::from(output_index))
828 .assume_init()
829 }
830 };
831
832 let bytes = unsafe {
835 let bytes_ptr = self
836 .output_buffer
837 .as_ptr()
838 .cast::<u8>()
839 .add(output_offset as usize);
840
841 slice::from_raw_parts(bytes_ptr, size as usize)
842 };
843
844 Ok((bytes, size))
845 }
846}