arb_stylus/
env.rs

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