1use std::collections::HashMap;
2
3use wasmtime::{
4 Caller, Config, Engine, Error as WasmError, Linker, StoreLimits,
5 StoreLimitsBuilder,
6};
7
8use thiserror::Error;
9
10use crate::config::MachineSpec;
11
12#[derive(Debug, Clone)]
20pub struct WasmLimits {
21 pub max_wasm_stack: usize,
23 pub memory_size: usize,
25 pub max_single_alloc: usize,
27 pub max_total_memory: usize,
29 pub max_table_elements: usize,
31 pub aggressive_compilation: bool,
34}
35
36impl Default for WasmLimits {
37 fn default() -> Self {
38 Self::build(4_096, 2)
39 }
40}
41
42impl WasmLimits {
43 pub fn build(ram_mb: u64, cpu_cores: usize) -> Self {
71 let memory_size = ((ram_mb / 512) as usize)
73 .saturating_mul(4 * 1024 * 1024)
74 .clamp(4 * 1024 * 1024, 32 * 1024 * 1024);
75
76 let max_total_memory = ((ram_mb / 512) as usize)
78 .saturating_mul(3 * 1024 * 1024)
79 .clamp(3 * 1024 * 1024, 24 * 1024 * 1024);
80
81 let max_single_alloc =
83 (max_total_memory / 3).clamp(1024 * 1024, 8 * 1024 * 1024);
84
85 let max_table_elements = (256 * cpu_cores.max(2)).min(2_048);
87
88 Self {
89 max_wasm_stack: 1024 * 1024, memory_size,
91 max_single_alloc,
92 max_total_memory,
93 max_table_elements,
94 aggressive_compilation: cpu_cores >= 4,
97 }
98 }
99}
100
101pub fn resolve_wasm_limits(spec: Option<MachineSpec>) -> WasmLimits {
107 let resolved = crate::config::resolve_spec(spec.as_ref());
108 WasmLimits::build(resolved.ram_mb, resolved.cpu_cores)
109}
110
111#[derive(Debug, Error, Clone)]
112pub enum ContractError {
113 #[error("memory allocation failed: {details}")]
114 MemoryAllocationFailed { details: String },
115
116 #[error("invalid pointer: {pointer}")]
117 InvalidPointer { pointer: usize },
118
119 #[error("write out of bounds: offset {offset} >= allocation size {size}")]
120 WriteOutOfBounds { offset: usize, size: usize },
121
122 #[error("allocation size {size} exceeds maximum of {max} bytes")]
123 AllocationTooLarge { size: usize, max: usize },
124
125 #[error("total memory {total} exceeds maximum of {max} bytes")]
126 TotalMemoryExceeded { total: usize, max: usize },
127
128 #[error("memory allocation would overflow")]
129 AllocationOverflow,
130
131 #[error("linker error [{function}]: {details}")]
132 LinkerError {
133 function: &'static str,
134 details: String,
135 },
136}
137
138#[derive(Debug)]
139pub struct MemoryManager {
140 memory: Vec<u8>,
141 map: HashMap<usize, usize>,
142 pub store_limits: StoreLimits,
143 max_single_alloc: usize,
144 max_total_memory: usize,
145}
146
147impl MemoryManager {
148 pub fn from_limits(limits: &WasmLimits) -> Self {
150 Self {
151 memory: Vec::new(),
152 map: HashMap::new(),
153 store_limits: StoreLimitsBuilder::new()
155 .memory_size(limits.memory_size)
156 .table_elements(limits.max_table_elements) .instances(1)
158 .tables(1)
159 .memories(1)
160 .trap_on_grow_failure(true)
161 .build(),
162 max_single_alloc: limits.max_single_alloc,
163 max_total_memory: limits.max_total_memory,
164 }
165 }
166}
167
168impl Default for MemoryManager {
169 fn default() -> Self {
170 Self::from_limits(&WasmLimits::default())
171 }
172}
173
174pub const MAX_FUEL: u64 = 10_000_000;
176pub const MAX_FUEL_COMPILATION: u64 = 50_000_000;
178
179impl MemoryManager {
180 pub fn alloc(&mut self, len: usize) -> Result<usize, ContractError> {
181 if len > self.max_single_alloc {
183 return Err(ContractError::AllocationTooLarge {
184 size: len,
185 max: self.max_single_alloc,
186 });
187 }
188
189 let current_len = self.memory.len();
190
191 let new_len = current_len
193 .checked_add(len)
194 .ok_or(ContractError::AllocationOverflow)?;
195
196 if new_len > self.max_total_memory {
197 return Err(ContractError::TotalMemoryExceeded {
198 total: new_len,
199 max: self.max_total_memory,
200 });
201 }
202
203 self.memory.resize(new_len, 0);
204 self.map.insert(current_len, len);
205 Ok(current_len)
206 }
207
208 pub fn write_byte(
209 &mut self,
210 start_ptr: usize,
211 offset: usize,
212 data: u8,
213 ) -> Result<(), ContractError> {
214 let len = self
216 .map
217 .get(&start_ptr)
218 .ok_or(ContractError::InvalidPointer { pointer: start_ptr })?;
219
220 if offset >= *len {
222 return Err(ContractError::WriteOutOfBounds { offset, size: *len });
223 }
224
225 self.memory[start_ptr + offset] = data;
226 Ok(())
227 }
228
229 pub fn read_byte(&self, ptr: usize) -> Result<u8, ContractError> {
230 if ptr >= self.memory.len() {
231 return Err(ContractError::InvalidPointer { pointer: ptr });
232 }
233 Ok(self.memory[ptr])
234 }
235
236 pub fn read_data(&self, ptr: usize) -> Result<&[u8], ContractError> {
237 let len = self
238 .map
239 .get(&ptr)
240 .ok_or(ContractError::InvalidPointer { pointer: ptr })?;
241 Ok(&self.memory[ptr..ptr + len])
242 }
243
244 pub fn get_pointer_len(&self, ptr: usize) -> isize {
245 let Some(result) = self.map.get(&ptr) else {
246 return -1;
247 };
248 *result as isize
249 }
250
251 pub fn add_data_raw(
252 &mut self,
253 bytes: &[u8],
254 ) -> Result<usize, ContractError> {
255 let ptr = self.alloc(bytes.len())?;
256 for (index, byte) in bytes.iter().enumerate() {
257 self.memory[ptr + index] = *byte;
258 }
259 Ok(ptr)
260 }
261}
262
263pub fn create_secure_wasmtime_config(limits: &WasmLimits) -> Config {
269 let mut config = Config::default();
270
271 config.consume_fuel(true);
273
274 config.max_wasm_stack(limits.max_wasm_stack);
276
277 let opt_level = if limits.aggressive_compilation {
280 wasmtime::OptLevel::SpeedAndSize
281 } else {
282 wasmtime::OptLevel::Speed
283 };
284 config.cranelift_opt_level(opt_level);
285
286 config
287}
288
289pub struct WasmRuntime {
294 pub engine: Engine,
295 pub limits: WasmLimits,
296}
297
298impl WasmRuntime {
299 pub fn new(spec: Option<MachineSpec>) -> Result<Self, wasmtime::Error> {
302 let limits = resolve_wasm_limits(spec);
303 let engine = Engine::new(&create_secure_wasmtime_config(&limits))?;
304 Ok(Self { engine, limits })
305 }
306}
307
308pub fn generate_linker(
309 engine: &Engine,
310) -> Result<Linker<MemoryManager>, ContractError> {
311 let mut linker: Linker<MemoryManager> = Linker::new(engine);
312
313 linker
315 .func_wrap(
316 "env",
317 "pointer_len",
318 |caller: Caller<'_, MemoryManager>, pointer: i32| {
319 caller.data().get_pointer_len(pointer as usize) as u32
320 },
321 )
322 .map_err(|e| ContractError::LinkerError {
323 function: "pointer_len",
324 details: e.to_string(),
325 })?;
326
327 linker
328 .func_wrap(
329 "env",
330 "alloc",
331 |mut caller: Caller<'_, MemoryManager>,
332 len: u32|
333 -> Result<u32, WasmError> {
334 caller
335 .data_mut()
336 .alloc(len as usize)
337 .map(|ptr| ptr as u32)
338 .map_err(WasmError::from)
339 },
340 )
341 .map_err(|e| ContractError::LinkerError {
342 function: "alloc",
343 details: e.to_string(),
344 })?;
345
346 linker
347 .func_wrap(
348 "env",
349 "write_byte",
350 |mut caller: Caller<'_, MemoryManager>,
351 ptr: u32,
352 offset: u32,
353 data: u32|
354 -> Result<(), WasmError> {
355 caller
356 .data_mut()
357 .write_byte(ptr as usize, offset as usize, data as u8)
358 .map_err(WasmError::from)
359 },
360 )
361 .map_err(|e| ContractError::LinkerError {
362 function: "write_byte",
363 details: e.to_string(),
364 })?;
365
366 linker
367 .func_wrap(
368 "env",
369 "read_byte",
370 |caller: Caller<'_, MemoryManager>,
371 index: i32|
372 -> Result<u32, WasmError> {
373 let ptr = usize::try_from(index).map_err(|_| {
374 ContractError::InvalidPointer { pointer: 0 }
375 })?;
376 caller
377 .data()
378 .read_byte(ptr)
379 .map(|b| b as u32)
380 .map_err(WasmError::from)
381 },
382 )
383 .map_err(|e| ContractError::LinkerError {
384 function: "read_byte",
385 details: e.to_string(),
386 })?;
387
388 Ok(linker)
389}