ab_executor_native/context/
ffi_call.rs

1use crate::context::{MethodDetails, NativeExecutorContext};
2use ab_contracts_common::env::{Env, EnvState, ExecutorContext};
3use ab_contracts_common::metadata::decode::{
4    ArgumentKind, MethodKind, MethodMetadataDecoder, MethodMetadataItem, MethodsContainerKind,
5};
6use ab_contracts_common::{Address, ContractError, MAX_TOTAL_METHOD_ARGS};
7use ab_executor_slots::{NestedSlots, SlotIndex, SlotKey};
8use ab_system_contract_address_allocator::AddressAllocator;
9use std::cell::UnsafeCell;
10use std::ffi::c_void;
11use std::mem::MaybeUninit;
12use std::ptr::NonNull;
13use std::{mem, ptr, slice};
14use tracing::{debug, error, warn};
15
16/// Read a pointer of type `$ty` from `$external` and advance `$external` past it
17macro_rules! read_ptr {
18    ($external:ident as $ty:ty) => {{
19        let ptr = NonNull::<NonNull<c_void>>::cast::<$ty>($external).read();
20
21        $external = $external.offset(1);
22
23        ptr
24    }};
25}
26
27/// Write a `$src` pointer of type `$ty` into `$internal`, advance `$internal` past written pointer
28/// and return pointer to the written location
29macro_rules! write_ptr {
30    ($src:expr => $internal:ident as $ty:ty) => {{
31        let ptr = NonNull::<*mut c_void>::cast::<$ty>($internal);
32        ptr.write($src);
33
34        $internal = $internal.offset(1);
35
36        ptr
37    }};
38}
39
40/// Read a pointer from `$external`, write into `$internal`, advance both `$external` and
41/// `$internal` by pointer size and return read pointer
42macro_rules! copy_ptr {
43    ($external:ident => $internal:ident as $ty:ty) => {{
44        let ptr;
45        {
46            let src = NonNull::<NonNull<c_void>>::cast::<$ty>($external);
47            let dst = NonNull::<*mut c_void>::cast::<$ty>($internal);
48            ptr = src.read();
49            dst.write(ptr);
50        }
51
52        $external = $external.offset(1);
53        $internal = $internal.offset(1);
54
55        ptr
56    }};
57}
58
59#[derive(Copy, Clone)]
60struct DelayedProcessingSlotReadOnly {
61    size: u32,
62}
63
64#[derive(Copy, Clone)]
65struct DelayedProcessingSlotReadWrite {
66    /// Pointer to `InternalArgs` where guest will store a pointer to potentially updated slot
67    /// contents
68    data_ptr: NonNull<*mut u8>,
69    /// Pointer to `InternalArgs` where guest will store potentially updated slot size,
70    /// corresponds to `data_ptr`, filled during the second pass through the arguments
71    /// (while reading `ExternalArgs`)
72    size: u32,
73    capacity: u32,
74    slot_index: SlotIndex,
75    /// Whether slot written must be non-empty.
76    ///
77    /// This is the case for state in `#[init]` methods.
78    must_be_not_empty: bool,
79}
80
81/// Stores details about arguments that need to be processed after FFI call.
82///
83/// It is also more efficient to store length and capacities compactly next to each other in memory.
84#[derive(Copy, Clone)]
85enum DelayedProcessing {
86    SlotReadOnly(DelayedProcessingSlotReadOnly),
87    SlotReadWrite(DelayedProcessingSlotReadWrite),
88}
89
90struct DelayedProcessingCollection<'a> {
91    buffer: &'a [UnsafeCell<MaybeUninit<DelayedProcessing>>; MAX_TOTAL_METHOD_ARGS as usize],
92    cursor: usize,
93}
94
95impl<'a> DelayedProcessingCollection<'a> {
96    #[inline(always)]
97    fn from_buffer(
98        buffer: &'a [UnsafeCell<MaybeUninit<DelayedProcessing>>; MAX_TOTAL_METHOD_ARGS as usize],
99    ) -> Self {
100        Self { buffer, cursor: 0 }
101    }
102
103    /// Insert new entry and get a shared reference to it, which doesn't inherit stack borrows tag.
104    ///
105    /// # Safety
106    /// Must not insert more entries than [`MAX_TOTAL_METHODS_ARGS`].
107    #[inline(always)]
108    unsafe fn insert_ro(
109        &mut self,
110        entry: DelayedProcessingSlotReadOnly,
111    ) -> &DelayedProcessingSlotReadOnly {
112        // SAFETY: Method contract is that the entry must exist, cursor is always moving forward and
113        // doesn't reference the same entry more than once here
114        let inserted = unsafe {
115            self.buffer
116                .get_unchecked(self.cursor)
117                .get()
118                .as_mut_unchecked()
119                .write(DelayedProcessing::SlotReadOnly(entry))
120        };
121        self.cursor += 1;
122        let DelayedProcessing::SlotReadOnly(entry) = inserted else {
123            unreachable!("Just inserted `DelayedProcessing::SlotReadOnly` entry; qed");
124        };
125        entry
126    }
127
128    /// Insert new entry and get a mutable reference to it, which doesn't inherit stack borrows tag.
129    ///
130    /// # Safety
131    /// Must not insert more entries than [`MAX_TOTAL_METHODS_ARGS`].
132    #[inline(always)]
133    unsafe fn insert_rw(
134        &mut self,
135        entry: DelayedProcessingSlotReadWrite,
136    ) -> &mut DelayedProcessingSlotReadWrite {
137        // SAFETY: Method contract is that the entry must exist, cursor is always moving forward and
138        // doesn't reference the same entry more than once here
139        let inserted = unsafe {
140            self.buffer
141                .get_unchecked(self.cursor)
142                .get()
143                .as_mut_unchecked()
144                .write(DelayedProcessing::SlotReadWrite(entry))
145        };
146        self.cursor += 1;
147        let DelayedProcessing::SlotReadWrite(entry) = inserted else {
148            unreachable!("Just inserted `DelayedProcessing::SlotReadWrite` entry; qed");
149        };
150        entry
151    }
152
153    fn iter(&self) -> impl Iterator<Item = &DelayedProcessing> {
154        self.buffer.iter().take(self.cursor).map(|entry| {
155            // SAFETY: All values were initialized
156            unsafe { entry.as_ref_unchecked().assume_init_ref() }
157        })
158    }
159}
160
161/// Special container that allows aliasing of `Env` stored inside it and holds onto slots
162enum MaybeEnv<Env, Slots> {
163    None(Slots),
164    ReadOnly(*mut UnsafeCell<Env>),
165    ReadWrite(*mut UnsafeCell<Env>),
166}
167
168impl<Env, Slots> Drop for MaybeEnv<Env, Slots> {
169    #[inline(always)]
170    fn drop(&mut self) {
171        match self {
172            MaybeEnv::None(_) => {}
173            &mut MaybeEnv::ReadOnly(env) | &mut MaybeEnv::ReadWrite(env) => {
174                // SAFETY: As `self` is being dropped, we can safely assume any aliasing has ended
175                // and drop the original `Box`
176                let _ = unsafe { Box::from_raw(env) };
177            }
178        }
179    }
180}
181
182impl<'env> MaybeEnv<MaybeUninit<Env<'env>>, ()> {
183    /// Insert a new value and get a pointer to it, value must be initialized later with
184    /// [`Self::initialize()`]
185    #[inline(always)]
186    fn insert_ro(&mut self) -> *const MaybeUninit<Env<'env>> {
187        let env = Box::into_raw(Box::new(UnsafeCell::new(MaybeUninit::uninit())));
188        let env_ptr = {
189            // SAFETY: Just initialized, no other references to the value
190            let env_ref = unsafe { env.as_ref_unchecked() };
191            env_ref.get().cast_const()
192        };
193        *self = Self::ReadOnly(env);
194        env_ptr
195    }
196
197    /// Insert a new value and get a pointer to it, value must be initialized later with
198    /// [`Self::initialize()`]
199    #[inline(always)]
200    fn insert_rw(&mut self) -> *mut MaybeUninit<Env<'env>> {
201        let env = Box::into_raw(Box::new(UnsafeCell::new(MaybeUninit::uninit())));
202        let env_ptr = {
203            // SAFETY: Just initialized, no other references to the value
204            let env_ref = unsafe { env.as_ref_unchecked() };
205            env_ref.get()
206        };
207        *self = Self::ReadWrite(env);
208        env_ptr
209    }
210
211    /// # Safety
212    /// Nothing must have a live reference to `self` or its internals
213    #[inline(always)]
214    unsafe fn initialize<'slots, CreateNestedContext>(
215        self,
216        slots: NestedSlots<'slots>,
217        env_state: EnvState,
218        create_nested_context: CreateNestedContext,
219    ) -> MaybeEnv<Env<'env>, NestedSlots<'env>>
220    where
221        CreateNestedContext:
222            FnOnce(NestedSlots<'slots>, bool) -> &'env mut NativeExecutorContext<'slots>,
223        'slots: 'env,
224    {
225        match self {
226            Self::None(()) => MaybeEnv::None(slots),
227            Self::ReadOnly(env_ro) => {
228                let env =
229                    Env::with_executor_context(env_state, create_nested_context(slots, false));
230                {
231                    // SAFETY: Nothing is accessing `env_ro` right now as per function signature,
232                    // and it is guaranteed to be initialized with `Self::insert_ro()` above
233                    let env_ro = unsafe { env_ro.as_mut_unchecked() };
234                    env_ro.get_mut().write(env);
235                }
236                // Very explicit cast to the initialized value since it was just written to
237                let env_ro = env_ro.cast::<UnsafeCell<Env<'env>>>();
238
239                // Prevent destructor from running and de-allocating `Env`
240                mem::forget(self);
241
242                MaybeEnv::ReadOnly(env_ro)
243            }
244            Self::ReadWrite(env_rw) => {
245                let env = Env::with_executor_context(env_state, create_nested_context(slots, true));
246                {
247                    // SAFETY: Nothing is accessing `env_rw` right now as per function signature,
248                    // and it is guaranteed to be initialized with `Self::insert_rw()` above
249                    let env_rw = unsafe { env_rw.as_mut_unchecked() };
250                    env_rw.get_mut().write(env);
251                }
252                // Very explicit cast to the initialized value since it was just written to
253                let env_rw = env_rw.cast::<UnsafeCell<Env<'env>>>();
254
255                // Prevent destructor from running and de-allocating `Env`
256                mem::forget(self);
257
258                MaybeEnv::ReadWrite(env_rw)
259            }
260        }
261    }
262}
263
264impl<'env> MaybeEnv<Env<'env>, NestedSlots<'env>> {
265    /// # Safety
266    /// Nothing must have a live reference to `self` or its internals
267    #[inline(always)]
268    unsafe fn get_slots_mut<'tmp>(&'tmp mut self) -> &'tmp mut NestedSlots<'env>
269    where
270        'env: 'tmp,
271    {
272        let env = match self {
273            MaybeEnv::None(slots) => {
274                return slots;
275            }
276            MaybeEnv::ReadOnly(env) | MaybeEnv::ReadWrite(env) => env,
277        };
278        // SAFETY: Nothing is accessing `env` right now as per function signature
279        let env = unsafe { env.as_mut_unchecked() };
280        let env = env.get_mut();
281        // SAFETY: this is the correct original type and nothing else is referencing it right now
282        let context = unsafe {
283            &mut *ptr::from_mut::<dyn ExecutorContext + 'tmp>(env.get_mut_executor_context())
284                .cast::<NativeExecutorContext<'env>>()
285        };
286        context.slots.get_mut()
287    }
288}
289
290#[inline(always)]
291#[expect(clippy::too_many_arguments, reason = "Internal API")]
292pub(super) fn make_ffi_call<'slots, 'external_args, CreateNestedContext>(
293    allow_env_mutation: bool,
294    is_allocate_new_address_method: bool,
295    parent_slots: &'slots mut NestedSlots<'slots>,
296    contract: Address,
297    method_details: MethodDetails,
298    external_args: &'external_args mut NonNull<NonNull<c_void>>,
299    env_state: EnvState,
300    create_nested_context: CreateNestedContext,
301) -> Result<(), ContractError>
302where
303    CreateNestedContext: FnOnce(NestedSlots<'slots>, bool) -> NativeExecutorContext<'slots>,
304{
305    // Allocate a buffer that will contain incrementally built `InternalArgs` that method expects,
306    // according to its metadata.
307    // `* 4` is due the worst case being to have a slot with 4 pointers: address + data + size +
308    // capacity.
309    let mut internal_args =
310        UnsafeCell::new([MaybeUninit::<*mut c_void>::uninit(); MAX_TOTAL_METHOD_ARGS as usize * 4]);
311    // Delayed processing of sizes as capacities since knowing them requires processing all
312    // arguments first.
313    //
314    // NOTE: It is important that this is never reallocated as it will invalidate all pointers to
315    // elements of this array!
316    let delayed_processing_buffer = [
317        UnsafeCell::new(MaybeUninit::uninit()),
318        UnsafeCell::new(MaybeUninit::uninit()),
319        UnsafeCell::new(MaybeUninit::uninit()),
320        UnsafeCell::new(MaybeUninit::uninit()),
321        UnsafeCell::new(MaybeUninit::uninit()),
322        UnsafeCell::new(MaybeUninit::uninit()),
323        UnsafeCell::new(MaybeUninit::uninit()),
324        UnsafeCell::new(MaybeUninit::uninit()),
325    ];
326
327    // Having large-ish `internal_args` and delayed_processing_buffer` in a separate stack frame
328    // results in a better data layout for performance
329    make_ffi_call_internal(
330        allow_env_mutation,
331        is_allocate_new_address_method,
332        parent_slots,
333        contract,
334        method_details,
335        external_args,
336        env_state,
337        create_nested_context,
338        &delayed_processing_buffer,
339        &mut internal_args,
340    )
341}
342
343#[inline(always)]
344#[expect(clippy::too_many_arguments, reason = "Internal API")]
345fn make_ffi_call_internal<'slots, 'external_args, CreateNestedContext>(
346    allow_env_mutation: bool,
347    is_allocate_new_address_method: bool,
348    parent_slots: &'slots mut NestedSlots<'slots>,
349    contract: Address,
350    method_details: MethodDetails,
351    external_args: &'external_args mut NonNull<NonNull<c_void>>,
352    env_state: EnvState,
353    create_nested_context: CreateNestedContext,
354    delayed_processing_buffer: &[UnsafeCell<MaybeUninit<DelayedProcessing>>;
355         MAX_TOTAL_METHOD_ARGS as usize],
356    internal_args: &mut UnsafeCell<[MaybeUninit<*mut c_void>; MAX_TOTAL_METHOD_ARGS as usize * 4]>,
357) -> Result<(), ContractError>
358where
359    CreateNestedContext: FnOnce(NestedSlots<'slots>, bool) -> NativeExecutorContext<'slots>,
360{
361    let MethodDetails {
362        recommended_state_capacity,
363        recommended_slot_capacity,
364        recommended_tmp_capacity,
365        mut method_metadata,
366        ffi_fn,
367    } = method_details;
368
369    let method_metadata_decoder =
370        MethodMetadataDecoder::new(&mut method_metadata, MethodsContainerKind::Unknown);
371    let (mut arguments_metadata_decoder, method_metadata_item) =
372        match method_metadata_decoder.decode_next() {
373            Ok(result) => result,
374            Err(error) => {
375                error!(%error, "Method metadata decoding error");
376                return Err(ContractError::InternalError);
377            }
378        };
379    let MethodMetadataItem {
380        method_kind,
381        num_arguments,
382        ..
383    } = method_metadata_item;
384
385    let number_of_arguments =
386        usize::from(num_arguments) + if method_kind.has_self() { 1 } else { 0 };
387
388    if number_of_arguments > usize::from(MAX_TOTAL_METHOD_ARGS) {
389        debug!(%number_of_arguments, "Too many arguments");
390        return Err(ContractError::BadInput);
391    }
392
393    let internal_args = NonNull::new(internal_args.get().cast::<MaybeUninit<*mut c_void>>())
394        .expect("Taken from non-null instance; qed");
395    // This pointer will be moving as the data structure is being constructed, while `internal_args`
396    // will keep pointing to the beginning
397    let mut internal_args_cursor = internal_args.cast::<*mut c_void>();
398    // This pointer will be moving as the data structure is being read, while `external_args` will
399    // keep pointing to the beginning
400    let mut external_args_cursor = *external_args;
401    let mut delayed_processing =
402        DelayedProcessingCollection::from_buffer(delayed_processing_buffer);
403
404    // `view_only == true` when only `#[view]` method is allowed
405    let (view_only, mut slots) = match method_kind {
406        MethodKind::Init
407        | MethodKind::UpdateStateless
408        | MethodKind::UpdateStatefulRo
409        | MethodKind::UpdateStatefulRw => {
410            if !allow_env_mutation {
411                warn!(allow_env_mutation, "Only `#[view]` methods are allowed");
412                return Err(ContractError::Forbidden);
413            }
414
415            let Some(slots) = parent_slots.new_nested_rw() else {
416                error!("Unexpected creation of non-read-only slots from read-only slots");
417                return Err(ContractError::InternalError);
418            };
419
420            (false, slots)
421        }
422        MethodKind::ViewStateless | MethodKind::ViewStateful => {
423            let slots = parent_slots.new_nested_ro();
424            (true, slots)
425        }
426    };
427
428    let mut maybe_env = MaybeEnv::None(());
429
430    // Handle `&self` and `&mut self`
431    match method_kind {
432        MethodKind::Init | MethodKind::UpdateStateless | MethodKind::ViewStateless => {
433            // No state handling is needed
434        }
435        MethodKind::UpdateStatefulRo | MethodKind::ViewStateful => {
436            let state_bytes = slots
437                .use_ro(SlotKey {
438                    owner: contract,
439                    contract: Address::SYSTEM_STATE,
440                })
441                .ok_or(ContractError::Forbidden)?;
442
443            if state_bytes.is_empty() {
444                warn!("Contract does not have state yet, can't call stateful method before init");
445                return Err(ContractError::Forbidden);
446            }
447
448            // SAFETY: Number of arguments checked above
449            let result = unsafe {
450                delayed_processing.insert_ro(DelayedProcessingSlotReadOnly {
451                    size: state_bytes.len(),
452                })
453            };
454
455            // SAFETY: `internal_args_cursor`'s memory is allocated with sufficient size above and
456            // aligned correctly
457            unsafe {
458                write_ptr!(state_bytes.as_ptr() => internal_args_cursor as *const u8);
459                write_ptr!(&result.size => internal_args_cursor as *const u32);
460            }
461        }
462        MethodKind::UpdateStatefulRw => {
463            if view_only {
464                warn!("Only `#[view]` methods are allowed");
465                return Err(ContractError::Forbidden);
466            }
467
468            let slot_key = SlotKey {
469                owner: contract,
470                contract: Address::SYSTEM_STATE,
471            };
472            let (slot_index, state_bytes) = slots
473                .use_rw(slot_key, recommended_state_capacity)
474                .ok_or(ContractError::Forbidden)?;
475
476            if state_bytes.is_empty() {
477                warn!("Contract does not have state yet, can't call stateful method before init");
478                return Err(ContractError::Forbidden);
479            }
480
481            let entry = DelayedProcessingSlotReadWrite {
482                // Is updated below
483                data_ptr: NonNull::dangling(),
484                size: state_bytes.len(),
485                capacity: state_bytes.capacity(),
486                slot_index,
487                must_be_not_empty: false,
488            };
489            // SAFETY: Number of arguments checked above
490            let result = unsafe { delayed_processing.insert_rw(entry) };
491
492            // SAFETY: `internal_args_cursor`'s memory is allocated with sufficient size above and
493            // aligned correctly
494            unsafe {
495                result.data_ptr =
496                    write_ptr!(state_bytes.as_mut_ptr() => internal_args_cursor as *mut u8);
497                write_ptr!(&mut result.size => internal_args_cursor as *mut u32);
498                write_ptr!(&result.capacity => internal_args_cursor as *const u32);
499            }
500        }
501    }
502
503    let mut new_address_ptr = None;
504
505    // Handle all other arguments one by one
506    for argument_index in 0..num_arguments {
507        let argument_kind = match arguments_metadata_decoder.decode_next() {
508            Some(Ok(item)) => item.argument_kind,
509            Some(Err(error)) => {
510                error!(%error, "Argument metadata decoding error");
511                return Err(ContractError::InternalError);
512            }
513            None => {
514                error!("Argument not found, invalid metadata");
515                return Err(ContractError::InternalError);
516            }
517        };
518
519        match argument_kind {
520            ArgumentKind::EnvRo => {
521                // Allocate and create a pointer now, the actual value will be inserted towards the
522                // end of the function
523                let env_ro = maybe_env.insert_ro().cast::<Env<'_>>();
524                // SAFETY: `internal_args_cursor`'s memory is allocated with sufficient size above
525                // and aligned correctly
526                unsafe {
527                    write_ptr!(env_ro => internal_args_cursor as *const Env<'_>);
528                }
529
530                // Size for `#[env]` is implicit and doesn't need to be added to `InternalArgs`
531            }
532            ArgumentKind::EnvRw => {
533                if view_only {
534                    return Err(ContractError::Forbidden);
535                }
536
537                // Allocate and create a pointer now, the actual value will be inserted towards the
538                // end of the function
539                let env_rw = maybe_env.insert_rw().cast::<Env<'_>>();
540
541                // SAFETY: `internal_args_cursor`'s memory is allocated with sufficient size above
542                // and aligned correctly
543                unsafe {
544                    write_ptr!(env_rw => internal_args_cursor as *mut Env<'_>);
545                }
546
547                // Size for `#[env]` is implicit and doesn't need to be added to `InternalArgs`
548            }
549            ArgumentKind::TmpRo | ArgumentKind::SlotRo => {
550                let tmp = matches!(argument_kind, ArgumentKind::TmpRo);
551
552                let (owner, contract) = if tmp {
553                    if view_only {
554                        return Err(ContractError::Forbidden);
555                    }
556
557                    // Null contact is used implicitly for `#[tmp]` since it is not possible for
558                    // this contract to write something there directly
559                    (&contract, Address::NULL)
560                } else {
561                    // SAFETY: `external_args_cursor`'s must contain a valid pointer to address,
562                    // moving right past that is safe
563                    (
564                        unsafe { &*read_ptr!(external_args_cursor as *const Address) },
565                        contract,
566                    )
567                };
568
569                let slot_key = SlotKey {
570                    owner: *owner,
571                    contract,
572                };
573                let slot_bytes = slots.use_ro(slot_key).ok_or(ContractError::Forbidden)?;
574
575                // SAFETY: Number of arguments checked above
576                let result = unsafe {
577                    delayed_processing.insert_ro(DelayedProcessingSlotReadOnly {
578                        size: slot_bytes.len(),
579                    })
580                };
581
582                // SAFETY: `internal_args_cursor`'s memory is allocated with sufficient size above
583                // and aligned correctly
584                unsafe {
585                    if !tmp {
586                        write_ptr!(owner => internal_args_cursor as *const Address);
587                    }
588                    write_ptr!(slot_bytes.as_ptr() => internal_args_cursor as *const u8);
589                    write_ptr!(&result.size => internal_args_cursor as *const u32);
590                }
591            }
592            ArgumentKind::TmpRw | ArgumentKind::SlotRw => {
593                if view_only {
594                    return Err(ContractError::Forbidden);
595                }
596
597                let tmp = matches!(argument_kind, ArgumentKind::TmpRw);
598
599                let (owner, contract, capacity) = if tmp {
600                    // Null contact is used implicitly for `#[tmp]` since it is not possible for
601                    // this contract to write something there directly
602                    (&contract, Address::NULL, recommended_tmp_capacity)
603                } else {
604                    // SAFETY: `external_args_cursor`'s must contain a valid pointer to address,
605                    // moving right past that is safe
606                    let address = unsafe { &*read_ptr!(external_args_cursor as *const Address) };
607
608                    (address, contract, recommended_slot_capacity)
609                };
610
611                let slot_key = SlotKey {
612                    owner: *owner,
613                    contract,
614                };
615                let (slot_index, slot_bytes) = slots
616                    .use_rw(slot_key, capacity)
617                    .ok_or(ContractError::Forbidden)?;
618
619                let entry = DelayedProcessingSlotReadWrite {
620                    // Is updated below
621                    data_ptr: NonNull::dangling(),
622                    size: slot_bytes.len(),
623                    capacity: slot_bytes.capacity(),
624                    slot_index,
625                    must_be_not_empty: false,
626                };
627                // SAFETY: Number of arguments checked above
628                let result = unsafe { delayed_processing.insert_rw(entry) };
629
630                // SAFETY: `internal_args_cursor`'s memory is allocated with sufficient size above
631                // and aligned correctly
632                unsafe {
633                    if !tmp {
634                        write_ptr!(owner => internal_args_cursor as *const Address);
635                    }
636                    result.data_ptr =
637                        write_ptr!(slot_bytes.as_mut_ptr() => internal_args_cursor as *mut u8);
638                    write_ptr!(&mut result.size => internal_args_cursor as *mut u32);
639                    write_ptr!(&result.capacity => internal_args_cursor as *const u32);
640                }
641            }
642            ArgumentKind::Input => {
643                // SAFETY: `external_args_cursor`'s must contain a pointers to input + size.
644                // `internal_args_cursor`'s memory is allocated with sufficient size above and
645                // aligned correctly.
646                unsafe {
647                    // Input
648                    copy_ptr!(external_args_cursor => internal_args_cursor as *const u8);
649                    // Size
650                    copy_ptr!(external_args_cursor => internal_args_cursor as *const u32);
651                }
652            }
653            ArgumentKind::Output => {
654                let last_argument = argument_index == num_arguments - 1;
655                // `#[init]` method returns state of the contract and needs to be stored accordingly
656                if matches!((method_kind, last_argument), (MethodKind::Init, true)) {
657                    if view_only {
658                        return Err(ContractError::Forbidden);
659                    }
660
661                    let slot_key = SlotKey {
662                        owner: contract,
663                        contract: Address::SYSTEM_STATE,
664                    };
665                    let (slot_index, state_bytes) = slots
666                        .use_rw(slot_key, recommended_state_capacity)
667                        .ok_or(ContractError::Forbidden)?;
668
669                    if !state_bytes.is_empty() {
670                        debug!("Can't initialize already initialized contract");
671                        return Err(ContractError::Forbidden);
672                    }
673
674                    let entry = DelayedProcessingSlotReadWrite {
675                        // Is updated below
676                        data_ptr: NonNull::dangling(),
677                        size: 0,
678                        capacity: state_bytes.capacity(),
679                        slot_index,
680                        must_be_not_empty: true,
681                    };
682                    // SAFETY: Number of arguments checked above
683                    let result = unsafe { delayed_processing.insert_rw(entry) };
684
685                    // SAFETY: `internal_args_cursor`'s memory is allocated with sufficient size
686                    // above and aligned correctly
687                    unsafe {
688                        result.data_ptr =
689                            write_ptr!(state_bytes.as_mut_ptr() => internal_args_cursor as *mut u8);
690                        write_ptr!(&mut result.size => internal_args_cursor as *mut u32);
691                        write_ptr!(&result.capacity => internal_args_cursor as *const u32);
692                    }
693                } else {
694                    // SAFETY: `external_args_cursor`'s must contain a pointers to input + size
695                    // + capacity.
696                    // `internal_args_cursor`'s memory is allocated with sufficient size above and
697                    // aligned correctly.
698                    unsafe {
699                        // Output
700                        if last_argument && is_allocate_new_address_method {
701                            let ptr = copy_ptr!(external_args_cursor => internal_args_cursor as *mut Address);
702                            new_address_ptr.replace(ptr);
703                        } else {
704                            copy_ptr!(external_args_cursor => internal_args_cursor as *mut u8);
705                        }
706                        // Size (might be a null pointer for trivial types)
707                        let size_ptr =
708                            copy_ptr!(external_args_cursor => internal_args_cursor as *mut u32);
709                        if !size_ptr.is_null() {
710                            // Override output size to be zero even if caller guest tried to put
711                            // something there
712                            size_ptr.write(0);
713                        }
714                        // Capacity
715                        copy_ptr!(external_args_cursor => internal_args_cursor as *const u32);
716                    }
717                }
718            }
719        }
720    }
721
722    let mut nested_context = None;
723    // SAFETY: No live references to `maybe_env`
724    let mut maybe_env = unsafe {
725        maybe_env.initialize(slots, env_state, |slots, allow_env_mutation| {
726            nested_context.insert(create_nested_context(slots, allow_env_mutation))
727        })
728    };
729
730    // Will only read initialized number of pointers, hence `NonNull<c_void>` even though there is
731    // likely slack capacity with uninitialized data
732    let internal_args = internal_args.cast::<NonNull<c_void>>();
733
734    // SAFETY: FFI function was generated at the same time as corresponding `Args` and must match
735    // ABI of the fingerprint or else it wouldn't compile
736    let result = Result::<(), ContractError>::from(unsafe { ffi_fn(internal_args) });
737
738    // SAFETY: No live references to `maybe_env`
739    let slots = unsafe { maybe_env.get_slots_mut() };
740
741    if let Err(error) = result {
742        slots.reset();
743
744        return Err(error);
745    }
746
747    // Catch new address allocation and add it to new contracts in slots for code and other things
748    // to become usable for it
749    if let Some(new_address_ptr) = new_address_ptr {
750        // Assert that the API has expected shape
751        let _: fn(&mut AddressAllocator, &mut Env<'_>) -> Result<Address, ContractError> =
752            AddressAllocator::allocate_address;
753        // SAFETY: Method call to address allocator succeeded, so it must have returned an address
754        let new_address = unsafe { new_address_ptr.read() };
755        if !slots.add_new_contract(new_address) {
756            warn!("Failed to add new contract returned by address allocator");
757            return Err(ContractError::InternalError);
758        }
759    }
760
761    for &entry in delayed_processing.iter() {
762        match entry {
763            DelayedProcessing::SlotReadOnly { .. } => {
764                // No processing is necessary
765            }
766            DelayedProcessing::SlotReadWrite(DelayedProcessingSlotReadWrite {
767                data_ptr,
768                size,
769                slot_index,
770                must_be_not_empty,
771                ..
772            }) => {
773                if must_be_not_empty && size == 0 {
774                    error!(
775                        %size,
776                        "Contract returned empty size where it is not allowed, likely state of \
777                        `#[init]` method"
778                    );
779                    return Err(ContractError::BadOutput);
780                }
781
782                // SAFETY: Correct pointer created earlier that is not used for anything else at the
783                // moment
784                let data_ptr = unsafe { data_ptr.as_ptr().read().cast_const() };
785                let slot_bytes = slots.access_used_rw(slot_index).expect(
786                    "Was used in `make_ffi_call` and must exist if `Slots` was not dropped \
787                    yet; qed",
788                );
789
790                // Guest created a different allocation for slot, copy bytes
791                if !ptr::eq(data_ptr, slot_bytes.as_ptr()) {
792                    if data_ptr.is_null() {
793                        error!("Contract returned `null` pointer for slot data");
794                        return Err(ContractError::BadOutput);
795                    }
796                    // SAFETY: For native execution guest behavior is assumed to be trusted and
797                    // provide a correct pointer and size
798                    let data = unsafe { slice::from_raw_parts(data_ptr, size as usize) };
799                    slot_bytes.copy_from_slice(data);
800                    continue;
801                }
802
803                if size > slot_bytes.capacity() {
804                    error!(
805                        %size,
806                        capacity = %slot_bytes.capacity(),
807                        "Contract returned invalid size for slot data in source allocation"
808                    );
809                    return Err(ContractError::BadOutput);
810                }
811                // Otherwise, set the size to what guest claims
812                //
813                // SAFETY: For native execution guest behavior is assumed to be trusted and provide
814                // the correct size
815                unsafe {
816                    slot_bytes.set_len(size);
817                }
818            }
819        }
820    }
821
822    Ok(())
823}