Skip to content

Wire format ​

The bytes of a compiled template, of the data a caller sends to run it, and of the registry entries a template keeps between runs. This is bytecode version 1, the only version the program accepts. The Rust definitions in common/src/template/wire.rs are the source of truth.

This page gives the bytes. What each construct means, and when a run fails, is in the language reference. Terms such as register, PDA, verifier, runtime accounts and Instructions sysvar are in the Glossary. This page adds:

  • Payload: the compiled template. The program stores it in the template account, after the account's own header.
  • Record: one fixed-size item in a table.
  • Opcode: the number that says what an instruction does.
  • Blob: the literal bytes at the end of the payload, such as instruction discriminators and bytes constants. Other records point into it by offset and length.
  • Loop: a FOREACH or REPEAT instruction and the body of instructions that follows it. A pass is one run of the body.

Template account ​

A template lives in its own account, at a PDA of the Ballista program derived from ['template', creator, templateId]. The account holds an 80-byte header, then the payload.

text
┌─────────────── template account (PDA) ───────────────┐
│ account header │ payload: header │ tables … │ blob   │
└───────────────────────────────────────────────────────┘
OffsetBytesField
01Discriminator, 1
11Account version, 2
21State: 0 uploading, 1 finalized
31PDA bump
432Creator
362Template ID
382Reserved, zero
404Payload length
444Bytes written so far
4832SHA-256 hash of the payload

A run reads the payload in place, without copying it, and only once the state is finalized.

Payload ​

The payload is a header followed by eight sections:

text
ProgramHeader
AccountConstraint[]
InputDescriptor[]
InstructionRecord[]
CpiDescriptor[]
CpiAccountRecord[]
DataSegment[]
PubkeyRecord[]
blob bytes
SectionRecord sizeNumber of records
Program header24 bytesOne
Account constraints8 bytesFixed account count plus accounts per batch row
Input descriptors4 bytesFixed input count plus row input count
Instructions16 bytesInstruction count
CPI descriptors12 bytesCPI descriptor count
CPI account records2 bytesCPI account record count
Data segments8 bytesData segment count
Pubkeys32 bytesPubkey count
Blob1 byteBlob length

Registries add no section. A registry's index and size, and a field's offset and type, travel in the immediates of the registry opcodes; see Registries.

Design rules ​

  • Multi-byte integers are little-endian.
  • Every record has one-byte alignment and no padding, so the program reads each table in place from account memory without copying it. In Rust the records derive the zerocopy crate's FromBytes, IntoBytes, KnownLayout, and Unaligned traits.
  • The header's counts determine where every section starts and ends.
  • Parsing rejects a payload that is truncated, has bytes left over, sets an unknown header flag, or uses the reserved header byte. The verifier rejects non-zero reserved bytes in records, an unused field that isn't 0xff or zero, and a data segment or CPI descriptor that nothing uses, so each template has one encoding.
  • Instructions refer to registers, accounts, and table records by index, and the verifier checks that every index is in range.
  • Literal data is stored once in the blob and referred to by offset and length.
  • The verifier checks the whole payload once. A run parses the structure again but relies on that check instead of repeating it.

Program header ​

The header is 24 bytes.

OffsetBytesField
04Magic bytes BVM1
41Bytecode version, 1
51Fixed account count
61Accounts per batch row (the batch stride), or 0 without a batch
71Maximum batch rows
81Fixed input count
91Register count
101Instruction count
111CPI descriptor count
122CPI account record count
142Data segment count
161Pubkey count
171Flags
182Blob length
201Minimum batch rows
211Row input count
221Account group count
231Reserved, zero

Flag bit 0 (PROGRAM_FLAG_EMIT_EVENT) makes the program log a run event after each successful run. The other bits are reserved and rejected.

Account constraints ​

Each fixed account, then each account of the batch row, has one 8-byte constraint record.

OffsetBytesField
01Flags: bit 0 signer, bit 1 writable, bit 2 executable
11Required address, as an index into the pubkey table, or 0xff for none
21Required owner, as an index into the pubkey table, or 0xff for none
31Reserved, zero
44Minimum data length

