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, PhantomInvariantLifetime};
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: PhantomInvariantLifetime<'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 // TODO: Use just `PhantomInvariantLifetime` once upgrading to the latest nightly that
260 // supports it
261 invariant_lifetime: PhantomInvariantLifetime::new(),
262 }
263 }
264
265 /// Shard index where execution is happening
266 #[inline(always)]
267 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
268 pub fn shard_index(&self) -> ShardIndex {
269 self.state.shard_index
270 }
271
272 /// Own address of the contract
273 #[inline(always)]
274 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
275 pub fn own_address(&self) -> Address {
276 self.state.own_address
277 }
278
279 /// Context of the execution
280 #[inline(always)]
281 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
282 pub fn context<'b>(self: &'b &'b mut Self) -> Address {
283 self.state.context
284 }
285
286 /// Caller of this contract
287 #[inline(always)]
288 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
289 pub fn caller<'b>(self: &'b &'b mut Self) -> Address {
290 self.state.caller
291 }
292
293 /// Lend an `#[output]` the method received, or a field of one, to calls as an `#[output]`.
294 ///
295 /// The memory stays borrowed until the method returns, so after a call its contents are only
296 /// accessible through the returned [`Lent`], which parses them first. The state of the
297 /// contract (including the state that an `#[init]` method returns through its last
298 /// `#[output]`) and `#[slot]` and `#[tmp]` arguments are trusted without being parsed, so they
299 /// can't be lent this way: lend a copy made with [`Lent::new()`] instead. Local memory is lent
300 /// with [`Lent::new()`], `Lent::<MaybeData<'_, _>>::from_uninit()` or
301 /// `Lent::<VariableElements<'_, _>>::from_uninit()`.
302 ///
303 /// Code generated by `#[contract]` gives a method `Env` with a lifetime that covers the call of
304 /// the method, so the method must declare its `#[output]` with that lifetime to lend it:
305 /// ```ignore
306 /// #[update]
307 /// pub fn forward<'a>(
308 /// #[env] env: &mut Env<'a>,
309 /// #[output] mode: &'a mut Mode,
310 /// ) -> Result<(), ContractError> {
311 /// let mut mode = env.lend_output(mode);
312 /// env.other_set_mode(MethodContext::Keep, OTHER_ADDRESS, &mut mode)?;
313 /// *mode.get_mut()? = Mode::B;
314 /// Ok(())
315 /// }
316 /// ```
317 ///
318 /// With elided lifetimes (`&mut Env<'_>` and `&mut Mode`) compilation will fail with "lifetime
319 /// may not live long enough", and rustc will suggest the signature above.
320 #[inline(always)]
321 pub fn lend_output<'b, T>(&self, value: &'b mut T) -> Lent<'b, T>
322 where
323 T: IoType,
324 'b: 'a,
325 {
326 // SAFETY: `value` stays borrowed for at least `'a`. Memory that code using an `Env`
327 // borrows for its lifetime is only accessed afterward by parsing it, unless its contents
328 // accept every bit pattern, as `Self::with_executor_context()` requires (the host upholds
329 // the same for an `Env` it gives to a guest).
330 unsafe { Lent::from_mut_unchecked(value) }
331 }
332
333 /// Call a method at specified address and with specified arguments.
334 ///
335 /// This is a shortcut for [`PreparedMethod::new()`] + [`Self::call_prepared()`].
336 ///
337 /// A call can leave contents behind in outputs that are not valid values of their types, even
338 /// when it succeeds, and it only initializes the return value, see [`ExternalArgs`].
339 #[inline(always)]
340 pub fn call<Args>(
341 &self,
342 contract: Address,
343 args: &mut Args,
344 method_context: MethodContext,
345 ) -> Result<(), ContractError>
346 where
347 Args: ExternalArgs,
348 {
349 let prepared_method = PreparedMethod::new(contract, args, method_context);
350 self.call_prepared(prepared_method)
351 }
352
353 /// Call prepared method.
354 ///
355 /// In most cases, this doesn't need to be called directly. Extension traits provide a more
356 /// convenient way to make method calls and are enough in most cases.
357 ///
358 /// A call can leave contents behind in outputs that are not valid values of their types, even
359 /// when it succeeds, and it only initializes the return value, see [`ExternalArgs`] and
360 /// [`PreparedMethod::new_untyped()`].
361 #[inline]
362 pub fn call_prepared(&self, method: PreparedMethod<'_>) -> Result<(), ContractError> {
363 cfg_select! {
364 feature = "executor" => self.executor_context.call(&self.state, &method),
365 feature = "guest" => __ab_host_call(&method).into(),
366 _ => {
367 let _: PreparedMethod<'_> = method;
368 Err(ContractError::InternalError)
369 }
370 }
371 }
372}