Help us shape the future of Go! Take the 2026 Go Developer Survey

vm

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Index

Constants

View Source
const (
	SectionInstructions uint16 = 0x01 // NUMB only
	SectionStringsPool  uint16 = 0x02
	SectionIntsPool     uint16 = 0x03
	SectionMaxRegisters uint16 = 0x04 // NUMB only; optional, absent => every bank defaults to maxRegDefault
)

Variables

View Source
var CurrentBytecodeVersion = BytecodeVersion{Major: 0, Minor: 1}

CurrentBytecodeVersion is the version Encode writes and the newest one the decoders read.

Functions

func Verify

func Verify(p Program) error

Verify statically checks that a program is safe to execute. It is opt-in: Exec assumes well-formed bytecode (the compiler's output always is) and does not call this. Run it on any program that did not come out of ir.Assemble in this process — anything decoded from a file or a wire.

A nil result guarantees the execution loop cannot read out of bounds: no truncated instruction, no jump into the middle of one, no out-of-range pool or register index, and no read of a register that was not written on every path reaching it.

It does not check that vars were supplied; for that see VerifyWithVars.

Types

type AssetMismatchError

type AssetMismatchError struct {
	Expected string
	Got      string
}

func (AssetMismatchError) Error

func (e AssetMismatchError) Error() string

type BadMetaValueError

type BadMetaValueError struct {
	Account string
	Key     string
	Raw     string
}

func (BadMetaValueError) Error

func (e BadMetaValueError) Error() string

type BytecodeVersion

type BytecodeVersion struct {
	Major uint16
	Minor uint16
}

BytecodeVersion identifies the bytecode wire format a program or vars blob was encoded with, as major.minor — two 16-bit header fields, major first. It versions the bytecode format only: the compiler and the library are versioned independently of it, and a new compiler release does not imply a new bytecode version.

The split carries the compatibility rule (see CanRead). A minor bump is additive — new opcodes, new sections, new optional operands — and leaves the meaning of everything an older writer could produce untouched, so a 1.1 reader runs 1.0 bytecode. A 1.0 reader does not accept 1.1 bytecode: it may happen to know every opcode a given blob uses, but that is not assumed. A major bump changes the meaning of existing encodings, so a 2.0 reader accepts no 1.x blob at all.

Major 0 is unstable: any 0.x change may change the meaning of existing encodings, so a 0.x reader accepts exactly its own version, older and newer minors alike. A host holding a 0.x blob that this build rejects should recompile the script from source rather than fail.

func PeekProgramVersion

func PeekProgramVersion(buf []byte) (BytecodeVersion, error)

PeekProgramVersion checks that buf starts with a valid NUMB header and returns its bytecode version, without parsing the sections that follow and without checking that this build can read it.

func PeekVarsVersion

func PeekVarsVersion(buf []byte) (BytecodeVersion, error)

PeekVarsVersion checks that buf starts with a valid NVAR header and returns its bytecode version, without parsing the sections that follow and without checking that this build can read it.

func (BytecodeVersion) CanRead

func (v BytecodeVersion) CanRead(encoded BytecodeVersion) bool

CanRead reports whether a reader at version v accepts a blob encoded with version encoded: the same major, and a minor no newer than the reader's. An unstable (0.x) reader accepts only its exact version.

func (BytecodeVersion) String

func (v BytecodeVersion) String() string

type CannotCastScopedAccountToString

type CannotCastScopedAccountToString struct {
	Account string
	Scope   string
}

func (CannotCastScopedAccountToString) Error

type DivideByZeroError

type DivideByZeroError struct {
	Numerator big.Int
}

func (DivideByZeroError) Error

func (e DivideByZeroError) Error() string

type ExecutionError

type ExecutionError interface {
	error
	// contains filtered or unexported methods
}

func Exec

func Exec[S Store](
	ctx context.Context,
	vm *Vm,
	vars *Vars,
	store S,
) (funds.ExecutionResult, ExecutionError)

type Instruction

type Instruction struct {
	Opcode byte
	A      byte
	B      byte
	C      byte
}

func NewBC

func NewBC(
	opcode Opcode,
	a byte,
	bc uint16,
) Instruction

func (Instruction) GetBC

func (i Instruction) GetBC() uint16

Little endian view of the b and c fields

type InternalError

type InternalError struct {
	Err error
}

InternalError signals a malformed program the VM cannot execute: a bug in whatever produced the bytecode, never a user-script error. Returned rather than panicked so the VM never crashes its host.

The mark violations land here rather than getting user-facing error types, since both are properties a verifier can decide from the instruction stream alone and neither is a legitimate outcome of a well-formed program.

func (InternalError) Error

func (e InternalError) Error() string

func (InternalError) Unwrap

func (e InternalError) Unwrap() error

type InvalidAccountName

type InvalidAccountName struct {
	Name string
}

func (InvalidAccountName) Error

func (e InvalidAccountName) Error() string

type InvalidAllotmentSum

type InvalidAllotmentSum struct {
	ActualSum big.Rat
}

func (InvalidAllotmentSum) Error

func (e InvalidAllotmentSum) Error() string

type InvalidColor

type InvalidColor struct {
	Color string
}

func (InvalidColor) Error

func (e InvalidColor) Error() string

type InvalidPostingError

type InvalidPostingError struct {
	Posting funds.Posting
}

InvalidPostingError is wrapped in an InternalError when a posting fails funds.ValidatePosting.

func (InvalidPostingError) Error

func (e InvalidPostingError) Error() string

type InvalidScope

type InvalidScope struct {
	Scope string
}

func (InvalidScope) Error

func (e InvalidScope) Error() string

type InvalidUncappedSource

type InvalidUncappedSource struct {
	Account string
}

func (InvalidUncappedSource) Error

func (e InvalidUncappedSource) Error() string

type MetadataNotFoundError

type MetadataNotFoundError struct {
	Account string
	Key     string
}

func (MetadataNotFoundError) Error

func (e MetadataNotFoundError) Error() string

type MissingFundsError

type MissingFundsError struct {
	Asset  string
	Needed *big.Int
	Got    *big.Int
}

func (MissingFundsError) Error

func (e MissingFundsError) Error() string

type NegativeAmountError

type NegativeAmountError struct {
	Amount big.Int
}

NegativeAmountError is a sent/saved amount that evaluated to negative — unlike NegativeBalanceError, it is not tied to any account.

func (NegativeAmountError) Error

func (e NegativeAmountError) Error() string

type NegativeBalanceError

type NegativeBalanceError struct {
	Account string
	Amount  big.Int
}

func (NegativeBalanceError) Error

func (e NegativeBalanceError) Error() string

type NegativePortionError

type NegativePortionError struct {
	Portion big.Rat
}

func (NegativePortionError) Error

func (e NegativePortionError) Error() string

type Opcode

type Opcode byte
const (
	// --- state & assertions (0x00) ---
	Op_SetCurrentAsset Opcode = 0x00

	Op_AssertSameAsset Opcode = 0x01

	// errors if the account name in str reg A is not well-formed
	Op_AssertValidAccount Opcode = 0x02

	// errors (NegativeBalanceError) if the amount in int reg A is negative;
	// B = account str reg (for the error)
	Op_AssertNonNegativeBalance Opcode = 0x03

	// checks the allotment leftover portion in reg A: errors if negative (portions
	// summing to > 1), and — when B == 1 (no `remaining` clause) — if non-zero
	Op_AssertLeftover Opcode = 0x04

	Op_CheckEnoughFunds Opcode = 0x05

	// errors if the color in str reg A is not well-formed
	Op_AssertValidColor Opcode = 0x06

	// errors (NegativeAmountError) if the amount in int reg A is negative —
	// a sent/saved amount, not tied to any account (unlike
	// Op_AssertNonNegativeBalance, which is a balance() read)
	Op_AssertNonNegativeAmount Opcode = 0x07

	// errors if the scope in str reg A is not well-formed
	Op_AssertValidScope Opcode = 0x08

	// errors (NegativePortionError) if the portion in reg A is negative — an
	// allotment clause portion
	Op_AssertNonNegativePortion Opcode = 0x09

	// errors (CannotCastScopedAccountToString) if the scope in str reg A is not
	// empty; B = account str reg (for the error)
	Op_AssertUnscoped Opcode = 0x0A

	// --- constants & variables (0x10) ---
	// may split into one opcode per expr_typ later
	Op_LoadInt Opcode = 0x10 // LoadConst (`Int)    -> b_c = const-pool index
	Op_LoadStr Opcode = 0x11 // LoadConst (`String) -> b_c = const-pool index

	Op_LoadVarInt Opcode = 0x12 // b_c = int-var index
	Op_LoadVarStr Opcode = 0x13 // b_c = string-var index

	// A = dest (bool reg); one opcode per constant, so there is no operand to decode
	Op_ConstTrue  Opcode = 0x15
	Op_ConstFalse Opcode = 0x16

	// --- metadata (0x20) ---
	// A = key (str reg), B = value (str reg)
	Op_SetTxMeta Opcode = 0x20

	// A = account (str reg), B = key (str reg), C = value (str reg); ext.A =
	// scope (str reg, 0xFF = unscoped)
	Op_SetAccountMeta Opcode = 0x21

	// meta(account, key) read, dispatched on the target type.
	// A = dest, B = account (str reg), C = key (str reg); ext.A = scope (str reg,
	// 0xFF = unscoped)
	Op_MetaStr     Opcode = 0x22
	Op_MetaInt     Opcode = 0x23
	Op_MetaPortion Opcode = 0x24

	// as above, but a monetary needs two destinations, so the amount's goes in an
	// ext word: A = dest asset (str reg), ext.A = dest amount (int reg), ext.B =
	// scope (str reg, 0xFF = unscoped)
	Op_MetaMonetary Opcode = 0x25

	// --- arithmetic & constructors (0x30) ---
	Op_AddInt Opcode = 0x30
	Op_SubInt Opcode = 0x31
	// 0x32 was Op_MinInt: a min is a comparison and a copy, so it is Op_LtInt
	// plus a branch. Reserved, do not reuse.
	Op_SubPortion Opcode = 0x33
	Op_MkPortion  Opcode = 0x34
	// 0x35 was Op_MkMonetary: a monetary is a (str asset, int amount) register
	// pair, so there is nothing to construct. Reserved, do not reuse.
	Op_AddString Opcode = 0x36

	// not adjacent to Op_SubPortion (0x33) because 0x32 is burned and 0x34..0x37
	// are taken
	Op_AddPortion Opcode = 0x38

	// an allotment share is a mul plus a floor (Op_PortionToInt)
	Op_MulPortion Opcode = 0x39

	// --- unary & conversions (0x40) ---
	// 0x40 was Op_GetAmount and 0x41 was Op_GetAsset: projecting a monetary is now
	// just naming one of its two registers. Reserved, do not reuse.
	//
	// One copy per register bank: A = dest, B = src, both in that bank. There is no
	// monetary copy — a monetary is a (str asset, int amount) pair, so copy the two
	// halves. The family is split across 0x42..0x43 and 0x4A..0x4B because
	// 0x44..0x49 were already spoken for.
	Op_IntCopy     Opcode = 0x42
	Op_PortionCopy Opcode = 0x43

	Op_NegInt          Opcode = 0x44
	Op_IntToString     Opcode = 0x45
	Op_PortionToString Opcode = 0x46

	// A = dest (str reg), B = asset (str reg), C = amount (int reg)
	Op_MonetaryToString Opcode = 0x47

	// the other two bank copies; see Op_IntCopy above
	Op_StrCopy  Opcode = 0x4A
	Op_BoolCopy Opcode = 0x4B

	// Op_IntToPortion is exact; Op_PortionToInt floors.
	Op_IntToPortion Opcode = 0x4C
	Op_PortionToInt Opcode = 0x4D

	// The most general form: account,cap,overdraft,color,scope
	// The 0xFF special register means NULL for cap,overdraft,color and scope
	// ext.A = overdraft (int reg), ext.B = color (str reg), ext.C = scope (str reg)
	Op_PullAccount Opcode = 0x50

	// account?, cap?, scope?
	Op_SendToAccount Opcode = 0x51

	// save: reduce balance of account A for asset B by amount C (C == nilReg =>
	// save all), floored at 0; ext.A = scope (str reg, 0xFF = unscoped)
	Op_Save Opcode = 0x52

	// reads the account balance from the run-state; A = dest, B = account, C =
	// asset; ext.A = scope (str reg, 0xFF = unscoped)
	Op_Balance Opcode = 0x54

	// opens a region at the current source-queue depth and posting count
	Op_MarkPush Opcode = 0x55

	// A = rewind flag. Always pops the innermost mark; A == 1 additionally repays
	// everything pulled and reverses everything posted since the matching
	// Op_MarkPush, while A == 0 commits it.
	Op_MarkEnd Opcode = 0x56

	// --- comparisons (0x60) ---
	// A = dest (bool reg) for all of them; the operand banks are what the opcode
	// implies. Op_IsZero is unary and the rest binary, but they are one group
	// because they are the whole set of bool *producers*.
	//
	// Only `<` and `==` exist, per type. The other surface operators are normalised
	// by the front end:
	//
	//	a <  b   ->  Lt(a, b)
	//	a >  b   ->  Lt(b, a)            operands swapped
	//	a <= b   ->  Not(Lt(b, a))
	//	a >= b   ->  Not(Lt(a, b))
	//	a == b   ->  Eq(a, b)
	//	a != b   ->  Not(Eq(a, b))
	Op_LtInt     Opcode = 0x60
	Op_EqInt     Opcode = 0x61
	Op_StrEq     Opcode = 0x62 // was 0x37
	Op_IsZero    Opcode = 0x63 // was 0x48
	Op_LtPortion Opcode = 0x64
	Op_EqPortion Opcode = 0x65

	// --- bool ops (0x70) ---
	// A = dest (bool reg), B = src (bool reg).
	Op_Not Opcode = 0x70 // was 0x49

	// --- control flow (0x90) ---
	// A = cond (bool reg); b_c = unsigned forward delta, added to the pc of the
	// next instruction. A quantity is not a condition: project it with Op_IsZero.
	Op_JmpIfFalse Opcode = 0x90
	// unconditional; b_c = unsigned forward delta, as above
	Op_Jmp Opcode = 0x91
	// the dual of Op_JmpIfFalse, so either edge of a bool can be the branch without
	// a negation instruction
	Op_JmpIfTrue Opcode = 0x92
)

Opcodes are grouped by category with gaps, so new instructions can be added to a category without renumbering. See instruction-encoding.md.

type Program

type Program struct {
	Instructions []Instruction

	StringsPool []string
	IntsPool    []big.Int

	MaxRegString  byte
	MaxRegInt     byte
	MaxRegPortion byte
	MaxRegBool    byte

	// Version is the bytecode version this program was assembled for, or (after
	// DecodeProgram) the version it was actually encoded with. Encode always
	// writes CurrentBytecodeVersion regardless of this field.
	Version BytecodeVersion
}

func DecodeProgram

func DecodeProgram(buf []byte) (Program, error)

func (Program) Encode

func (p Program) Encode() []byte

TODO review AI blob

type Store

type Store interface {
	GetBalance(
		ctx context.Context,
		account string,
		scope string,
		asset string,
		color string,
	) (*big.Int, error)

	GetMetadata(
		ctx context.Context,
		account,
		scope,
		key string,
	) (string, bool, error)
}

type StoreError

type StoreError struct {
	Wrapped error
}

StoreError wraps an error returned by the host Store (balance or metadata fetch): neither a script error nor a bytecode bug, so the wrapped error is preserved for the host to inspect.

func (StoreError) Error

func (e StoreError) Error() string

func (StoreError) Unwrap

func (e StoreError) Unwrap() error

type UnsupportedBytecodeVersionError

type UnsupportedBytecodeVersionError struct {
	Encoded   BytecodeVersion
	Supported BytecodeVersion
}

UnsupportedBytecodeVersionError is what the decoders return for a blob this build cannot read: another major, a minor newer than CurrentBytecodeVersion, or, while the current version is unstable, any other 0.x minor.

func (UnsupportedBytecodeVersionError) Error

type Vars

type Vars struct {
	StringsPool []string
	IntsPool    []big.Int

	// Version is the bytecode version these vars were built for, or (after
	// DecodeVars) the version they were actually encoded with. Encode always
	// writes CurrentBytecodeVersion regardless of this field.
	Version BytecodeVersion
}

func DecodeVars

func DecodeVars(buf []byte) (Vars, error)

func (Vars) Encode

func (v Vars) Encode() []byte

type VerifiedVarsInfo

type VerifiedVarsInfo struct {
	// contains filtered or unexported fields
}

VerifiedVarsInfo records, opaquely, the variable-pool sizes a Program was found to require by a successful VerifyWithVars call. A caller that keeps the Program around — e.g. one compiled artifact reused across many calls with (normally) the same Vars shape — can hold on to this instead of the Program's raw pool-size requirements, and later ask CheckVars whether a new Vars value would still satisfy VerifyWithVars, without paying for the static pass again. The fields are deliberately unexported: what "shape" means is this package's business, not a caller's.

func VerifyWithVars

func VerifyWithVars(p Program, vars *Vars) (VerifiedVarsInfo, error)

VerifyWithVars is Verify plus the check that vars carries every variable the program loads. A nil *Vars is only legal for a program that reads none. On success it also returns the VerifiedVarsInfo backing that check; see its doc for why a caller would want to keep it.

func (VerifiedVarsInfo) CheckVars

func (info VerifiedVarsInfo) CheckVars(vars *Vars) bool

CheckVars reports, in O(1) and without re-running verification, whether vars is guaranteed to satisfy the Program this VerifiedVarsInfo was obtained from — i.e. whether VerifyWithVars(thatProgram, vars) would succeed, without calling it. A false result is not itself a rejection: it only means the caller must call VerifyWithVars to get an authoritative answer (and, on failure, a precise error).

type Vm

type Vm struct {
	Program Program
	// contains filtered or unexported fields
}

func NewVm

func NewVm(
	program Program,
) *Vm

NewVm sizes each register bank from the count the program declares. MaxRegX is a count and 0xFF is the nil-register sentinel, so the real indices are 0..MaxRegX-1 and this is exact rather than an upper bound.

Nothing here re-derives those counts: a program whose instructions name a register beyond its own declaration is malformed, and Exec is entitled to assume it isn't — see Verify, which is what checks it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL