ab_contracts_common/
env.rs

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