Account references ​

Instructions, CPI descriptors, and CPI account records name an account with a one-byte reference. A value below 0x80 is the index of a fixed account. A value with bit 0x80 set names the account at position value & 0x7f in the current batch row, and is valid only inside FOREACH, never inside REPEAT. Account group members have no references: a CPI descriptor forwards a group, and the group opcodes name one by its index.

Input descriptors ​

The inputs table holds one 4-byte descriptor per input: the fixed inputs first, then the row inputs.

OffsetBytesField
01Value type: 1 bool, 2 u64, 3 i64, 4 u128, 5 pubkey, 6 bytes
11Reserved, zero
22Maximum length: 1 to 1,024 for bytes, zero for every other type

A LOAD_INPUT whose a operand has bit 0x80 set loads row input a & 0x7f of the current row, and is valid only inside FOREACH, never inside REPEAT.

Instruction record ​

Every instruction is 16 bytes:

OffsetBytesFieldMeaning
01opcodeThe operation
11destinationThe register that receives the result, or 0xff for none
23a, b, cOperands: registers, account references, table indices, or counts, depending on the opcode
51flagsBit 0 on read opcodes: the offset comes from register b. Zero for every other opcode
68immediateA constant, a packed range, a length, a read opcode, a loop's carry mask, a packed registry open or field, or a packed group filter
142reservedMust be zero

A packed range holds a start (a blob offset or a first table index) in its low 32 bits and a length in its high 32 bits.

Opcodes ​

REQUIRE, INVOKE, FOREACH, REPEAT, EMIT, SET_RETURN_DATA, OPEN_REGISTRY, and WRITE_REGISTRY produce no value. Every other instruction writes its result to the destination register.

A field an opcode doesn't use is fixed: the destination of an opcode that produces no value, and each operand and the immediate that its row below doesn't name. An unused destination or operand must be 0xff, and an unused immediate zero. The verifier refuses anything else with InvalidBatch for FOREACH, InvalidLoop for REPEAT, InvalidRegistry for opcodes 75 to 77,InvalidAccountGroup for opcodes 78 to 80, and InvalidInstruction otherwise.

