1
//! Shape contracts for eval-mode wasm modules. The rpc eval channel
2
//! depends on these exports being present; without them host fns that
3
//! produce typed entity returns (`list-accounts`, `get-account`, ...)
4
//! trap at runtime with "module missing 'alloc_<kind>' export".
5

            
6
use nomiscript::{Compiler, Program, Reader};
7
use wasmparser::{ExternalKind, Parser, Payload};
8

            
9
2
fn module_export_names(wasm: &[u8]) -> Vec<String> {
10
2
    let mut names = Vec::new();
11
100
    for payload in Parser::new(0).parse_all(wasm) {
12
100
        let payload = payload.expect("wasm parse");
13
100
        if let Payload::ExportSection(reader) = payload {
14
34
            for export in reader {
15
34
                let export = export.expect("export read");
16
34
                if matches!(export.kind, ExternalKind::Func) {
17
32
                    names.push(export.name.to_string());
18
32
                }
19
            }
20
98
        }
21
    }
22
2
    names
23
2
}
24

            
25
/// Eval-mode modules register the per-entity `alloc_<kind>` helpers
26
/// (one `struct.new` over the typed fields). They MUST also be
27
/// exported — host fns reach them via `caller.get_export("alloc_<kind>")`
28
/// when producing entity refs from `server::command::*` results.
29
/// The helpers stay unexported in script mode where they aren't
30
/// needed.
31
#[test]
32
1
fn eval_mode_exports_every_entity_allocator() {
33
1
    let program: Program = Reader::parse("nil").unwrap();
34
1
    let mut compiler = Compiler::new();
35
1
    let mut symbols = nomiscript::SymbolTable::with_builtins();
36
1
    let (bytes, _ty) = compiler
37
1
        .compile_eval_with_type(&program, &mut symbols)
38
1
        .expect("compile eval");
39

            
40
1
    let exports = module_export_names(&bytes);
41
1
    let required = [
42
1
        "alloc_account",
43
1
        "alloc_commodity_entity",
44
1
        "alloc_transaction",
45
1
        "alloc_split",
46
1
        "alloc_tag_entity",
47
1
        "alloc_price",
48
1
        "alloc_ssh_key",
49
1
    ];
50
7
    for name in required {
51
7
        assert!(
52
77
            exports.iter().any(|e| e == name),
53
            "eval-mode module missing export {name:?}. exports: {exports:?}"
54
        );
55
    }
56
1
}
57

            
58
#[test]
59
1
fn eval_mode_exports_nomi_eval_and_pair_new() {
60
    // Sanity check: locks in the public-export surface so a regression
61
    // in one of the canonical exports trips immediately.
62
1
    let program: Program = Reader::parse("nil").unwrap();
63
1
    let mut compiler = Compiler::new();
64
1
    let mut symbols = nomiscript::SymbolTable::with_builtins();
65
1
    let (bytes, _ty) = compiler
66
1
        .compile_eval_with_type(&program, &mut symbols)
67
1
        .expect("compile eval");
68
1
    let exports = module_export_names(&bytes);
69
1
    assert!(exports.contains(&"nomi-eval".to_string()), "{exports:?}");
70
1
    assert!(exports.contains(&"pair_new".to_string()), "{exports:?}");
71
1
}