A realm that runs Brainfuck programs on chain, a slice at a time.
It is a demo of p/moul/x/vm/bf (the machine) and
p/moul/x/vm/vmkit (the host ABI, the fuel meter, the
instance store), and carries no logic of its own.
What it exists to show is the thing gno realm code cannot do for itself: a
program that runs out of fuel does not fail, it pauses. The realm keeps the
snapshot, and the next caller pays for the next slice. Upload a program, call
Step a few times, and watch one computation finish across several
transactions.
Calls
function
what
Upload(src, input, budget)
compile and store a program, returns its id. Compilation happens here, so a malformed program is rejected by the transaction that submitted it
Step(id, fuel)
run one slice and keep the snapshot. Anyone may pay for a slice, not only the owner
Remove(id)
delete an instance. Owner only
Render("/") lists the instances and offers three sample programs.
Render("/<id>") shows one instance: its compiled program, status, fuel, and
output.
Limits
Everything a caller can grow is bounded, because all of it is storage somebody
pays a deposit on: 64 instances, 64 KiB of source, 1 KiB of input, 4 KiB of
output, 5,000,000 fuel per slice, 30,000 tape cells. A guest that writes past
the output cap is trapped rather than truncated, so the rendered output is never
a lie.
Untrusted input
A program's source is caller-supplied, and everything outside the eight
operators is a comment that may hold anything at all. The page never renders it:
it shows the program the machine actually compiled, which cannot contain a
backtick or a newline and so cannot break out of its code fence. Guest output is
arbitrary bytes and is escaped the same way, backtick included.
No instance is funded, so Host.Send always returns ErrNotGranted. That is
the capability rule doing its job, not a missing feature.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
Dependency graph:
🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.
Overview
Package bfdemo is the playground for the guest-VM work: a realm that runs Brainfuck programs on chain, a slice at a time.
It is a demo of two libraries and carries no logic of its own: p/moul/x/vm/bf(/p/moul/x/vm/bf/v0) is the machine, and p/moul/x/vm/vmkit(/p/moul/x/vm/vmkit/v0) is the host ABI, the fuel meter and the instance store.
What it exists to show is the thing gno realm code cannot do for itself: a program that runs out of fuel does not fail, it pauses. The realm keeps the snapshot, and the next caller pays for the next slice. Upload a program, call Step a few times, and watch one computation finish across several transactions.
1const( 2// MaxInstances is how many programs the realm keeps at once. Past it, 3// an upload has to wait for someone to remove one. 4MaxInstances=64 5// MaxOutput caps the bytes one program may write. Past it the guest is 6// trapped rather than truncated, so rendered output is never a lie. 7MaxOutput=4096 8// MaxInput caps the call input a program can be given. 9MaxInput=102410// DefaultFuel is the budget an upload gets when it asks for none, and11// the slice size Step uses when asked for none.12DefaultFuel=10000013// MaxSliceFuel bounds one transaction's work regardless of what the14// caller asked for.15MaxSliceFuel=500000016)
Step runs one slice of the instance: up to `fuel` guest ops, then stop and keep the snapshot. Anyone may pay for a slice, not only the owner: a paused program that only its owner can advance is a worse demo and no safer, since the program and its budget were both fixed at upload.
Upload compiles src and stores it as a new instance, returning its id.
Compilation happens here rather than at the first Step, so an unbalanced program is rejected by the transaction that submitted it instead of costing somebody else the gas later.