OpcodeNameOperandsResult
1LOAD_INPUTa: input index; bit 0x80 selects a row inputThe input's type
2CONST_BOOLa: 0 or 1bool
3CONST_U64immediate: the valueu64
4CONST_I64immediate: the value, two's complementi64
5CONST_U128immediate: packed blob range of exactly 16 bytesu128
6CONST_PUBKEYa: pubkey table indexpubkey
7CONST_BYTESimmediate: packed blob range of at most 1,024 bytesbytes
8ACCOUNT_KEYa: account referencepubkey
9ACCOUNT_OWNERa: account referencepubkey
10ACCOUNT_LAMPORTSa: account referenceu64
11ACCOUNT_DATA_LENa: account referenceu64
12ACCOUNT_IS_EMPTYa: account referencebool, true when the account has no data
13READ_U64a: account reference; immediate: byte offsetu64
14READ_I64as READ_U64i64
15READ_U128as READ_U64u128
16READ_PUBKEYas READ_U64pubkey
17CLOCK_SLOTnoneu64
18CLOCK_TIMESTAMPnonei64
19ADDa, b: registers of the same numeric typeThat type; fails on overflow
20SUBas ADDThat type; fails on overflow
21MULas ADDThat type; fails on overflow
22DIVas ADDThat type; fails on a zero divisor or overflow
23EQa, b: registers of the same typebool
24NEas EQbool
25LTa, b: registers of the same numeric typebool
26LTEas LTbool
27GTas LTbool
28GTEas LTbool
29ANDa, b: bool registersbool
30ORa, b: bool registersbool
31NOTa: bool registerbool
32MINa, b: registers of the same numeric typeThat type
33MAXas MINThat type
34SELECTa: bool condition; b: value if true; c: value if false, the same type as bThe type of b
35CAST_U64a: numeric registeru64; fails if the value does not fit
36CAST_I64a: numeric registeri64; fails if the value does not fit
37CAST_U128a: numeric registeru128; fails if the value does not fit
38LOOP_INDEXnone; inside a loop onlyu64, the zero-based row or pass index
40REQUIREa: bool register; the run fails if it is falsenone
41INVOKEa: CPI descriptor index; b: a bool guard register, or 0xff for nonenone
42FOREACHa: body length in instructions; immediate: carry mask, one bit per register whose value survives each rownone
43READ_U8as READ_U64u64
44READ_U16as READ_U64u64
45READ_U32as READ_U64u64
46READ_BOOLas READ_U64; fails unless the byte is 0 or 1bool
47DERIVE_PDAa: the executable program account; immediate: packed range of 1 to 15 data segments used as seedspubkey; searches for the canonical bump
48RETURN_DATAa: a read opcode (13 to 16, 43 to 46, or 60) that selects width and type; immediate: byte offsetThe read's type; must directly follow an INVOKE with no guard
49MOVEa: source registerA copy of a; used to update carried registers
50CREATE_PDAas DERIVE_PDA, plus b: a u64 register holding the bumppubkey; derives once, and fails with InvalidPdaDerivation if the bump exceeds 255 or the result is on the ed25519 curve (not a valid program address)
51MUL_DIVa, b, c: three u64 or three u128 registersThat type: a × b ÷ c rounded down, with the product held exactly; fails on a zero c or a result that does not fit
52MUL_DIV_CEILas MUL_DIVAs MUL_DIV, rounded up
53REMas ADDThat type: a mod b, with the sign of a; fails on a zero divisor or the i64 minimum mod −1
54SHLa: a u64 or u128 register; b: a u64 register, the shiftThe type of a; fails rather than shift out a set bit
55SHRas SHLThe type of a, rounded down; a shift of the full width or more gives 0
56BIT_ANDa, b: two u64 or two u128 registersThat type
57BIT_ORas BIT_ANDThat type
58BIT_XORas BIT_ANDThat type
59POW10a: a u64 registeru128, 10 to the power a; fails if a is above 38
60READ_I32as READ_U64i64, sign-extended from 4 bytes
61REPEATa: body length in instructions; b: the u64 count register, read once at the start; c: maximum passes, 1 to 255; immediate: carry mask, as FOREACHnone; fails with LoopCountExceeded if the count is above c
62EMITimmediate: packed range of data segments, the first a literal tagnone; logs the encoded bytes as one Program data: field
63SET_RETURN_DATAimmediate: packed range of data segmentsnone; sets the encoded bytes as the run's return data
64INSTRUCTION_COUNTa: the Instructions sysvar accountu64, the number of instructions in the transaction
65INSTRUCTION_INDEXas INSTRUCTION_COUNTu64, the index of the top-level instruction this run is part of
66INSTRUCTION_PROGRAMa: the sysvar account; b: a u64 register, the instruction indexpubkey, that instruction's program
67INSTRUCTION_ACCOUNT_COUNTas INSTRUCTION_PROGRAMu64, how many accounts it names
68INSTRUCTION_ACCOUNTas INSTRUCTION_PROGRAM, plus c: a u64 register, the account positionpubkey, that account's key
69INSTRUCTION_ACCOUNT_FLAGSas INSTRUCTION_ACCOUNTu64: bit 0 signer, bit 1 writable
70INSTRUCTION_DATA_LENas INSTRUCTION_PROGRAMu64, the length of its data
71READ_INSTRUCTION_DATAas INSTRUCTION_PROGRAM, plus c: a u64 register, the byte offset; immediate: a read opcode that selects width and type, as for RETURN_DATAThe read's type
72READ_INSTRUCTION_BYTESas READ_INSTRUCTION_DATA, but the immediate is a length, 1 to 1,024bytes of exactly that length
73READ_ACCOUNT_BYTESa: account reference; b: a u64 register, the byte offset; immediate: a length, 1 to 1,024bytes of exactly that length; fails with WritableAccountBytesRead if the account is writable
74BYTES_LENa: a bytes registeru64, its length
75OPEN_REGISTRYa: the entry account; b: a pubkey register holding the key, or 0xff for the zero key; c: the payer account; immediate: a packed registry opennone; checks the entry or creates it, then keeps it open for the rest of the run
76READ_REGISTRYa: an entry account opened earlier; immediate: a packed fieldThe field, with the type its read opcode gives
77WRITE_REGISTRYa: the value register; b: an entry account opened earlier; immediate: a packed fieldnone; writes the value into the field
78GROUP_LENGTHa: an account group indexu64, how many members the caller passed
79GROUP_ANYa: an account group index; b: a program, as a pubkey table index; c: a second program, or 0xff; immediate: a packed group filterbool, whether any member matches
80GROUP_COUNTas GROUP_ANYu64, how many members match

