Skip to main content

System library

Entities

entity block {
block_height: integer;
block_rid: byte_array;
timestamp;
}

entity transaction {
tx_rid: byte_array;
tx_hash: byte_array;
tx_data: byte_array;
block;
}

It's impossible to create, modify, or delete the values of these entities in the code.

Namespaces

Chain_context

chain_context.args: module_args - module arguments specified in config.yml. The type is module_args, which must be a user-defined struct inside of the entry point to the module with name module_name. You can't access the args field if the module_args struct isn't defined.

Example of module_args:

struct module_args {
name: text;
age: integer;
}

Corresponding module configuration:

blockchains:
module-args-example:
module: example
moduleArgs:
example:
name: Alice
age: 46

Code that reads module_args:

function f() {
print(chain_context.args.name);
print(chain_context.args.age);
}

Every module can have its own module_args. Reading chain_context.args returns the args for the current module, and the type of chain_context.args is different for different modules: it's the module_args struct defined in that module.

chain_context.blockchain_rid: byte_array - blockchain RID

chain_context.raw_config: gtv - blockchain configuration object, for example, {"gtx":{"rell":{"mainFile":"main.rell"}}}

Op_context

You can only use system namespace op_context in an operation or a function called from an operation, but not in a query.

  • op_context.block_height: integer - The height of the block currently getting built

  • op_context.last_block_time: integer - The timestamp of the last block, in milliseconds (like System.currentTimeMillis() in Java). Returns -1 if there is no last block (the block currently being built is the first block)

  • op_context.op_index: integer - Index of the operation being executed in the transaction (0 == first operation)

  • op_context.exists: boolean - New property, which is true if the code gets called from an operation (directly or indirectly). Unlike other properties of op_context, this property is always available but returns false if there is no operation.

  • op_context.get_signers(): list<byte_array> - Returns pubkeys of the signers of the current transaction

  • op_context.is_signer(pubkey: byte_array): boolean - Checks if the pubkey is one of the signers of the current transaction

  • op_context.get_all_operations(): list<gtx_operation> - Returns all operations of the current transaction

  • op_context.transaction: transaction - The transaction currently getting built

    You must not use op_context.transaction.block because, except block_height, its attributes are null, so reading them gives a run-time error.

Crypto

Namespace used for cryptographic functions.

  • crypto.keccak256(byte_array): byte_array - Calculates a Keccak256 hash of a byte array and returns a byte array of size 32.
    For example, crypto.keccak256('Hello world!'.to_bytes()) produces x'ecd0e108a98e192af1d2c25055f4e3bed784b5c877204e73219a5203251feaab'.

  • crypto.sha256(byte_array): byte_array - Calculates an SHA-256 hash of a byte array and returns a byte array of size 32.
    For example, crypto.sha256('Hello world!'.to_bytes()) produces x'c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a'.

  • crypto.privkey_to_pubkey(privkey: byte_array, compress: boolean = false): byte_array - Calculates a public key from the private key.
    It takes a 32-byte private key and returns either a 65-byte (compress = false) or a 33-byte (compress = true) public key.
    For example, crypto.privkey_to_pubkey(x'000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F', true) produces x'036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2'.

  • verify_signature(message: byte_array, pubkey: pubkey, signature: byte_array): boolean - Returns true if the public key matches the signature's signer. It's simply a 64-byte value which is a concatenation of two 32-byte values r and s.
    For example,

    val pubkey = x'036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2';
    // = crypto.privkey_to_pubkey(x'000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F', true)

    val message = x'DEADBEEF';
    val signature = x'8ac02f17b508815fa9495177395925e41fd7db595ad35e54a56be6284e5b8e0824a3bd0e056dcfded7f8073d509b2b674607a06571abebdcb0bd27b12372aff2';

    val ok = crypto.verify_signature(message, pubkey, signature);
    print(ok); // prints "true"
  • crypto.eth_sign(hash: byte_array, privkey: byte_array): (byte_array, byte_array, integer) - Calculates an Ethereum signature.
    It takes a hash (byte array of arbitrary size) and a private key (32 bytes) and returns values r, s, and rec_id that are accepted by eth_ecrecover().

  • eth_ecrecover(r: byte_array, s: byte_array, rec_id: integer, hash: byte_array): byte_array - Calculates Ethererum public key from a signature and hash.
    For example, crypto.eth_ecrecover(r, s, rec_id, hash) produces something like this x'f117f07ef53a4c90e1d6573c62ceeb5caebeb94c68ecd5d4c088f1e2395ed4438193b2b00c56fa62494f5bfb180c437ff301d8eb6993034bf0d8bc4a0a93bf0d'.

    Example - calculate an Ethereum signature with eth_sign() and recover the public key with eth_ecrecover()
    val privkey = x'000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f';
    val pubkey = crypto.privkey_to_pubkey(privkey);

    val hash = 'Hello'.to_bytes();
    val (r, s, rec_id) = crypto.eth_sign(hash, privkey);

    val recovered_pubkey = x'04' + crypto.eth_ecrecover(r, s, rec_id, hash);
    require(recovered_pubkey == pubkey);

