The FunC cookbook was created to consolidate all the knowledge and best practices from experienced FunC developers in one place.
The goal is to make it easier for future developers to build smart contracts efficiently.
Unlike the rest of the official FunC documentation, this guide focuses on solving everyday challenges that FunC developers encounter
during smart contract development.
Statements
How to use flags in if statements
To check whether an event is relevant, use a flag variable of type integer.
The flag can either be 0, representing false, or -1, representing true.
See absence of boolean type.
When checking the flag in if..else statements, it is unnecessary to use
the == operator, since a 0 evaluates to false, and any nonzero value is
considered to be true in if..else statements.
int flag = 0; ;; false
;; ...
;; ...
if (flag) { ;; No need to use flag == -1
;; do something
}
else {
;; reject
}
How to write a repeat loop
A repeat loop helps execute an action a fixed number of times. The example below computes
exponentiation of number to the exponent exponent, and illustrates it with specific values number = 2 and exponent = 5:
int number = 2;
int exponent = 5;
int result = 1; ;; Will store the final result: number^exponent
repeat(exponent) { ;; The repeat multiplies variable "number",
;; exactly an "exponent" number of times
result *= number;
}
;; result holds value 32
How to write a while loop
A while loop is useful when the number of iterations is unknown.
The following example processes the references in the message cell.
Each cell can store up to four references to other cells:
cell inner_cell = begin_cell() ;; Create a new empty builder
.store_uint(123, 16) ;; Store uint with value 123 and length 16 bits
.end_cell(); ;; Convert builder to a cell
;; Create a cell, which will have two references to inner_cell
cell message = begin_cell()
.store_ref(inner_cell) ;; Store cell as reference
.store_ref(inner_cell) ;; A second time
.end_cell();
slice msg = message.begin_parse(); ;; Convert cell to slice
while (msg.slice_refs_empty?() != -1) { ;; Iterate while there are refs to process
;; Recall that -1 is true.
cell inner_cell = msg~load_ref(); ;; Load cell from slice msg
;; do something
}
References:
How to write a do until loop
Use a do..until loop when the loop must execute at least once.
int flag = 0;
do {
;; iterate this as long as "flag" is false (0).
;; It would execute at least once even if the flag
;; were already true before entering the loop.
} until (flag == -1); ;; Stop when "flag" becomes -1
Cells and slices
How to determine if a slice is empty
Before working with a slice, checking whether it contains any data is essential to ensure proper processing.
The slice_empty? method can be used for this purpose. However, it returns 0 (false) if the slice contains at least one bit of data or one reference.
;; Creating empty slice
slice empty_slice = ""; ;; compile-time built-in "" produces a slice with no bits
;; `slice_empty?()` returns `true` because the slice doesn't have any `bits` and `refs`.
empty_slice.slice_empty?();
;; Creating slice which contains bits only
slice slice_with_bits_only = "Hello, world!"; ;; compile-time built-in produces a slice
;; containing the ASCII binary code of the string
;; `slice_empty?()` returns `false` because the slice has bits.
slice_with_bits_only.slice_empty?();
;; Create an empty dummy cell for use later
cell dummy_cell = begin_cell().end_cell();
;; Creating slice which contains refs only
slice slice_with_refs_only = begin_cell()
.store_ref(dummy_cell) ;; Add the dummy cell as a reference
.end_cell() ;; this creates the cell
.begin_parse(); ;; this creates a slice from the cell
;; `slice_empty?()` returns `false` because the slice has cell references.
slice_with_refs_only.slice_empty?();
;; Creating slice which contains bits and refs
slice slice_with_bits_and_refs = begin_cell()
.store_slice("Hello, world!") ;; Add bits from the slice computed
;; using the compile-time built-in
.store_ref(dummy_cell) ;; Add the dummy cell as a reference
.end_cell()
.begin_parse();
;; `slice_empty?()` returns `false` because the slice has bits and references.
slice_with_bits_and_refs.slice_empty?();
References:
How to determine if slice is empty (no bits, but may have refs)
If only the presence of bits matters and the cell references in the slice can be ignored, use the slice_data_empty? function.
;; Creating empty slice
slice empty_slice = ""; ;; compile-time built-in "" produces a slice with no bits
;; `slice_data_empty?()` returns `true` because the slice doesn't have any bits.
empty_slice.slice_data_empty?();
;; Creating slice which contains bits only
slice slice_with_bits_only = "Hello, world!"; ;; compile-time built-in produces a slice
;; containing the ASCII binary code of the string
;; `slice_data_empty?()` returns `false` because the slice has bits.
slice_with_bits_only.slice_data_empty?();
;; Create an empty dummy cell for use later
cell dummy_cell = begin_cell().end_cell();
;; Creating slice which contains refs only
slice slice_with_refs_only = begin_cell()
.store_ref(dummy_cell) ;; Add the dummy cell as a reference
.end_cell() ;; this creates the cell
.begin_parse(); ;; this creates a slice from the cell
;; `slice_data_empty?()` returns `true` because the slice doesn't have any bits
slice_with_refs_only.slice_data_empty?();
;; Creating slice which contains bits and refs
slice slice_with_bits_and_refs = begin_cell()
.store_slice("Hello, world!") ;; Add bits from the slice computed
;; using the compile-time built-in
.store_ref(dummy_cell) ;; Add the dummy cell as a reference
.end_cell()
.begin_parse();
;; `slice_data_empty?()` returns `false` because the slice has bits.
slice_with_bits_and_refs.slice_data_empty?();
References:
How to determine if slice is empty (no refs, but may have bits)
If only cell references are of interest, their presence can be checked using the slice_refs_empty? function.
;; Creating empty slice
slice empty_slice = ""; ;; compile-time built-in "" produces a slice with no bits
;; `slice_refs_empty?()` returns `true` because the slice doesn't have any cell references.
empty_slice.slice_refs_empty?();
;; Creating slice which contains bits only
slice slice_with_bits_only = "Hello, world!"; ;; compile-time built-in produces a slice
;; containing the ASCII binary code of the string
;; `slice_refs_empty?()` returns `true` because the slice doesn't have any cell references.
slice_with_bits_only.slice_refs_empty?();
;; Create an empty dummy cell for use later
cell dummy_cell = begin_cell().end_cell();
;; Creating slice which contains refs only
slice slice_with_refs_only = begin_cell()
.store_ref(dummy_cell) ;; Add the dummy cell as a reference
.end_cell() ;; this creates the cell
.begin_parse(); ;; this creates a slice from the cell
;; `slice_refs_empty?()` returns `false` because the slice has cell references.
slice_with_refs_only.slice_refs_empty?();
;; Creating slice which contains bits and refs
slice slice_with_bits_and_refs = begin_cell()
.store_slice("Hello, world!") ;; Add bits from the slice computed
;; using the compile-time built-in
.store_ref(dummy_cell) ;; Add the dummy cell as a reference
.end_cell()
.begin_parse();
;; `slice_refs_empty?()` returns `false` because the slice has cell references.
slice_with_bits_and_refs.slice_refs_empty?();
References:
How to determine if a cell is empty
To check whether a cell contains any data, it must first be converted into a slice.
- If only the data bits matter, use
slice_data_empty?.
- If only cell references matter, use
slice_refs_empty?.
- If the presence of any data (bits or cell references) needs to be checked, use
slice_empty?.
;; Create an empty dummy cell for use later
cell dummy_cell = begin_cell().end_cell();
cell cell_with_bits_and_refs = begin_cell()
.store_uint(1337, 16)
.store_ref(dummy_cell)
.end_cell();
;; To check that cell_with_bits_and_refs is empty,
;; first obtain a slice
slice cs = cell_with_bits_and_refs.begin_parse();
;; Determine if the slice is empty.
if (cs.slice_empty?()) {
;; Cell is empty
}
else {
;; Cell is not empty
}
References:
Determine if the data bits of slices are equal
There are three ways to check if the data bits of two slices are equal:
- Comparing their hashes.
- Using the SDEQ asm instruction.
- Using the
equal_slice_bits function.
int are_slices_equal_1? (slice a, slice b) {
return a.slice_hash() == b.slice_hash();
}
int are_slices_equal_2? (slice a, slice b) asm "SDEQ";
int are_slices_equal_3? (slice a, slice b) {
return equal_slice_bits(a, b);
}
() main () {
slice s1 = "Some text"; ;; load a slice with the ASCII code of the string
slice s2 = "Some text";
~dump(are_slices_equal_1?(s1, s2)); ;; -1 = true
~dump(are_slices_equal_2?(s1, s2)); ;; -1 = true
~dump(are_slices_equal_3?(s1, s2)); ;; -1 = true
s1 = "Text";
;; load a slice with the address encoded in the provided string
s2 = "EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF"a;
~dump(are_slices_equal_1?(s1, s2)); ;; 0 = false
~dump(are_slices_equal_2?(s1, s2)); ;; 0 = false
~dump(are_slices_equal_3?(s1, s2)); ;; 0 = false
}
Ways 2 and 3 only check the data bits of the two slices, but way 1 also takes into account the cell references in the slices.
This means that if the two slices have the same data bits but different cell references, way 1 will answer 0 (false), but way 2 and 3 will answer -1 (true).
For example, the following two slices have the same data bits, but different cell references.
Comparing their hashes would return 0 (false), but comparing them using SDEQ or equal_slice_bits would return -1 (true).;; Create a dummy cell to add as a reference later
cell dummy = begin_cell().end_cell();
slice s1 = begin_cell()
.store_slice("Some text") ;; Add the slice data bits
.store_ref(dummy) ;; Add a dummy reference
.end_cell()
.begin_parse(); ;; Transform the cell to slice
slice s2 = "Some text"; ;; This slice contains only data bits
~dump(are_slices_equal_1?(s1, s2)); ;; 0 = false
~dump(are_slices_equal_2?(s1, s2)); ;; -1 = true
~dump(are_slices_equal_3?(s1, s2)); ;; -1 = true
References:
Determine if the cells are equal
Determine whether two cells are equal by comparing their hashes.
int are_cells_equal? (cell a, cell b) {
return a.cell_hash() == b.cell_hash();
}
() main () {
cell a = begin_cell()
.store_uint(123, 16)
.end_cell();
cell b = begin_cell()
.store_uint(123, 16)
.end_cell();
~dump(are_cells_equal?(a, b)); ;; -1 = true
cell dummy_cell = begin_cell().end_cell();
cell c = begin_cell() ;; Like cell a, but it has an extra reference
.store_uint(123, 16)
.store_ref(dummy_cell)
.end_cell();
~dump(are_cells_equal?(a, c)); ;; 0 = false
}
References:
How to get only the data bits from a slice
If the cell references within a slice are not needed, the raw data bits can be extracted for further processing
using the function preload_bits:
;; Define a slice with data bits and two cell references
slice s = begin_cell()
.store_slice("Some data bits...")
.store_ref(begin_cell().end_cell()) ;; some reference
.store_ref(begin_cell().end_cell()) ;; some reference
.end_cell()
.begin_parse();
;; Extract the data bits as a new slice
slice s_only_data = s.preload_bits(s.slice_bits());
Function preload_bits requires as argument the amount of bits to extract.
s.slice_bits() obtains the amount of data bits
in the slice.
References:
How to build a StateInit cell
The code follows the TL-B for StateInit:
_ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
code:(Maybe ^Cell) data:(Maybe ^Cell)
library:(HashmapE 256 SimpleLib) = StateInit;
which states that code and data should be added as cell references.
Fields split_depth and special are usually set to None (i.e., 0)
in standard programming tasks. The library field usually set to 0 as well.
See more details for the fields in the StateInit TL-B in its article.
cell build_stateinit(cell init_code, cell init_data) {
var state_init = begin_cell()
.store_uint(0, 1) ;; split_depth: Maybe (## 5), set to "None"
.store_uint(0, 1) ;; special: Maybe TickTock, set to "None"
.store_uint(1, 1) ;; code: Maybe ^Cell, set to "Some cell",
;; but the cell is provided later as a reference
.store_uint(1, 1) ;; data: Maybe ^Cell, set to "Some cell",
;; but the cell is provided later as a reference
.store_uint(0, 1) ;; library: HashmapE 256 SimpleLib, set to 0
.store_ref(init_code) ;; the code as a cell reference
.store_ref(init_data) ;; the data as a cell reference
.end_cell();
return state_int;
}
References:
Data structures
How to determine if a dict is empty
The dict_empty? function checks whether a dictionary contains any data.
This method is functionally equivalent to cell_null?, as a null cell typically represents an empty dictionary.
cell d = new_dict(); ;; Create an empty dictionary
;; Set value of key 0 to be a slice containing the ASCII string "hello"
d~udict_set(256, 0, "hello");
;; Set value of key 1 to be a slice containing the ASCII string "world"
d~udict_set(256, 1, "world");
if (d.dict_empty?()) { ;; Determine if the dict is empty
;; dict is empty
}
else {
;; dict is not empty.
;; For dictionary d, execution flow will enter this
;; branch, since d has two elements.
}
In d~udict_set(256, 0, "hello"), the function expects unsigned 256-bit integers as keys; also,
d~udict_set(256, 0, "hello") will mutate the dictionary d, since udict_set is called using
modifying notation with the symbol ~.
References:
How to store and load a dictionary in permanent storage
The logic for loading a dictionary from local storage is as follows:
;; Obtain the contract's local persistent storage
slice local_storage = get_data().begin_parse();
;; This initial assignment ensures that if the condition below
;; fails to find a dictionary already in local storage,
;; `dictionary_cell` will remain with a new empty dictionary.
cell dictionary_cell = new_dict();
if (~ slice_empty?(local_storage)) {
;; A dictionary is already in local storage, load it
dictionary_cell = local_storage~load_dict();
}
Storing the dictionary in local storage is also simple:
set_data(begin_cell().store_dict(dictionary_cell).end_cell());
References:
How to iterate dictionaries
To iterate a dictionary from the smallest to biggest key, first call a dict_get_min? primitive to obtain the smallest key in the dictionary,
and then call a dict_get_next? primitive inside a loop while checking a flag for existence of further key-value pairs to process.
Similarly, to iterate a dictionary from the biggest to smallest key, first call a dict_get_max? primitive to obtain the biggest key in the dictionary,
and then call a dict_get_prev? primitive inside a loop while checking a flag for existence of further key-value pairs to process.
;; Initialize an example dictionary.
;; keys will be unsigned 256-bit integers. Values are slices.
;; Since keys are unsigned,
;; should use 'unsigned' versions of the
;; dict_set, dict_get_min?, dict_get_max?,
;; dict_get_next?, and dict_get_prev? primitives.
cell d = new_dict();
d~udict_set(256, 1, "value 1"); ;; Map key 1 to a slice containing string "value 1"
d~udict_set(256, 5, "value 2"); ;; Map key 5 to a slice containing string "value 2"
d~udict_set(256, 12, "value 3"); ;; Map key 12 to a slice containing string "value 3"
;; First, iterate from smallest to biggest key:
;; Obtain the smallest key.
(int key, slice val, int flag) = d.udict_get_min?(256);
;; Repeat while there are keys to iterate, indicated by "flag".
while (flag) {
;; do something with "key" and "val".
;; Obtain the smallest key bigger than "key".
;; i.e., the one next to "key" in numerical value.
;; "flag" will indicate if such key exists.
(key, val, flag) = d.udict_get_next?(256, key);
}
;; Now, iterate from biggest to smallest key:
;; Obtain the biggest key.
(key, val, flag) = d.udict_get_max?(256);
;; Repeat while there are keys to iterate, indicated by "flag".
while (flag) {
;; do something with "key" and "val".
;; Obtain the biggest key smaller than "key",
;; i.e., the one previous to "key" in numerical value.
;; "flag" will indicate if there is a previous key.
(key, val, flag) = d.udict_get_prev?(256, key);
}
References:
How to delete a value from a dictionary
Use the dict_delete? primitives to delete keys in a dictionary.
;; Initialize an example dictionary.
;; keys will be unsigned 256-bit integers. Values are slices.
;; Since keys are unsigned,
;; should use 'unsigned' versions of the
;; dict_set, dict_delete?, dict_get? primitives.
cell names = new_dict();
names~udict_set(256, 27, "Alice"); ;; Map key 27 to a slice containing string "Alice"
names~udict_set(256, 25, "Bob");
;; Delete key-value pair with key 27
;; This mutates "names" dictionary, because
;; udict_delete? is called using ~
names~udict_delete?(256, 27);
;; Look up the key 27
;; since it was deleted, the "found" flag returns 0
(slice val, int found) = names.udict_get?(256, 27);
~dump(found); ;; 0, means that key was not found in the dictionary
References:
How to determine if a tuple is empty
When working with tuples, checking for existing values before extracting them is crucial.
Extracting a value from an empty tuple will result in an error: “not a tuple of valid size” - exit code 7.
;; Declare the tlen assembler function because it's not present in stdlib
;; tlen determines the length of a tuple
int tlen (tuple t) asm "TLEN";
() main () {
tuple t = empty_tuple(); ;; t is []
t~tpush(13); ;; t is [13]
t~tpush(37); ;; t is [13, 37]
if (t.tlen() == 0) { ;; Evaluates to false
;; tuple is empty
}
else {
;; tuple is not empty
}
}
The tlen assembler function uses
the TVM instruction TLEN to determine the number of elements in the tuple.
The tpush function appends an element to the tuple, so that it becomes the last element.
References:
Basic operations with tuples
;; tlen determines the length of a tuple
int tlen (tuple t) asm "TLEN";
;; tpop removes the last element in the tuple and
;; returns the mutated tuple and the element.
;; The type of the returned element is arbitrary.
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
() main () {
;; creating an empty tuple
tuple names = empty_tuple();
;; push new items
;; Each element is a slice storing an ASCII string
names~tpush("Naito");
names~tpush("Shiraki");
names~tpush("Akamatsu");
names~tpush("Takaki");
;; names is ["Naito", "Shiraki", "Akamatsu", "Takaki"]
;; pop last item, "Takaki"
slice last_name = names~tpop();
;; get first item, "Naito"
slice first_name = names.first();
;; get an item by index, "Akamatsu"
;; First element has index 0
slice best_name = names.at(2);
;; getting the length of the tuple
;; returns 3, because "Takaki" was popped
int number_names = names.tlen();
}
The tlen assembler function uses the TVM instruction TLEN to determine the number of elements in the tuple.
The tpop assembler function uses the TVM instruction TPOP to detach the last element from the tuple, and
it returns the mutated tuple and the detached element.
References:
How to iterate tuples (both directions)
When working with arrays or stacks in FunC, tuples are essential. The first step is learning how to iterate through tuple values for processing.
;; tlen determines the length of a tuple
int tlen (tuple t) asm "TLEN";
;; Casts any type into a tuple
forall X -> tuple to_tuple (X x) asm "NOP";
() main () {
;; Cast the fixed length tuple to an arbitrary length tuple.
;; This is necessary, because in FunC, fixed-length tuples
;; like [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] cannot be assigned
;; directly to arbitrary length tuples.
;; Hence, this does NOT compile:
;; tuple t = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
tuple t = to_tuple([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
int len = t.tlen(); ;; 10
;; Iterate tuple starting from index 0
;; up to index 9
int i = 0;
while (i < len) {
int x = t.at(i); ;; Obtain the i-th element
;; do something with x
i = i + 1;
}
;; Iterate tuple starting from index 9
;; down to index 0
i = len - 1; ;; 9
while (i >= 0) {
int x = t.at(i); ;; Obtain the i-th element
;; do something with x
i = i - 1;
}
}
The tlen assembler function uses the TVM instruction TLEN to determine the number of elements in the tuple.
The to_tuple casts any type into an arbitrary length tuple, which leads to run-time errors if to_tuple is used to cast non-tuple types.
Be careful to only cast fixed-length tuples, like [1, 2]. The to_tuple is essentially a dummy function that does nothing,
because it uses the No operation NOP instruction. The only purpose of to_tuple is to tell the type-checker
to accept the input to to_tuple as a tuple.
References:
Iterating n-nested tuples
Sometimes, while traversing the elements of a tuple, there is the need to iterate through nested tuples.
The following example iterates through a tuple starting from the last index,
and finds the biggest number, irrespective if there are nested tuples.
For example, in the tuple [[2,6],[1,[3,[3,5]]], 3], the example finds 6 as the biggest number.
;; Determines the number of elements in a tuple.
int tuple_length (tuple t) asm "TLEN";
;; Removes the last element in the tuple and
;; returns the mutated tuple and the element.
;; The type of the returned element is arbitrary.
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
;; Checks if the given argument is a tuple
forall X -> int is_tuple (X x) asm "ISTUPLE";
;; Casts the given argument to an arbitrary length tuple.
forall X -> tuple to_tuple (X x) asm "NOP";
;; Casts the given argument to an int.
forall X -> int to_int (X x) asm "NOP";
;; Define a global variable.
;; It will store the biggest number found.
global int max_value;
() iterate_tuple (tuple t) impure {
repeat (t.tuple_length()) {
;; Remove the last element in the tuple.
var value = t~tpop();
if (is_tuple(value)) {
;; If the element is a tuple, cast it to a tuple.
tuple tuple_value = to_tuple(value);
;; And then, recursively iterate the element.
iterate_tuple(tuple_value);
} else {
;; The element is not a tuple, so it must be an int.
;; Cast it to an int.
int int_value = to_int(value);
if (int_value > max_value) {
;; Remember the value in the global variable
;; because it is the biggest so far.
max_value = int_value;
}
}
}
}
() main () {
;; Create an example tuple
tuple t = to_tuple([[2,6], [1, [3, [3, 5]]], 3]);
;; Determine the length of the tuple
int len = t.tuple_length();
;; Set the maximum so far to 0
max_value = 0;
;; Find the maximum value in the tuple
iterate_tuple(t);
~dump(max_value); ;; 6
}
The tuple_length assembler function uses the TVM instruction TLEN to determine the number of elements in the tuple.
The tpop assembler function uses the TVM instruction TPOP to detach the last element from the tuple, and
it returns the mutated tuple and the detached element.
The is_tuple assembler function uses the TVM instruction ISTUPLE to determine if the argument is a tuple or not.
The to_tuple and to_int cast any type into an arbitrary length tuple and integer, respectively. This leads to run-time errors if
to_tuple and to_int are used to cast non-tuple and non-integer types, respectively. The to_tuple and to_int are essentially dummy functions that do nothing,
because they use the No operation NOP instruction. The only purpose of to_tuple and to_int is to tell the type-checker
to accept the input to to_tuple as a tuple, and the input to to_int as an int.
References:
Casting types in tuples
If a tuple contains various data types [cell, slice, int, tuple, ...], there is the need to check the value and cast it accordingly before processing.
The following snippet illustrates this idea.
;; Check if argument is null
forall X -> int is_null (X x) asm "ISNULL";
;; Check if argument is an int
forall X -> int is_int (X x) asm "<{ TRY:<{ 0 PUSHINT ADD DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
;; Check if argument is a cell
forall X -> int is_cell (X x) asm "<{ TRY:<{ CTOS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
;; Check if argument is a slice
forall X -> int is_slice (X x) asm "<{ TRY:<{ SBITS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
;; Check if argument is a tuple
forall X -> int is_tuple (X x) asm "ISTUPLE";
;; Cast functions. They do not carry an operation,
;; since they use the "NOP" TVM instruction.
;; Their sole purpose is to tell the type-checker
;; that it should see the argument as having
;; the desired type.
forall X -> int cast_to_int (X x) asm "NOP";
forall X -> cell cast_to_cell (X x) asm "NOP";
forall X -> slice cast_to_slice (X x) asm "NOP";
forall X -> tuple cast_to_tuple (X x) asm "NOP";
;; Removes the last element in the tuple and
;; returns the mutated tuple and the element.
;; The type of the returned element is arbitrary.
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
;; Process value of arbitrary type
forall X -> () resolve_type (X value) impure {
;; "value" has an arbitrary type.
;; Check its type and cast it.
if (is_null(value)) {
;; Do something with the null.
} elseif (is_int(value)) {
int valueAsInt = cast_to_int(value);
;; Do something with the int.
} elseif (is_slice(value)) {
slice valueAsSlice = cast_to_slice(value);
;; Do something with the slice.
} elseif (is_cell(value)) {
cell valueAsCell = cast_to_cell(value);
;; Do something with the cell.
} elseif (is_tuple(value)) {
tuple valueAsTuple = cast_to_tuple(value);
;; Do something with the tuple.
}
}
() main () {
;; Create an initially empty tuple.
tuple t = empty_tuple();
;; Add some values of different types to the tuple.
t~tpush("Some text");
t~tpush(4);
;; The tuple is ["Some text", 4].
;; Since the tuple has values of different types,
;; when extracting an element, use the
;; keyword "var".
var value = t~tpop();
;; Process the value.
resolve_type(value);
}
The is_int function uses Fift code. Intuitively, the Fift code implements the
following FunC-like pseudocode:
forall X -> int is_int (X x) {
try {
;; Attempt to add 0 to the argument x.
;; If this fails, then x is not an integer.
x + 0;
return -1;
} catch (_, _) {
return 0;
}
}
Similarly, the is_cell carries out the following FunC-like pseudocode, which makes use
of the CTOS TVM instruction:
forall X -> int is_cell (X x) {
try {
;; Under the assumption that x is a cell,
;; CTOS will attempt to get a slice from cell x.
;; If x turns out to be not a cell, CTOS will fail.
CTOS(x);
return -1;
} catch (_, _) {
return 0;
}
}
Finally, the is_slice carries out the following FunC-like pseudocode, which makes use
of the SBITS TVM instruction:
forall X -> int is_slice (X x) {
try {
;; Under the assumption that x is a slice,
;; SBITS will attempt to get the number of data bits
;; in the slice.
;; If x turns out to be not a slice, SBITS will fail.
SBITS(x);
return -1;
} catch (_, _) {
return 0;
}
}
References:
Reversing tuples
The following example reverses any tuple. For example, given the input [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
the reverse_tuple produces the output [10, 9, 8, 7, 6, 5, 4, 3, 2, 1].
;; Determines the number of elements in a tuple.
int tuple_length (tuple t) asm "TLEN";
;; Removes the last element in the tuple and
;; returns the mutated tuple and the element.
;; The type of the returned element is arbitrary.
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
;; Casts the given argument to an arbitrary length tuple.
forall X -> tuple to_tuple (X x) asm "NOP";
tuple reverse_tuple (tuple t1) {
;; Create an initially empty tuple.
;; This variable will be the accumulated result.
tuple t2 = empty_tuple();
repeat (t1.tuple_length()) {
;; Take the last element in the original tuple.
var value = t1~tpop();
;; And append it to the new tuple.
t2~tpush(value);
}
return t2;
}
() main () {
;; Create an example tuple.
tuple t = to_tuple([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
;; Reverse the tuple.
tuple reversed_t = reverse_tuple(t);
~dump(reversed_t); ;; [10 9 8 7 6 5 4 3 2 1]
}
The tuple_length assembler function uses the TVM instruction TLEN to determine the number of elements in the tuple.
The tpop assembler function uses the TVM instruction TPOP to detach the last element from the tuple, and
it returns the mutated tuple and the detached element.
The to_tuple casts any type into an arbitrary length tuple, which leads to run-time errors if to_tuple is used to cast non-tuple types.
The to_tuple is essentially a dummy function that does nothing,
because it uses the No operation NOP instruction. The only purpose of to_tuple is to tell the type-checker
to accept the input to to_tuple as a tuple.
References:
How to remove an item with a certain index from a tuple
;; Determines the number of elements in a tuple.
int tlen (tuple t) asm "TLEN";
(tuple, ()) remove_item (tuple old_tuple, int place) {
;; The result tuple so far. Initially it is empty.
tuple new_tuple = empty_tuple();
int i = 0;
while (i < old_tuple.tlen()) {
int el = old_tuple.at(i);
;; When the place to delete is reached, skip
;; that element by not pushing it to the result tuple.
if (i != place) {
;; Since the current index i is not equal to
;; the place to delete, push the element to
;; the end of the result tuple.
new_tuple~tpush(el);
}
i += 1;
}
return (new_tuple, ());
}
() main () {
;; Create an example tuple, intially empty
tuple numbers = empty_tuple();
;; Insert some integers to the tuple
numbers~tpush(19);
numbers~tpush(999);
numbers~tpush(54);
~dump(numbers); ;; [19 999 54]
numbers~remove_item(1);
~dump(numbers); ;; [19 54]
}
The tlen assembler function uses the TVM instruction TLEN to determine the number of elements in the tuple.
References:
Determine if tuples are equal
The approach involves iterating through both tuples and comparing each value recursively.
Since tuples can contain different data types, check types and cast values dynamically.
;; Determines the number of elements in a tuple.
int tuple_length (tuple t) asm "TLEN";
;; Removes the last element in the tuple and
;; returns the mutated tuple and the element.
;; The type of the returned element is arbitrary.
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
;; Cast functions. They do not carry an operation,
;; since they use the "NOP" TVM instruction.
;; Their sole purpose is to tell the type-checker
;; that it should see the argument as having
;; the desired type.
forall X -> int cast_to_int (X x) asm "NOP";
forall X -> cell cast_to_cell (X x) asm "NOP";
forall X -> slice cast_to_slice (X x) asm "NOP";
forall X -> tuple cast_to_tuple (X x) asm "NOP";
;; Check if argument is null.
forall X -> int is_null (X x) asm "ISNULL";
;; Check if argument is an int.
forall X -> int is_int (X x) asm "<{ TRY:<{ 0 PUSHINT ADD DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
;; Check if argument is a cell.
forall X -> int is_cell (X x) asm "<{ TRY:<{ CTOS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
;; Check if argument is a slice.
forall X -> int is_slice (X x) asm "<{ TRY:<{ SBITS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
;; Check if argument is a tuple.
forall X -> int is_tuple (X x) asm "ISTUPLE";
;; Determine if two slices are equal,
;; by using the TVM instruction SDEQ.
int are_slices_equal? (slice a, slice b) asm "SDEQ";
;; Determine if two cells are equal by using their hashes.
int are_cells_equal? (cell a, cell b) {
return a.cell_hash() == b.cell_hash();
}
(int) are_tuples_equal? (tuple t1, tuple t2) {
int equal? = -1; ;; initial value to true
if (t1.tuple_length() != t2.tuple_length()) {
;; if tuples differ in length, they cannot be equal
return 0;
}
;; At this point, both tuples have the same length
int i = t1.tuple_length();
;; Iterate the tuples back to front,
;; comparing corresponding elements at each iteration.
;; Iterate while there are still elements to check
;; in the tuples, and they remain equal.
;; Recall that the AND boolean operator
;; is emulated using the bitwise AND operator.
while (i > 0 & equal?) {
;; Take the last element of both tuples
;; and remove them from the tuples.
;; This updates the tuples implicitly.
var v1 = t1~tpop();
var v2 = t2~tpop();
;; Check the types of both elements.
;; If they have different types, the
;; execution will enter the "else" branch,
;; marking the "equal?" flag to false.
;; However, if they have the same type,
;; check that their values
;; are equal or not.
if (is_null(v1) & is_null(v2)) {
;; nulls are always equal
} elseif (is_int(v1) & is_int(v2)) {
if (cast_to_int(v1) != cast_to_int(v2)) {
equal? = 0;
}
} elseif (is_slice(v1) & is_slice(v2)) {
if (~ are_slices_equal?(cast_to_slice(v1), cast_to_slice(v2))) {
equal? = 0;
}
} elseif (is_cell(v1) & is_cell(v2)) {
if (~ are_cells_equal?(cast_to_cell(v1), cast_to_cell(v2))) {
equal? = 0;
}
} elseif (is_tuple(v1) & is_tuple(v2)) {
;; recursively determine nested tuples
if (~ are_tuples_equal?(cast_to_tuple(v1), cast_to_tuple(v2))) {
equal? = 0;
}
} else {
equal? = 0;
}
;; Decrease the counter
i -= 1;
}
return equal?;
}
() main () {
;; Create two equal tuples
tuple t1 = cast_to_tuple([[2, 6], [1, [3, [3, 5]]], 3]);
tuple t2 = cast_to_tuple([[2, 6], [1, [3, [3, 5]]], 3]);
;; are_tuples_equal? returns -1, i.e., true
~dump(are_tuples_equal?(t1, t2));
}
For function are_slices_equal? refer to recipe “Determine if the data bits of slices are equal”.
For function are_cells_equal? refer to recipe “Determine if the cells are equal”.
For explanation of the functions that check types, refer to the recipe “Casting types in tuples”.
References:
Basic operations in lisp-style lists
Lisp-style lists are represented as nested tuples. For example, the list 1, 2, 3 is represented as the
nested tuple [1, [2, [3, null]]], where the value null acts as a marker for the end of the list.
Use the cons function to add an element at the front of the provided list. For example,
if lst is the list 2, 3, then cons(1, lst) is the list 1, 2, 3. Internally, cons(1, lst) builds the tuple [1, lst], where
lst is the tuple [2, [3, null]].
The null? function checks if the provided argument is the null value, or equivalently, the empty list.
The following snippet illustrates these functions:
;; An initially empty list.
;; "null" represents the empty list.
;; The null value is obtained by calling the null() function.
tuple numbers = null();
;; Attach 100 as first element of the list
numbers = cons(100, numbers);
;; Attach 200 as first element of the list,
;; now 100 is the second element of the list
numbers = cons(200, numbers);
;; "numbers" is the list: 200, 100.
;; Or equivalently, the tuple: [200, [100, null]]
;; Is the list null or equivalently, empty?
if (numbers.null?()) {
;; list-style list is empty
} else {
;; list-style list is not empty
}
References:
How to iterate through a lisp-style list
As described in the recipe “Basic operations in lisp-style lists”,
Lisp-style lists are represented as nested tuples.
It is possible to iterate a lisp-style list by using the function list-next,
which returns the head of the list and the rest of the list. The following snippet illustrates its usage.
() main () {
;; Create an example list.
;; Initially it is empty, indicated
;; by the null value.
tuple l = null();
l = cons(1, l);
l = cons(2, l);
l = cons(3, l);
;; Here, l is the list 3, 2, 1,
;; or [3, [2, [1, null]]]
;; Iterate the list
;; while the list is not empty
while (~ l.null?()) {
;; Store the list's head in x
;; and update variable l so that it now
;; contains the rest of the list
var x = l~list_next();
;; Do something with x
}
}
References:
How to iterate a cell tree
Each cell can store up to 1023 bits of data and 4 cell references. Therefore, it is possible to represent
complex tree structures by linking cells using cell references.
Given a tree of cells, use any tree traversal algorithm
to access each cell in the tree. For example, the snippet below uses the
iterative version of pre-order traversal,
which makes use of a stack, instead of recursive calls.
The stack is implemented using the same technique for lisp-style lists.
The only difference is that the list grows by appending elements to the end of the list, instead of at the front.
The last element in the list is the top of the stack.
More concretely, if s is the current stack, append an element x by constructing the tuple [s, x].
This tuple [s, x] is the new stack, with top element x.
For example, the stack 1, 2, 3, where 3 is the top element, is represented using nested tuples
as [[[null, 1], 2], 3], where null represents the empty stack.
;; Sets "head" to be the new top element of stack "tail".
;; For example, if t is "tail" and x is "head",
;; this function produces the tuple [t, x],
;; which means that x is the new top element of the stack [t, x].
;; The function returns the new stack and the unit value (),
;; just to be able to use this function using modifying notation.
forall X -> (tuple, ()) push_back (tuple tail, X head) asm "CONS";
;; Removes the last element from the stack, and
;; returns the modified stack and the removed element.
;; For example, if the stack is [t, x], this
;; function returns the stack t, and the element x.
forall X -> (tuple, X) pop_back (tuple t) asm "UNCONS";
() main () {
;; An example tree of cells.
cell c = begin_cell()
.store_uint(1, 16)
.store_ref(begin_cell()
.store_uint(2, 16)
.end_cell())
.store_ref(begin_cell()
.store_uint(3, 16)
.store_ref(begin_cell()
.store_uint(4, 16)
.end_cell())
.store_ref(begin_cell()
.store_uint(5, 16)
.end_cell())
.end_cell())
.end_cell();
;; Initialize the stack. null plays the roll
;; of the empty stack.
tuple stack = null();
;; Push the root cell into the stack to
;; process it in the loop
stack~push_back(c);
;; Iterate while there are cells in the stack
while (~ stack.null?()) {
;; Pop a cell from the stack and convert it
;; to a slice to be able to process it.
;; The call stack~pop_back() implicitly
;; modifies the stack.
slice s = stack~pop_back().begin_parse();
;; Do something with the data in s
;; If the current slice has any refs,
;; add them to the stack.
;; slice_refs returns the number of
;; refs in slice s.
repeat (s.slice_refs()) {
;; Obtain the cell using s~load_ref().
;; This implicitly modifies the slice s
;; so that it is ready to read the next
;; cell reference.
;; Then, push the cell to the stack.
stack~push_back(s~load_ref());
}
}
}
References:
Contracts
How to determine if the contract state is empty
Consider a smart contract that keeps a counter stored in its state, that tracks the number of internal messages the contract has received.
When the contract receives its first message, the contract state is empty, which means that the counter has not been initialized yet.
It is important to handle all scenarios by checking if the state is empty and initializing the counter accordingly.
() recv_internal() {
;; "get_data" returns the persistent contract state, as a cell
cell contract_data = get_data();
;; Obtain a slice from the contract data cell, for reading
slice cs = contract_data.begin_parse();
;; Check that the contract state is empty
if (cs.slice_empty?()) {
;; Contract data is empty, so create a fresh counter.
int counter = 1;
;; To save the counter into the contract state,
;; create a cell containing the counter.
;; The counter is stored as an unsigned 32-bit integer.
cell new_data = begin_cell().store_uint(counter, 32).end_cell();
;; Save the data cell into the contract state by using "set_data".
set_data(new_data);
} else {
;; Contract data is not empty.
;; Get our counter from the contract state and increase it
;; The counter is an unsigned 32-bit integer.
int counter = cs~load_uint(32) + 1;
;; To save the counter into the contract state,
;; create a cell containing the counter.
;; The counter is stored as an unsigned 32-bit integer.
cell new_data = begin_cell().store_uint(counter, 32).end_cell();
;; Save the data cell into the contract state by using "set_data".
set_data(new_data);
}
}
References:
How to update the smart contract logic
Below is an example of a simple CounterV1 smart contract that allows the counter to be incremented and includes logic for updating the contract.
() recv_internal (slice in_msg_body) {
int op = in_msg_body~load_uint(32);
if (op == op::increase) {
int increase_by = in_msg_body~load_uint(32);
ctx_counter += increase_by;
save_data();
return ();
}
if (op == op::upgrade) {
cell code = in_msg_body~load_ref();
set_code(code);
return ();
}
}
After interacting with the contract, and realizing that the functionality for decrementing the counter is missing. Fix this by copying the code from CounterV1 and adding a new decrease function next to the existing increase function. The updated code will look like this:
() recv_internal (slice in_msg_body) {
int op = in_msg_body~load_uint(32);
if (op == op::increase) {
int increase_by = in_msg_body~load_uint(32);
ctx_counter += increase_by;
save_data();
return ();
}
if (op == op::decrease) {
int decrease_by = in_msg_body~load_uint(32);
ctx_counter -= decrease_by;
save_data();
return ();
}
if (op == op::upgrade) {
cell code = in_msg_body~load_ref();
set_code(code);
return ();
}
}
Once the CounterV2 smart contract is ready, compile it off-chain into a cell and send an upgrade message to the CounterV1 contract:
await contractV1.sendUpgrade(provider.sender(), {
code: await compile('ContractV2'),
value: toNano('0.05'),
});
References:
Messages
When a smart contract needs to send an internal message, it must first construct the message as a cell.
This includes specifying technical flags, the recipient’s address, and additional data.
The most common case involves sending an internal message that is bounceable, with no StateInit,
and with message body serialized in the same message cell. The following build_message function
illustrates this common case. The function receives as parameters the destination
address dest_addr encoded as a slice, the amount in nanotons to send amount,
and the requested operation opcode opcode:
cell build_message(slice dest_addr, int amount, int opcode) {
cell msg = begin_cell()
;; Sets 5 headers to their default values.
;; In particular, the bounceable header is true.
.store_uint(0x18, 1 + 1 + 1 + 1 + 2)
.store_slice(dest_addr) ;; destination address
.store_coins(amount) ;; amount to send in nanotons
;; Sets 7 more headers to their default values.
;; Sets default value 0 to all the 7 headers.
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
;; Message body starts here.
;; Usually starts with an unsigned 32-bits integer,
;; called "opcode", that identifies the requested operation.
.store_uint(op, 32)
;; The next fields depend on the opcode.
;; For example, an opcode representing a lookup operation
;; would need to include the data to lookup
;; ......
.end_cell();
return msg;
}
The call store_uint(0x18, 1 + 1 + 1 + 1 + 2) sets 5 headers to their default values. The sum 1 + 1 + 1 + 1 + 2
represents the number of bits occupied by each header, i.e., the first header occupies 1 bit, the second header 1 bit, and so on
until the 5th header which occupies 2 bits. The hexadecimal number 0x18 is a shorthand for the 6 bits 011000, which represents
the default values for each of the headers, i.e., the first header has value 0, the second header 1, and so on until the
5th header, which has the two bits 00.
Among these 5 headers, the third one is probably the most interesting for a programmer, because it corresponds to the bounceable flag,
which is set to 1 (true) by default. If the flag is required to be 0 (false), use hexadecimal 0x10 instead of 0x18,
because 0x10 corresponds to the 6 bits 010000.
The call store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) sets 7 further headers to the default value 0. The sum 1 + 4 + 4 + 64 + 32 + 1 + 1
represents the number of bits occupied by each header. Among these 7 headers, the last two are probably the most interesting for a programmer,
because they correspond to the StateInit header and the message body ref header, respectively.
In particular, the default headers state that the message has no StateInit, and that the message body is not stored as a cell reference,
but directly in the cell, together with the headers.
Refer to recipes “How to send a deploy message” and
“How to set the message body as a ref in an internal message”
for examples on how to manipulate StateInit and the message body ref headers, respectively.
For further details on all the headers, see the sending messages page and
the TLB for messages.
Here is an example on how to use function build_message to send a message:
;; Create a slice from a string containing the destination address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
;; The amount to send in nanotons
int amount = 1000000000;
;; The opcode for the requested operation
int op = 20;
;; Create the message cell
cell msg = build_message(addr, amount, op);
;; Send the message, using default sending mode 0
send_raw_message(msg, 0);
Refer to the sending messages page for further details on sending modes in the send_raw_message function.
References:
How to set the message body as a ref in an internal message
If there is sufficient space, the message body can be stored in the same cell together with the message headers,
as shown in the recipe “How to build an internal message with default headers”.
If there is not enough space in the message cell, the message body can be stored as a cell reference to the message, as shown in the following
function. The function receives the destination address, the amount to send, and the message body as a separate cell.
cell build_message(slice dest_addr, int amount, cell message_body) {
cell msg = begin_cell()
;; Sets 5 headers to their default values.
;; In particular, the bounceable header is true.
.store_uint(0x18, 1 + 1 + 1 + 1 + 2)
.store_slice(dest_addr) ;; destination address
.store_coins(amount) ;; amount to send in nanotons
;; Sets 6 more headers to their default values.
;; Sets default value 0 to all the 6 headers.
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1)
;; Activate the message body ref header
;; to indicate that the message body is included as a
;; cell reference
.store_uint(1, 1)
;; Store the message body as a cell reference
.store_ref(message_body)
.end_cell();
return msg;
}
The call store_uint(0x18, 1 + 1 + 1 + 1 + 2) sets 5 headers to their default values, as in the recipe
“How to build an internal message with default headers”.
The call store_uint(0, 1 + 4 + 4 + 64 + 32 + 1) sets 6 further headers to the default value 0, as in
the first 6 headers in recipe “How to build an internal message with default headers”.
The last header, corresponding to the message body ref header, is set with the call store_uint(1, 1), which indicates that the
message body will be included as a cell reference.
For further details on all the headers, see the sending messages page and
the TLB for messages.
Here is an example on how to use function build_message to send a message:
;; Create a slice from a string containing the destination address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
;; The amount to send in nanotons
int amount = 1000000000;
;; Create a cell storing the message body
cell message_body = begin_cell()
.store_uint(20, 32) ;; include an opcode
.store_slice("❤") ;; include further data required by the opcode
.end_cell();
;; Create the message cell
cell msg = build_message(addr, amount, message_body);
;; Send the message, using default sending mode 0
send_raw_message(msg, 0);
Refer to the sending messages page for further details on sending modes in the send_raw_message function.
References:
How to set the message body as a slice in an internal message
If the message body needs to be included directly in the message cell, but the message body is already in a separate slice,
write the slice into the message cell, as
in the following function. The function receives the destination address, the amount to send, and the message body as a slice.
cell build_message(slice dest_addr, int amount, slice message_body) {
cell msg = begin_cell()
;; Sets 5 headers to their default values.
;; In particular, the bounceable header is true.
.store_uint(0x18, 1 + 1 + 1 + 1 + 2)
.store_slice(dest_addr) ;; destination address
.store_coins(amount) ;; amount to send in nanotons
;; Sets 7 more headers to their default values.
;; Sets default value 0 to all the 7 headers.
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
;; Message body starts here.
;; Write the message body slice
.store_slice(message_body)
.end_cell();
return msg;
}
The function writes the default values for all the message headers, as in the recipe
“How to build an internal message with default headers”.
For further details on all the headers, see the sending messages page and
the TLB for messages.
Here is an example on how to use function build_message to send a message:
;; Create a slice from a string containing the destination address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
;; The amount to send in nanotons
int amount = 1000000000;
;; Create a slice storing the message body
slice message_body = begin_cell()
.store_uint(20, 32) ;; include an opcode
.store_slice("❤") ;; include further data required by the opcode
.end_cell()
.begin_parse(); ;; Transform the cell to a slice
;; Create the message cell
cell msg = build_message(addr, amount, message_body);
;; Send the message, using default sending mode 0
send_raw_message(msg, 0);
Refer to the sending messages page for further details on sending modes in the send_raw_message function.
References:
A “comment” is an ASCII string encoded as a slice. To send a message with a comment,
write a 0 opcode followed by the comment, as done in the following function.
The function receives the destination address, the amount to send, and the comment encoded as a slice.
cell build_message(slice dest_addr, int amount, slice comment) {
cell msg = begin_cell()
;; Sets 5 headers to their default values.
;; In particular, the bounceable header is true.
.store_uint(0x18, 1 + 1 + 1 + 1 + 2)
.store_slice(dest_addr) ;; destination address
.store_coins(amount) ;; amount to send in nanotons
;; Sets 7 more headers to their default values.
;; Sets default value 0 to all the 7 headers.
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
;; Message body starts here.
;; Write a 0 opcode, which means simple
;; message with a comment
.store_uint(0, 32)
;; Write the slice containing the comment
.store_slice(comment)
.end_cell();
return msg;
}
The function writes the default values for all the message headers, as in the recipe
“How to build an internal message with default headers”.
For further details on all the headers, see the sending messages page and
the TLB for messages.
Here is an example on how to use function build_message to send a message:
;; Create a slice from a string containing the destination address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
;; The amount to send in nanotons
int amount = 1000000000;
;; Create a slice storing the comment
slice comment = "Hello from FunC!";
;; Create the message cell
cell msg = build_message(addr, amount, comment);
;; Send the message, using default sending mode 0
send_raw_message(msg, 0);
Refer to the sending messages page for further details on sending modes in the send_raw_message function.
References:
A cell can store up to 1023 bits of data, which means up to 127 8-bit characters.
If there is a need to send a message with a really long comment, split the comment into several slices.
Each slice should have at most 127 chars. Each slice should have a reference to the next one,
forming a snake-like structure.
The following example illustrates the idea:
;; Create a slice from a string containing the destination address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
;; The amount to send in nanotons
int amount = 1000000000;
;; Create a cell storing the message body
;; It contains a long string, that must be split into several
;; slices, linked by references in a snake-like structure.
;; Each slice has at most 127 chars
cell message_body = begin_cell()
;; zero opcode, i.e., message with comment
.store_uint(0, 32)
;; The long string starts here
.store_slice("long, long, long message...")
;; Create a reference to the next part of the string
.store_ref(begin_cell()
.store_slice(" store a string of almost any length, ")
;; And another reference to the last part of the string
.store_ref(begin_cell()
.store_slice(" as long as each slice has at most 127 chars.")
.end_cell())
.end_cell())
.end_cell();
;; Create the message cell
cell msg = build_message(addr, amount, message_body);
;; Send the message, using default sending mode 0
send_raw_message(msg, 0);
The build_message function is exactly the function used in the
recipe How to set the message body as a ref in an internal message.
Refer to the sending messages page for further details on sending modes in the send_raw_message function.
References:
How to send a deploy message
When sending a deploy message, prepare a StateInit cell, as done in the recipe How to build a StateInit cell.
Once the StateInit cell is ready, prepare a message cell in which the message body is included with the headers, or the
message body is included as a separate cell.
The following function illustrates the case for the message body included in the same cell as the headers.
The function receives the destination address, the amount to send, the StateInit cell, and
the message body as a slice.
() deploy(slice dest_addr, int amount, cell state_init, slice message_body) {
cell msg = begin_cell()
;; Sets 5 headers to their default values.
;; In particular, the bounceable header is true.
.store_uint(0x18, 1 + 1 + 1 + 1 + 2)
.store_slice(dest_addr) ;; destination address
.store_coins(amount) ;; amount to send in nanotons
;; Sets 5 more headers to their default values.
;; Sets default value 0 to all the 5 headers.
.store_uint(0, 1 + 4 + 4 + 64 + 32)
;; Sets the StateInit header to the 2-bit integer 3,
;; which signals that a StateInit cell is included
;; as a separate reference cell.
.store_uint(3, 2)
;; Set the message body header to the 1-bit integer 0,
;; which indicates that the message body is
;; included in the same cell.
.store_uint(0, 1)
;; Message body starts here.
;; Write the message body slice
.store_slice(message_body)
;; Include the StateInit cell as a reference
.store_ref(state_init)
.end_cell();
;; Send the deploy message in standard mode
send_raw_message(msg, 0);
}
The function writes the default values for all the message headers, following the same strategy as in the recipe
“How to build an internal message with default headers”,
with the exception of the last two headers: the StateInit header and the message body header.
According to the TLB for internal messages, the StateInit header satisfies:
init:(Maybe (Either StateInit ^StateInit))
which means that the header needs at most two bits, one for deciding the Maybe and, in case the Maybe bit is active,
another for deciding the Either. In the function above, the StateInit header was set to the 2-bit integer 3, which corresponds to the binary
11. The first bit corresponds to the Maybe, and since it is active, it signals that there is a StateInit
in the message. The second bit corresponds to the Either, and since it is active, it signals that the right branch
of the Either was chosen, i.e., the branch ^StateInit, which means that the StateInit is included as a reference cell
in the message.
The message body header was set to the 1-bit value 0, which means that the message body in included in the same cell
together with the headers.
As a second example, the following function also includes a StateInit, but the message body is included as a separate cell.
The function receives the destination address, the amount to send, the StateInit cell, and
the message body as a cell.
() deploy_body_cell(slice dest_addr, int amount, cell state_init, cell message_body) {
cell msg = begin_cell()
;; Sets 5 headers to their default values.
;; In particular, the bounceable header is true.
.store_uint(0x18, 1 + 1 + 1 + 1 + 2)
.store_slice(dest_addr) ;; destination address
.store_coins(amount) ;; amount to send in nanotons
;; Sets 5 more headers to their default values.
;; Sets default value 0 to all the 5 headers.
.store_uint(0, 1 + 4 + 4 + 64 + 32)
;; Sets the StateInit header to the 2-bit integer 3,
;; which signals that a StateInit cell is included
;; as a separate reference cell.
.store_uint(3, 2)
;; Set the message body header to the 1-bit integer 1,
;; which indicates that the message body is
;; included as a separate cell
.store_uint(1, 1)
;; Include the StateInit cell as a reference
.store_ref(state_init)
;; Include the message body as a reference
.store_ref(message_body)
.end_cell();
;; Send the deploy message in standard mode
send_raw_message(msg, 0);
}
For further details on all the headers, see the sending messages page and
the TLB for messages.
Refer to the sending messages page for further details on sending modes in the send_raw_message function.
References:
How to send a message with the entire balance
To transfer the entire balance of a smart contract, use send mode 128.
This is particularly useful for proxy contracts that receive payments and forward them to the main contract.
;; Create a slice from a string containing the destination address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
;; The amount to send in nanotons.
;; In this case, it is irrelevant the sent amount,
;; because, later, the send mode 128 will include
;; all the contract's balance in the message
int amount = 0;
;; The comment to send
slice comment = "Hello from FunC!";
;; Create the message cell
cell msg = build_message(addr, amount, comment);
;; Send the message, using mode 128
;; The mode 128 sends the entire contract's balance in the message
send_raw_message(msg, 128);
The build_message function is exactly the function used in the
recipe How to send a message containing a comment.
Refer to the sending messages page for further details on sending modes in the send_raw_message function.
References:
How to send a message in a proxy contract
A proxy contract facilitates message exchange between a user and a main contract.
The proxy contract redirects messages based on the parameters it receives in the incoming message.
For example, this is a simple example of a proxy contract.
It expects that the incoming message body in_msg_body contains the message mode,
destination address, and the slice to send as body.
() recv_internal (slice in_msg_body) {
;; In the incoming message body,
;; the first byte contains the message mode
;; for the redirected message
int mode = in_msg_body~load_uint(8);
;; Then, the destination address
slice addr = in_msg_body~load_msg_addr();
;; And finally, everything that is left in "in_msg_body"
;; will be the redirected message's body
slice body = in_msg_body;
;; The amount to send in nanotons
int amount = 1000000000;
;; Create the message cell
cell msg = build_message(addr, amount, body);
;; Send the message, using the provided mode.
send_raw_message(msg, mode);
}
The build_message function is exactly the function used in the
recipe How to set the message body as a slice in an internal message.
Refer to the sending messages page for further details on sending modes in the send_raw_message function.
References:
Functions
How to write custom functions using asm keyword
Many features in FunC come from predefined methods in the Standard library.
However, there are many functionalities that the standard library does not cover, but are available as TVM instructions.
In such cases, it is possible to define functions that make use of the TVM instructions.
For example, while the function tpush, which adds an element to the end of a tuple, exists in the standard library,
there is no tpop function, which removes the last element in a tuple and returns the modified tuple and the removed element.
But there is a TVM instruction TPOP that does precisely this.
So, define the function tpop as an assembler function that wraps the TPOP instruction:
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
The return type (tuple, X) indicates that the function produces the modified tuple and the extracted element as a result.
The function is polymorphic in the sense that the type of the returned element X
can be any type. The function name uses the symbol ~ to indicate that this function can be called
using modifying notation.
For example, if it is certain that tuple t has only integer elements, call the function using
modifying notation, like this:
which will assign the removed element to elem and also modify tuple t implicitly.
As another example, the following function determines the length of a tuple by wrapping the TLEN TVM instruction:
int tuple_length (tuple t) asm "TLEN";
Further examples taken from the Standard library:
slice begin_parse(cell c) asm "CTOS";
builder begin_cell() asm "NEWC";
cell end_cell(builder b) asm "ENDC";
References:
How to use modifying notation on functions
To use modifying notation on a function, define the function so
that it has a type of the form (A, ...) -> (A, B), for arbitrary type B. Functions of this type usually mutate their first
argument and return the mutated argument as their first result.
For example, a function f of type (slice, int, slice) -> (slice, cell), can be called using modifying notation as
cell result = s~f(0, "hello"), where s is some slice. The modifying notation is a shorthand for the standard function call
(s, cell result) = f(s, 0, "hello"), where s is reassigned after the call to f.
For a more concrete example, the following defines a function that reads a digit from a slice that stores ASCII digits.
The function receives the slice as an argument and produces two results. The first result is the modified slice,
so that it is ready to read the next digit.
The second result is the loaded digit.
(slice, int) load_digit (slice s) {
;; Load 8 bits (one char) from slice.
;; x stores the decimal ASCII code of the char.
;; Since load_uint is called using modifying notation,
;; slice s is implicitly modified,
;; and it is now ready to read the next 8 bits.
int x = s~load_uint(8);
;; Char '0' has code 48. Substract it
;; to get the digit as a number.
x -= 48;
;; Return the modified slice and the digit.
return (s, x);
}
The function can be called using modifying notation as follows.
() main () {
;; Create a slice containing some ASCII digits
slice s = "258";
;; Proceed to read the digits as integers
;; using modifying notation on the function call.
;; This ensures that the slice gets implicitly modified
;; after reading each digit.
int c1 = s~load_digit();
int c2 = s~load_digit();
int c3 = s~load_digit();
;; here s contains no more data,
;; and c1 = 2, c2 = 5, c3 = 8
}
Refer to the modifying notation page for further details.
References:
Integer utilities
How to get the current time
Use the function now to obtain the current UNIX timestamp.
int current_time = now();
;; Is the current time bigger than some timestamp?
if (current_time > 1672080143) {
;; do some stuff
}
References:
How to generate a random number
This method is not cryptographically secure.
For more details, see the Random number generation section.
;; do this only once before the first invocation of
;; any of the functions "rand" or "random", to ensure that
;; they will have a different seed every time the contract
;; executes.
randomize_lt();
;; Generate a random number in the range 0..9.
int a = rand(10);
;; Generate a random number in the range 0..999999.
int b = rand(1000000);
;; Generate an unsigned 256-bit integer.
int c = random();
References:
Modulo operations
As an example, let’s say there is a need to perform the following calculation for all 256 numbers:
(xp + zp) * (xp - zp).
Since these operations are commonly used in cryptography, modulo operator for montgomery curves should be used.
Note:
Variable names like xp+zp are valid as long as there are no spaces between the operators.
(int) modulo_operations (int xp, int zp) {
;; 2^255 - 19 is a prime number for montgomery curves, meaning all operations should be done against its prime
int prime = 57896044618658097711785492504343953926634992332820282019728792003956564819949;
;; muldivmod handles the next two lines itself
;; int xp+zp = (xp + zp) % prime;
;; int xp-zp = (xp - zp + prime) % prime;
(_, int xp+zp*xp-zp) = muldivmod(xp + zp, xp - zp, prime);
return xp+zp*xp-zp;
}
Reference: muldivmod
How to raise a number to a power
To compute n^e, the naive approach multiplies e - 1 times the number n by itself. For example, n^3
means to multiply by n 2 times: (n * n) * n.
The following code implements such idea. It has a complexity linear on e.
int pow (int n, int e) {
if (e == 0) {
return 1;
}
int i = 0;
int value = n; ;; "value" always stores the initial "n"
while (i < e - 1) { ;; Repeat e-1 times
n *= value; ;; Multiply by the initial "n"
;; and accumulate the result.
i += 1;
}
;; n stores the accumulated result
return n;
}
There is a more efficient way to compute n^e called binary exponentiation.
It has a complexity base-2 logarithmic on e, i.e., O(log_2 e).
This is the recursive implementation of the algorithm. Refer to the article for details.
int binpow (int n, int e) {
if (e == 0) {
return 1;
}
if (e == 1) {
return n;
}
int p = binpow(n, e / 2);
p *= p;
if ((e % 2) == 1) {
p *= n;
}
return p;
}
An usage example of binpow:
() main () {
int num = binpow(2, 3);
~dump(num); ;; 8
}
References:
How to convert the ASCII digits in a slice into an int
;; Example slice containing ASCII digits
slice string_number = "26052021";
;; This variable will hold the final number
int number = 0;
;; Repeat while there is still data in the slice
while (~ string_number.slice_empty?()) {
;; Load 8 bits (one char) from the slice.
;; "x" stores the decimal ASCII code of the char.
int x = string_number~load_uint(8);
;; Char '0' has code 48. Substract it
;; to get the digit as a number.
x -= 48;
;; Accumulate the result.
number = (number * 10) + x;
}
References:
How to convert an int into ASCII digits stored in a slice
;; Example number to transform
int n = 261119911;
;; This will store the partially constructed string
builder string = begin_cell();
;; "chars" will be a lisp-style list, where each element
;; is the decimal ASCII code of a digit.
;; The list will store the ASCII codes of each digit
;; in the number.
;; So, since the number is 261119911, the list will be:
;; ASCII code of 2, ASCII code of 6, ASCII code of 1, .....
;; Initially, the list has the "null" value, to signal
;; that it is empty.
tuple chars = null();
do {
;; Obtain the righmost digit in n
;; and remove it from n
int r = n~divmod(10);
;; Transform the digit "r" into its decimal ASCII code
;; and place it as first element in the list.
;; Even tough the digits of n are processed
;; starting from the rigthmost, the list
;; is filled back to front, ensuring that
;; ASCII codes in the list match the order
;; of the digits in the original "n"
chars = cons(r + 48, chars);
} until (n == 0); ;; Stop until all digits are processed.
;; Now, iterate the list.
do {
;; Get the element at the head of the list
;; and remove it from the list
int char = chars~list_next();
;; Store the decimal ASCII code as a binary 8-bit ASCII code
string~store_uint(char, 8);
} until (null?(chars)); ;; Stop until reaching the end of the list
;; Transform the cell into a slice
slice result = string.end_cell().begin_parse();
References:
Errors
How to throw errors
The following snippet summarizes the main ways of throwing exceptions in FunC,
by using functions throw_if, throw_unless and throw.
int number = 198;
;; The error 35 is thrown only if the number is greater than 50.
;; In general, throw_if(n, cond) will throw error "n"
;; only if "cond" is true.
throw_if(35, number > 50);
;; the error 39 is thrown only if the number is NOT EQUAL to 198.
;; In general, throw_unless(n, cond) will throw error "n"
;; only if "cond" is false.
throw_unless(39, number == 198);
;; the error 36 is thrown unconditionally
throw(36);
References:
Addresses
Generate an internal address
When deploying a new contract, there is the need to generate its internal address because it is initially unknown.
The internal address can be generated from the contract’s StateInit, which contains the code and data of the new contract.
According to the MsgAddressInt TLB, an internal address is composed of the following headers:
addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256;
Therefore, to create an internal address, the above headers must be written in a cell, where address header is computed
using the hash of the contract’s StateInit. The following function creates an internal address that follows these headers,
and stores the address in a cell, which finally gets transformed into a slice, as slices tend to be the preferred
format for storing internal addresses.
slice generate_internal_address (int workchain_id, cell state_init) {
return begin_cell()
;; Header addr_std$10, requires the bits 10,
;; i.e., 2 as an unsigned integer
.store_uint(2, 2)
.store_uint(0, 1) ;; anycast header defaults to nothing
;; workchain_id, 8-bits signed integer
.store_int(workchain_id, 8)
;; The address as 256-bit unsigned integer.
;; Use the function cell_hash to compute
;; a hash of the provided state_init.
;; This hash acts as the address.
.store_uint(cell_hash(state_init), 256)
.end_cell().begin_parse();
}
Refer to recipe “How to build a StateInit cell”. Additionally,
refer to recipe “How to send a deploy message” for sending a deploy message.
Further information on workchain IDs are found in the docs.
References:
Generate an external address
Use the TL-B scheme from block to determine the address format to generate an external address.
(int) ubitsize (int a) asm "UBITSIZE";
slice generate_external_address (int address) {
;; addr_extern$01 len:(## 9) external_address:(bits len) = MsgAddressExt;
int address_length = ubitsize(address);
return begin_cell()
.store_uint(1, 2) ;; addr_extern$01
.store_uint(address_length, 9)
.store_uint(address, address_length)
.end_cell().begin_parse();
}
Since there is a need to find the exact number of bits occupied by the address, declare an asm function with the UBITSIZE opcode. This function will return the minimum number of bits required to store a given number.
Reference: TVM instructions