Problem Statement / Feature Objective
Tariff evaluation requires executing user-defined rate functions in a sandboxed environment. The current approach compiles tariff formulas to native Rust closures, which poses a critical security risk: a malicious or buggy tariff formula can induce arbitrary memory corruption, infinite loops, or side-channel leakage of neighboring meter data. A WebAssembly (WASM) sandbox must be introduced that compiles tariff functions to WASM bytecode at registration time, executes them with precise fuel metering, enforces deterministic execution, and exposes a limited host-import surface for reading meter readings and producing scalar outputs.
Technical Invariants & Bounds
- Fuel metering: each WASM tariff function is allocated 1,000,000 fuel units per invocation; fuel consumed per instruction is per the standard wasmi/infinity fuel schedule.
- Execution timeout: hard wall-clock limit of 50 ms per evaluation, enforced by a tokio
timeout wrapping the WASM call.
- Memory limit: WASM linear memory capped at 64 pages (4 MB); any
memory.grow beyond this traps.
- Stack depth: max call stack depth of 128 frames; enforced by the WASM interpreter's call-depth counter.
- Host imports exposed:
read_meter_value(key: i32, ptr: i32) -> i32 — copies a f64 from the meter reading table to WASM linear memory at ptr; store_result(ptr: i32) -> i32 — reads a string from WASM memory and returns a parsed tariff tier index.
- WASM binary size limit: 256 KB per tariff function; compilation must complete within 200 ms.
- Determinism: WASM execution must be fully deterministic across runs; no access to host clock, random, or I/O.
Codebase Navigation Guide
src/tariff/wasm_engine.rs — new module: WASM instantiation, fuel metering, import resolution, invocation.
src/tariff/compiler.rs — existing tariff AST-to-native compiler; add method compile_to_wasm(ast: &TariffAST) -> Vec<u8> that emits hand-rolled WASM bytecode (or uses wasm_encoder crate) for arithmetic + conditional logic.
src/tariff/registry.rs — tariff function registry; change storage to hold (wasm_bytes: Vec<u8>, compiled_instance: wasmi::Instance).
src/tariff/evaluator.rs — evaluation entry point; replace direct closure invocation with sandboxed_evaluate(tariff_id, meter_readings) -> Result<f64>.
src/host/meter_store.rs — provides the host-side implementations of the import functions; bridges to the in-memory meter reading table.
Cargo.toml — add dependencies: wasmi = { version = "0.31", features = ["core"] }, wasm-encoder = "0.41".
tests/tariff/wasm_sandbox_test.rs — test suite covering fuel exhaustion, memory exhaustion, infinite loop detection, and deterministic output.
Implementation Blueprint
- In
wasm_engine.rs, define SandboxConfig { fuel_limit: u64, memory_pages: u32, stack_depth: u32 }. Implement struct WasmSandbox { instance: wasmi::Instance, store: wasmi::Store<TariffCtx> } where TariffCtx holds a reference to the current meter readings.
- Initialize the store with
wasmi::Store::new(wasmi::Engine::default(), ctx). Before calling instance.get_export, configure the FuelConsumptionMeter via store.set_fuel(fuel_limit).
- Implement the host import resolver: create a
wasmi::Externs that maps the imported function names to closures that read/write from WASM linear memory via store.get_memory(&instance, "memory"). Use memory.read(ptr, buf) and memory.write(ptr, buf) for safe access.
- In
compiler.rs, emit WASM bytecode for arithmetic expressions using wasm_encoder::Module. The emitted module exports a single evaluate() function that takes no parameters (reads via imports) and returns an f64. Use standard WASM opcodes for add, sub, mul, div, comparisons, and br_if for conditionals.
- In
registry.rs, store the compiled WASM bytes per tariff ID. On first evaluation, instantiate the WASM module and cache the instance. On subsequent evaluations with the same tariff and config, reset the fuel counter and re-run. If fuel or wall-clock limit is exceeded, return a TariffError::SandboxViolation that includes the exhausted resource type.
- Add a
wasmtime-style instance recycling pool in wasm_engine.rs to avoid instantiation overhead: pre-instantiate up to 32 cached instances per unique tariff, using a mpsc channel as a free-list.
- Write property-based tests: generate random arithmetic expressions, compile to both native and WASM, verify outputs match within 1 ULP of f64 precision. Also craft adversarial inputs to test fuel exhaustion — ensure the sandbox traps before host resources are impacted.
Problem Statement / Feature Objective
Tariff evaluation requires executing user-defined rate functions in a sandboxed environment. The current approach compiles tariff formulas to native Rust closures, which poses a critical security risk: a malicious or buggy tariff formula can induce arbitrary memory corruption, infinite loops, or side-channel leakage of neighboring meter data. A WebAssembly (WASM) sandbox must be introduced that compiles tariff functions to WASM bytecode at registration time, executes them with precise fuel metering, enforces deterministic execution, and exposes a limited host-import surface for reading meter readings and producing scalar outputs.
Technical Invariants & Bounds
timeoutwrapping the WASM call.memory.growbeyond this traps.read_meter_value(key: i32, ptr: i32) -> i32— copies a f64 from the meter reading table to WASM linear memory at ptr;store_result(ptr: i32) -> i32— reads a string from WASM memory and returns a parsed tariff tier index.Codebase Navigation Guide
src/tariff/wasm_engine.rs— new module: WASM instantiation, fuel metering, import resolution, invocation.src/tariff/compiler.rs— existing tariff AST-to-native compiler; add methodcompile_to_wasm(ast: &TariffAST) -> Vec<u8>that emits hand-rolled WASM bytecode (or useswasm_encodercrate) for arithmetic + conditional logic.src/tariff/registry.rs— tariff function registry; change storage to hold(wasm_bytes: Vec<u8>, compiled_instance: wasmi::Instance).src/tariff/evaluator.rs— evaluation entry point; replace direct closure invocation withsandboxed_evaluate(tariff_id, meter_readings) -> Result<f64>.src/host/meter_store.rs— provides the host-side implementations of the import functions; bridges to the in-memory meter reading table.Cargo.toml— add dependencies:wasmi = { version = "0.31", features = ["core"] },wasm-encoder = "0.41".tests/tariff/wasm_sandbox_test.rs— test suite covering fuel exhaustion, memory exhaustion, infinite loop detection, and deterministic output.Implementation Blueprint
wasm_engine.rs, defineSandboxConfig { fuel_limit: u64, memory_pages: u32, stack_depth: u32 }. Implementstruct WasmSandbox { instance: wasmi::Instance, store: wasmi::Store<TariffCtx> }whereTariffCtxholds a reference to the current meter readings.wasmi::Store::new(wasmi::Engine::default(), ctx). Before callinginstance.get_export, configure theFuelConsumptionMeterviastore.set_fuel(fuel_limit).wasmi::Externsthat maps the imported function names to closures that read/write from WASM linear memory viastore.get_memory(&instance, "memory"). Usememory.read(ptr, buf)andmemory.write(ptr, buf)for safe access.compiler.rs, emit WASM bytecode for arithmetic expressions usingwasm_encoder::Module. The emitted module exports a singleevaluate()function that takes no parameters (reads via imports) and returns an f64. Use standard WASM opcodes for add, sub, mul, div, comparisons, and br_if for conditionals.registry.rs, store the compiled WASM bytes per tariff ID. On first evaluation, instantiate the WASM module and cache the instance. On subsequent evaluations with the same tariff and config, reset the fuel counter and re-run. If fuel or wall-clock limit is exceeded, return aTariffError::SandboxViolationthat includes the exhausted resource type.wasmtime-style instance recycling pool inwasm_engine.rsto avoid instantiation overhead: pre-instantiate up to 32 cached instances per unique tariff, using ampscchannel as a free-list.