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