Language
Statically Typed
Beo instructions is a statically typed language, which means we must know the type of each variable before executing a circuit.
Explicit Types Required
There is no undefined or null value in Beo instructions. When assigning a new variable, the type of the value must be explicitly stated.
Pass by Value
Expressions in Beo instructions are always passed by value, which means their values are always copied when they are used as function inputs or in right sides of assignments.
Register based
There are no variable names in Beo instructions. All variables are stored in registers denoted rX where X is a non-negative whole number starting from 0 r0, r1, r2, etc..
Data Types and Values
Booleans
Beo instructions supports the traditional true or false boolean values. The explicit boolean type for booleans in statements is required.
function main:
input r0: boolean.private;Integers
Beo instructions supports signed integer types i8, i16, i32, i64, i128 and unsigned integer types u8, u16, u32, u64, u128.
info
Higher bit length integers generate more constraints in the circuit, which can slow down computation time.
Field Elements
Beo instructions supports the field type for elements of the base field of the elliptic curve. These are unsigned integers less than the modulus of the base field, so the largest field element is 8444461749428370424248824938781546531375899335154063827935233455917409239040field.
Group Elements
The set of affine points on the elliptic curve passed into the Beo instructions compiler forms a group. The curve is a Twisted Edwards curve with a = -1 and d = 3021. Beo instructions supports a subgroup of the group, generated by a generator point, as a primitive data type. A group element is denoted by the x-coordinate of its point; for example, 2group means the point (2, 5553594316923449299484601589326170487897520766531075014687114064346375156608). The generator point is 1540945439182663264862696551825005342995406165131907382295858612069623286213group.
Scalar Elements
Beo instructions supports the scalar type for elements of the scalar field defined by the elliptic curve subgroup. These are unsigned integers less than the modulus of the scalar field, so the largest scalar is 2111115437357092606062206234695386632838870926408408195193685246394721360382scalar.
Addresses
Addresses are defined to enable compiler-optimized routines for parsing and operating over addresses.
Signatures
Beo uses a Schnorr signatures scheme to sign messages with an Beo private key. Signatures can be verified in Beo instructions using the sign.verify instruction.
Layout of an Beo Program
An Beoprogram contains declarations of a Program ID, Imports, Functions, Closures, Structs, Records, Mappings, and Finalize. Ordering is only enforced for imports which must be at the top of file. Declarations are locally accessible within a program file. If you need a declaration from another program file, you must import it.
Program ID
A program ID is declared as {name}.{network}. The first character of a name must be lowercase. name can contain lowercase letters, numbers, and underscores. Currently, beo is the only supported network domain.
Import
An import is declared as import {ProgramID};.
Imports fetch other declarations by their program ID and bring them into the current file scope. You can import dependencies that are downloaded to the imports directory.
Function
A function is declared as function {name}:.
Functions contain instructions that can compute values. Functions must be in a program's current scope to be called.
Function Inputs
A function input is declared as input {register} as {type}.{visibility};.
Function inputs must be declared just after the function name declaration.
Function Outputs
A function output is declared as output {register} as {type}.{visibility};.
Function outputs must be declared at the end of the function definition.
Call a Function
In the Beo protocol, calling a function creates a transition that can consume and produce records on-chain. Use the beo run CLI command to pass inputs to a function and execute the program.
In Testnet, program functions cannot call other internal program functions. If you would like to develop "helper functions" that are called internally within a program, try writing a closure.
Call an Imported Function
Beo programs can externally call other Beo programs using the call {program}/{function} {register} into {register} instruction.
Closure
A closure is declared as closure {name}:.
Closures contain instructions that can compute values. Closures are helper functions that cannot be executed directly. Closures may be called by other functions.
Call a Closure
Beo programs can internally call other Beo closures using the call {name} {register} into {register} instruction.
Struct
A struct is a data type declared as struct {name}:.
Structs contain component declarations {name} as {type}.
To instantiate a struct in a program use the cast instruction.
Array
An array is a data type declared as [{value}, {value}].
Arrays contain a list of values of the same type [{type}; {length}].
Arrays can be initialized using the cast opcode.
Arrays can be indexed using {name}[{index}].
Arrays can be nested.
info
Beo instructions currently only support fixed-length static arrays.
Record
A record type is declared as record {name}:.
Records contain component declarations {name} as {type}.{visibility};.
Record data structures must contain the owner declaration as shown below.
When passing a record as input to a program function the _nonce as group.{visibility} declaration is also required.
To instantiate a record in a program use the cast instruction.
Special Operands
self.signer
The self.signer operand returns the user address that originated the transition.
This is particularly useful in intermediate programs that need to modify the state of the original caller rather than their own state.
In the example below, the transfer_public_as_signer function uses self.signer to decrement the balance from the original user's account rather than from the intermediate program's account.
self.caller
The self.caller operand returns the address of the immediate caller of the program.
Mapping
A mapping is declared as mapping {name}:. Mappings contain key-value pairs and must be defined within a program.
Mappings are stored publicly on-chain. It is not possible to store data privately in a mapping.
Contains
A contains command that checks if a key exists in a mapping, e.g. contains accounts[r0] into r1;.
Get
A get command that retrieves a value from a mapping, e.g. get accounts[r0] into r1;.
Get or Use
A get command that uses the provided default in case of failure, e.g. get.or_use account[r1] 0u64 into r5;.
Set
A set command that sets a value in a mapping, e.g. set r0 into accounts[r0];.
Remove
A remove command that removes a key-value pair from a mapping, e.g. remove accounts[r0];.
Finalize
A finalize is declared as finalize {name}:.
A finalize must immediately follow a function, and must have the same name;
note
Previously, a finalize function was executed on chain after the zero-knowledge proof of the execution of the associated function is verified; Upon success of the finalize function, the program logic was executed.
Upon failure of the finalize function, the program logic was reverted.
Futures
A future is equivalent to the call graph of the on-chain execution and is explicitly used when finalizing an execution. Instead of constructing the call graph implicitly from the code, the transition/circuit explicitly outputs a future, specifying which code blocks to run on-chain and how to run them.
future type
A user can declare a future type by specifying a Locator followed by the tag .future. For example, credits.beo/mint_public.future. A function can only output a future and a finalize block can only take a future in as input. A closure cannot output a future or take a future in as input.
async call
A user can make an asynchronous call to the finalize block via the async keyword. For example, async mint_public r0 r1 into r2;. Note that the associated function must be specified. This operation produces a Future as output. async takes the place of the finalize command, which was allowed in the body of a function after the output statements.
await command
A user can evaluate a future inside of a finalize block using the await command. For example, await r0;. An await command can only be used in a finalize block. The operand must be a register containing a Future.
Indexing a future.
A register containing a future can be indexed using the existing index syntax. For example, r0[0u32]. This would get the input of the future at that specific index. Accesses can be nested to match the nested structure of a future.
Future example
There are a number of rules associated with using these components.
If a function has a finalize block, it must have exactly one async instruction.
If a function has a finalize block, it's last output must be a future.
If a function does not have a finalize block, it cannot have an async instruction`.
All futures created by calls need to be input to the async instruction in the order they were produced.
An async call must reference the same function.
All calls must be made before invoking async.
The input futures types in a finalize block must match the order in which they were created in the function.
All futures in a finalize must be await-ed and in the order in which they were specified.
Instructions can be interleaved between invocations of call, async, and await.
Finalize Commands
The following commands are supported in Beo Instructions to provide additional program functionality.
block.height
The block.height command returns the height of the block in which the program is executed (latest block height + 1).
This can be useful for managing time-based access control to a program.
The block.height command must be called within a finalize block.
network.id
The network.id command returns the ID of the network on which the program is executed.
This can be useful for managing network-specific program behavior.
The network.id command must be called within a finalize block.
Currently supported network IDs are:
0: Mainnet
1: Testnet
2: Canarynet
rand.chacha
The rand.chacha command returns a random number generated by the ChaCha20 algorithm.
This command supports sampling a random address, boolean, field, group, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, and scalar.
Up to two additional seeds can be provided to the rand.chacha command.
Currently, only ChaCha20 is supported, however, in the future, other random number generators may be supported.
Hash
Beo Instructions supports the following syntax for hashing to standard types.
Checkout the Beo Instructions opcodes for a full list of supported hashing algorithms.
Commit
Beo Instructions supports the following syntax for committing to standard types.
Note that the commit command requires any type as the first argument, and a scalar as the second argument.
Checkout the Beo Instructions opcodes for a full list of supported commitment algorithms.
position, branch.eq, branch.neq
The position command, e.g. position exit, indicates a point to branch execution to.
The branch.eq command, e.g. branch.eq r0 r1 to exit, which branches execution to the position indicated by exit if r0 and r1 are equal.
The branch.neq command, e.g. branch.neq r0 r1 to exit, which branches execution to the position indicated by exit if r0 and r1 are not equal.
** Example ** The finalize block exits successfully if the input is 0u8 and fails otherwise.
Program Interoperability
The examples in this section will use the following environment.
.env
Child and Parent Program
The following example demonstrates how a program parent.beo can call another program child.beo.
./imports/child.Beo
./parent.Beo
User Callable Program
By asserting assert.eq self.caller self.signer; on line 4, a developer can restrict their function such that it can only be called by users.
./imports/child.Beo
./parent.Beo
Program Callable Program
By asserting assert.neq self.caller self.signer; on line 4, a developer can restrict their function such that it can only be called by other programs.
restrict.Beo
Last updated