iVendNextDevelopers Request a sandbox

8. Sandboxed functions

For: a builder whose logic must run inside a sale and give the same answer on the server and on an offline till. Reference implementation: the kit's README and contract (the commands below are the README's) · the first measurements are summarised in the repository's README.

A sandboxed function is a small program you write in Rust and compile to WebAssembly. The shop would run the same file on its server and on the till, so a decision gives the same answer online and offline. This chapter shows how to build one and check it yourself with the kit.

Read this first

When to use it

Rung 5 of the ladder (chapter 3), the last before asking for an extension point (chapter 9): only when a decision must be computed by your own logic and must also work on a till with no network. Try configuration (chapter 4) and a till key (chapter 6) first. Write it in Rust, the language measured. A function that only ever runs on the server may also be compiled from JavaScript (see What is refused).

Steps

  1. Install what the kit needs.

    • Rust, installed with rustup at its default place (the kit looks for ~/.cargo/bin/cargo), with the wasm32-unknown-unknown target.
    • Node 22 or later.
    • Python 3.10 or later with the one library the README names, wasmtime==49.0.0.

    Then build the two meters once, each with cargo build --release: the fuel meter in tools/wasm_kit/meter and the stack meter in tools/wasm_kit/stackmeter. The kit looks for them at those paths, and publishing fails without them. Run every command from the repository's root. (The two test hosts the kit uses are in tools/wasm_kit/vendor/ and tools/wasm_kit/hosts/.)

  2. Copy the template and write your function. Edit one file only, src/point.rs. It ships with a discount adjuster as the worked example.

    cp -r tools/wasm_kit/template my_point
    
  3. Write to the contract.

    • One message in, one answer out, both UTF-8 JSON.
    • Numbers are whole numbers. Money is in integer minor units, quantity in integer thousandths (qty_milli). No fractions, signs or exponents in numbers; nesting at most 32.
    • Check the input in the published order. On the first bad field, answer {"error":"invalid_input","at":"<first failing path>"}.
    • The function sees only what the host gives it: pp.read_input, pp.write_output, pp.log, an exported run() and its memory. A clock, randomness, the network or a start function is refused when the module loads.
    • Limits: fuel 11 000 000 units; memory 10 MiB; an answer at most 512 KiB; the log 1 KiB; a fresh instance on every call.
    • Keep the template's own JSON code. It uses whole numbers only, on purpose; a common JSON library brings in floating-point instructions, which a till function may not have.
  4. Build. This runs your Rust tests first. Give the folder of your crate, relative or absolute.

    python3 tools/wasm_kit/kit.py build my_point
    

    The raw module lands in my_point/target/wasm32-unknown-unknown/release/, named after your crate.

  5. Publish. Scan, stack meter, fuel meter, memory cap, size cap and load checks, in that order; it writes a *.publish.json with the size, sha256 and bounds.

    python3 tools/wasm_kit/kit.py publish <raw.wasm> <out.wasm>
    

    A till function may have no floating-point instruction, no SIMD, no atomics, keeps recursion shallow (the stack bound is 10 000 units, written into the module), and is at most 256 KB once metered. A function that never runs on a till has a shorter path, in step 7.

  6. Prove the same answer on both hosts, on samples of your own in the shape of the template's samples/samples.json (an input and the expected answer; must_answer marks the realistic ones, the rest must at least agree). A stress basket may run out of fuel, but it must do so identically on both hosts:

    python3 tools/wasm_kit/kit.py parity <out.wasm> my_point/samples/samples.json --metered
    
  7. Run the local admission. It takes your raw build, publishes it itself and runs every sample on both hosts. A trap on a realistic sample, a split between the hosts or a wrong answer is a refusal, named. A module that already carries a meter is refused, because a module's own counter is never trusted:

    python3 tools/wasm_kit/kit.py admit <raw.wasm> my_point/samples/samples.json
    

    A function that never runs on a till skips steps 5 and 6 and runs the same command with --server-only added. The module is then checked as given, with no size cap, and run on the server host only: no browser host, no parity, no kit meter (the server host's own fuel limit still applies), and floats and SIMD are allowed, atomics never.

    This is the check you run yourself. It does not certify your module; what we run on a submission, and the signing of a module's sha256 that is planned for it, are not covered by the kit yet (chapter 17).

What is refused, and why

When your service is down, and when the till is offline

Technical notes

What the kit has proven

What it has not

Why it is built this way

See also

3. The ladder and the four rules · 6. Till keys · 9. The extension-point catalogue and requests · 14. Testing and release · 17. Submitting, support, feedback

This page in the kit