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