charms_app_runner/
lib.rs

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