Introduction
Smart Contracts can be programmed using our own interpreted language running in a Virtual Machine, allowing safe and secure execution of code on the blockchain.
Smart Contracts are a way to automate the execution of a contract, without the need of a third party. They are self-executing contracts with the terms of the agreement directly written into lines of code.
A lot of use cases can be found for Smart Contracts, such as:
- Decentralized applications (dApps)
- Tokenization of assets
- Voting systems
- Supply chain management
- etc.
Please note that they are not yet available on the mainnet, but are planned to be released in the future.
You can find the documentation of the XVM (opens in a new tab) below.
XELIS VM
XVM (XELIS Virtual Machine) is a fully customizable VM proposed with its suite including Lexer, Parser, Assembler, Compiler and the Virtual Machine.
The name of the language used is Silex, which its syntax is inspired by Rust.
Unlike other VMs, XVM is designed to be fully customizable (including the default std!), allowing you to define your own types, functions, structures, enums. It is also designed to directly include primitive types.
Crates
vm
is the main crate that contains Virtual Machine to execute a (op-code) compiled program.assembler
is the crate that contains the assembler to convert an source code of raw instructions into a program.compiler
is the crate that contains the compiler to convert an AST (Abstract Syntax Tree) program into an op-code program.parser
is the crate that contains the parser to convert a list of tokens into an AST (Abstract Syntax Tree) program.lexer
is the crate that contains the lexer to convert a source code into a list of tokens.
All the verifications are mainly made at the level of the Parser to check the conformity of the code to be interpreted.
The different primitive types are:
u8
(unsigned 8 bits)u16
(unsigned 16 bits)u32
(unsigned 32 bits)u64
(unsigned 64 bits)u128
(unsigned 128 bits)u256
(unsigned 256 bits)bool
string
struct
enum
variantsoptional<T>
where T is another type (it allow the value to be nullable)range<T>
where T is a number type (it allow to iterate over a range of values in a foreach, or have some functions likecontains
)map<K, V>
where K is a key type and V is a value type (it allow to have a key-value store)blob
is a raw data type allowing to store any kind of data (like images, files..)
Arrays of any type are also supported, but they must contain only one type of value (example: u64[]
and with multi-depth too).
File extension is .slx
for the source code.
OpCodes
The opcodes are the instructions that the VM will execute. They are generated by the compiler and are executed by the VM. See the opcodes.md (opens in a new tab) file for more information.
Documentation
the semicolon is optional, thus can be added if desired without any difference in the code.
Recursive functions are allowed, but limited to a configurable depth.
A environment system is completely customizable to set your own native functions. This helps to manage exactly what a program can interact with. Custom structs are also available.
Numbers
An error will be returned by the interpreter if an overflow is detected without causing a panic.
Rules
- The value must be greater than or equal to
0
. - You can put
_
(underscore) for a better readability. - If no type is specified on the value, then
u64
will be the default. - Array indexes are
u32
types. - You can precise the type by adding
u8
,u16
,u32
,u64
,u128
oru256
after the value.
Examples
let my_u8: u8 = 10
let my_u16: u16 = 70
let my_u32: u32 = 999
let my_int: u64 = 25655
let my_u128: u128 = 100_000_000u128
let my_u256: u256 = 100_000_000u256
Each type can be casted into another type, if an overflow is detected, the value will be truncated.
let my_u8: u8 = 255
let my_u16: u16 = my_u8 as u16
Also, each type have a min
and max
value that can be used.
let min: u8 = u8::MIN
let max: u8 = u8::MAX
Variable
for constant variable, it must be declared outside a function, with const
keyword.
Rules
- Every variable must be declared with
let
orconst
keyword. - Variable name must alphanumeric characters.
- Must provide value type.
- If no value is set,
null
is set by default.
Examples
const hello: string = "hello"
...
let world: string = "world"
Casting
Values of built-in types can be casted into other built-in types easily using the keyword as
.
In case of an overflow, no error will be returned, but the value will be truncated.
Rules
- Both value types must be a built-in type.
Examples
let id: u128 = 1337
let b: u8 = id as u8
// id_str equals "255" due to the truncation
let id_str: string = id as string
Import
Instead of having one file with all your code, you can have multiple files that will be compiled into one final program.
Rules
- Have a unique alias if set
- No circular import
- ends with
.xel
if its a local import
Examples
math
namespace
import "math.xel" as math;
...
math.sum(a, b)
no namespace:
sum(a, b)
Function
entry
function is a "public callable" function and must return a u64
value.
Rules
- Must starts with
func
orentry
keyword. - Signature is based on function name and parameters.
- For type functions, the type must not be primitive.
- Recursive functions are allowed.
Examples
entry foo() { ... }
fn foo() { ... }
fn foo() -> u64 { ... }
fn foo(a: u64, b: u64) { ... }
fn (f Foo) bar() { ... }
Structure
A structure can contain other structures.
Rules
- The name must be unique.
- Name should start with a uppercase letter.
- Only letters are allowed in name.
- The last field does not need a comma.
Examples
struct MyStruct {
message: string,
value: u64
}
Enum
An enum is a type that can have multiple variants.
Rules
- The name must be unique.
- Name should start with a uppercase letter.
- Variants must be unique.
- Each variants can contains fields or not.
Examples
enum MyEnum {
A,
B,
C {
value: u64
},
D {
name: string,
value: u64
}
}
Optional
An optional type is a type that can be null
.
Rules
- The type must be specified.
- The value can be set to
null
.
Examples
let my_optional: optional<u64> = null
...
let opt: optional<string> = "Hello World!"
let s = opt.unwrap()
Range
A range is a type that can be used to iterate over a range of values.
Rules
- The type must be specified and be a number type.
- The start and end values must be of the same type.
- The end value must be greater than the start value.
Examples
let my_range: range<u64> = 0..10
let _: bool = my_range.contains(5)
Map
A map is a key-value store where the key and value can be of any type based on the declaration. It is backed by a HashMap.
Rules
- The key and value types must be specified.
- Key type can't be a map.
Examples
let my_map: map<string, u64> = {"hello": 10, "world": 20}
my_map.insert("foo", 30)
my_map.remove("hello")
Ternary
Rules
- A
bool
condition is required. - The two values that can be returned must be of the same type.
Examples
let score: u64 = is_winner() ? 20 : 0
Negate operator
Rules
- A
bool
condition is required after it.
Examples
let negative: bool = !condition
Array
Rules
- All values must be of the same specified type.
Examples
let array: u64[] = [10, 20, 30, 40]
...
let dim: u64[][] = [[34, 17], [8, 14], [0, 69]]
If
Rules
- Have a
bool
condition.
Examples
if condition {
...
}
if (i > 20 && i != 25) || i == 0 {
...
}
Else
Rules
- It must be preceded by an
if
condition.
Examples
else {
...
}
Else if
Rules
- It must be preceded by an
if
or anelse if
condition. - Have a boolean condition.
Examples
else if condition {
...
}
else if my_struct != null {
...
}
While
Rules
- Have a boolean condition.
Examples
while condition {
...
}
Foreach
Rules
- Have the name of a variable.
- Have an array to go through
Examples
foreach val in values {
...
}
You can also do on specific ranges:
foreach i in 0..10 {
...
}
For
Rules
- Have the name of a variable.
- Have a boolean condition.
- Have an assign operator.
Examples
for i: u64 = 0; i < 10; i += 1 {
...
}
Break
Rules
- Must be in a loop (
foreach
,for
,while
).
Examples
while condition {
if i % 10 == 0 {
break;
}
...
}
Continue
Rules
- Must be in a loop (
foreach
,for
,while
).
Examples
while condition {
if i % 10 == 0 {
continue;
}
...
}
Return
Rules
- Must not have any code after.
- If the function returns a value, the return must return a value.
Examples
fn foo() -> string {
return "Hello World!"
}
fn bar() {
if condition {
return
}
foo()
}
Scope
Allows you to isolate a part of the code / variables created.
Rules
- No specific rules.
Examples
{
...
}