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 builtop_context.last_block_time: integer
- The timestamp of the last block, in milliseconds (likeSystem.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 istrue
if the code gets called from an operation (directly or indirectly). Unlike other properties ofop_context
, this property is always available but returnsfalse
if there is no operation.op_context.get_signers(): list<byte_array>
- Returns pubkeys of the signers of the current transactionop_context.is_signer(pubkey: byte_array): boolean
- Checks if the pubkey is one of the signers of the current transactionop_context.get_all_operations(): list<gtx_operation>
- Returns all operations of the current transactionop_context.transaction: transaction
- The transaction currently getting builtYou must not use
op_context.transaction.block
because, exceptblock_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 aKeccak256
hash of a byte array and returns a byte array of size 32.
For example,crypto.keccak256('Hello world!'.to_bytes())
producesx'ecd0e108a98e192af1d2c25055f4e3bed784b5c877204e73219a5203251feaab'
.crypto.sha256(byte_array): byte_array
- Calculates anSHA-256
hash of a byte array and returns a byte array of size 32.
For example,crypto.sha256('Hello world!'.to_bytes())
producesx'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)
producesx'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 valuesr
ands
.
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 valuesr
,s
, andrec_id
that are accepted byeth_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 thisx'f117f07ef53a4c90e1d6573c62ceeb5caebeb94c68ecd5d4c088f1e2395ed4438193b2b00c56fa62494f5bfb180c437ff301d8eb6993034bf0d8bc4a0a93bf0d'
.Example - calculate an Ethereum signature with
eth_sign()
and recover the public key witheth_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 integerabs(big_integer): big_integer
- absolute value of a big integerabs(decimal): decimal
- absolute value of a decimalempty(T?): boolean
- returnstrue
if the argument isnull
or an empty collection andfalse
otherwise; for nullable collections checks both conditionsempty(list<T>): boolean
empty(set<T>): boolean
empty(map<K, V>): boolean
exists(T?): boolean
- opposite toempty()
exists(list<T>): boolean
exists(set<T>): boolean
exists(map<K, V>): boolean
log(...)
- print a message to the log (same usage asprint
)max(integer, integer): integer
- maximum of two integer valuesmax(big_integer, big_integer): big_integer
- maximum of two big integer valuesmax(decimal, decimal): decimal
- maximum of two decimal valuemin(integer, integer): integer
- minimum of two integer valuesmin(big_integer, big_integer): big_integer
- minimum of two big integer valuesmin(decimal, decimal): decimal
- minimum of two decimal valuesprint(...)
- print a message to STDOUT:print()
- prints an empty lineprint('Hello', 123)
- prints"Hello 123"
keccak256(byte_array): byte_array
- Calculates aKeccak256
hash of a byte array, returns a byte array of size 32.sha256(byte_array): byte_array
- Calculates aSHA-256
hash of a byte array, returns a byte array of size 32.verify_signature(message: byte_array, pubkey: pubkey, signature: byte_array): boolean
- returnstrue
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 thisx'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, ornull
on failure.try_call(f: ()->unit): boolean
- same as the previous, but for functions that returnunit
. Returnstrue
on success orfalse
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 strings
to an integer and assigns it toint_or_null
, or assignsNone
if the conversion fails.val int_or_default = try_call(**integer.from_hex(s, *), -1**);
- converts the hexadecimal strings
to an integer and assigns it toint_or_default
, or assigns-1
if the conversion fails.val l = try_call(**list<integer>.from_gtv(my_gtv, *)**);
- converts the gtvmy_gtv
to a list of integers and assigns it tol
, or assignsNone
if the conversion fails.
text.like(pattern): boolean
- simple pattern matching function, equivalent to the SQLLIKE
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 insideuser @* {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 isfalse
Checking for null
require(T?[, text]): T
- throws an exception if the argument isnull
, otherwise returns the argumentrequire_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 listrequire_not_empty(set<T>[, text]): set<T>
- throws an exception if the argument is an empty set, otherwise returns the setrequire_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).
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 tof(x = *, y = *, z = *)
and returns(integer, text, boolean) -> decimal
f(z = *, y = *, x = *)
returns(boolean, text, integer) -> decimal
f(y = *)
is equivalent tof(y = *, x = *, z = *)
and returns(text, integer, boolean) -> decimal
f(*, z = *)
is equivalent tof(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.