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/// Environment state
50#[derive(Debug, Copy, Clone, TrivialType)]
51#[repr(C)]
52pub struct EnvState {
53    /// Shard index where execution is happening
54    pub shard_index: ShardIndex,
55    /// Explicit padding, contents does not matter
56    pub padding_0: [u8; 4],
57    /// Own address of the contract
58    pub own_address: Address,
59    /// Context of the execution
60    pub context: Address,
61    /// Caller of this contract
62    pub caller: Address,
63}
64
65/// Executor context that can be used to interact with executor
66#[cfg(feature = "executor")]
67pub trait ExecutorContext: core::fmt::Debug {
68    /// Call prepared method
69    fn call(
70        &self,
71        previous_env_state: &EnvState,
72        prepared_methods: &mut PreparedMethod<'_>,
73    ) -> Result<(), ContractError>;
74}
75
76#[cfg(all(feature = "executor", feature = "guest", not(any(doc, unix, windows))))]
77compile_error!(
78    "`executor` and `guest` features are mutually exclusive due to it affecting `Env` layout"
79);
80
81/// Ephemeral execution environment.
82///
83/// In guest environment equivalent to just [`EnvState`], while on Unix and Windows an executor
84/// context is also present
85#[derive(Debug)]
86#[repr(C)]
87pub struct Env<'a> {
88    state: EnvState,
89    #[cfg(feature = "executor")]
90    executor_context: &'a mut dyn ExecutorContext,
91    phantom_data: PhantomData<&'a ()>,
92}
93
94// TODO: API to "attach" data structures to the environment to make sure pointers to it can be
95//  returned safely, will likely require `Pin` and return some reference from which pointer is to
96//  be created
97impl<'a> Env<'a> {
98    /// Instantiate environment with executor context
99    #[cfg(feature = "executor")]
100    #[inline(always)]
101    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
102    pub fn with_executor_context(
103        state: EnvState,
104        executor_context: &'a mut dyn ExecutorContext,
105    ) -> Self {
106        Self {
107            state,
108            executor_context,
109            phantom_data: PhantomData,
110        }
111    }
112
113    /// Instantiate environment with executor context
114    #[cfg(feature = "executor")]
115    #[inline(always)]
116    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
117    pub fn get_mut_executor_context(&mut self) -> &mut dyn ExecutorContext {
118        self.executor_context
119    }
120
121    /// Shard index where execution is happening
122    #[inline(always)]
123    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
124    pub fn shard_index(&self) -> ShardIndex {
125        self.state.shard_index
126    }
127
128    /// Own address of the contract
129    #[inline(always)]
130    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
131    pub fn own_address(&self) -> Address {
132        self.state.own_address
133    }
134
135    /// Context of the execution
136    #[inline(always)]
137    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
138    pub fn context<'b>(self: &'b &'b mut Self) -> Address {
139        self.state.context
140    }
141
142    /// Caller of this contract
143    #[inline(always)]
144    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
145    pub fn caller<'b>(self: &'b &'b mut Self) -> Address {
146        self.state.caller
147    }
148
149    /// Call a method at specified address and with specified arguments.
150    ///
151    /// This is a shortcut for [`Self::prepare_method_call()`] + [`Self::call_prepared()`].
152    #[inline(always)]
153    pub fn call<Args>(
154        &self,
155        contract: Address,
156        args: &mut Args,
157        method_context: MethodContext,
158    ) -> Result<(), ContractError>
159    where
160        Args: ExternalArgs,
161    {
162        let prepared_method = Self::prepare_method_call(contract, args, method_context);
163        self.call_prepared(prepared_method)
164    }
165
166    /// Prepare a single method for calling at specified address and with specified arguments.
167    ///
168    /// The result is to be used with [`Self::call_prepared()`] afterward.
169    #[inline(always)]
170    #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
171    pub fn prepare_method_call<Args>(
172        contract: Address,
173        args: &mut Args,
174        method_context: MethodContext,
175    ) -> PreparedMethod<'_>
176    where
177        Args: ExternalArgs,
178    {
179        PreparedMethod {
180            contract,
181            fingerprint: Args::FINGERPRINT,
182            // TODO: Method on `ExternalArgs` that returns an iterator over pointers
183            external_args: NonNull::from_mut(args).cast::<NonNull<c_void>>(),
184            method_context,
185            phantom: PhantomData,
186        }
187    }
188
189    /// Call prepared method.
190    ///
191    /// In most cases, this doesn't need to be called directly. Extension traits provide a more
192    /// convenient way to make method calls and are enough in most cases.
193    #[inline]
194    pub fn call_prepared(&self, method: PreparedMethod<'_>) -> Result<(), ContractError> {
195        #[cfg(feature = "executor")]
196        {
197            let mut method = method;
198            self.executor_context.call(&self.state, &mut method)
199        }
200        #[cfg(all(feature = "guest", not(feature = "executor")))]
201        {
202            let _ = method;
203            todo!()
204        }
205        #[cfg(not(any(feature = "executor", feature = "guest")))]
206        {
207            let _ = method;
208            Err(ContractError::InternalError)
209        }
210    }
211}