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#[cfg(test)]
13mod tests;
14
15use crate::EXTERNAL_ARGS_BUFFER_SIZE;
16use ab_contracts_common::MAX_TOTAL_METHOD_ARGS;
17use ab_contracts_common::env::{MethodContext, PreparedMethod};
18use ab_contracts_common::method::MethodFingerprint;
19use ab_core_primitives::address::Address;
20use ab_io_type::MAX_ALIGNMENT;
21use ab_io_type::trivial_type::TrivialType;
22use core::ffi::c_void;
23use core::mem::{MaybeUninit, offset_of};
24use core::num::{NonZeroU8, NonZeroUsize};
25use core::ops::{Deref, DerefMut};
26use core::ptr::NonNull;
27use core::{hint, ptr, slice};
28
29#[derive(Copy, Clone)]
30#[repr(C)]
31struct FfiDataSizeCapacityRo {
32    data_ptr: NonNull<u8>,
33    size: u32,
34    capacity: u32,
35}
36
37#[derive(Copy, Clone)]
38#[repr(C)]
39struct FfiDataSizeCapacityRw {
40    data_ptr: *mut u8,
41    size: u32,
42    capacity: u32,
43}
44
45#[derive(Debug, Copy, Clone, Eq, PartialEq, TrivialType)]
46#[repr(u8)]
47pub enum TransactionMethodContext {
48    /// Call contract under [`Address::NULL`] context (corresponds to [`MethodContext::Reset`])
49    Null,
50    /// Call contract under context of the wallet (corresponds to [`MethodContext::Replace`])
51    Wallet,
52}
53
54const impl TryFrom<u8> for TransactionMethodContext {
55    type Error = ();
56
57    #[inline(always)]
58    fn try_from(value: u8) -> Result<Self, Self::Error> {
59        Ok(match value {
60            0 => Self::Null,
61            1 => Self::Wallet,
62            _ => return Err(()),
63        })
64    }
65}
66
67#[derive(Debug, Copy, Eq, PartialEq, Clone)]
68pub enum TransactionInputType {
69    Value { alignment_power: u8 },
70    OutputIndex { output_index: u8 },
71}
72
73#[derive(Debug, Copy, Eq, PartialEq, Clone)]
74pub enum TransactionSlotType {
75    Address,
76    OutputIndex { output_index: u8 },
77}
78
79/// The type of transaction slot could be either explicit slot address or output index.
80///
81/// Specifically, if the previous method has `#[output]` or return value, those values are collected
82/// and pushed into a virtual "stack". Then, if [`Self::slot_type()`] returns
83/// [`TransactionSlotType::OutputIndex`], then the corresponding input will use the value at
84/// `output_index` of this stack instead of what was specified in `external_args`. This allows
85/// composing calls to multiple methods into more sophisticated workflows without writing special
86/// contracts for this.
87#[derive(Debug, Copy, Clone)]
88pub struct TransactionSlot(TransactionSlotType);
89
90impl TransactionSlot {
91    /// Explicit slot address
92    #[inline(always)]
93    pub const fn new_address() -> Self {
94        Self(TransactionSlotType::Address)
95    }
96
97    /// Output index value.
98    ///
99    /// Valid index values are 0..=127.
100    #[inline(always)]
101    pub const fn new_output_index(output_index: u8) -> Option<Self> {
102        if output_index > 0b0111_1111 {
103            return None;
104        }
105
106        Some(Self(TransactionSlotType::OutputIndex { output_index }))
107    }
108
109    /// Create an instance from `u8`
110    #[inline(always)]
111    pub const fn from_u8(n: u8) -> Self {
112        // The first bit is set to 1 for explicit slot address and 0 for output index
113        if n & 0b1000_0000 == 0 {
114            Self(TransactionSlotType::OutputIndex { output_index: n })
115        } else {
116            Self(TransactionSlotType::Address)
117        }
118    }
119
120    /// Convert instance into `u8`
121    #[inline(always)]
122    pub const fn into_u8(self) -> u8 {
123        // The first bit is set to 1 for explicit slot address and 0 for output index
124        match self.0 {
125            TransactionSlotType::Address => 0b1000_0000,
126            TransactionSlotType::OutputIndex { output_index } => output_index,
127        }
128    }
129
130    /// Returns `Some(output_index)` or `None` if explicit slot address
131    #[inline(always)]
132    pub const fn slot_type(self) -> TransactionSlotType {
133        self.0
134    }
135}
136
137/// The type of transaction input could be either explicit value or output index.
138///
139/// Specifically, if the previous method has `#[output]` or return value, those values are collected
140/// and pushed into a virtual "stack". Then, if [`Self::input_type()`] returns
141/// [`TransactionInputType::OutputIndex`], then the corresponding input will use the value at
142/// `output_index` of this stack instead of what was specified in `external_args`. This allows
143/// composing calls to multiple methods into more sophisticated workflows without writing special
144/// contracts for this.
145#[derive(Debug, Copy, Clone)]
146pub struct TransactionInput(TransactionInputType);
147
148impl TransactionInput {
149    /// Regular input value with specified alignment.
150    ///
151    /// Valid alignment values are: 1, 2, 4, 8, 16.
152    #[inline(always)]
153    pub const fn new_value(alignment: NonZeroU8) -> Option<Self> {
154        match alignment.get() {
155            1 | 2 | 4 | 8 | 16 => Some(Self(TransactionInputType::Value {
156                alignment_power: alignment.ilog2() as u8,
157            })),
158            _ => None,
159        }
160    }
161
162    /// Output index value.
163    ///
164    /// Valid index values are 0..=127.
165    #[inline(always)]
166    pub const fn new_output_index(output_index: u8) -> Option<Self> {
167        if output_index > 0b0111_1111 {
168            return None;
169        }
170
171        Some(Self(TransactionInputType::OutputIndex { output_index }))
172    }
173
174    /// Create an instance from `u8`
175    #[inline(always)]
176    pub const fn from_u8(n: u8) -> Self {
177        // The first bit is set to 1 for value and 0 for output index
178        if n & 0b1000_0000 == 0 {
179            Self(TransactionInputType::OutputIndex { output_index: n })
180        } else {
181            Self(TransactionInputType::Value {
182                alignment_power: n & 0b0111_1111,
183            })
184        }
185    }
186
187    /// Convert instance into `u8`
188    #[inline(always)]
189    pub const fn into_u8(self) -> u8 {
190        // The first bit is set to 1 for value and 0 for output index
191        match self.0 {
192            TransactionInputType::Value { alignment_power } => 0b1000_0000 | alignment_power,
193            TransactionInputType::OutputIndex { output_index } => output_index,
194        }
195    }
196
197    /// Returns `Some(output_index)` or `None` if regular input value
198    #[inline(always)]
199    pub const fn input_type(self) -> TransactionInputType {
200        self.0
201    }
202}
203
204/// Errors for [`TransactionPayloadDecoder`]
205#[derive(Debug, thiserror::Error)]
206pub enum TransactionPayloadDecoderError {
207    /// Payload too small
208    #[error("Payload too small")]
209    PayloadTooSmall,
210    /// Too many arguments
211    #[error("Too many arguments")]
212    TooManyArguments(u8),
213    /// `ExternalArgs` buffer too small
214    #[error("`ExternalArgs` buffer too small")]
215    ExternalArgsBufferTooSmall,
216    /// Output index not found
217    #[error("Output index not found: {0}")]
218    OutputIndexNotFound(u8),
219    /// Invalid output index size for slot
220    #[error("Invalid output index {output_index} size for slot: {size}")]
221    InvalidSlotOutputIndexSize { output_index: u8, size: u32 },
222    /// Invalid output index alignment for slot
223    #[error("Invalid output index {output_index} alignment for slot: {alignment}")]
224    InvalidSlotOutputIndexAlign { output_index: u8, alignment: u32 },
225    /// Alignment power is too large
226    #[error("Alignment power is too large: {0}")]
227    AlignmentPowerTooLarge(u8),
228    /// Invalid method context
229    #[error("Invalid method context: {0}")]
230    InvalidMethodContext(u8),
231    /// Output buffer too small
232    #[error("Output buffer too small")]
233    OutputBufferTooSmall,
234    /// Output buffer offsets too small
235    #[error("Output buffer offsets too small")]
236    OutputBufferOffsetsTooSmall,
237}
238
239#[derive(Debug, Copy, Clone)]
240pub struct OutputBufferDetails {
241    /// Offset of output bytes inside `output_buffer`
242    output_offset: u32,
243    /// Size of the output buffer `output_offset` points to.
244    ///
245    /// NOTE: It temporarily stores the offset (in bytes) into `external_args_buffer` while
246    /// decoding a method. Before decoding the next method, the previous `external_args_buffer`
247    /// is read and an updated size is read from it to correct the value.
248    size_or_external_args_offset: u32,
249}
250
251#[derive(Debug, Copy, Clone)]
252struct OutputBufferOffsetsCursor {
253    /// Cursor pointing to the first free entry before the last method decoding, which allows
254    /// updating output sizes using [`OutputBufferDetails::size_or_external_args_offset`] field
255    before_last: usize,
256    /// Current cursor pointing to the next free entry in `output_buffer_details`
257    current: usize,
258}
259
260/// Decoder for transaction payload created using `TransactionPayloadBuilder`.
261#[derive(Debug)]
262pub struct TransactionPayloadDecoder<'a> {
263    payload: &'a [u8],
264    external_args_buffer: &'a mut [*mut c_void; EXTERNAL_ARGS_BUFFER_SIZE],
265    // TODO: Cast `output_buffer` into `&'a mut [MaybeUninit<u8>]` and remove
266    //  `output_buffer_cursor`
267    output_buffer: &'a mut [MaybeUninit<u128>],
268    output_buffer_cursor: usize,
269    output_buffer_details: &'a mut [MaybeUninit<OutputBufferDetails>],
270    output_buffer_offsets_cursor: OutputBufferOffsetsCursor,
271    map_context: fn(TransactionMethodContext) -> MethodContext,
272}
273
274impl<'a> TransactionPayloadDecoder<'a> {
275    /// Create a new instance.
276    ///
277    /// The size of `external_args_buffer` defines the max number of bytes allocated for
278    /// `ExternalArgs`, which impacts the number of arguments that can be represented by
279    /// `ExternalArgs`. The size is specified in pointers with `#[slot]` argument using one
280    /// pointer, while `#[input]` and `#[output]` use one pointer and size + capacity each.
281    ///
282    /// The size of `output_buffer` defines how big the total size of `#[output]` and return values
283    /// could be in all methods of the payload together.
284    ///
285    /// The size of `output_buffer_details` defines how many `#[output]` arguments and return values
286    /// could exist in all methods of the payload together.
287    #[inline]
288    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
289    pub fn new(
290        payload: &'a [u128],
291        external_args_buffer: &'a mut [*mut c_void; EXTERNAL_ARGS_BUFFER_SIZE],
292        output_buffer: &'a mut [MaybeUninit<u128>],
293        output_buffer_details: &'a mut [MaybeUninit<OutputBufferDetails>],
294        map_context: fn(TransactionMethodContext) -> MethodContext,
295    ) -> Self {
296        debug_assert_eq!(align_of_val(payload), usize::from(MAX_ALIGNMENT));
297        debug_assert_eq!(align_of_val(output_buffer), usize::from(MAX_ALIGNMENT));
298
299        // SAFETY: Memory is valid and bound by an argument's lifetime
300        let payload =
301            unsafe { slice::from_raw_parts(payload.as_ptr().cast::<u8>(), size_of_val(payload)) };
302
303        Self {
304            payload,
305            external_args_buffer,
306            output_buffer,
307            output_buffer_cursor: 0,
308            output_buffer_details,
309            output_buffer_offsets_cursor: OutputBufferOffsetsCursor {
310                before_last: 0,
311                current: 0,
312            },
313            map_context,
314        }
315    }
316
317    /// Decode the next method (if any) in the payload
318    #[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    /// Decode the next method (if any) in the payload without checking size.
326    ///
327    /// # Safety
328    /// Must be used with trusted input created using `TransactionPayloadBuilder` or pre-verified
329    /// using [`Self::decode_next_method()`] earlier.
330    #[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/// Write to an external arguments pointer and move it forward.
339///
340/// # Safety
341/// `external_args` must have enough capacity for the written value, and the current offset must
342/// have the correct alignment for the type being written.
343#[inline(always)]
344unsafe fn write_external_args<T>(external_args: &mut NonNull<c_void>, value: T) {
345    // SAFETY: guaranteed by this function signature
346    unsafe {
347        external_args.cast::<T>().write(value);
348        *external_args = external_args.byte_add(size_of::<T>());
349    }
350}
351
352/// # Safety
353/// When `VERIFY == false` input must be trusted and created using `TransactionPayloadBuilder` or
354/// pre-verified using `VERIFY == true` earlier.
355struct TransactionPayloadDecoderInternal<'tmp, 'decoder, const VERIFY: bool>(
356    &'tmp mut TransactionPayloadDecoder<'decoder>,
357);
358
359impl<'decoder, const VERIFY: bool> Deref
360    for TransactionPayloadDecoderInternal<'_, '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<const VERIFY: bool> DerefMut for TransactionPayloadDecoderInternal<'_, '_, VERIFY> {
372    #[inline(always)]
373    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
374    fn deref_mut(&mut self) -> &mut Self::Target {
375        self.0
376    }
377}
378
379impl<'tmp, 'decoder, const VERIFY: bool> TransactionPayloadDecoderInternal<'tmp, 'decoder, VERIFY> {
380    #[inline(always)]
381    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
382    fn decode_next_method(
383        mut self,
384    ) -> Result<Option<PreparedMethod<'tmp>>, TransactionPayloadDecoderError> {
385        if self.payload.len() <= usize::from(MAX_ALIGNMENT) {
386            return Ok(None);
387        }
388
389        self.update_output_buffer_details();
390
391        let contract = self.get_trivial_type::<Address>()?;
392        let method_fingerprint = self.get_trivial_type::<MethodFingerprint>()?;
393        let method_context = (self.map_context)(self.read_method_context()?);
394
395        let mut transaction_slots_inputs =
396            [MaybeUninit::<u8>::uninit(); MAX_TOTAL_METHOD_ARGS as usize];
397
398        let num_slot_arguments = self.read_u8()?;
399        for transaction_slot in transaction_slots_inputs
400            .iter_mut()
401            .take(usize::from(num_slot_arguments))
402        {
403            transaction_slot.write(self.read_u8()?);
404        }
405
406        let num_input_arguments = self.read_u8()?;
407        for transaction_input in transaction_slots_inputs
408            .iter_mut()
409            .skip(usize::from(num_slot_arguments))
410            .take(usize::from(num_input_arguments))
411        {
412            transaction_input.write(self.read_u8()?);
413        }
414
415        let num_output_arguments = self.read_u8()?;
416
417        // This can be off by 1 due to `self` not included in `ExternalArgs`, but it is good enough
418        // for this context
419        let number_of_arguments = u16::from(num_slot_arguments)
420            + u16::from(num_input_arguments)
421            + u16::from(num_output_arguments);
422        if VERIFY {
423            if number_of_arguments > u16::from(MAX_TOTAL_METHOD_ARGS) {
424                return Err(TransactionPayloadDecoderError::TooManyArguments(
425                    u8::try_from(number_of_arguments).unwrap_or(u8::MAX),
426                ));
427            }
428        } else {
429            // SAFETY: The unverified version, see struct description
430            unsafe {
431                hint::assert_unchecked(number_of_arguments <= u16::from(MAX_TOTAL_METHOD_ARGS));
432            }
433        }
434
435        let (transaction_slots, transaction_inputs) = transaction_slots_inputs
436            .split_at_checked(usize::from(num_slot_arguments))
437            .expect("Total number of arguments was checked to fit above; qed");
438        let transaction_inputs = transaction_inputs
439            .get(..usize::from(num_input_arguments))
440            .expect("Total number of arguments was checked to fit above; qed");
441        // SAFETY: Just initialized elements above
442        let (transaction_slots, transaction_inputs) = unsafe {
443            (
444                transaction_slots.assume_init_ref(),
445                transaction_inputs.assume_init_ref(),
446            )
447        };
448
449        let external_args = NonNull::new(self.external_args_buffer.as_mut_ptr())
450            .expect("Not null; qed")
451            .cast::<c_void>();
452        {
453            let external_args_cursor = &mut external_args.clone();
454
455            for &transaction_slot in transaction_slots {
456                let address = match TransactionSlot::from_u8(transaction_slot).slot_type() {
457                    TransactionSlotType::Address => self.get_trivial_type::<Address>()?,
458                    TransactionSlotType::OutputIndex { output_index } => {
459                        let (bytes, size) = self.get_from_output_buffer(output_index)?;
460
461                        if VERIFY && size != Address::SIZE {
462                            return Err(
463                                TransactionPayloadDecoderError::InvalidSlotOutputIndexSize {
464                                    output_index,
465                                    size,
466                                },
467                            );
468                        }
469
470                        // SAFETY: All bytes are valid as long as alignment and size match
471                        let maybe_address = unsafe { Address::from_bytes(bytes) };
472
473                        if VERIFY {
474                            let Some(address) = maybe_address else {
475                                let error =
476                                    TransactionPayloadDecoderError::InvalidSlotOutputIndexAlign {
477                                        output_index,
478                                        alignment: align_of_val(bytes) as u32,
479                                    };
480                                return Err(error);
481                            };
482
483                            address
484                        } else {
485                            // SAFETY: The unverified version, see struct description
486                            unsafe { maybe_address.unwrap_unchecked() }
487                        }
488                    }
489                };
490
491                // SAFETY: Size of `self.external_args_buffer` is statically sized for worst-case,
492                // address is correctly aligned
493                unsafe {
494                    write_external_args(external_args_cursor, ptr::from_ref(address));
495                }
496            }
497
498            for &transaction_input in transaction_inputs {
499                let (bytes, size) = match TransactionInput::from_u8(transaction_input).input_type()
500                {
501                    TransactionInputType::Value { alignment_power } => {
502                        let alignment = Self::alignment_from_power(alignment_power)?;
503
504                        let size = *self.get_trivial_type::<u32>()?;
505                        let bytes = self.get_bytes(size, alignment)?;
506
507                        (bytes, size)
508                    }
509                    TransactionInputType::OutputIndex { output_index } => {
510                        self.get_from_output_buffer(output_index)?
511                    }
512                };
513
514                // SAFETY: Size of `self.external_args_buffer` is statically sized for worst-case,
515                // buffer is correctly aligned
516                unsafe {
517                    write_external_args(
518                        external_args_cursor,
519                        FfiDataSizeCapacityRo {
520                            data_ptr: NonNull::from_ref(bytes).as_non_null_ptr(),
521                            size,
522                            capacity: size,
523                        },
524                    );
525                }
526            }
527
528            for _ in 0..num_output_arguments {
529                let recommended_capacity = *self.get_trivial_type::<u32>()?;
530                let alignment_power = *self.get_trivial_type::<u8>()?;
531                let alignment = Self::alignment_from_power(alignment_power)?;
532
533                // SAFETY: `external_args_cursor` is created from `external_args` and is within the
534                // same allocation
535                let external_args_size_offset = unsafe {
536                    external_args_cursor
537                        .byte_add(offset_of!(FfiDataSizeCapacityRw, size))
538                        .byte_offset_from_unsigned(external_args)
539                };
540
541                let data = self.allocate_output_buffer(
542                    recommended_capacity,
543                    alignment,
544                    external_args_size_offset as u32,
545                )?;
546
547                // SAFETY: Size of `self.external_args_buffer` is statically sized for worst-case,
548                // buffer is correctly aligned
549                unsafe {
550                    write_external_args(
551                        external_args_cursor,
552                        FfiDataSizeCapacityRw {
553                            data_ptr: data.as_ptr(),
554                            size: 0,
555                            capacity: recommended_capacity,
556                        },
557                    );
558                }
559            }
560        }
561
562        let decoder = self.0;
563        Ok(Some(PreparedMethod::new_untyped(
564            *contract,
565            *method_fingerprint,
566            &mut *decoder.external_args_buffer,
567            method_context,
568        )))
569    }
570
571    /// Get alignment from its power of two stored in the payload, which must not exceed
572    /// [`MAX_ALIGNMENT`]
573    #[inline(always)]
574    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
575    fn alignment_from_power(
576        alignment_power: u8,
577    ) -> Result<NonZeroUsize, TransactionPayloadDecoderError> {
578        if VERIFY {
579            if u32::from(alignment_power) > MAX_ALIGNMENT.ilog2() {
580                return Err(TransactionPayloadDecoderError::AlignmentPowerTooLarge(
581                    alignment_power,
582                ));
583            }
584        } else {
585            // SAFETY: The unverified version, see struct description
586            unsafe {
587                hint::assert_unchecked(u32::from(alignment_power) <= MAX_ALIGNMENT.ilog2());
588            }
589        }
590
591        Ok(NonZeroUsize::new(1 << alignment_power).expect("Power of two is not zero; qed"))
592    }
593
594    /// Get a reference to a [`TrivialType`] value inside the payload
595    #[inline(always)]
596    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
597    fn get_trivial_type<T>(&mut self) -> Result<&'decoder T, TransactionPayloadDecoderError>
598    where
599        T: TrivialType,
600    {
601        self.ensure_alignment(NonZeroUsize::new(align_of::<T>()).expect("Not zero; qed"))?;
602
603        let bytes;
604        if VERIFY {
605            (bytes, self.payload) = self
606                .payload
607                .split_at_checked(size_of::<T>())
608                .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
609        } else {
610            // SAFETY: The unverified version, see struct description
611            (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size_of::<T>()) };
612        }
613
614        // SAFETY: Correctly aligned bytes of the correct size
615        let value_ref = unsafe { bytes.as_ptr().cast::<T>().as_ref().expect("Not null; qed") };
616
617        Ok(value_ref)
618    }
619
620    /// Get a reference to opaque bytes inside the payload
621    #[inline(always)]
622    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
623    fn get_bytes(
624        &mut self,
625        size: u32,
626        alignment: NonZeroUsize,
627    ) -> Result<&'decoder [u8], TransactionPayloadDecoderError> {
628        self.ensure_alignment(alignment)?;
629
630        let bytes;
631        if VERIFY {
632            (bytes, self.payload) = self
633                .payload
634                .split_at_checked(size as usize)
635                .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
636        } else {
637            // SAFETY: The unverified version, see struct description
638            (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size as usize) };
639        }
640
641        Ok(bytes)
642    }
643
644    /// Read [`TransactionMethodContext`] from its byte, which in untrusted input might not be a
645    /// valid discriminant
646    #[inline(always)]
647    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
648    fn read_method_context(
649        &mut self,
650    ) -> Result<TransactionMethodContext, TransactionPayloadDecoderError> {
651        let value = self.read_u8()?;
652
653        if VERIFY {
654            TransactionMethodContext::try_from(value)
655                .map_err(|()| TransactionPayloadDecoderError::InvalidMethodContext(value))
656        } else {
657            // SAFETY: The unverified version, see struct description
658            Ok(unsafe { TransactionMethodContext::try_from(value).unwrap_unchecked() })
659        }
660    }
661
662    #[inline(always)]
663    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
664    fn read_u8(&mut self) -> Result<u8, TransactionPayloadDecoderError> {
665        let value;
666        if VERIFY {
667            (value, self.payload) = self
668                .payload
669                .split_first()
670                .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
671        } else {
672            // SAFETY: The unverified version, see struct description
673            (value, self.payload) = unsafe { self.payload.split_first().unwrap_unchecked() };
674        }
675
676        Ok(*value)
677    }
678
679    #[inline(always)]
680    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
681    fn ensure_alignment(
682        &mut self,
683        alignment: NonZeroUsize,
684    ) -> Result<(), TransactionPayloadDecoderError> {
685        let alignment = alignment.get();
686        debug_assert!(alignment <= usize::from(MAX_ALIGNMENT));
687
688        // Optimized version of the following that expects `alignment` to be a power of 2:
689        // let unaligned_by = self.payload.as_ptr().addr() % alignment;
690        let unaligned_by = self.payload.as_ptr().addr() & (alignment - 1);
691        if unaligned_by > 0 {
692            // SAFETY: Subtracted value is always smaller than alignment
693            let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
694            if VERIFY {
695                self.payload = self
696                    .payload
697                    .split_off(padding_bytes..)
698                    .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
699            } else {
700                // SAFETY: The unverified version, see struct description
701                self.payload = unsafe { self.payload.get_unchecked(padding_bytes..) };
702            }
703        }
704        Ok(())
705    }
706
707    /// Returns a tuple of `(size_ptr, output_ptr)` of a newly allocated output buffer
708    #[inline(always)]
709    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
710    fn allocate_output_buffer(
711        &mut self,
712        capacity: u32,
713        output_alignment: NonZeroUsize,
714        external_args_size_offset: u32,
715    ) -> Result<NonNull<u8>, TransactionPayloadDecoderError> {
716        if VERIFY && self.output_buffer_details.len() == self.output_buffer_offsets_cursor.current {
717            return Err(TransactionPayloadDecoderError::OutputBufferOffsetsTooSmall);
718        }
719
720        let (output_offset, output_ptr) = self
721            .allocate_output_buffer_ptr(output_alignment, capacity as usize)
722            .ok_or(TransactionPayloadDecoderError::OutputBufferTooSmall)?;
723
724        // SAFETY: Checked above that `output_buffer_details` is not full
725        let output_buffer_details = unsafe {
726            // Borrow checker doesn't understand that these variables are disjoint
727            let output_buffer_offsets_cursor = self.output_buffer_offsets_cursor.current;
728            self.output_buffer_details
729                .get_unchecked_mut(output_buffer_offsets_cursor)
730        };
731        output_buffer_details.write(OutputBufferDetails {
732            output_offset: output_offset as u32,
733            size_or_external_args_offset: external_args_size_offset,
734        });
735        self.output_buffer_offsets_cursor.current += 1;
736
737        Ok(output_ptr)
738    }
739
740    /// Returns `None` if output buffer is not large enough
741    #[inline(always)]
742    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
743    fn allocate_output_buffer_ptr<T>(
744        &mut self,
745        alignment: NonZeroUsize,
746        size: usize,
747    ) -> Option<(usize, NonNull<T>)> {
748        let alignment = alignment.get();
749        debug_assert!(alignment <= usize::from(MAX_ALIGNMENT));
750
751        // Optimized version of the following that expects `alignment` to be a power of 2:
752        // let unaligned_by = self.output_buffer_cursor % alignment;
753        let unaligned_by = self.output_buffer_cursor & (alignment - 1);
754        // SAFETY: Subtracted value is always smaller than alignment
755        let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
756
757        let (offset, new_output_buffer_cursor) = if VERIFY {
758            let offset = self.output_buffer_cursor.checked_add(padding_bytes)?;
759            let new_output_buffer_cursor = offset.checked_add(size)?;
760
761            if new_output_buffer_cursor > size_of_val(self.output_buffer) {
762                return None;
763            }
764
765            (offset, new_output_buffer_cursor)
766        } else {
767            // SAFETY: The unverified version, see struct description
768            unsafe {
769                let offset = self.output_buffer_cursor.unchecked_add(padding_bytes);
770                (offset, offset.unchecked_add(size))
771            }
772        };
773
774        // SAFETY: Bounds and alignment checks are done above
775        let buffer_ptr = unsafe {
776            NonNull::from_mut(&mut *self.output_buffer)
777                .cast::<T>()
778                .byte_add(offset)
779        };
780        self.output_buffer_cursor = new_output_buffer_cursor;
781
782        Some((offset, buffer_ptr))
783    }
784
785    /// Update all [`OutputBufferDetails::size_or_external_args_offset`] to store sizes rather than
786    /// offsets into `external_args_buffer` and advances [`OutputBufferOffsetsCursor::before_last`]
787    #[inline(always)]
788    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
789    fn update_output_buffer_details(&mut self) {
790        #[expect(
791            clippy::rest_pattern_accessible_field,
792            reason = "Do not need other fields"
793        )]
794        let TransactionPayloadDecoder {
795            external_args_buffer,
796            output_buffer_details,
797            output_buffer_offsets_cursor,
798            ..
799        } = &mut **self;
800
801        // SAFETY: Elements in the selected range were initialized
802        for output_buffer_details in unsafe {
803            output_buffer_details
804                .get_unchecked_mut(
805                    output_buffer_offsets_cursor.before_last..output_buffer_offsets_cursor.current,
806                )
807                .assume_init_mut()
808        } {
809            // SAFETY: Protected invariant in `decode_next_method`
810            output_buffer_details.size_or_external_args_offset = unsafe {
811                external_args_buffer
812                    .as_ptr()
813                    .cast::<u32>()
814                    .byte_add(output_buffer_details.size_or_external_args_offset as usize)
815                    .read()
816            };
817        }
818
819        output_buffer_offsets_cursor.before_last = output_buffer_offsets_cursor.current;
820    }
821
822    #[inline(always)]
823    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
824    fn get_from_output_buffer(
825        &self,
826        output_index: u8,
827    ) -> Result<(&[u8], u32), TransactionPayloadDecoderError> {
828        let OutputBufferDetails {
829            output_offset,
830            size_or_external_args_offset: size,
831        } = if VERIFY {
832            if usize::from(output_index) < self.output_buffer_offsets_cursor.current {
833                // SAFETY: Checked that index is initialized
834                unsafe {
835                    self.output_buffer_details
836                        .get_unchecked(usize::from(output_index))
837                        .assume_init()
838                }
839            } else {
840                return Err(TransactionPayloadDecoderError::OutputIndexNotFound(
841                    output_index,
842                ));
843            }
844        } else {
845            // SAFETY: The unverified version, see struct description
846            unsafe {
847                self.output_buffer_details
848                    .get_unchecked(usize::from(output_index))
849                    .assume_init()
850            }
851        };
852
853        // SAFETY: Offset was created as the result of writing value at the correct
854        // offset into `output_buffer_details` earlier
855        let bytes = unsafe {
856            let bytes_ptr = self
857                .output_buffer
858                .as_ptr()
859                .cast::<u8>()
860                .add(output_offset as usize);
861
862            slice::from_raw_parts(bytes_ptr, size as usize)
863        };
864
865        Ok((bytes, size))
866    }
867}