ab_contracts_common/env.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
#[cfg(any(unix, windows))]
extern crate alloc;
use crate::method::{ExternalArgs, MethodFingerprint};
use crate::{Address, ContractError, ShardIndex};
use ab_contracts_io_type::trivial_type::TrivialType;
#[cfg(any(unix, windows))]
use alloc::sync::Arc;
use core::ffi::c_void;
use core::marker::PhantomData;
use core::ptr::NonNull;
/// Context for method call.
///
/// Initially, context is [`Address::NULL`]. For each call into another contract, the context of the
/// current method can be either preserved, reset to [`Address::NULL`] or replaced with the current
/// contract's address.
#[derive(Debug, Copy, Clone, Eq, PartialEq, TrivialType)]
#[repr(u8)]
pub enum MethodContext {
/// Keep current context
Keep,
/// Reset context to [`Address::NULL`]
Reset,
/// Replace context with current contract's address
Replace,
}
/// Method to be called by the executor
#[derive(Debug)]
#[repr(C)]
#[must_use]
// TODO: Once solidified, replace some pointers with inline data
pub struct PreparedMethod<'a> {
/// Address of the contract that contains a function to below fingerprint
pub contract: NonNull<Address>,
/// Fingerprint of the method being called
pub fingerprint: NonNull<MethodFingerprint>,
/// Anonymous pointer to a struct that implements `ExternalArgs` of the method with above
/// `fingerprint`
pub external_args: NonNull<NonNull<c_void>>,
/// Context for method call
pub method_context: NonNull<MethodContext>,
// TODO: Some flags that allow re-origin and other things or those will be separate host fns?
_phantom: PhantomData<&'a ()>,
}
/// Environment state
#[derive(Debug, Copy, Clone, TrivialType)]
#[repr(C)]
pub struct EnvState {
/// Shard index where execution is happening
pub shard_index: ShardIndex,
/// Own address of the contract
pub own_address: Address,
/// Context of the execution
pub context: Address,
/// Caller of this contract
pub caller: Address,
}
/// Executor context that can be used to interact with executor
#[cfg(any(unix, windows))]
pub trait ExecutorContext: alloc::fmt::Debug {
/// Call multiple methods
fn call_many(
&self,
previous_env_state: &EnvState,
prepared_methods: &[PreparedMethod<'_>],
) -> Result<(), ContractError>;
}
/// Ephemeral execution environment.
///
/// In guest environment equivalent to just [`EnvState`], while on Unix and Windows an executor
/// context is also present
#[derive(Debug)]
#[repr(C)]
pub struct Env {
state: EnvState,
#[cfg(any(unix, windows))]
executor_context: Arc<dyn ExecutorContext>,
}
// TODO: API to "attach" data structures to the environment to make sure pointers to it can be
// returned safely, will likely require `Pin` and return some reference from which pointer is to
// be created
impl Env {
/// Instantiate environment with executor context
#[cfg(any(unix, windows))]
pub fn with_executor_context(
state: EnvState,
executor_context: Arc<dyn ExecutorContext>,
) -> Self {
Self {
state,
executor_context,
}
}
/// Shard index where execution is happening
pub fn shard_index(&self) -> ShardIndex {
self.state.shard_index
}
/// Own address of the contract
pub fn own_address(&self) -> &Address {
&self.state.own_address
}
/// Context of the execution
pub fn context<'a>(self: &'a &'a mut Self) -> &'a Address {
&self.state.context
}
/// Caller of this contract
pub fn caller<'a>(self: &'a &'a mut Self) -> &'a Address {
&self.state.caller
}
/// Call a single method at specified address and with specified arguments.
///
/// This is a shortcut for [`Self::prepare_method_call()`] + [`Self::call_many()`].
pub fn call<Args>(
&self,
contract: &Address,
args: &mut Args,
method_context: &MethodContext,
) -> Result<(), ContractError>
where
Args: ExternalArgs,
{
let prepared_method = Self::prepare_method_call(contract, args, method_context);
self.call_many([prepared_method])
}
/// Prepare a single method for calling at specified address and with specified arguments.
///
/// The result is to be used with [`Self::call_many()`] afterward.
pub fn prepare_method_call<'a, Args>(
contract: &'a Address,
args: &'a mut Args,
method_context: &'a MethodContext,
) -> PreparedMethod<'a>
where
Args: ExternalArgs,
{
PreparedMethod {
contract: NonNull::from_ref(contract),
fingerprint: NonNull::from_ref(&Args::FINGERPRINT),
external_args: NonNull::from_mut(args).cast::<NonNull<c_void>>(),
method_context: NonNull::from_ref(method_context).cast(),
_phantom: PhantomData,
}
}
/// Invoke provided methods and wait for the result.
///
/// The remaining gas will be split equally between all individual invocations.
pub fn call_many<'a, const N: usize>(
&'a self,
methods: [PreparedMethod<'a>; N],
) -> Result<(), ContractError> {
#[cfg(any(unix, windows))]
{
self.executor_context.call_many(&self.state, &methods)
}
#[cfg(all(feature = "guest", not(any(unix, windows))))]
{
let _ = methods;
todo!()
}
#[cfg(not(any(unix, windows, feature = "guest")))]
compile_error!(
"Contracts support either native environment with Unix or Windows target OS or guest \
environment with `guest` feature, but neither is configured"
)
}
}