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
16macro_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
27macro_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
40macro_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 data_ptr: NonNull<*mut u8>,
69 size: u32,
73 capacity: u32,
74 slot_index: SlotIndex,
75 must_be_not_empty: bool,
79}
80
81#[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 #[inline(always)]
108 unsafe fn insert_ro(
109 &mut self,
110 entry: DelayedProcessingSlotReadOnly,
111 ) -> &DelayedProcessingSlotReadOnly {
112 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 #[inline(always)]
133 unsafe fn insert_rw(
134 &mut self,
135 entry: DelayedProcessingSlotReadWrite,
136 ) -> &mut DelayedProcessingSlotReadWrite {
137 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 unsafe { entry.as_ref_unchecked().assume_init_ref() }
157 })
158 }
159}
160
161enum 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 let _ = unsafe { Box::from_raw(env) };
177 }
178 }
179 }
180}
181
182impl<'env> MaybeEnv<MaybeUninit<Env<'env>>, ()> {
183 #[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 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 #[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 let env_ref = unsafe { env.as_ref_unchecked() };
205 env_ref.get()
206 };
207 *self = Self::ReadWrite(env);
208 env_ptr
209 }
210
211 #[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 let env_ro = unsafe { env_ro.as_mut_unchecked() };
234 env_ro.get_mut().write(env);
235 }
236 let env_ro = env_ro.cast::<UnsafeCell<Env<'env>>>();
238
239 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 let env_rw = unsafe { env_rw.as_mut_unchecked() };
250 env_rw.get_mut().write(env);
251 }
252 let env_rw = env_rw.cast::<UnsafeCell<Env<'env>>>();
254
255 mem::forget(self);
257
258 MaybeEnv::ReadWrite(env_rw)
259 }
260 }
261 }
262}
263
264impl<'env> MaybeEnv<Env<'env>, NestedSlots<'env>> {
265 #[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 let env = unsafe { env.as_mut_unchecked() };
280 let env = env.get_mut();
281 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 let mut internal_args =
310 UnsafeCell::new([MaybeUninit::<*mut c_void>::uninit(); MAX_TOTAL_METHOD_ARGS as usize * 4]);
311 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 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 let mut internal_args_cursor = internal_args.cast::<*mut c_void>();
398 let mut external_args_cursor = *external_args;
401 let mut delayed_processing =
402 DelayedProcessingCollection::from_buffer(delayed_processing_buffer);
403
404 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 match method_kind {
432 MethodKind::Init | MethodKind::UpdateStateless | MethodKind::ViewStateless => {
433 }
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 let result = unsafe {
450 delayed_processing.insert_ro(DelayedProcessingSlotReadOnly {
451 size: state_bytes.len(),
452 })
453 };
454
455 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 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 let result = unsafe { delayed_processing.insert_rw(entry) };
491
492 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 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 let env_ro = maybe_env.insert_ro().cast::<Env<'_>>();
524 unsafe {
527 write_ptr!(env_ro => internal_args_cursor as *const Env<'_>);
528 }
529
530 }
532 ArgumentKind::EnvRw => {
533 if view_only {
534 return Err(ContractError::Forbidden);
535 }
536
537 let env_rw = maybe_env.insert_rw().cast::<Env<'_>>();
540
541 unsafe {
544 write_ptr!(env_rw => internal_args_cursor as *mut Env<'_>);
545 }
546
547 }
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 (&contract, Address::NULL)
560 } else {
561 (
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 let result = unsafe {
577 delayed_processing.insert_ro(DelayedProcessingSlotReadOnly {
578 size: slot_bytes.len(),
579 })
580 };
581
582 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 (&contract, Address::NULL, recommended_tmp_capacity)
603 } else {
604 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 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 let result = unsafe { delayed_processing.insert_rw(entry) };
629
630 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 unsafe {
647 copy_ptr!(external_args_cursor => internal_args_cursor as *const u8);
649 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 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 data_ptr: NonNull::dangling(),
677 size: 0,
678 capacity: state_bytes.capacity(),
679 slot_index,
680 must_be_not_empty: true,
681 };
682 let result = unsafe { delayed_processing.insert_rw(entry) };
684
685 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 unsafe {
699 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 let size_ptr =
708 copy_ptr!(external_args_cursor => internal_args_cursor as *mut u32);
709 if !size_ptr.is_null() {
710 size_ptr.write(0);
713 }
714 copy_ptr!(external_args_cursor => internal_args_cursor as *const u32);
716 }
717 }
718 }
719 }
720 }
721
722 let mut nested_context = None;
723 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 let internal_args = internal_args.cast::<NonNull<c_void>>();
733
734 let result = Result::<(), ContractError>::from(unsafe { ffi_fn(internal_args) });
737
738 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 if let Some(new_address_ptr) = new_address_ptr {
750 let _: fn(&mut AddressAllocator, &mut Env<'_>) -> Result<Address, ContractError> =
752 AddressAllocator::allocate_address;
753 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 }
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 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 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 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 unsafe {
816 slot_bytes.set_len(size);
817 }
818 }
819 }
820 }
821
822 Ok(())
823}