Кулинарная книга FunC
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.
Основная ц ель создания кулинарной книги FunC - собрать весь опыт разработчиков FunC в одном месте, чтобы будущие разработчики могли им воспользоваться!
По сравнению с документацией FunC, эта статья больше сосредоточена на повседневных задачах каждого разработчика FunC, которые необходимо решать при разработке смарт-контрактов.
Основы
Как написать оператор if
Допустим, мы хотим проверить, имеет ли значение какое-либо событие. Для этого мы используем переменную флага. Помните, что в FunC true — это -1, а false — это 0.
int flag = 0; ;; false
if (flag) {
;; do something
}
else {
;; reject the transaction
}
💡 Примечание
Нам не нужен оператор
==, потому что значение0— этоfalse, поэтому любое другое значение будетtrue.
💡 Полезные ссылки
Как написать цикл repeat
Как пример, возьмем возведение в степень
int number = 2;
int multiplier = number;
int degree = 5;
repeat(degree - 1) {
number *= multiplier;
}
💡 Полезные ссылки
Как написать цикл while
While полезен, когда мы не знаем, как часто выполнять определенное действие. Например, возьмем cell, которая, как известно, хранит до четырех ссылок на другие ячейки.
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
cell message = begin_cell()
.store_ref(inner_cell) ;; store cell as reference
.store_ref(inner_cell)
.end_cell();
slice msg = message.begin_parse(); ;; convert cell to slice
while (msg.slice_refs_empty?() != -1) { ;; we should remind that -1 is true
cell inner_cell = msg~load_ref(); ;; load cell from slice msg
;; do something
}
💡 Полезные ссылки
Как написать цикл do until
Когда нам нужно, чтобы цикл выполнился хотя бы один раз, мы используем do until.
int flag = 0;
do {
;; do something even flag is false (0)
} until (flag == -1); ;; -1 is true
💡 Полезные ссылки
Как определить, пуст ли срез
Перед тем, как работать с slice, необходимо проверить, есть ли у него какие-либо данные, чтобы правильно их обработать. Для этого можно использовать slice_empty?(), но нужно учитывать, что он вернет 0 (false), если есть хотя бы один bit данных или одна ref.
;; creating empty slice
slice empty_slice = "";
;; `slice_empty?()` returns `true`, because 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!";
;; `slice_empty?()` returns `false`, because slice have any `bits`
slice_with_bits_only.slice_empty?();
;; creating slice which contains refs only
slice slice_with_refs_only = begin_cell()
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_empty?()` returns `false`, because slice have any `refs`
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!")
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_empty?()` returns `false`, because slice have any `bits` and `refs`
slice_with_bits_and_refs.slice_empty?();
💡 Полезные ссылки
"slice_empty?()" в документации
Как определить, является ли срез пустым (не имеет битов, но может иметь ссылки)
Если нам нужно проверить только bits и неважно, есть ли какие-либо refs в slice, то нам следует использовать slice_data_empty?().
;; creating empty slice
slice empty_slice = "";
;; `slice_data_empty?()` returns `true`, because slice doesn't have any `bits`
empty_slice.slice_data_empty?();
;; creating slice which contains bits only
slice slice_with_bits_only = "Hello, world!";
;; `slice_data_empty?()` returns `false`, because slice have any `bits`
slice_with_bits_only.slice_data_empty?();
;; creating slice which contains refs only
slice slice_with_refs_only = begin_cell()
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_data_empty?()` returns `true`, because 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!")
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_data_empty?()` returns `false`, because slice have any `bits`
slice_with_bits_and_refs.slice_data_empty?();
💡 Полезные ссылки
"slice_data_empty?()" в документации
Как определить, является ли срез пустым (не имеет ссылок, но может иметь биты)
В случае, если нас интересуют только refs, мы должны проверить их наличие с помощью slice_refs_empty?().
;; creating empty slice
slice empty_slice = "";
;; `slice_refs_empty?()` returns `true`, because slice doesn't have any `refs`
empty_slice.slice_refs_empty?();
;; creating slice which contains bits only
slice slice_with_bits_only = "Hello, world!";
;; `slice_refs_empty?()` returns `true`, because slice doesn't have any `refs`
slice_with_bits_only.slice_refs_empty?();
;; creating slice which contains refs only
slice slice_with_refs_only = begin_cell()
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_refs_empty?()` returns `false`, because slice have any `refs`
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!")
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_refs_empty?()` returns `false`, because slice have any `refs`
slice_with_bits_and_refs.slice_refs_empty?();
💡 Полезные ссылки
"slice_refs_empty?()" в документациии
"store_slice()" в документациии
Как определить, пуста ли ячейка
Чтобы проверить, есть ли какие-либо данные в cell, мы должны сначала преобразовать ее в slice. Если нас интересуют только bits, мы должны использовать slice_data_empty?(), если только refs - slice_refs_empty?(). В случае, если мы хотим проверить наличие каких-либо данных, независимо от того, являются ли они bit или ref, мы должны использовать slice_empty?().
cell cell_with_bits_and_refs = begin_cell()
.store_uint(1337, 16)
.store_ref(null())
.end_cell();
;; Change `cell` type to slice with `begin_parse()`
slice cs = cell_with_bits_and_refs.begin_parse();
;; determine if slice is empty
if (cs.slice_empty?()) {
;; cell is empty
}
else {
;; cell is not empty
}
💡 Полезные ссылки
Как определить, пуст ли словарь
Существует метод dict_empty?() для проверки наличия данных в словаре. Этот метод эквивалентен cell_null?(), поскольку обычно null-ячейка является пустым словарем.
cell d = new_dict();
d~udict_set(256, 0, "hello");
d~udict_set(256, 1, "world");
if (d.dict_empty?()) { ;; Determine if dict is empty
;; dict is empty
}
else {
;; dict is not empty
}
💡 Полезные ссылки
"dict_empty?()" в документации
"new_dict()" в документации создание пустого словаря
"dict_set()" в документации добавление некоторых элементов в словарь d с помощью функции, чтобы он не был пустым
Как определить, пуст ли кортеж
При работе с tuples всегда важно знать, есть ли внутри какие-либо значения для извлечения. Если мы попытаемся извлечь значение из пустого tuple, то получим ошибку: "кортеж недопустимого размера" с exit code 7.
;; Declare tlen function because it's not presented in stdlib
(int) tlen (tuple t) asm "TLEN";
() main () {
tuple t = empty_tuple();
t~tpush(13);
t~tpush(37);
if (t.tlen() == 0) {
;; tuple is empty
}
else {
;; tuple is not empty
}
}
💡 Примечание
Мы объявляем функцию сборки tlen. Вы можете прочитать больше здесь и посмотреть список всех команд ассемблера.
💡 Полезные ссылки
Как определить, пуст ли lisp-подобный список
tuple numbers = null();
numbers = cons(100, numbers);
if (numbers.null?()) {
;; list-style list is empty
} else {
;; list-style list is not empty
}
Мы добавляем число 100 в наш lisp-подобный список с помощью функции cons, поэтому он не пуст.
Как определить, что состояние контракта пустое
Допустим, у нас есть counter, который хранит количество транзакций. Эта переменная недоступна во время первой транзакции в состоянии смарт-контракта, поскольку состояние пустое, поэтому необходимо обработать такой случай. Если состояние пустое, мы создаем переменную counter и сохраняем ее.
;; `get_data()` will return the data cell from contract state
cell contract_data = get_data();
slice cs = contract_data.begin_parse();
if (cs.slice_empty?()) {
;; contract data is empty, so we create counter and save it
int counter = 1;
;; create cell, add counter and save in contract state
set_data(begin_cell().store_uint(counter, 32).end_cell());
}
else {
;; contract data is not empty, so we get our counter, increase it and save
;; we should specify correct length of our counter in bits
int counter = cs~load_uint(32) + 1;
set_data(begin_cell().store_uint(counter, 32).end_cell());
}
💡 Примечание
Мы можем определить, что состояние контракта пустое, определив, что ячейка пуста.
💡 Полезные ссылки
"begin_parse()" в документации
Как построить ячейку внутреннего сообщения
Если мы хотим, чтобы контракт отправлял внутреннее сообщение, мы должны сначала правильно создать его как ячейку, указав технические флаги, адрес получателя и ост альные данные.
;; We use literal `a` to get valid address inside slice from string containing address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
int amount = 1000000000;
;; we use `op` for identifying operations
int op = 0;
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(amount)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_uint(op, 32)
.end_cell();
send_raw_message(msg, 3); ;; mode 3 - pay fees separately and ignore errors
💡 Примечание
В этом примере мы используем литерал
aдля получения адреса. Подробнее о строковых литералах можно узнать в документации
💡 Примечание
Подробнее можно узнать в документации. Также можно перейти к макету по этой ссылке.
💡 Полезные ссылки
"store_slice()" в документации
Как включить тело в качестве ссылки на внутреннюю ячейку сообщения
В теле сообщения, которое следует за флагами и другими техническими данными, мы можем отправить int, slice и cell. В последнем случае необходимо установить бит р авным 1 перед store_ref(), чтобы указать, что cell будет продолжена.
Мы также можем отправить тело сообщения внутри той же cell, что и заголовок, если уверены, что у нас достаточно места. В этом случае нам нужно установить бит равным 0.
;; We use literal `a` to get valid address inside slice from string containing address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
int amount = 1000000000;
int op = 0;
cell message_body = begin_cell() ;; Creating a cell with message
.store_uint(op, 32)
.store_slice("❤")
.end_cell();
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(amount)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1) ;; default message headers (see sending messages page)
.store_uint(1, 1) ;; set bit to 1 to indicate that the cell will go on
.store_ref(message_body)
.end_cell();
send_raw_message(msg, 3); ;; mode 3 - pay fees separately and ignore errors
💡 Примечание
В этом примере мы используем литерал
aдля получения адреса. Подробнее о строковых литералах можно узнать в документации
💡 Примечание
В этом примере мы использовали режим 3, чтобы взять входящие ton и отправить ровно столько, сколько указано (сумма), при этом выплачивая комиссию с баланса контракта и игнорируя ошибки. Режим 64 необходим для возврата всех полученных ton, вычитая комиссию, а режим 128 отправит весь баланс.
💡 Примечание
Мы создаем сообщение, но добавляем тело сообщения отдельно.
💡 Полезные ссылки
"store_slice()" в документации
Как включить тело в качестве среза во внутреннюю ячейку сообщения
При отправке сообщений тело сообщения может быть отправлено либо как cell, либо как slice. В этом примере мы отправляем тело сообщения внутри slice.
;; We use literal `a` to get valid address inside slice from string containing address
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
int amount = 1000000000;
int op = 0;
slice message_body = "❤";
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(amount)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_uint(op, 32)
.store_slice(message_body)
.end_cell();
send_raw_message(msg, 3); ;; mode 3 - pay fees separately and ignore errors
💡 Примечание
В этом примере мы используем литерал
aдля получения адреса. Подробнее о строковых литералах можно узнать в документации
💡 Примечание
В этом примере мы использовали режим 3, чтобы взять входящие ton и отправить ровно столько, сколько указано (сумма), при этом выплачивая комиссию из баланса контракта и игнорируя ошибки. Режим 64 необходим для возврата всех полученных ton, вычитая комиссию, а режим 128 отправит весь баланс.
💡 Примечание
Мы строим сообщение, но добавляем сообщение как срез.
Как перебирать кортежи (в обоих направлениях)
Если мы хотим работать с массивом или стеком в FunC, то там понадобится кортеж. И в первую очередь нам нужно уметь перебирать значения, чтобы работать с ними.
(int) tlen (tuple t) asm "TLEN";
forall X -> (tuple) to_tuple (X x) asm "NOP";
() main () {
tuple t = to_tuple([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
int len = t.tlen();
int i = 0;
while (i < len) {
int x = t.at(i);
;; do something with x
i = i + 1;
}
i = len - 1;
while (i >= 0) {
int x = t.at(i);
;; do something with x
i = i - 1;
}
}
💡 Примечание
Мы объявляем функцию ассемблера
tlen. Вы можете прочитать больше здесь и посмотреть список всех команд ассемблера.Также мы объявляем функцию
to_tuple. Она просто изменяет тип данных любого ввода на кортеж, поэтому будьте осторожны при ее использовании.
Как писать собственные функции с использованием ключевого слова asm
При использовании любых функций мы фактически используем заранее подготовленные для нас методы внутри stdlib.fc. Но на самом деле, нам доступно гораздо больше возможностей, и нам нужно научиться писать их самим.
Например, у нас есть метод tpush, который добавляет элемент в tuple, но без tpop. В этом случае мы должны сделать это:
;; ~ means it is modifying method
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
Если мы хотим узнать длину tuple для итерации, мы должны написать новую функцию с инструкцией TLEN asm:
int tuple_length (tuple t) asm "TLEN";
Некоторые примеры функций, уже известных нам из stdlib.fc:
slice begin_parse(cell c) asm "CTOS";
builder begin_cell() asm "NEWC";
cell end_cell(builder b) asm "ENDC";
💡 Полезные ссылки:
Итерация n-вложенных кортежей
Иногда мы хотим итерировать вложенные кортежи. Следующий пример выполнит итерацию и выведет все элементы в кортеже в формате [[2,6],[1,[3,[3,5]]], 3], начиная с заголовка
int tuple_length (tuple t) asm "TLEN";
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
forall X -> int is_tuple (X x) asm "ISTUPLE";
forall X -> tuple cast_to_tuple (X x) asm "NOP";
forall X -> int cast_to_int (X x) asm "NOP";
forall X -> (tuple) to_tuple (X x) asm "NOP";
;; define global variable
global int max_value;
() iterate_tuple (tuple t) impure {
repeat (t.tuple_length()) {
var value = t~tpop();
if (is_tuple(value)) {
tuple tuple_value = cast_to_tuple(value);
iterate_tuple(tuple_value);
}
else {
if(value > max_value) {
max_value = value;
}
}
}
}
() main () {
tuple t = to_tuple([[2,6], [1, [3, [3, 5]]], 3]);
int len = t.tuple_length();
max_value = 0; ;; reset max_value;
iterate_tuple(t); ;; iterate tuple and find max value
~dump(max_value); ;; 6
}
💡 Полезные ссылки
Основные операции с кортежами
(int) tlen (tuple t) asm "TLEN";
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
() main () {
;; creating an empty tuple
tuple names = empty_tuple();
;; push new items
names~tpush("Naito Narihira");
names~tpush("Shiraki Shinichi");
names~tpush("Akamatsu Hachemon");
names~tpush("Takaki Yuichi");
;; pop last item
slice last_name = names~tpop();
;; get first item
slice first_name = names.first();
;; get an item by index
slice best_name = names.at(2);
;; getting the length of the list
int number_names = names.tlen();
}
Определение типа X
В следующем примере проверяется, содержится ли в кортеже какое-либо значение, но кортеж содержит значения X (cell, slice, int, tuple, int). Нам нужно проверить значение и привести его в соответствие.
forall X -> int is_null (X x) asm "ISNULL";
forall X -> int is_int (X x) asm "<{ TRY:<{ 0 PUSHINT ADD DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_cell (X x) asm "<{ TRY:<{ CTOS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_slice (X x) asm "<{ TRY:<{ SBITS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_tuple (X x) asm "ISTUPLE";
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";
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
forall X -> () resolve_type (X value) impure {
;; value here is of type X, since we dont know what is the exact value - we would need to check what is the value and then 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 () {
;; creating an empty tuple
tuple stack = empty_tuple();
;; let's say we have tuple and do not know the exact types of them
stack~tpush("Some text");
stack~tpush(4);
;; we use var because we do not know type of value
var value = stack~tpop();
resolve_type(value);
}
💡 Полезные ссылки
Как получить текущее время
int current_time = now();
if (current_time > 1672080143) {
;; do some stuff
}
Как сгенерировать случайное число
Ознакомьтесь с генерацией случайных чисел для получения дополнительной информации.
randomize_lt(); ;; do this once
int a = rand(10);
int b = rand(1000000);
int c = random();
Операции по модулю
В качестве примера предположим, что мы хотим выполнить следующее вычисление всех 256 чисел: (xp + zp)*(xp-zp). Поскольку большинство этих операций используются в криптографии, в следующем примере мы используем оператор по модулю для кривых Монтгомери.
Обратите внимание, что xp+zp — это допустимое имя переменной (без пробелов между ними).
(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;
}
💡 Полезные ссылки
Как вызывать ошибки
int number = 198;
throw_if(35, number > 50); ;; the error will be triggered only if the number is greater than 50
throw_unless(39, number == 198); ;; the error will be triggered only if the number is NOT EQUAL to 198
throw(36); ;; the error will be triggered anyway
Стандартные коды исключений tvm
Переворачивание кортежей
Поскольку кортеж хранит данные в виде стека, иногда нам приходится переворачивать его, чтобы прочитать данные с другого конца.
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
int tuple_length (tuple t) asm "TLEN";
forall X -> (tuple) to_tuple (X x) asm "NOP";
(tuple) reverse_tuple (tuple t1) {
tuple t2 = empty_tuple();
repeat (t1.tuple_length()) {
var value = t1~tpop();
t2~tpush(value);
}
return t2;
}
() main () {
tuple t = to_tuple([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
tuple reversed_t = reverse_tuple(t);
~dump(reversed_t); ;; [10 9 8 7 6 5 4 3 2 1]
}
💡 Полезные ссылки
Как удалить элемент с определенным индексом из списка
int tlen (tuple t) asm "TLEN";
(tuple, ()) remove_item (tuple old_tuple, int place) {
tuple new_tuple = empty_tuple();
int i = 0;
while (i < old_tuple.tlen()) {
int el = old_tuple.at(i);
if (i != place) {
new_tuple~tpush(el);
}
i += 1;
}
return (new_tuple, ());
}
() main () {
tuple numbers = empty_tuple();
numbers~tpush(19);
numbers~tpush(999);
numbers~tpush(54);
~dump(numbers); ;; [19 999 54]
numbers~remove_item(1);
~dump(numbers); ;; [19 54]
}
Определить, равны ли срезы
Есть два разных способа определить равенство. Один основан на хэше среза, а другой — на использовании инструкции SDEQ asm.
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";
() main () {
slice a = "Some text";
slice b = "Some text";
~dump(are_slices_equal_1?(a, b)); ;; -1 = true
a = "Text";
;; We use literal `a` to get valid address inside slice from string containing address
b = "EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF"a;
~dump(are_slices_equal_2?(a, b)); ;; 0 = false
}
💡 Полезные ссылки
Определяем, равны ли ячейки
Мы можем легко определить равенство ячеек на основе их хеша.
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
}
💡 Полезные ссылки
Определить, равны ли кортежи
Более продвинутым примером может быть повторение и сравнение каждого из значений кортежа. Поскольку они равны X, нам нужно проверить и привести к соответствующему типу, и, если это кортеж, выполнить его рекурсивную итерацию.
int tuple_length (tuple t) asm "TLEN";
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
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";
forall X -> int is_null (X x) asm "ISNULL";
forall X -> int is_int (X x) asm "<{ TRY:<{ 0 PUSHINT ADD DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_cell (X x) asm "<{ TRY:<{ CTOS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_slice (X x) asm "<{ TRY:<{ SBITS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS";
forall X -> int is_tuple (X x) asm "ISTUPLE";
int are_slices_equal? (slice a, slice b) asm "SDEQ";
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 are differ in length they cannot be equal
return 0;
}
int i = t1.tuple_length();
while (i > 0 & equal?) {
var v1 = t1~tpop();
var v2 = t2~tpop();
if (is_null(t1) & is_null(t2)) {
;; 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;
}
i -= 1;
}
return equal?;
}
() main () {
tuple t1 = cast_to_tuple([[2, 6], [1, [3, [3, 5]]], 3]);
tuple t2 = cast_to_tuple([[2, 6], [1, [3, [3, 5]]], 3]);
~dump(are_tuples_equal?(t1, t2)); ;; -1
}
💡 Полезные ссылки
Генерация внутреннего адреса
Нам нужно сгенерировать внутренний адрес, когда наш контракт должен развернуть новый контракт, но мы не знаем его адрес. Предположим, у нас уже есть state_init — код и данные нового контракта.
Создает внутренний адрес для соответствующего MsgAddressInt TLB.
(slice) generate_internal_address (int workchain_id, cell state_init) {
;; addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt;
return begin_cell()
.store_uint(2, 2) ;; addr_std$10
.store_uint(0, 1) ;; anycast nothing
.store_int(workchain_id, 8) ;; workchain_id: -1
.store_uint(cell_hash(state_init), 256)
.end_cell().begin_parse();
}
() main () {
slice deploy_address = generate_internal_address(workchain(), state_init);
;; then we can deploy new contract
}
💡 Примечание
В этом примере мы используем
workchain()для получения идентификатора воркчейна. Подробнее о идентификаторе воркчейна можно узнать в документации.
💡 Полезные ссылки
Генерация внешнего адреса
Мы используем схему TL-B из block.tlb, чтобы понять, как нам нужно создать адрес в этом формате.
(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();
}
Поскольку нам нужно определить количество бит, занимаемых адресом, необходимо также объявить функцию asm с кодом операции UBITSIZE, которая вернет минимальное количество бит, необходимое для хранения числа.
💡 Полезные ссылки
Как хранить и загружать словарь в локальном хранилище
Логика загрузки словаря
slice local_storage = get_data().begin_parse();
cell dictionary_cell = new_dict();
if (~ slice_empty?(local_storage)) {
dictionary_cell = local_storage~load_dict();
}
В то время как логика хранения словаря похожа на следующий пример:
set_data(begin_cell().store_dict(dictionary_cell).end_cell());
💡 Полезные ссылки
Как отправить простое сообщение
Обычно мы отправляем tons с комментарием в виде простого сообщения. Чтобы указать, что тело сообщения является comment, мы должны установить 32 bits перед текстом сообщения в 0.
cell msg = begin_cell()
.store_uint(0x18, 6) ;; flags
.store_slice("EQBIhPuWmjT7fP-VomuTWseE8JNWv2q7QYfsVQ1IZwnMk8wL"a) ;; destination address
.store_coins(100) ;; amount of nanoTons to send
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_uint(0, 32) ;; zero opcode - means simple transfer message with comment
.store_slice("Hello from FunC!") ;; comment
.end_cell();
send_raw_message(msg, 3); ;; mode 3 - pay fees separately, ignore errors
💡 Полезные ссылки
Как отправить сообщение с помощью входящего аккаунта
Пример контракта ниже пригодится нам, если нам нужно выполнить какие-либо действия между пользователем и основным контрактом, то есть нам нужен прокси-контракт.
() recv_internal (slice in_msg_body) {
{-
This is a simple example of a proxy-contract.
It will expect in_msg_body to contain message mode, body and destination address to be sent to.
-}
int mode = in_msg_body~load_uint(8); ;; first byte will contain msg mode
slice addr = in_msg_body~load_msg_addr(); ;; then we parse the destination address
slice body = in_msg_body; ;; everything that is left in in_msg_body will be our new message's body
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(100) ;; just for example
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_slice(body)
.end_cell();
send_raw_message(msg, mode);
}
💡 Полезные ссылки
Как отправить сообщение со всем балансом
Если нам нужно отправить весь баланс смарт-контракта, то в этом случае нам нужно использовать отправить mode 128. Примером такого случая может служить прокси-контракт, который принимает платежи и пересылает их основному контракту.
cell msg = begin_cell()
.store_uint(0x18, 6) ;; flags
.store_slice("EQBIhPuWmjT7fP-VomuTWseE8JNWv2q7QYfsVQ1IZwnMk8wL"a) ;; destination address
.store_coins(0) ;; we don't care about this value right now
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; default message headers (see sending messages page)
.store_uint(0, 32) ;; zero opcode - means simple transfer message with comment
.store_slice("Hello from FunC!") ;; comment
.end_cell();
send_raw_message(msg, 128); ;; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract
💡 Полезные ссылки
Как отправить сообщение с длинным текстовым комментарием
Как мы знаем, в одну cell помещается только 127 символов (< 1023 бит). Если нам нужно больше - нужно организовать ячейки змейкой.
{-
If we want to send a message with really long comment, we should split the comment to several slices.
Each slice should have <1023 bits of data (127 chars).
Each slice should have a reference to the next one, forming a snake-like structure.
-}
cell body = begin_cell()
.store_uint(0, 32) ;; zero opcode - simple message with comment
.store_slice("long long long message...")
.store_ref(begin_cell()
.store_slice(" you can store string of almost any length here.")
.store_ref(begin_cell()
.store_slice(" just don't forget about the 127 chars limit for each slice")
.end_cell())
.end_cell())
.end_cell();
cell msg = begin_cell()
.store_uint(0x18, 6) ;; flags
;; We use literal `a` to get valid address inside slice from string containing address
.store_slice("EQBIhPuWmjT7fP-VomuTWseE8JNWv2q7QYfsVQ1IZwnMk8wL"a) ;; destination address
.store_coins(100) ;; amount of nanoTons to send
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1) ;; default message headers (see sending messages page)
.store_uint(1, 1) ;; we want to store body as a ref
.store_ref(body)
.end_cell();
send_raw_message(msg, 3); ;; mode 3 - pay fees separately, ignore errors
💡 Полезные ссылки
Как получить только биты данных из среза (без ссылок)
Если нас не интересуют refs внутри slice, то мы можем получить отдельные данные и работать с ними.
slice s = begin_cell()
.store_slice("Some data bits...")
.store_ref(begin_cell().end_cell()) ;; some references
.store_ref(begin_cell().end_cell()) ;; some references
.end_cell().begin_parse();
slice s_only_data = s.preload_bits(s.slice_bits());
💡 Полезные ссылки
"Примитивы срезов" в документации
Как определить собственный метод изменения
Методы изменения позволяют изменять данные в пределах одной переменной. Это можно сравнить со ссылками в других языках программирования.
(slice, (int)) load_digit (slice s) {
int x = s~load_uint(8); ;; load 8 bits (one char) from slice
x -= 48; ;; char '0' has code of 48, so we substract it to get the digit as a number
return (s, (x)); ;; return our modified slice and loaded digit
}
() main () {
slice s = "258";
int c1 = s~load_digit();
int c2 = s~load_digit();
int c3 = s~load_digit();
;; here s is equal to "", and c1 = 2, c2 = 5, c3 = 8
}
💡 Полезные ссылки
Как возвести число в степень n
;; Unoptimized variant
int pow (int a, int n) {
int i = 0;
int value = a;
while (i < n - 1) {
a *= value;
i += 1;
}
return a;
}
;; Optimized variant
(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;
}
() main () {
int num = binpow(2, 3);
~dump(num); ;; 8
}
Как преобразовать строку в целое число
slice string_number = "26052021";
int number = 0;
while (~ string_number.slice_empty?()) {
int char = string_number~load_uint(8);
number = (number * 10) + (char - 48); ;; we use ASCII table
}
~dump(number);
Как преобразовать целое число в строку
int n = 261119911;
builder string = begin_cell();
tuple chars = null();
do {
int r = n~divmod(10);
chars = cons(r + 48, chars);
} until (n == 0);
do {
int char = chars~list_next();
string~store_uint(char, 8);
} until (null?(chars));
slice result = string.end_cell().begin_parse();
~dump(result);
Как перебирать словари
Словари очень полезны при работе с большим количеством данных. Мы можем получить минимальные и максимальные значения ключей, используя встроенные методы dict_get_min? и dict_get_max? соответственно. Кроме того, мы можем использовать dict_get_next? для перебирания словаря.
cell d = new_dict();
d~udict_set(256, 1, "value 1");
d~udict_set(256, 5, "value 2");
d~udict_set(256, 12, "value 3");
;; iterate keys from small to big
(int key, slice val, int flag) = d.udict_get_min?(256);
while (flag) {
;; do something with pair key->val
(key, val, flag) = d.udict_get_next?(256, key);
}
💡 Полезные ссылки
"Примитивы словарей" в документации
"dict_get_max?()" в документации
"dict_get_min?()" в документации
Как удалить значение из словарей
cell names = new_dict();
names~udict_set(256, 27, "Alice");
names~udict_set(256, 25, "Bob");
names~udict_delete?(256, 27);
(slice val, int key) = names.udict_get?(256, 27);
~dump(val); ;; null() -> means that key was not found in a dictionary