Global functions

  • abs(integer): integer - absolute value of an integer

  • abs(big_integer): big_integer - absolute value of a big integer

  • abs(decimal): decimal - absolute value of a decimal

  • empty(T?): boolean - returns true if the argument is null or an empty collection and false otherwise; for nullable collections checks both conditions

  • empty(list<T>): boolean

  • empty(set<T>): boolean

  • empty(map<K, V>): boolean

  • exists(T?): boolean - opposite to empty()

  • exists(list<T>): boolean

  • exists(set<T>): boolean

  • exists(map<K, V>): boolean

  • log(...) - print a message to the log (same usage as print)

  • max(integer, integer): integer - maximum of two integer values

  • max(big_integer, big_integer): big_integer - maximum of two big integer values

  • max(decimal, decimal): decimal - maximum of two decimal value

  • min(integer, integer): integer - minimum of two integer values

  • min(big_integer, big_integer): big_integer - minimum of two big integer values

  • min(decimal, decimal): decimal - minimum of two decimal values

  • print(...) - print a message to STDOUT:

  • print() - prints an empty line

  • print('Hello', 123) - prints "Hello 123"

  • keccak256(byte_array): byte_array - Calculates a Keccak256 hash of a byte array, returns a byte array of size 32.

  • sha256(byte_array): byte_array - Calculates a SHA-256 hash of a byte array, returns a byte array of size 32.

  • verify_signature(message: byte_array, pubkey: pubkey, signature: byte_array): boolean - returns true if the given signature is a result of signing the message with a private key corresponding to the given public key.

  • eth_ecrecover(r: byte_array, s: byte_array, rec_id: integer, hash: byte_array): byte_array - Calculates Ethererum public key from a signature and hash.
    For example, crypto.eth_ecrecover(r, s, rec_id, hash) produces something like this x'f117f07ef53a4c90e1d6573c62ceeb5caebeb94c68ecd5d4c088f1e2395ed4438193b2b00c56fa62494f5bfb180c437ff301d8eb6993034bf0d8bc4a0a93bf0d'.

  • try_call() - catches exceptions of a function and returns a fallback value.

    • try_call(f: ()->T): T? - calls the function, returns the result on success, or null on failure.

    • try_call(f: ()->unit): boolean - same as the previous, but for functions that return unit. Returns true on success or false on failure.

    • try_call(f: ()->T, fallback: T): T - calls the function, returns the result on success or the fallback value on failure. The fallback expression is evaluated only when the function fails.

    Examples of try_call fuction
    • val int_or_null = try_call(**integer.from_hex(s, *)**); - converts the hexadecimal string s to an integer and assigns it to int_or_null, or assigns None if the conversion fails.
    • val int_or_default = try_call(**integer.from_hex(s, *), -1**); - converts the hexadecimal string s to an integer and assigns it to int_or_default, or assigns -1 if the conversion fails.
    • val l = try_call(**list<integer>.from_gtv(my_gtv, *)**); - converts the gtv my_gtv to a list of integers and assigns it to l, or assigns None if the conversion fails.
  • text.like(pattern): boolean - simple pattern matching function, equivalent to the SQL LIKE clause. Special character "_" matches any single character and "%" matches any string of zero or more characters.

    Example of a like function
    • print(name.like(% von %)) - returns all names that have a von inside
    • user @* {name.like(Vi_tor)} - returns all users that have one character between Vi and tor (e.g Victor or Viktor)

Require function

Checking a boolean condition

  • require(boolean[, text]) - throws an exception if the argument is false

