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
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/// 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<'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        // SAFETY: Just initialized elements above
417        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        // This can be off by 1 due to `self` not included in `ExternalArgs`, but it is good enough
430        // for this context
431        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                        // SAFETY: All bytes are valid as long as alignment and size match
462                        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                            // SAFETY: The unverified version, see struct description
477                            unsafe { maybe_address.unwrap_unchecked() }
478                        }
479                    }
480                };
481
482                // SAFETY: Size of `self.external_args_buffer` is statically sized for worst-case,
483                // address is correctly aligned
484                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                        // Optimized version of the following:
494                        // let alignment = 2usize.pow(u32::from(alignment_power));
495                        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                            // SAFETY: The unverified version, see struct description
503                            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                // SAFETY: Size of `self.external_args_buffer` is statically sized for worst-case,
520                // buffer is correctly aligned
521                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                // Optimized version of the following:
537                // let alignment = 2usize.pow(u32::from(alignment_power));
538                let alignment = if VERIFY {
539                    1_usize.checked_shl(u32::from(alignment_power)).ok_or(
540                        TransactionPayloadDecoderError::AlignmentPowerTooLarge(alignment_power),
541                    )?
542                } else {
543                    // SAFETY: The unverified version, see struct description
544                    unsafe { 1_usize.unchecked_shl(u32::from(alignment_power)) }
545                };
546
547                // SAFETY: `external_args_cursor` is created from `external_args` and is within the
548                // same allocation
549                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                // SAFETY: Size of `self.external_args_buffer` is statically sized for worst-case,
562                // buffer is correctly aligned
563                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    /// Get a reference to a [`TrivialType`] value inside the payload
586    #[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            // SAFETY: The unverified version, see struct description
602            (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size_of::<T>()) };
603        }
604
605        // SAFETY: Correctly aligned bytes of the correct size
606        let value_ref = unsafe { bytes.as_ptr().cast::<T>().as_ref().expect("Not null; qed") };
607
608        Ok(value_ref)
609    }
610
611    /// Get a reference to opaque bytes inside the payload
612    #[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            // SAFETY: The unverified version, see struct description
629            (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            // SAFETY: The unverified version, see struct description
646            (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        // Optimized version of the following that expects `alignment` to be a power of 2:
662        // let unaligned_by = self.payload.as_ptr().addr() % alignment;
663        let unaligned_by = self.payload.as_ptr().addr() & (alignment - 1);
664        if unaligned_by > 0 {
665            // SAFETY: Subtracted value is always smaller than alignment
666            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                // SAFETY: Subtracted value is always smaller than alignment
674                self.payload = unsafe { self.payload.get_unchecked(padding_bytes..) };
675            }
676        }
677        Ok(())
678    }
679
680    /// Returns a tuple of `(size_ptr, output_ptr)` of a newly allocated output buffer
681    #[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        // SAFETY: Checked above that `output_buffer_details` is not full
698        let output_buffer_details = unsafe {
699            // Borrow checker doesn't understand that these variables are disjoint
700            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    /// Returns `None` if output buffer is not large enough
714    #[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        // Optimized version of the following that expects `alignment` to be a power of 2:
725        // let unaligned_by = self.output_buffer_cursor % alignment;
726        let unaligned_by = self.output_buffer_cursor & (alignment - 1);
727        // SAFETY: Subtracted value is always smaller than alignment
728        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            // SAFETY: The unverified version, see struct description
743            unsafe {
744                self.output_buffer_cursor
745                    .unchecked_add(padding_bytes)
746                    .unchecked_add(size)
747            }
748        };
749
750        // SAFETY: Bounds and alignment checks are done above
751        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    /// Update all [`OutputBufferDetails::size_or_external_args_offset`] to store sizes rather than
765    /// offsets into `external_args_buffer` and advances [`OutputBufferOffsetsCursor::before_last`]
766    #[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        // SAFETY: Elements in the selected range were initialized
777        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            // SAFETY: Protected invariant in `decode_next_method`
785            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                // SAFETY: Checked that index is initialized
809                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            // SAFETY: The unverified version, see struct description
821            unsafe {
822                self.output_buffer_details
823                    .get_unchecked(usize::from(output_index))
824                    .assume_init()
825            }
826        };
827
828        // SAFETY: Offset was created as the result of writing value at the correct
829        // offset into `output_buffer_details` earlier
830        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}