Skip to main content

ab_system_contract_simple_wallet_base/
payload.rs

1//! This module contains generic utilities for serializing and deserializing method calls to/from
2//! payload bytes.
3//!
4//! It can be reused to implement a different wallet implementation as well as read and verify the
5//! contents of the transaction (for example, to display it on the screen of the hardware wallet).
6//!
7//! Builder interface requires heap allocations and can be enabled with `payload-builder` feature,
8//! while the rest works in `no_std` environment without a global allocator.
9
10#[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    /// Call contract under [`Address::NULL`] context (corresponds to [`MethodContext::Reset`])
48    Null,
49    /// Call contract under context of the wallet (corresponds to [`MethodContext::Replace`])
50    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/// The type of transaction slot could be either explicit slot address or output index.
79///
80/// Specifically, if the previous method has `#[output]` or return value, those values are collected
81/// and pushed into a virtual "stack". Then, if [`Self::slot_type()`] returns
82/// [`TransactionSlotType::OutputIndex`], then the corresponding input will use the value at
83/// `output_index` of this stack instead of what was specified in `external_args`. This allows
84/// composing calls to multiple methods into more sophisticated workflows without writing special
85/// contracts for this.
86#[derive(Debug, Copy, Clone)]
87pub struct TransactionSlot(TransactionSlotType);
88
89impl TransactionSlot {
90    /// Explicit slot address
91    #[inline(always)]
92    pub const fn new_address() -> Self {
93        Self(TransactionSlotType::Address)
94    }
95
96    /// Output index value.
97    ///
98    /// Valid index values are 0..=127.
99    #[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    /// Create an instance from `u8`
109    #[inline(always)]
110    pub const fn from_u8(n: u8) -> Self {
111        // The first bit is set to 1 for explicit slot address and 0 for output index
112        if n & 0b1000_0000 == 0 {
113            Self(TransactionSlotType::OutputIndex { output_index: n })
114        } else {
115            Self(TransactionSlotType::Address)
116        }
117    }
118
119    /// Convert instance into `u8`
120    #[inline(always)]
121    pub const fn into_u8(self) -> u8 {
122        // The first bit is set to 1 for explicit slot address and 0 for output index
123        match self.0 {
124            TransactionSlotType::Address => 0b1000_0000,
125            TransactionSlotType::OutputIndex { output_index } => output_index,
126        }
127    }
128
129    /// Returns `Some(output_index)` or `None` if explicit slot address
130    #[inline(always)]
131    pub const fn slot_type(self) -> TransactionSlotType {
132        self.0
133    }
134}
135
136/// The type of transaction input could be either explicit value or output index.
137///
138/// Specifically, if the previous method has `#[output]` or return value, those values are collected
139/// and pushed into a virtual "stack". Then, if [`Self::input_type()`] returns
140/// [`TransactionInputType::OutputIndex`], then the corresponding input will use the value at
141/// `output_index` of this stack instead of what was specified in `external_args`. This allows
142/// composing calls to multiple methods into more sophisticated workflows without writing special
143/// contracts for this.
144#[derive(Debug, Copy, Clone)]
145pub struct TransactionInput(TransactionInputType);
146
147impl TransactionInput {
148    /// Regular input value with specified alignment.
149    ///
150    /// Valid alignment values are: 1, 2, 4, 8, 16.
151    #[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    /// Output index value.
162    ///
163    /// Valid index values are 0..=127.
164    #[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    /// Create an instance from `u8`
174    #[inline(always)]
175    pub const fn from_u8(n: u8) -> Self {
176        // The first bit is set to 1 for value and 0 for output index
177        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    /// Convert instance into `u8`
187    #[inline(always)]
188    pub const fn into_u8(self) -> u8 {
189        // The first bit is set to 1 for value and 0 for output index
190        match self.0 {
191            TransactionInputType::Value { alignment_power } => 0b1000_0000 | alignment_power,
192            TransactionInputType::OutputIndex { output_index } => output_index,
193        }
194    }
195
196    /// Returns `Some(output_index)` or `None` if regular input value
197    #[inline(always)]
198    pub const fn input_type(self) -> TransactionInputType {
199        self.0
200    }
201}
202
203/// Errors for [`TransactionPayloadDecoder`]
204#[derive(Debug, thiserror::Error)]
205pub enum TransactionPayloadDecoderError {
206    /// Payload too small
207    #[error("Payload too small")]
208    PayloadTooSmall,
209    /// Too many arguments
210    #[error("Too many arguments")]
211    TooManyArguments(u8),
212    /// `ExternalArgs` buffer too small
213    #[error("`ExternalArgs` buffer too small")]
214    ExternalArgsBufferTooSmall,
215    /// Output index not found
216    #[error("Output index not found: {0}")]
217    OutputIndexNotFound(u8),
218    /// Invalid output index size for slot
219    #[error("Invalid output index {output_index} size for slot: {size}")]
220    InvalidSlotOutputIndexSize { output_index: u8, size: u32 },
221    /// Invalid output index alignment for slot
222    #[error("Invalid output index {output_index} alignment for slot: {alignment}")]
223    InvalidSlotOutputIndexAlign { output_index: u8, alignment: u32 },
224    /// Alignment power is too large
225    #[error("Alignment power is too large: {0}")]
226    AlignmentPowerTooLarge(u8),
227    /// Output buffer too small
228    #[error("Output buffer too small")]
229    OutputBufferTooSmall,
230    /// Output buffer offsets too small
231    #[error("Output buffer offsets too small")]
232    OutputBufferOffsetsTooSmall,
233}
234
235#[derive(Debug, Copy, Clone)]
236pub struct OutputBufferDetails {
237    /// Offset of output bytes inside `output_buffer`
238    output_offset: u32,
239    /// Size of the output buffer `output_offset` points to.
240    ///
241    /// NOTE: It temporarily stores the offset (in bytes) into `external_args_buffer` while
242    /// decoding a method. Before decoding the next method, the previous `external_args_buffer`
243    /// is read and an updated size is read from it to correct the value.
244    size_or_external_args_offset: u32,
245}
246
247#[derive(Debug, Copy, Clone)]
248struct OutputBufferOffsetsCursor {
249    /// Cursor pointing to the first free entry before the last method decoding, which allows
250    /// updating output sizes using [`OutputBufferDetails::size_or_external_args_offset`] field
251    before_last: usize,
252    /// Current cursor pointing to the next free entry in `output_buffer_details`
253    current: usize,
254}
255
256/// Decoder for transaction payload created using `TransactionPayloadBuilder`.
257#[derive(Debug)]
258pub struct TransactionPayloadDecoder<'a> {
259    payload: &'a [u8],
260    external_args_buffer: &'a mut [*mut c_void; EXTERNAL_ARGS_BUFFER_SIZE],
261    // TODO: Cast `output_buffer` into `&'a mut [MaybeUninit<u8>]` and remove
262    //  `output_buffer_cursor`
263    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    /// Create a new instance.
272    ///
273    /// The size of `external_args_buffer` defines the max number of bytes allocated for
274    /// `ExternalArgs`, which impacts the number of arguments that can be represented by
275    /// `ExternalArgs`. The size is specified in pointers with `#[slot]` argument using one
276    /// pointer, `#[input]` two pointers, and `#[output]` three pointers each.
277    ///
278    /// The size of `output_buffer` defines how big the total size of `#[output]` and return values
279    /// could be in all methods of the payload together.
280    ///
281    /// The size of `output_buffer_details` defines how many `#[output]` arguments and return values
282    /// could exist in all methods of the payload together.
283    #[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        // SAFETY: Memory is valid and bound by an argument's lifetime
296        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    /// Decode the next method (if any) in the payload
314    #[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    /// Decode the next method (if any) in the payload without checking size.
322    ///
323    /// # Safety
324    /// Must be used with trusted input created using `TransactionPayloadBuilder` or pre-verified
325    /// using [`Self::decode_next_method()`] earlier.
326    #[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/// Write to an external arguments pointer and move it forward.
335///
336/// # Safety
337/// `external_args` must have enough capacity for the written value, and the current offset must
338/// have the correct alignment for the type being written.
339#[inline(always)]
340unsafe fn write_external_args<T>(external_args: &mut NonNull<c_void>, value: T) {
341    // SAFETY: guaranteed by this function signature
342    unsafe {
343        external_args.cast::<T>().write(value);
344        *external_args = external_args.byte_add(size_of::<T>());
345    }
346}
347
348/// # Safety
349/// When `VERIFY == false` input must be trusted and created using `TransactionPayloadBuilder` or
350/// pre-verified using `VERIFY == true` earlier.
351struct 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        // SAFETY: Just initialized elements above
415        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        // This can be off by 1 due to `self` not included in `ExternalArgs`, but it is good enough
428        // for this context
429        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                        // SAFETY: All bytes are valid as long as alignment and size match
460                        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                            // SAFETY: The unverified version, see struct description
475                            unsafe { maybe_address.unwrap_unchecked() }
476                        }
477                    }
478                };
479
480                // SAFETY: Size of `self.external_args_buffer` is statically sized for worst-case,
481                // address is correctly aligned
482                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                        // Optimized version of the following:
492                        // let alignment = 2usize.pow(u32::from(alignment_power));
493                        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                            // SAFETY: The unverified version, see struct description
501                            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                // SAFETY: Size of `self.external_args_buffer` is statically sized for worst-case,
518                // buffer is correctly aligned
519                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                // Optimized version of the following:
535                // let alignment = 2usize.pow(u32::from(alignment_power));
536                let alignment = if VERIFY {
537                    1_usize.checked_shl(u32::from(alignment_power)).ok_or(
538                        TransactionPayloadDecoderError::AlignmentPowerTooLarge(alignment_power),
539                    )?
540                } else {
541                    // SAFETY: The unverified version, see struct description
542                    unsafe { 1_usize.unchecked_shl(u32::from(alignment_power)) }
543                };
544
545                // SAFETY: `external_args_cursor` is created from `external_args` and is within the
546                // same allocation
547                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                // SAFETY: Size of `self.external_args_buffer` is statically sized for worst-case,
560                // buffer is correctly aligned
561                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    /// Get a reference to a [`TrivialType`] value inside the payload
584    #[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            // SAFETY: The unverified version, see struct description
600            (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size_of::<T>()) };
601        }
602
603        // SAFETY: Correctly aligned bytes of the correct size
604        let value_ref = unsafe { bytes.as_ptr().cast::<T>().as_ref().expect("Not null; qed") };
605
606        Ok(value_ref)
607    }
608
609    /// Get a reference to opaque bytes inside the payload
610    #[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            // SAFETY: The unverified version, see struct description
627            (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            // SAFETY: The unverified version, see struct description
644            (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        // Optimized version of the following that expects `alignment` to be a power of 2:
660        // let unaligned_by = self.payload.as_ptr().addr() % alignment;
661        let unaligned_by = self.payload.as_ptr().addr() & (alignment - 1);
662        if unaligned_by > 0 {
663            // SAFETY: Subtracted value is always smaller than alignment
664            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                // SAFETY: Subtracted value is always smaller than alignment
672                self.payload = unsafe { self.payload.get_unchecked(padding_bytes..) };
673            }
674        }
675        Ok(())
676    }
677
678    /// Returns a tuple of `(size_ptr, output_ptr)` of a newly allocated output buffer
679    #[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        // SAFETY: Checked above that `output_buffer_details` is not full
696        let output_buffer_details = unsafe {
697            // Borrow checker doesn't understand that these variables are disjoint
698            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    /// Returns `None` if output buffer is not large enough
712    #[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        // Optimized version of the following that expects `alignment` to be a power of 2:
723        // let unaligned_by = self.output_buffer_cursor % alignment;
724        let unaligned_by = self.output_buffer_cursor & (alignment - 1);
725        // SAFETY: Subtracted value is always smaller than alignment
726        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            // SAFETY: The unverified version, see struct description
741            unsafe {
742                self.output_buffer_cursor
743                    .unchecked_add(padding_bytes)
744                    .unchecked_add(size)
745            }
746        };
747
748        // SAFETY: Bounds and alignment checks are done above
749        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    /// Update all [`OutputBufferDetails::size_or_external_args_offset`] to store sizes rather than
763    /// offsets into `external_args_buffer` and advances [`OutputBufferOffsetsCursor::before_last`]
764    #[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        // SAFETY: Elements in the selected range were initialized
779        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            // SAFETY: Protected invariant in `decode_next_method`
787            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                // SAFETY: Checked that index is initialized
811                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            // SAFETY: The unverified version, see struct description
823            unsafe {
824                self.output_buffer_details
825                    .get_unchecked(usize::from(output_index))
826                    .assume_init()
827            }
828        };
829
830        // SAFETY: Offset was created as the result of writing value at the correct
831        // offset into `output_buffer_details` earlier
832        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}