FunC 开发手册
创建 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 的引用。
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
之前,需要检查它是否有数据以便正确处理。我们可以使用 slice_empty?()
来做到这一点,但我们必须考虑到,如果有至少一个 bit
的数据 或一个 ref
,它将返回 -1
(true
)。
;; 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 是否为空(不含任何 bits,但可能包含 refs)
如果我们只需要检查 bits
,不关心 slice
中是否有任何 refs
,那么我们应该使用 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 是否为空(没有任何 refs,但可能有 bits)
如果我们只对 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?();
💡 有用的链接
如何确定 cell 是否为空
要检查 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 是否为空
有一个 dict_empty?()
方法可以检查 dict 中是否有数据。这个方法相当于 cell_null?()
,因为通常一个空的 cell 就是一个空字典。
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
}
💡 有用的链接
文档中的“new_dict()” 创建空字典
[文档中的“dict_set()”](/develop/
如何确定 tuple 是否为空
在处理 tuple
时,重要的是始终知道里面是否有任何值可供提取。如果我们试图从一个空的 "元组 "中提取值,就会出现错误:不是有效大小的元组",并显示 "退出代码 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
}
}
💡 Noted
我们正在声明 tlen 汇编函数。你可以在 此处 和 list of all assembler commands 阅读更多内容。
💡 注意
我们声明了 tlen 汇编函数。你可以在这里阅读更多,并查看所有汇编指令列表。
文档中的"tpush()"
如何判断 lisp 风格列表是否为空
tuple numbers = null();
numbers = cons(100, numbers);
if (numbers.null?()) {
;; list-style list is empty
} else {
;; list-style list is not empty
}
我们使用 cons函数将数字 100 添加到列表样式的列表中,因此它不是空的。
如何确定合约状态为空
假设我们有一个存储交易数量的 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());
}
💡 Noted
我们可以通过判断 cell is empty 来确定合约状态为空。
💡 注意
我们可以通过确定 cell 是否为空 来确定合约的状态是否为空。
文档中的"begin_parse()"
文档中的 "slice_empty?()"
文档中的 "set_data?()"
如何建立内部信息 cell
如果我们想让合约发送内部邮件,首先应将其创建为 cell ,并指定技术标志、收件人地址和其他数据。
;; 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
💡 Noted
在本例中,我们使用字面量
a
来获取地址。有关字符串字面量的更多信息,请参阅 docs
💡 注意
在这个例子中,我们使用字面量
a
获取地址。你可以在文档中找到更多关于字符串字面量的信息。
💡 注意
如何将正文作为内部报文 cell 的 ref 来包含
在标志和其他技术数据之后的报文正文中,我们可以发送 int
, slice
和 cell
。对于后者,有必要在 store_ref()
之前将位设置为 1
,以表示 cell
将继续。
在跟着标志位和其他技术数据的消息体中,我们可以发送 int
、slice
和 cell
。在后者的情况下,在 store_ref()
之前必须将位设置为 1
,以表明 cell
将继续传输。
;; 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
💡 Noted
在本例中,我们使用字面量
a
来获取地址。有关字符串字面量的更多信息,请参阅 docs
💡 注意
在这个例子中,我们使用字面量
a
获取地址。你可以在文档中找到更多关于字符串字面量的信息。
💡 注意
在这个例子中,我们使用node 3 接收进来的 tons 并发送确切的指定金额(amount),同时从合约余额中支付佣金并忽略错误。mode 64 用于返回所有接收到的 tons,扣除佣金,mode 128 将发送整个余额。
💡 注意
我们正在构建消息,但单独添加消息体。
如何将正文作为片段包含在内部报文 cell 中
发送信息时,信息正文可以作为 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
💡 Noted
在本例中,我们使用字面量
a
来获取地址。有关字符串字面量的更多信息,请参阅 docs
💡 注意
在这个例子中,我们使用字面量
a
获取地址。你可以在文档中找到更多关于字符串字面量的信息。
💡 注意
在这个例子中,我们使用 mode 3 接收进来的 tons 并发送确切的指定金额(amount),同时从合约余额中支付佣金并忽略错误。mode 64 用于返回所有接收到的 tons,扣除佣金,mode 128 将发送整个余额。
如何迭代 tuples(双向)
如果我们想在 FunC 中处理数组或堆栈,那么 tuple 就是必要的。首先,我们需要能够遍历值来处理它们。
(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;
}
}
💡 Noted
我们正在声明
tlen
汇编函数。您可以 在此 阅读更多内容,也可以查看 所有汇编命令列表。我们还声明了
to_tuple
函数。它只是将任何输入的数据类型更改为元组,因此使用时要小心。
如何使用 asm
关键字编写自己的函数
在使用任何功能时,我们实际上使用的是 stdlib.fc
内为我们预先准备好的方法。但事实上,我们还有更多的机会,我们需要学会自己编写。
当使用任何功能时,实际上我们使用的是为我们预先准备好的 stdlib.fc
中的方法。但事实上,我们有更多的机会可以使用,我们需要学会自己编写它们。
;; ~ means it is modifying method
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
如果我们想知道用于迭代的 "元组 "的长度,则应使用 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";
💡 Useful links:
迭代嵌套的 n 个 tuples
有时我们想迭代嵌套的 tuples。以下示例将从头开始迭代并打印格式为 [[2,6],[1,[3,[3,5]]], 3]
的 tuple 中的所有项目
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
}
💡 Useful links
基本的 tuple 操作
(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);
}
💡 Useful links
如何获取当前时间
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;
}
💡 Useful links
如何抛出错误
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
反转 tuples
由于 tuple 以堆栈的形式存储数据,有时我们必须反转 tuple 才能从另一端读取数据。
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]
}
💡 Useful links
文档中的"tpush()"
如何从列表中删除具有特定索引的项目
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]
}
确定 slice 是否相等
我们有两种不同的方法来确定相等性。一种是基于 slice 散列,另一种是使用 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
}
判断cell是否相等
确定 cell 是否相等
我们可以根据哈希值轻松确定 cell 是否相等。
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
}
💡 Useful links
docs 中的"cell_hash()"