ab_contracts_common/
env.rs

1use crate::ContractError;
2use crate::method::{ExternalArgs, MethodFingerprint};
3use ab_core_primitives::address::Address;
4use ab_core_primitives::shard::ShardIndex;
5use ab_io_type::trivial_type::TrivialType;
6use core::ffi::c_void;
7use core::marker::PhantomData;
8use core::ptr::NonNull;
9
10/// Context for method call.
11///
12/// The correct mental model for context is "user of the child process," where "process" is a method
13/// call. Essentially, something executed with a context of a contract can be thought as done
14/// "on behalf" of that contract, which depending on circumstances may or may not be desired.
15///
16/// Initially, context is [`Address::NULL`]. For each call into another contract, the context of the
17/// current method can be either preserved, reset to [`Address::NULL`] or replaced with the current
18/// contract's address. Those are the only options. Contracts do not have privileges to change
19/// context to the address of an arbitrary contract.
20#[derive(Debug, Copy, Clone, Eq, PartialEq, TrivialType)]
21#[repr(u8)]
22pub enum MethodContext {
23    /// Keep current context
24    Keep,
25    /// Reset context to [`Address::NULL`]
26    Reset,
27    /// Replace context with current contract's address
28    Replace,
29}
30
31/// Method to be called by the executor
32#[derive(Debug)]
33#[repr(C)]
34#[must_use]
35pub struct PreparedMethod<'a> {
36    /// Address of the contract that contains a function to below fingerprint
37    pub contract: Address,
38    /// Fingerprint of the method being called
39    pub fingerprint: MethodFingerprint,
40    /// Anonymous pointer to a struct that implements `ExternalArgs` of the method with above
41    /// `fingerprint`
42    pub external_args: NonNull<NonNull<c_void>>,
43    /// Context for method call
44    pub method_context: MethodContext,
45    /// Used to tie the lifetime to `ExternalArgs`
46    pub phantom: PhantomData<&'a ()>,
47}
48
49#[cfg(feature = "guest")]
50unsafe extern "C" {
51    /// Host-level API
52    fn __ab_host_call_import(method: *const PreparedMethod<'_>) -> crate::ExitCode;
53}
54
55/// Internal wrapper around [`__ab_host_call_import()`] that will always have a single copy (never
56/// inlined) in the binary and will be possible to locate in the final binary for inspection and
57/// rewriting if necessary while completely ignoring relocation of `__ab_host_call_import()`
58#[cfg(feature = "guest")]
59#[unsafe(no_mangle)]
60#[inline(never)]
61#[doc(hidden)]
62pub extern "C" fn __ab_host_call(method: &PreparedMethod<'_>) -> crate::ExitCode {
63    // SAFETY: FFI call with correct argument, there are no other safety requirements
64    unsafe { __ab_host_call_import(method) }
65}
66
67/// Environment state
68#[derive(Debug, Copy, Clone, TrivialType)]
69#[repr(C)]
70pub struct EnvState {
71    /// Shard index where execution is happening
72    pub shard_index: ShardIndex,
73    /// Explicit padding, contents must be all zeroes
74    pub padding_0: [u8; 4],
75    /// Own address of the contract
76    pub own_address: Address,
77    /// Context of the execution
78    pub context: Address,
79    /// Caller of this contract
80    pub caller: Address,
81}
82
83/// Executor context that can be used to interact with executor
84#[cfg(feature = "executor")]
85pub trait ExecutorContext: core::fmt::Debug {
86    /// Call prepared method
87    fn call(
88        &self,
89        previous_env_state: &EnvState,
90        prepared_methods: &mut PreparedMethod<'_>,
91    ) -> Result<(), ContractError>;
92}
93
94#[cfg(all(feature = "executor", feature = "guest", not(any(doc, unix, windows))))]
95compile_error!(
96    "`executor` and `guest` features are mutually exclusive due to it affecting `Env` layout"
97);
98
99/// Ephemeral execution environment.
100///
101/// In guest environment equivalent to just [`EnvState`], while on Unix and Windows an executor
102/// context is also present
103#[derive(Debug)]
104#[repr(C)]
105pub struct Env<'a> {
106    state: EnvState,
107    #[cfg(feature = "executor")]
108    executor_context: &'a mut dyn ExecutorContext,
109    phantom_data: PhantomData<&'a ()>,
110}
111
112// TODO: API to "attach" data structures to the environment to make sure pointers to it can be
113//  returned safely, will likely require `Pin` and return some reference from which pointer is to
114//  be created
115impl<'a> Env<'a> {
116    /// Instantiate environment with executor context
117    #[cfg(feature = "executor")]
118    #[inline(always)]
119    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
120    pub fn with_executor_context(
121        state: EnvState,
122        executor_context: &'a mut dyn ExecutorContext,
123    ) -> Self {
124        Self {
125            state,
126            executor_context,
127            phantom_data: PhantomData,
128        }
129    }
130
131    /// Instantiate environment with executor context
132    #[cfg(feature = "executor")]
133    #[inline(always)]
134    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
135    pub fn get_mut_executor_context(&mut self) -> &mut dyn ExecutorContext {
136        self.executor_context
137    }
138
139    /// Shard index where execution is happening
140    #[inline(always)]
141    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
142    pub fn shard_index(&self) -> ShardIndex {
143        self.state.shard_index
144    }
145
146    /// Own address of the contract
147    #[inline(always)]
148    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
149    pub fn own_address(&self) -> Address {
150        self.state.own_address
151    }
152
153    /// Context of the execution
154    #[inline(always)]
155    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
156    pub fn context<'b>(self: &'b &'b mut Self) -> Address {
157        self.state.context
158    }
159
160    /// Caller of this contract
161    #[inline(always)]
162    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
163    pub fn caller<'b>(self: &'b &'b mut Self) -> Address {
164        self.state.caller
165    }
166
167    /// Call a method at specified address and with specified arguments.
168    ///
169    /// This is a shortcut for [`Self::prepare_method_call()`] + [`Self::call_prepared()`].
170    #[inline(always)]
171    pub fn call<Args>(
172        &self,
173        contract: Address,
174        args: &mut Args,
175        method_context: MethodContext,
176    ) -> Result<(), ContractError>
177    where
178        Args: ExternalArgs,
179    {
180        let prepared_method = Self::prepare_method_call(contract, args, method_context);
181        self.call_prepared(prepared_method)
182    }
183
184    /// Prepare a single method for calling at specified address and with specified arguments.
185    ///
186    /// The result is to be used with [`Self::call_prepared()`] afterward.
187    #[inline(always)]
188    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
189    pub fn prepare_method_call<Args>(
190        contract: Address,
191        args: &mut Args,
192        method_context: MethodContext,
193    ) -> PreparedMethod<'_>
194    where
195        Args: ExternalArgs,
196    {
197        PreparedMethod {
198            contract,
199            fingerprint: Args::FINGERPRINT,
200            // TODO: Method on `ExternalArgs` that returns an iterator over pointers
201            external_args: NonNull::from_mut(args).cast::<NonNull<c_void>>(),
202            method_context,
203            phantom: PhantomData,
204        }
205    }
206
207    /// Call prepared method.
208    ///
209    /// In most cases, this doesn't need to be called directly. Extension traits provide a more
210    /// convenient way to make method calls and are enough in most cases.
211    #[inline]
212    pub fn call_prepared(&self, method: PreparedMethod<'_>) -> Result<(), ContractError> {
213        #[cfg(feature = "executor")]
214        {
215            let mut method = method;
216            self.executor_context.call(&self.state, &mut method)
217        }
218        #[cfg(all(feature = "guest", not(feature = "executor")))]
219        {
220            __ab_host_call(&method).into()
221        }
222        #[cfg(not(any(feature = "executor", feature = "guest")))]
223        {
224            let _ = method;
225            Err(ContractError::InternalError)
226        }
227    }
228}