1use anyhow::{Result, bail, ensure};
2use charms_data::{App, B32, Data, Transaction, is_simple_transfer, util};
3use rand::{RngExt, SeedableRng, rngs::StdRng};
4use sha2::{Digest, Sha256};
5use std::{
6 collections::BTreeMap,
7 io::Write,
8 sync::{Arc, Mutex},
9};
10use wasmi::{Caller, CompilationMode, Config, Engine, Extern, Linker, Memory, Module, Store};
11
12#[derive(Clone)]
13pub struct AppRunner {
14 pub count_cycles: bool,
15 pub engine: Engine,
16}
17
18#[derive(Clone)]
19struct HostState {
20 stdin: Arc<Mutex<Vec<u8>>>, stderr: Arc<Mutex<dyn Write>>, prng: Arc<Mutex<StdRng>>,
23}
24
25fn read_i32(memory: &Memory, caller: &mut Caller<'_, HostState>, ptr: i32) -> Result<i32> {
27 let data = read_memory(memory, caller, ptr as usize, 4)?;
28 Ok(i32::from_le_bytes(data.try_into().unwrap()))
29}
30
31fn write_i32(
32 memory: &Memory,
33 caller: &mut Caller<'_, HostState>,
34 ptr: i32,
35 value: i32,
36) -> Result<()> {
37 let data = value.to_le_bytes();
38 write_memory(memory, caller, ptr as usize, &data)
39}
40
41fn read_memory(
42 memory: &Memory,
43 caller: &mut Caller<'_, HostState>,
44 ptr: usize,
45 len: usize,
46) -> Result<Vec<u8>> {
47 let mut buffer = vec![0; len];
48 memory.read(caller, ptr, &mut buffer)?;
49 Ok(buffer)
50}
51
52fn write_memory(
53 memory: &Memory,
54 caller: &mut Caller<'_, HostState>,
55 ptr: usize,
56 data: &[u8],
57) -> Result<()> {
58 memory.write(caller, ptr, data)?;
59 Ok(())
60}
61
62fn fd_read_impl(
63 mut caller: Caller<'_, HostState>,
64 fd: i32,
65 iovs: i32,
66 iovs_len: i32,
67 nread: i32,
68) -> Result<i32> {
69 if fd != 0 {
70 return Ok(-1); }
72
73 let memory = caller
74 .get_export("memory")
75 .and_then(Extern::into_memory)
76 .ok_or_else(|| anyhow::anyhow!("No memory export"))?;
77
78 let iov_size = 8;
80 let mut iov_info = Vec::new();
81 for i in 0..iovs_len {
82 let iov_addr = iovs + i * iov_size;
83 let buf_ptr = read_i32(&memory, &mut caller, iov_addr).unwrap() as usize;
84 let buf_len = read_i32(&memory, &mut caller, iov_addr + 4).unwrap() as usize;
85 iov_info.push((buf_ptr, buf_len));
86 }
87
88 let stdin_data = {
90 let state = caller.data();
91 let mut stdin = state.stdin.lock().unwrap();
92
93 let mut total_read = 0;
94 let mut operations = Vec::new();
95
96 for (buf_ptr, buf_len) in iov_info {
97 let to_read = buf_len.min(stdin.len());
99 if to_read == 0 {
100 break; }
102 let data = stdin.drain(..to_read).collect::<Vec<_>>();
103 operations.push((buf_ptr, data));
104 total_read += to_read;
105 }
106
107 (operations, total_read)
108 };
109
110 for (buf_ptr, data) in stdin_data.0 {
112 write_memory(&memory, &mut caller, buf_ptr, &data).unwrap();
113 }
114
115 write_i32(&memory, &mut caller, nread, stdin_data.1 as i32)?;
117
118 Ok(0) }
120
121fn fd_write_impl(
122 mut caller: Caller<'_, HostState>,
123 fd: i32,
124 iovs: i32,
125 iovs_len: i32,
126 nwritten: i32,
127) -> Result<i32> {
128 if fd != 2 {
129 bail!("can only write to stderr"); }
131
132 let memory = caller
133 .get_export("memory")
134 .and_then(Extern::into_memory)
135 .ok_or_else(|| anyhow::anyhow!("No memory export"))?;
136
137 let iov_size = 8; let mut total_written = 0;
140 let mut all_data = Vec::new();
141
142 for i in 0..iovs_len {
143 let iov_addr = iovs + i * iov_size;
144 let buf_ptr = read_i32(&memory, &mut caller, iov_addr)? as usize;
146 let buf_len = read_i32(&memory, &mut caller, iov_addr + 4)? as usize;
147
148 let data = read_memory(&memory, &mut caller, buf_ptr, buf_len)?;
150 all_data.extend_from_slice(&data);
151 total_written += buf_len;
152 }
153
154 {
156 let state = caller.data_mut();
157 let mut stderr = state.stderr.lock().unwrap();
158 stderr.write_all(&all_data)?;
159 }
160
161 write_i32(&memory, &mut caller, nwritten, total_written as i32)?;
163
164 Ok(0) }
166
167fn fd_write(
168 caller: Caller<'_, HostState>,
169 fd: i32,
170 iovs: i32,
171 iovs_len: i32,
172 nwritten: i32,
173) -> i32 {
174 let result = fd_write_impl(caller, fd, iovs, iovs_len, nwritten);
175 result.unwrap_or_else(|e| {
176 eprintln!("error: {}", e);
177 -1
178 })
179}
180
181fn fd_read(caller: Caller<'_, HostState>, fd: i32, iovs: i32, iovs_len: i32, nread: i32) -> i32 {
182 fd_read_impl(caller, fd, iovs, iovs_len, nread).unwrap_or_else(|e| {
183 eprintln!("error: {}", e);
184 -1
185 })
186}
187
188fn environ_sizes_get_impl(
189 mut caller: Caller<'_, HostState>,
190 environc_ptr: i32,
191 environ_buf_size_ptr: i32,
192) -> Result<i32> {
193 let memory = caller
194 .get_export("memory")
195 .and_then(Extern::into_memory)
196 .ok_or_else(|| anyhow::anyhow!("No memory export"))?;
197
198 write_i32(&memory, &mut caller, environc_ptr, 0)?;
200 write_i32(&memory, &mut caller, environ_buf_size_ptr, 0)?;
202
203 Ok(0) }
205
206fn environ_get_impl(
207 _caller: Caller<'_, HostState>,
208 _environ_ptr: i32,
209 _environ_buf_ptr: i32,
210) -> Result<i32> {
211 Ok(0) }
214
215fn environ_sizes_get(
216 caller: Caller<'_, HostState>,
217 environc_ptr: i32,
218 environ_buf_size_ptr: i32,
219) -> i32 {
220 environ_sizes_get_impl(caller, environc_ptr, environ_buf_size_ptr).unwrap_or_else(|e| {
221 eprintln!("error: {}", e);
222 -1
223 })
224}
225
226fn environ_get(caller: Caller<'_, HostState>, environ_ptr: i32, environ_buf_ptr: i32) -> i32 {
227 environ_get_impl(caller, environ_ptr, environ_buf_ptr).unwrap_or_else(|e| {
228 eprintln!("error: {}", e);
229 -1
230 })
231}
232
233fn random_get(mut caller: Caller<'_, HostState>, buf: i32, buf_len: i32) -> i32 {
234 let memory = caller
235 .get_export("memory")
236 .and_then(Extern::into_memory)
237 .expect("No memory export");
238 let mut bytes = vec![0u8; buf_len as usize];
239 caller.data().prng.lock().unwrap().fill(&mut bytes);
240 memory
241 .write(&mut caller, buf as usize, &bytes)
242 .expect("failed to write random bytes");
243 0
244}
245
246const MAX_FUEL_PER_RUN: u64 = 1000000000;
247
248impl AppRunner {
249 pub fn new(count_cycles: bool) -> Self {
250 let mut config = Config::default();
251 if count_cycles {
252 config.consume_fuel(true);
253 }
254 config.compilation_mode(CompilationMode::Lazy);
255 Self {
256 count_cycles,
257 engine: Engine::new(&config),
258 }
259 }
260
261 pub fn vk(&self, binary: &[u8]) -> B32 {
262 let hash = Sha256::digest(binary);
263 B32(hash.into())
264 }
265
266 pub fn run(
267 &self,
268 app_binary: &[u8],
269 app: &App,
270 tx: &Transaction,
271 x: &Data,
272 w: &Data,
273 ) -> Result<u64> {
274 let vk = self.vk(app_binary);
275 ensure!(app.vk == vk, "app.vk mismatch");
276
277 let stdin_content = util::write(&(app, tx, x, w))?;
278
279 let prng_seed: [u8; 32] = Sha256::digest(&stdin_content).into();
280 let state = HostState {
281 stdin: Arc::new(Mutex::new(stdin_content)),
282 stderr: Arc::new(Mutex::new(std::io::stderr())),
283 prng: Arc::new(Mutex::new(StdRng::from_seed(prng_seed))),
284 };
285
286 let mut store = Store::new(&self.engine, state.clone());
287 if self.count_cycles {
288 store.set_fuel(MAX_FUEL_PER_RUN)?;
289 }
290 let mut linker = Linker::new(&self.engine);
291
292 linker.func_wrap("wasi_snapshot_preview1", "fd_write", fd_write)?;
293 linker.func_wrap("wasi_snapshot_preview1", "fd_read", fd_read)?;
294 linker.func_wrap("wasi_snapshot_preview1", "environ_get", environ_get)?;
295 linker.func_wrap(
296 "wasi_snapshot_preview1",
297 "environ_sizes_get",
298 environ_sizes_get,
299 )?;
300 linker.func_wrap(
301 "wasi_snapshot_preview1",
302 "proc_exit",
303 |_: Caller<'_, HostState>, _: i32| {},
304 )?;
305 linker.func_wrap("wasi_snapshot_preview1", "random_get", random_get)?;
306
307 let module = Module::new(&self.engine, app_binary)?;
308
309 let instance = linker.instantiate_and_start(&mut store, &module)?;
310
311 let Some(main_func) = instance.get_func(&store, "_start") else {
312 unreachable!("we should have a main function")
313 };
314 let result = main_func.typed::<(), ()>(&store)?.call(&mut store, ());
315
316 state.stderr.lock().unwrap().flush()?;
317
318 result.map_err(|e| anyhow::anyhow!("error running wasm: {:?}", e))?;
319
320 let cycles = match self.count_cycles {
321 true => MAX_FUEL_PER_RUN - store.get_fuel()?,
322 false => 0,
323 };
324 Ok(cycles)
325 }
326
327 pub fn run_all(
328 &self,
329 app_binaries: &BTreeMap<B32, Vec<u8>>,
330 tx: &Transaction,
331 app_public_inputs: &BTreeMap<App, Data>,
332 app_private_inputs: &BTreeMap<App, Data>,
333 ) -> Result<Vec<u64>> {
334 let empty = Data::empty();
335 let app_cycles = app_public_inputs
336 .iter()
337 .map(|(app, x)| {
338 let w = app_private_inputs.get(app).unwrap_or(&empty);
339 if x.is_empty() && w.is_empty() && is_simple_transfer(app, tx) {
340 eprintln!("➡️ simple transfer w.r.t. app: {}", app);
341 return Ok(0);
342 }
343 match app_binaries.get(&app.vk) {
344 Some(app_binary) => {
345 let cycles = self.run(app_binary, app, tx, x, w)?;
346 eprintln!("✅ app contract satisfied: {}", app);
347 Ok(cycles)
348 }
349 None => bail!("app binary not found: {}", app),
350 }
351 })
352 .collect::<Result<_>>()?;
353
354 Ok(app_cycles)
355 }
356}