arb_stylus/
env.rs

1use std::marker::PhantomData;
2
3use arbos::programs::types::EvmData;
4use wasmer::{FunctionEnvMut, Global, Memory, MemoryView, Pages, StoreMut, Value};
5
6use crate::{
7    config::{CompileConfig, StylusConfig},
8    error::Escape,
9    evm_api::EvmApi,
10    ink::Ink,
11    meter::{GasMeteredMachine, MachineMeter, MeteredMachine, HOSTIO_INK},
12};
13
14pub type WasmEnvMut<'a, E> = FunctionEnvMut<'a, WasmEnv<E>>;
15
16/// The WASM execution environment.
17///
18/// Contains all state needed during Stylus program execution,
19/// including the EVM API bridge, metering state, and I/O buffers.
20#[derive(Debug)]
21pub struct WasmEnv<E: EvmApi> {
22    /// The instance's arguments.
23    pub args: Vec<u8>,
24    /// The instance's return data.
25    pub outs: Vec<u8>,
26    /// WASM linear memory.
27    pub memory: Option<Memory>,
28    /// Ink metering state via WASM globals.
29    pub meter: Option<MeterData>,
30    /// Bridge to EVM state (storage, calls, etc.).
31    pub evm_api: E,
32    /// EVM context data (block info, sender, etc.).
33    pub evm_data: EvmData,
34    /// Cached length of the last call's return data.
35    pub evm_return_data_len: u32,
36    /// Compile-time configuration.
37    pub compile: CompileConfig,
38    /// Runtime configuration (set when running).
39    pub config: Option<StylusConfig>,
40    /// WASM global for ink metering (set after instantiation).
41    pub ink_global: Option<Global>,
42    /// WASM global for ink status (set after instantiation).
43    pub ink_status_global: Option<Global>,
44    _phantom: PhantomData<E>,
45}
46
47impl<E: EvmApi> WasmEnv<E> {
48    pub fn new(
49        compile: CompileConfig,
50        config: Option<StylusConfig>,
51        evm_api: E,
52        evm_data: EvmData,
53    ) -> Self {
54        Self {
55            compile,
56            config,
57            evm_api,
58            evm_data,
59            args: vec![],
60            outs: vec![],
61            memory: None,
62            meter: None,
63            ink_global: None,
64            ink_status_global: None,
65            evm_return_data_len: 0,
66            _phantom: PhantomData,
67        }
68    }
69
70    /// Create a HostioInfo and charge the standard hostio cost plus `ink`.
71    pub fn start<'a>(
72        env: &'a mut WasmEnvMut<'_, E>,
73        ink: Ink,
74    ) -> Result<HostioInfo<'a, E>, Escape> {
75        let mut info = Self::program(env)?;
76        info.buy_ink(HOSTIO_INK.saturating_add(ink))?;
77        Ok(info)
78    }
79
80    /// Create a HostioInfo for accessing host functionality.
81    pub fn program<'a>(env: &'a mut WasmEnvMut<'_, E>) -> Result<HostioInfo<'a, E>, Escape> {
82        let (env, store) = env.data_and_store_mut();
83        let memory = env.memory.clone().expect("WASM memory not initialized");
84        let mut info = HostioInfo {
85            env,
86            memory,
87            store,
88            start_ink: Ink(0),
89        };
90        if info.env.evm_data.tracing {
91            info.start_ink = info.ink_ready()?;
92        }
93        Ok(info)
94    }
95
96    pub fn meter_mut(&mut self) -> &mut MeterData {
97        self.meter.as_mut().expect("not metered")
98    }
99
100    pub fn meter(&self) -> &MeterData {
101        self.meter.as_ref().expect("not metered")
102    }
103}
104
105/// Ink meter data stored as raw pointers to WASM globals.
106///
107/// These point into the wasmer Store's global storage and are used
108/// for fast ink reads/writes without going through the full wasmer API.
109#[derive(Clone, Copy, Debug)]
110pub struct MeterData {
111    ink_left: u64,
112    ink_status: u32,
113}
114
115impl MeterData {
116    pub fn new() -> Self {
117        Self {
118            ink_left: 0,
119            ink_status: 0,
120        }
121    }
122
123    pub fn ink(&self) -> Ink {
124        Ink(self.ink_left)
125    }
126
127    pub fn status(&self) -> u32 {
128        self.ink_status
129    }
130
131    pub fn set_ink(&mut self, ink: Ink) {
132        self.ink_left = ink.0;
133    }
134
135    pub fn set_status(&mut self, status: u32) {
136        self.ink_status = status;
137    }
138}
139
140unsafe impl Send for MeterData {}
141
142/// Wrapper providing access to host I/O operations during WASM execution.
143///
144/// Bundles the WasmEnv, Memory, and Store together for convenient access
145/// in host function implementations.
146pub struct HostioInfo<'a, E: EvmApi> {
147    pub env: &'a mut WasmEnv<E>,
148    pub memory: Memory,
149    pub store: StoreMut<'a>,
150    pub start_ink: Ink,
151}
152
153impl<E: EvmApi> HostioInfo<'_, E> {
154    pub fn config(&self) -> StylusConfig {
155        self.env.config.expect("no config")
156    }
157
158    pub fn pricing(&self) -> crate::config::PricingParams {
159        self.config().pricing
160    }
161
162    pub fn view(&self) -> MemoryView<'_> {
163        self.memory.view(&self.store)
164    }
165
166    pub fn memory_size(&self) -> Pages {
167        self.memory.ty(&self.store).minimum
168    }
169
170    pub fn read_fixed<const N: usize>(
171        &self,
172        ptr: u32,
173    ) -> Result<[u8; N], wasmer::MemoryAccessError> {
174        let mut data = [0u8; N];
175        self.view().read(ptr as u64, &mut data)?;
176        Ok(data)
177    }
178
179    pub fn read_slice(&self, ptr: u32, len: u32) -> Result<Vec<u8>, wasmer::MemoryAccessError> {
180        let mut data = vec![0u8; len as usize];
181        self.view().read(ptr as u64, &mut data)?;
182        Ok(data)
183    }
184
185    pub fn write_slice(&self, ptr: u32, data: &[u8]) -> Result<(), wasmer::MemoryAccessError> {
186        self.view().write(ptr as u64, data)
187    }
188
189    pub fn write_u32(&self, ptr: u32, value: u32) -> Result<(), wasmer::MemoryAccessError> {
190        self.view().write(ptr as u64, &value.to_le_bytes())
191    }
192}
193
194impl<E: EvmApi> MeteredMachine for HostioInfo<'_, E> {
195    fn ink_left(&self) -> MachineMeter {
196        let vm = self.env.meter();
197        match vm.status() {
198            0 => MachineMeter::Ready(vm.ink()),
199            _ => MachineMeter::Exhausted,
200        }
201    }
202
203    fn set_meter(&mut self, meter: MachineMeter) {
204        // Write to WASM globals so middleware sees hostio gas charges.
205        if let Some(ref g) = self.env.ink_global {
206            let _ = g.set(&mut self.store, Value::I64(meter.ink().0 as i64));
207        }
208        if let Some(ref g) = self.env.ink_status_global {
209            let _ = g.set(&mut self.store, Value::I32(meter.status() as i32));
210        }
211        // Also update MeterData.
212        let vm = self.env.meter_mut();
213        vm.set_ink(meter.ink());
214        vm.set_status(meter.status());
215    }
216
217    // Override buy_ink to read current value from WASM globals first,
218    // then deduct and write back to both globals and MeterData.
219    fn buy_ink(&mut self, ink: Ink) -> Result<(), Escape> {
220        // Read current ink from WASM globals (reflects middleware charges).
221        let current = if let Some(ref g) = self.env.ink_global {
222            if let Value::I64(v) = g.get(&mut self.store) {
223                Ink(v as u64)
224            } else {
225                self.ink_ready()?
226            }
227        } else {
228            self.ink_ready()?
229        };
230        if current < ink {
231            self.set_meter(MachineMeter::Exhausted);
232            return Escape::out_of_ink();
233        }
234        self.set_meter(MachineMeter::Ready(current - ink));
235        Ok(())
236    }
237
238    fn require_ink(&mut self, ink: Ink) -> Result<(), Escape> {
239        let current = if let Some(ref g) = self.env.ink_global {
240            if let Value::I64(v) = g.get(&mut self.store) {
241                Ink(v as u64)
242            } else {
243                self.ink_ready()?
244            }
245        } else {
246            self.ink_ready()?
247        };
248        if current < ink {
249            return Escape::out_of_ink();
250        }
251        Ok(())
252    }
253}
254
255impl<E: EvmApi> GasMeteredMachine for HostioInfo<'_, E> {
256    fn pricing(&self) -> crate::config::PricingParams {
257        self.config().pricing
258    }
259}
260
261impl<E: EvmApi> std::ops::Deref for HostioInfo<'_, E> {
262    type Target = WasmEnv<E>;
263    fn deref(&self) -> &Self::Target {
264        self.env
265    }
266}
267
268impl<E: EvmApi> std::ops::DerefMut for HostioInfo<'_, E> {
269    fn deref_mut(&mut self) -> &mut Self::Target {
270        self.env
271    }
272}