Opcodes 0 and 39 are unassigned, as is every number above 80.

Read opcodes (13 to 16, 43 to 46, and 60) with the dynamic-offset flag take their offset from the u64 register in b, and must have a zero immediate. Without the flag, b is unused, and the immediate offset plus the read width must fit inside the account's declared minimum data length.

Loops ​

  • A template holds at most eight loops, FOREACH and REPEAT counted together. Each sits at the top level, and its body is the a instructions after it, which cannot include another loop.
  • Every FOREACH runs over the same batch rows, from the first. A template with a batch has at least one FOREACH, and one without a batch has none.
  • A REPEAT needs a body, a maximum of at least 1, and a destination of 0xff, and its body cannot name a row account or row input. The verifier rejects a REPEAT that breaks these rules, or a ninth loop, with InvalidLoop.
  • The worst-case CPI count adds, for each loop, the INVOKEs in its body times its maximum (the header's maximum batch rows for FOREACH, c for REPEAT), plus the INVOKEs outside loops, plus 3 for each OPEN_REGISTRY. It must be at most 64.

Outputs ​

The verifier holds EMIT and SET_RETURN_DATA to the output rules, and rejects a break with InvalidOutput:

  • Both encode their data segments the way a CPI encodes its data. dst, a, b, and c must be 0xff, the range must name at least one segment, and the worst-case length, counting a bytes register at its maximum, must be at most 1,024 bytes.
  • An EMIT's first segment is a literal of at least 4 bytes that does not start with BEV.
  • SET_RETURN_DATA appears once, outside every loop, with no INVOKE at a later index.

Introspection and byte reads ​

What these read, and when a run fails, is under Introspection. In the bytecode:

  • Opcodes 64 to 72 name, in a, a fixed account whose constraint pins its address to the Instructions sysvar. The verifier rejects any other account with InvalidIntrospection.
  • Indexes, positions, and offsets are u64 registers.
  • READ_ACCOUNT_BYTES may name any declared account, including a row account inside FOREACH, but only one that is read-only in this instruction.

Account groups ​

A GROUP_ANY or GROUP_COUNT immediate packs its filter:

OffsetBytesField
02The filter's first data segment
21Match segments, 1 to 4
31Except segments, 0 to 4
44Minimum data length a member needs

The match segments come first, then the except segments, in one run. A match segment's kind is DATA_REG_BOOL, DATA_REG_U64, DATA_REG_I64, DATA_REG_U128 or DATA_REG_PUBKEY, its register holds a value of exactly that type, its offset field is the account-data offset to compare at, and its length field is zero. An except segment is a DATA_REG_PUBKEY with offset and length zero.

The verifier checks that a names a declared group, that b and any c are pubkey table indices, that the counts and segments are in range, and that the minimum length covers every match's bytes. It rejects a break with InvalidAccountGroup (6133). GROUP_LENGTH takes b and c as 0xff and a zero immediate.

Registries ​

An OPEN_REGISTRY immediate packs the registry it opens:

OffsetBytesField
01Registry index, below 8
12Size of the registry's fields, 1 to 512 bytes
31The fixed account pinned to the System program, which creating an entry calls
44Zero

A READ_REGISTRY or WRITE_REGISTRY immediate packs one field:

OffsetBytesField
02Field offset, counted from the end of the entry's 72-byte header
21A read opcode (13 to 16, 43 to 46, or 60) that sets the field's width and type
35Zero

The verifier checks:

  • OPEN_REGISTRY sits at the top level, never in a loop body, and never after a SET_RETURN_DATA or any INVOKE, including one in a loop body. A template holds at most 8, opens each entry account at most once, and gives every open of one registry index the same size.
  • The entry account is a fixed account declared writable and nothing else: no signer or executable flag, no address or owner pin, and a minimum data length of 0. The payer is a fixed account declared signer and writable. The System program account is a fixed account pinned to the System program's address.
  • b is a set pubkey register, or 0xff for the zero key of 32 zero bytes.
  • No CPI account record lists an entry account writable, whether it is invoked or not.
  • READ_REGISTRY and WRITE_REGISTRY name an account opened by an OPEN_REGISTRY at a lower index, and a field that fits inside that registry's size. A read's b and c, and a write's destination and c, are 0xff.
  • A read takes any read opcode. A write takes only READ_BOOL, READ_U64, READ_I64, READ_U128, or READ_PUBKEY, whose width holds every value of its type, and its value register must have that type.
  • No read opcode (13 to 16, 43 to 46, or 60) and no READ_ACCOUNT_BYTES names an entry account, wherever either sits: fields are read only with READ_REGISTRY. The entry's key, owner, lamports, data length, and emptiness stay readable.

The verifier rejects a template that breaks these rules with InvalidRegistry (6132). It also counts each open as 3 CPIs toward the limit of 64, since creating an entry whose address already holds lamports takes a transfer, an allocate, and an assign.

At run time, an open checks the entry or creates it, as Registry entries describes, and marks it borrowed for the rest of the run. Every CPI comes after the opens, so one that passes an open entry writable, through any slot or account group, fails with RegistryReentry (6026). The run-time rules and errors are under Registries.

CPI descriptors ​

Each CPI the template can make is described once by a 12-byte descriptor. An INVOKE names a descriptor, and a loop can invoke the same descriptor on every pass. The verifier refuses a descriptor that no INVOKE names with InvalidCpi.

OffsetBytesField
01Program account reference; the account must be declared executable
11Account group forwarded after the listed accounts, or 0xff for none
22Index of the first CPI account record
41Number of CPI account records, at most 64
51Number of data segments
62Index of the first data segment
82Maximum instruction data length: the sum of the segments' maximum lengths, at most 4,096
102Reserved, zero

Group members follow the listed accounts in the CPI. They keep the writable flag the transaction gave them and are never passed as signers.

CPI account records ​

Each record is 2 bytes:

OffsetBytesField
01Account reference
11Flags: bit 0 signer, bit 1 writable. May not include a flag that the account's constraint lacks

Data segments ​

Each segment is 8 bytes. CPI descriptors use segments to build instruction data, EMIT and SET_RETURN_DATA use them to build their output, DERIVE_PDA and CREATE_PDA use them as seeds, and GROUP_ANY and GROUP_COUNT use them as filter entries. Every segment must have one of these uses. The first three check the fields below alike, and refuse a bad segment with InvalidDataSegment and its index in this table; a filter entry follows Account groups. The verifier refuses an unused segment with InvalidDataSegment.

OffsetBytesField
01Kind: 0 literal, 1 u8, 2 u16, 3 u32, 4 u64, 5 i64, 6 u128, 7 pubkey, 8 bool, 9 bytes
11Source register, or 0xff for a literal
22Literal: offset into the blob. Zero otherwise
42Literal: length. Zero otherwise
62Reserved, zero

Kinds 1 to 4 encode a u64 or u128 register at that width, and fail the run if the value does not fit. Kind 9 copies a bytes register as is, with no length prefix. A seed may not exceed 32 bytes.

Pubkeys and blob ​

After the data segments come the pubkey table (32 bytes per key, used by account constraints and CONST_PUBKEY), then the blob. The payload must end exactly where the blob ends.

Run data ​

The Run instruction's data is the byte 5 followed by the run data, which may be at most 1,024 bytes. Its first account is the template account, followed by the runtime accounts.

text
group lengths [u8; account group count] | fixed input values | row input values × iterations

Values are encoded by type: bool as one byte (0 or 1), u64 and i64 as eight little-endian bytes, u128 as sixteen, pubkey as thirty-two, and bytes as a little-endian u16 length followed by the bytes. Runtime accounts come in this order: fixed accounts, batch rows, then account group members, group by group in declaration order. The number of rows is (accounts − fixed − Σ group lengths) / stride. The division must be exact, and the result must lie between the batch's minimum and maximum.

Registry entries ​

A registry entry is an account Ballista owns: a 72-byte header, then the registry's fields.

OffsetBytesField
04Magic bytes BREG
41Entry version, 1
51Registry index
62Reserved, zero
832Template address
4032Key
72Registry sizeFields, zero when the entry is created

The account is exactly 72 bytes plus the registry's size, so at most 584 bytes. A field offset in READ_REGISTRY or WRITE_REGISTRY counts from byte 72. A written bool is one byte, 0 or 1.

The entry's address is a PDA of the Ballista program, with the canonical bump and these seeds:

text
"registry" | template address (32 bytes) | registry index (1 byte) | key (32 bytes)

The key is the 32 bytes the template computes: an address such as the caller's, or all zeros for a template-wide entry. findRegistryEntryAddress (TypeScript, from @jac0xb/ballista/kit) and find_registry_entry_address (Rust) return the address and bump.

An open handles two cases:

  • The account is owned by Ballista. Its size and header must match the running template, the open's registry index and size, and the key. The address is not derived again: only Ballista writes an entry's header, and only at the address that header derives.
  • The account has no data and is owned by the System program. It must be at the derived address. With no lamports there, the System program's CreateAccount makes it, funded by the payer with the rent-exempt minimum for its size. If the address already holds lamports, the payer transfers only what is missing, then Allocate and Assign make the account Ballista's. Ballista then writes the header.

Anything else fails with InvalidRegistryEntry, and so does a second open of an account already open in this run: two entry accounts whose keys come out equal are one account. Ballista signs CreateAccount, Allocate, and Assign with the entry's seeds, and no other call in a run (When Ballista signs). Nothing closes an entry, so its rent stays locked.

Error codes ​

A custom error code is kind | (context << 16). Runtime kinds start at 6000 (0x1770) and verifier kinds at 6100 (0x17D4). The full table, with hex forms, is in Error codes.

Run event ​

When the header sets PROGRAM_FLAG_EMIT_EVENT, a successful run logs this 47-byte record with sol_log_data. It appears in the transaction logs as a Program data: line.

text
"BEV1" | version u8 | iterations u8 | expanded u8 | executed u64 | template address [u8; 32]
FieldMeaning
versionBytecode version, 1
iterationsBatch rows in the run. REPEAT passes are not counted
expandedINVOKE instructions reached, counting each loop pass separately
executedBitmask, one bit per invoke reached, in order and counting from bit 0. A bit is set when that invoke ran, and clear when its guard skipped it
template addressThe template account that ran

Each EMIT also logs a Program data: line, which its tag keeps from passing for this event; see Output.

Why this is zero-copy ​

Parsing a payload copies nothing: the parser returns slices that point into the template account's memory for every record table. Running a template allocates its buffers once per run: the decoded inputs, the register file, one set of buffers for building CPIs, reused by every CPI in the run, one register snapshot shared by every loop, and, at the first EMIT or SET_RETURN_DATA, one 1,024-byte output buffer that every later output reuses. PDA seeds are assembled on the stack, and byte reads borrow the sysvar's or the account's data. Heap use therefore does not grow with the number of CPIs, loops, or outputs a run performs. "Zero-copy" describes how the stored payload is read, not an execution engine that never allocates.

The example payloads in fixtures/ are produced by the TypeScript compiler. The Rust tests verify them and run them against the program, and check that the Rust SDK compiles every docs template to the same bytes as the TypeScript compiler. Both SDKs derive entry addresses against fixtures/registry-entry-addresses.txt, vectors the program's own derivation produces.

BALLISTA / A SMALL MACHINE FOR COMPLEX TRANSACTIONS