Checking for null

  • require(T?[, text]): T - throws an exception if the argument is null, otherwise returns the argument
  • require_not_empty(T?[, text]): T - same as the previous one

Checking for an empty collection

  • require_not_empty(list<T>[, text]): list<T> - throws an exception if the argument is an empty list, otherwise returns the list
  • require_not_empty(set<T>[, text]): set<T> - throws an exception if the argument is an empty set, otherwise returns the set
  • require_not_empty(map<K,V>[, text]): map<K,V> - throws an exception if the argument is an empty map, otherwise returns the map

Passing a nullable collection to require_not_empty, gives an exception if the argument is either null or an empty collection.

Example of require_not_empty function
val x: integer? = calculate();
val y = require(x, "x is null"); // type of "y" is "integer", not "integer?"

val p: list<integer> = get_list();
require_not_empty(p, "List is empty");

val q: list<integer>? = try_to_get_list();
require(q); // fails if q is null
require_not_empty(q); // fails if q is null or an empty list

Partial functions

You can use a wildcard symbol * to create a reference to a function, that is, to obtain a value of a function type that allows you to call the function:

function f(x: integer, y: integer) = x * y;

val g = f(*); // Type of "g" is (integer, integer) -> integer
g(123, 456); // Invocation of f(123, 456) via "g".

The f() notation is a partial application of a function. It allows specifying values for some parameters and wildcards * for others, returning a value of a function type accepting the parameters for which wildcards were specified:

val g = f(123, *);          // Type of "g" is (integer) -> integer
g(456); // Calls f(123, 456).

val h = f(*, 456); // Type of "h" is (integer) -> integer
h(123); // Calls f(123, 456).

Details of partial application syntax

If a wildcard symbol * is specified for at least one parameter, all the unspecified parameters that don't have default values are also considered wildcards. Thus, the exact value of type (integer, integer) -> integer.

Wildcard symbol * specified as the last parameter without a name has a special meaning. It doesn't correspond to a particular function parameter but determines that the call expression is a partial application. Thus, it's unnecessary to specify * for each function parameter. It's enough to write f(*), regardless of the number of parameters f has (even if it's zero).

note

Suppose a wildcard without a name is specified as the last parameter. In that case, there must be no other wildcard parameters because otherwise, the previous one isn't needed (as the call is already a partial application) and may be confusing.

Parameters with default values that aren't explicitly specified as wildcards are bound to their default values. The default values are calculated at the moment of partial application. Consider the following function:

 function p(x: integer, y: integer = 1) = x * y;

The following expressions return the same value of type (integer) -> integer:

  • p(*)

  • p(x = *)

  • p(x = *, y = 1)

The code to include both parameters into a function type that is to get (integer, integer) -> integer:

 p(y = *)
p(x = *, y = *)

Note, for instance, that rules 1 and 2 imply that for a single-parameter function with a default value:

 function r(x: integer = 123): integer { ... }
  • r(*) returns () -> integer (as the last unnamed * isn't assigned to a particular parameter)

  • r(x = *)) returns (integer) -> integer

The order of named wildcard parameters matters. Consider the following function:

function f(x: integer, y: text, z: boolean): decimal { ... }
  • f(*) is equivalent to f(x = *, y = *, z = *) and returns (integer, text, boolean) -> decimal
  • f(z = *, y = *, x = *) returns (boolean, text, integer) -> decimal
  • f(y = *) is equivalent to f(y = *, x = *, z = *) and returns (text, integer, boolean) -> decimal
  • f(*, z = *) is equivalent to f(x = *, z = *, y = *) and returns (integer, boolean, text) -> decimal

Partial application of system functions

Most system library functions can be partially applied to turn them into function values. However, some library functions are overloaded, for example:

  • abs(integer): integer

  • abs(decimal): decimal

You must know the type of the function value to partially apply an overloaded function:

val f: (integer) -> integer = abs(*);   // The type of variable "f" allows to determine which "abs" to use.

Member functions (methods of types text,list<T>, etc.) can be partially-applied too:

val l = [5, 6, 7];
val f = l.size(*); // Type of "f" is () -> integer.
print(f()); // Prints 3.

l.add(8);
l.add(9);
print(f()); // Prints 5.

Some system functions don't support partial application: print(), log(), require(), text.format(), and so on.