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}