Skip to main content

ab_contracts_common/
env.rs

1use crate::ContractError;
2use crate::method::{ExternalArgs, MethodFingerprint};
3use crate::output::Lent;
4use ab_core_primitives::address::Address;
5use ab_core_primitives::shard::ShardIndex;
6use ab_io_type::IoType;
7use ab_io_type::trivial_type::TrivialType;
8use core::ffi::c_void;
9use core::marker::PhantomData;
10use core::ptr::NonNull;
11
12/// Context for method call.
13///
14/// The correct mental model for context is "user of the child process," where "process" is a method
15/// call. Essentially, something executed with a context of a contract can be thought as done
16/// "on behalf" of that contract, which depending on circumstances may or may not be desired.
17///
18/// Initially, the context is [`Address::NULL`]. For each call into another contract, the context of
19/// the current method can be either preserved, reset to [`Address::NULL`] or replaced with the
20/// current contract's address. Those are the only options. Contracts do not have privileges to
21/// change context to the address of an arbitrary contract.
22#[derive(Debug, Copy, Clone, Eq, PartialEq, TrivialType)]
23#[repr(u8)]
24pub enum MethodContext {
25    /// Keep current context
26    Keep,
27    /// Reset context to [`Address::NULL`]
28    Reset,
29    /// Replace context with the current contract's address
30    Replace,
31}
32
33/// Method to be called by the executor.
34///
35/// It only describes a method call and nothing in it is checked: the executor reads arguments
36/// behind `external_args` according to metadata of the method with `fingerprint`, and either checks
37/// them against that metadata or trusts the caller (like the native executor does). The arguments
38/// meet the safety requirements of [`Self::new_untyped()`] for the lifetime `'a`, which
39/// [`Self::new()`] ensures with typed arguments. Fields can't be modified after creation, so that a
40/// method prepared with [`Self::new()`] can't be retargeted to a different method or arguments by
41/// accident.
42#[derive(Debug)]
43#[repr(C)]
44#[must_use]
45#[expect(
46    clippy::partial_pub_fields,
47    reason = "Lifetime marker is an implementation detail"
48)]
49pub struct PreparedMethod<'a> {
50    /// Address of the contract that contains a function to the below fingerprint
51    pub mut(self) contract: Address,
52    /// Fingerprint of the method being called
53    pub mut(self) fingerprint: MethodFingerprint,
54    /// Anonymous pointer to a struct that implements `ExternalArgs` of the method with the above
55    /// `fingerprint`
56    pub mut(self) external_args: NonNull<c_void>,
57    /// Context for method call
58    pub mut(self) method_context: MethodContext,
59    /// Used to tie the lifetime to `ExternalArgs`
60    phantom: PhantomData<&'a ()>,
61}
62
63impl<'a> PreparedMethod<'a> {
64    /// Prepare a single method for calling at specified address and with specified arguments.
65    ///
66    /// The result is to be used with [`Env::call_prepared()`] afterward.
67    #[inline(always)]
68    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
69    pub fn new<Args>(contract: Address, args: &'a mut Args, method_context: MethodContext) -> Self
70    where
71        Args: ExternalArgs,
72    {
73        Self {
74            contract,
75            fingerprint: Args::FINGERPRINT,
76            external_args: NonNull::from_mut(args).cast::<c_void>(),
77            method_context,
78            phantom: PhantomData,
79        }
80    }
81
82    /// Prepare a single method for calling with arguments laid out dynamically, like in a
83    /// transaction payload.
84    ///
85    /// Prefer [`Self::new()`] when the type of arguments is known.
86    ///
87    /// # Safety
88    /// `external_args` must start with arguments laid out like [`ExternalArgs`] of the method with
89    /// `fingerprint`, and for the lifetime `'a`:
90    /// * the pointer of each slot must be aligned and point to an [`Address`] valid for reads
91    /// * the pointer of each input must be valid for reads of its size, with that many bytes
92    ///   initialized
93    /// * the pointer of each output must be valid for writes of its capacity and for reads of its
94    ///   size, which must not exceed the capacity, with that many bytes initialized
95    /// * the pointer of the return value (if the method has one) must be aligned and valid for
96    ///   writes of a value of the return type, while its contents don't need to be initialized
97    /// * outputs and the return value must not overlap each other, slot addresses, inputs or
98    ///   `external_args`, and must not be accessed other than through `external_args`, while slot
99    ///   addresses and inputs must not be written to
100    ///
101    /// A call can leave contents behind in outputs that are not valid values of their types, even
102    /// when it succeeds, and it only initializes the return value. Afterward, neither may be
103    /// accessed as a value of its type without being parsed first, unless every bit pattern is a
104    /// valid value.
105    ///
106    /// Whether sizes, capacities and alignment of inputs and outputs match the method's metadata
107    /// is not part of this contract, since only the executor has the metadata and guarantees it:
108    /// the RISC-V VM checks them, while the native executor trusts callers and only catches
109    /// mismatches with debug assertions. Neither are their contents, which the called method
110    /// parses.
111    #[inline(always)]
112    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
113    pub unsafe fn new_untyped<Args>(
114        contract: Address,
115        fingerprint: MethodFingerprint,
116        external_args: &'a mut Args,
117        method_context: MethodContext,
118    ) -> Self {
119        Self {
120            contract,
121            fingerprint,
122            external_args: NonNull::from_mut(external_args).cast::<c_void>(),
123            method_context,
124            phantom: PhantomData,
125        }
126    }
127}
128
129#[cfg(feature = "guest")]
130unsafe extern "C" {
131    /// Host-level API
132    fn __ab_host_call_import(method: &PreparedMethod<'_>) -> crate::ExitCode;
133}
134
135/// Internal wrapper around [`__ab_host_call_import()`] that will always have a single copy (never
136/// inlined) in the binary and will be possible to locate in the final binary for inspection and
137/// rewriting if necessary while completely ignoring relocation of `__ab_host_call_import()`
138#[cfg(feature = "guest")]
139#[unsafe(no_mangle)]
140#[inline(never)]
141#[doc(hidden)]
142pub extern "C" fn __ab_host_call(method: &PreparedMethod<'_>) -> crate::ExitCode {
143    // SAFETY: FFI call with correct argument, there are no other safety requirements
144    unsafe { __ab_host_call_import(method) }
145}
146
147/// Environment state
148#[derive(Debug, Copy, Clone, TrivialType)]
149#[repr(C)]
150pub struct EnvState {
151    /// Shard index where execution is happening
152    pub shard_index: ShardIndex,
153    /// Explicit padding, contents must be all zeroes
154    pub padding_0: [u8; 4],
155    /// Own address of the contract
156    pub own_address: Address,
157    /// Context of the execution
158    pub context: Address,
159    /// Caller of this contract
160    pub caller: Address,
161}
162
163/// Executor context that can be used to interact with executor.
164///
165/// # Safety
166/// [`Self::call()`] gets pointers to arguments of a method call in [`PreparedMethod`], and callers
167/// like methods generated by `#[contract]` rely on implementations to:
168/// * write only initialized bytes, including when returning an error
169/// * write nothing but the contents of outputs within their capacity, the sizes of outputs in
170///   [`ExternalArgs`] and the return value (if there is one)
171/// * not read the return value, or bytes of outputs beyond their size, before writing them, since
172///   they may be uninitialized
173/// * keep the size of each output in [`ExternalArgs`] valid for its type and within its capacity,
174///   with that many bytes of the output initialized, since extension methods set it on the output
175///   they were given, as [`OutputArg::set_output_size()`] requires
176/// * initialize the return value (if there is one) before returning `Ok(())`
177/// * not access memory behind any of these pointers after [`Self::call()`] returns
178///
179/// Contents don't need to be valid values of their types: extension methods parse return values,
180/// and contents of outputs are only accessed after being parsed, unless every bit pattern is a
181/// valid value (see [`OutputArg`]).
182///
183/// [`OutputArg`]: crate::output::OutputArg
184/// [`OutputArg::set_output_size()`]: crate::output::OutputArg::set_output_size
185#[cfg(feature = "executor")]
186pub unsafe trait ExecutorContext: core::fmt::Debug {
187    /// Call prepared method
188    fn call(
189        &self,
190        previous_env_state: &EnvState,
191        prepared_method: &PreparedMethod<'_>,
192    ) -> Result<(), ContractError>;
193}
194
195#[cfg(all(feature = "executor", feature = "guest", not(any(doc, unix, windows))))]
196compile_error!(
197    "`executor` and `guest` features are mutually exclusive due to it affecting `Env` layout"
198);
199
200// TODO: Make `Env` immovable once the `Move` trait is available in nightly, see
201//  https://github.com/rust-lang/rust/issues/149607 and
202//  https://goals.rust-lang.org/2026/move-trait.html. Code that gets `&mut Env` couldn't replace it
203//  with another `Env` then, so the type system would guarantee that an `Env` given to code by
204//  reference is the same afterward, and the second requirement in the `# Safety` of
205//  `Env::with_executor_context()` could be removed
206/// Ephemeral execution environment.
207///
208/// In guest environment equivalent to just [`EnvState`], while on Unix and Windows an executor
209/// context is also present
210#[derive(Debug)]
211#[repr(C)]
212pub struct Env<'a> {
213    state: EnvState,
214    #[cfg(feature = "executor")]
215    executor_context: &'a dyn ExecutorContext,
216    /// Makes `Env` invariant over `'a`.
217    ///
218    /// Code that gets `Env<'a>` can lend memory to calls with [`Env::lend_output()`], which keeps
219    /// it borrowed for `'a`, and a call can leave contents behind in that memory that are not a
220    /// valid value. The borrow stops that code from accessing the memory again before `'a` ends,
221    /// and afterward the memory is only accessed by parsing it first (see
222    /// `Env::with_executor_context()`). If `Env<'a>` could be shortened to `Env<'short>`, the code
223    /// could lend memory borrowed only for `'short` and then access it directly once `'short`
224    /// ends, while it may still hold an invalid value.
225    invariant_lifetime: PhantomData<fn(&'a ()) -> &'a ()>,
226}
227
228// TODO: API to "attach" data structures to the environment to make sure pointers to it can be
229//  returned safely, will likely require `Pin` and return some reference from which pointer is to
230//  be created
231impl<'a> Env<'a> {
232    /// Instantiate environment with executor context.
233    ///
234    /// # Safety
235    /// Code that uses the returned `Env` may rely on its lifetime `'a`: memory it borrows for `'a`
236    /// (see [`Self::lend_output()`]) stays inaccessible to others until `'a` ends, and contents it
237    /// leaves behind in that memory may not be a valid value. Hence:
238    /// * memory borrowed for `'a` by code that uses the `Env` must not be accessed after `'a` ends
239    ///   other than by parsing it, unless its contents accept every bit pattern. For example, a
240    ///   method that creates an `Env` to lend memory it received must give it a lifetime that lasts
241    ///   until the method returns, like the lifetime of the `Env` it received.
242    /// * once the `Env` was given to code by reference, it must not be used afterward other than
243    ///   being dropped, since that code may have replaced it with another `Env` whose lifetime only
244    ///   lasts until it returns
245    // TODO: Remove the second requirement once `Env` is immovable with the `Move` trait
246    //  (https://github.com/rust-lang/rust/issues/149607): code given the `Env` by reference
247    //  couldn't replace it then, so the type system would guarantee that it is the same `Env`
248    //  afterward
249    #[cfg(feature = "executor")]
250    #[inline(always)]
251    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
252    pub unsafe fn with_executor_context(
253        state: EnvState,
254        executor_context: &'a dyn ExecutorContext,
255    ) -> Self {
256        Self {
257            state,
258            executor_context,
259            invariant_lifetime: PhantomData,
260        }
261    }
262
263    /// Shard index where execution is happening
264    #[inline(always)]
265    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
266    pub fn shard_index(&self) -> ShardIndex {
267        self.state.shard_index
268    }
269
270    /// Own address of the contract
271    #[inline(always)]
272    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
273    pub fn own_address(&self) -> Address {
274        self.state.own_address
275    }
276
277    /// Context of the execution
278    #[inline(always)]
279    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
280    pub fn context<'b>(self: &'b &'b mut Self) -> Address {
281        self.state.context
282    }
283
284    /// Caller of this contract
285    #[inline(always)]
286    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
287    pub fn caller<'b>(self: &'b &'b mut Self) -> Address {
288        self.state.caller
289    }
290
291    /// Lend an `#[output]` the method received, or a field of one, to calls as an `#[output]`.
292    ///
293    /// The memory stays borrowed until the method returns, so after a call its contents are only
294    /// accessible through the returned [`Lent`], which parses them first. The state of the
295    /// contract (including the state that an `#[init]` method returns through its last
296    /// `#[output]`) and `#[slot]` and `#[tmp]` arguments are trusted without being parsed, so they
297    /// can't be lent this way: lend a copy made with [`Lent::new()`] instead. Local memory is lent
298    /// with [`Lent::new()`], `Lent::<MaybeData<_>>::from_uninit()` or
299    /// `Lent::<VariableElements<_>>::from_uninit()`.
300    ///
301    /// Code generated by `#[contract]` gives a method `Env` with a lifetime that covers the call of
302    /// the method, so the method must declare its `#[output]` with that lifetime to lend it:
303    /// ```ignore
304    /// #[update]
305    /// pub fn forward<'a>(
306    ///     #[env] env: &mut Env<'a>,
307    ///     #[output] mode: &'a mut Mode,
308    /// ) -> Result<(), ContractError> {
309    ///     let mut mode = env.lend_output(mode);
310    ///     env.other_set_mode(MethodContext::Keep, OTHER_ADDRESS, &mut mode)?;
311    ///     *mode.get_mut()? = Mode::B;
312    ///     Ok(())
313    /// }
314    /// ```
315    ///
316    /// With elided lifetimes (`&mut Env<'_>` and `&mut Mode`) compilation will fail with "lifetime
317    /// may not live long enough", and rustc will suggest the signature above.
318    #[inline(always)]
319    pub fn lend_output<'b, T>(&self, value: &'b mut T) -> Lent<'b, T>
320    where
321        T: IoType,
322        'b: 'a,
323    {
324        // SAFETY: `value` stays borrowed for at least `'a`. Memory that code using an `Env`
325        // borrows for its lifetime is only accessed afterward by parsing it, unless its contents
326        // accept every bit pattern, as `Self::with_executor_context()` requires (the host upholds
327        // the same for an `Env` it gives to a guest).
328        unsafe { Lent::from_mut_unchecked(value) }
329    }
330
331    /// Call a method at specified address and with specified arguments.
332    ///
333    /// This is a shortcut for [`PreparedMethod::new()`] + [`Self::call_prepared()`].
334    ///
335    /// A call can leave contents behind in outputs that are not valid values of their types, even
336    /// when it succeeds, and it only initializes the return value, see [`ExternalArgs`].
337    #[inline(always)]
338    pub fn call<Args>(
339        &self,
340        contract: Address,
341        args: &mut Args,
342        method_context: MethodContext,
343    ) -> Result<(), ContractError>
344    where
345        Args: ExternalArgs,
346    {
347        let prepared_method = PreparedMethod::new(contract, args, method_context);
348        self.call_prepared(prepared_method)
349    }
350
351    /// Call prepared method.
352    ///
353    /// In most cases, this doesn't need to be called directly. Extension traits provide a more
354    /// convenient way to make method calls and are enough in most cases.
355    ///
356    /// A call can leave contents behind in outputs that are not valid values of their types, even
357    /// when it succeeds, and it only initializes the return value, see [`ExternalArgs`] and
358    /// [`PreparedMethod::new_untyped()`].
359    #[inline]
360    pub fn call_prepared(&self, method: PreparedMethod<'_>) -> Result<(), ContractError> {
361        cfg_select! {
362            feature = "executor" => self.executor_context.call(&self.state, &method),
363            feature = "guest" => __ab_host_call(&method).into(),
364            _ => {
365                let _: PreparedMethod<'_> = method;
366                Err(ContractError::InternalError)
367            }
368        }
369    }
370}