FunC 开发手册
创建 FunC 开发手册的核心原因是将所有 FunC 开发者的经验汇集在一个地方,以便未来的开发者们使用!
与 FunC 文档相比,本文更侧重于 FunC 开发者在智能合约开发过程中每天都要解决的任务。
基础知识
如何编写 if 语句
假设我们想检查某个事件是否相关。为此,我们使用标志变量。记住在 FunC 中 true
是 -1
而 false
是 0
。
int flag = 0; ;; false
if (flag) {
;; 做一些事情
}
else {
;; 拒绝交易
}
💡 注意
我们不需要使用
==
操作符,因为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() ;; 创建一个新的空构建器
.store_uint(123, 16) ;; 存储值为 123 且长度为 16 位的 uint
.end_cell(); ;; 将构建器转换为 cell
cell message = begin_cell()
.store_ref(inner_cell) ;; 将 cell 作为引用存储
.store_ref(inner_cell)
.end_cell();
slice msg = message.begin_parse(); ;; 将 cell 转换为 slice
while (msg.slice_refs_empty?() != -1) { ;; 我们应该记住 -1 是 true
cell inner_cell = msg~load_ref(); ;; 从 slice msg 中加载 cell
;; 做一些事情
}
💡 有用的链接
如何编写 do until 循环
当我们需要循环至少运行一次时,我们使用 do until
。
int flag = 0;
do {
;; 即使 flag 是 false (0) 也做一些事情
} until (flag == -1); ;; -1 是 true
💡 有用的链接
如何确定 slice 是否为空
在处理 slice
之前,需要检查它是否有数据以便正确处理。我们可以使用 slice_empty?()
来做到这一点,但我们必须考虑到,如果有至少一个 bit
的数据或一个 ref
,它将返回 -1
(true
)。
;; 创建空 slice
slice empty_slice = "";
;; `slice_empty?()` 返回 `true`,因为 slice 没有任何 `bits` 和 `refs`
empty_slice.slice_empty?();
;; 创建仅包含 bits 的 slice
slice slice_with_bits_only = "Hello, world!";
;; `slice_empty?()` 返回 `false`,因为 slice 有 `bits`
slice_with_bits_only.slice_empty?();
;; 创建仅包含 refs 的 slice
slice slice_with_refs_only = begin_cell()
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_empty?()` 返回 `false`,因为 slice 有 `refs`
slice_with_refs_only.slice_empty?();
;; 创建包含 bits 和 refs 的 slice
slice slice_with_bits_and_refs = begin_cell()
.store_slice("Hello, world!")
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_empty?()` 返回 `false`,因为 slice 有 `bits` 和 `refs`
slice_with_bits_and_refs.slice_empty?();
💡 有用的链接
如何确定 slice 是否为空(不含任何 bits,但可能包含 refs)
如果我们只需要检查 bits
,不关心 slice
中是否有任何 refs
,那么我们应该使用 slice_data_empty?()
。
;; 创建空 slice
slice empty_slice = "";
;; `slice_data_empty?()` 返回 `true`,因为 slice 没有任何 `bits`
empty_slice.slice_data_empty?();
;; 创建仅包含 bits 的 slice
slice slice_with_bits_only = "Hello, world!";
;; `slice_data_empty?()` 返回 `false`,因为 slice 有 `bits`
slice_with_bits_only.slice_data_empty?();
;; 创建仅包含 refs 的 slice
slice slice_with_refs_only = begin_cell()
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_data_empty?()` 返回 `true`,因为 slice 没有 `bits`
slice_with_refs_only.slice_data_empty?();
;; 创建包含 bits 和 refs 的 slice
slice slice_with_bits_and_refs = begin_cell()
.store_slice("Hello, world!")
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_data_empty?()` 返回 `false`,因为 slice 有 `bits`
slice_with_bits_and_refs.slice_data_empty?();
💡 有用的链接
如何确定 slice 是否为空(没有任何 refs,但可能有 bits)
如果我们只对 refs
感兴趣,我们应该使用 slice_refs_empty?()
来检查它们的存在。
;; 创建空 slice
slice empty_slice = "";
;; `slice_refs_empty?()` 返回 `true`,因为 slice 没有任何 `refs`
empty_slice.slice_refs_empty?();
;; 创建只包含 bits 的 slice
slice slice_with_bits_only = "Hello, world!";
;; `slice_refs_empty?()` 返回 `true`,因为 slice 没有任何 `refs`
slice_with_bits_only.slice_refs_empty?();
;; 创建只包含 refs 的 slice
slice slice_with_refs_only = begin_cell()
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_refs_empty?()` 返回 `false`,因为 slice 有 `refs`
slice_with_refs_only.slice_refs_empty?();
;; 创建包含 bits 和 refs 的 slice
slice slice_with_bits_and_refs = begin_cell()
.store_slice("Hello, world!")
.store_ref(null())
.end_cell()
.begin_parse();
;; `slice_refs_empty?()` 返回 `false`,因为 slice 有 `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();
;; 将 `cell` 类型更改为 slice,使用 `begin_parse()`
slice cs = cell_with_bits_and_refs.begin_parse();
;; 确定 slice 是否为空
if (cs.slice_empty?()) {
;; cell 为空
}
else {
;; cell 不为空
}
💡 有用的链接
如何确定 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?()) { ;; 确定 dict 是否为空
;; dict 为空
}
else {
;; dict 不为空
}
💡 有用的链接
文档中的“new_dict()” 创建空字典
[文档中的“dict_set()”](/develop/
func/stdlib/#dict_set) 为 dict d 添加一些元素,所以它不为空
如何确定 tuple 是否为空
在处理 tuples
时,始终知道内部是否有值以供提取是很重要的。如果我们尝试从空的 tuple
中提取值,将会得到一个错误:“not a tuple of valid size”,exit code 7。
;; 声明 tlen 函数,因为它在 stdlib 中没有提供
(int) tlen (tuple t) asm "TLEN";
() main () {
tuple t = empty_tuple();
t~tpush(13);
t~tpush(37);
if (t.tlen() == 0) {
;; tuple 为空
}
else {
;; tuple 不为空
}
}
💡 注意
💡 有用的链接
如何确定 Lisp 类型的列表是否为空
tuple numbers = null();
numbers = cons(100, numbers);
if (numbers.null?()) {
;; Lisp 类型的列表为空
} else {
;; Lisp 类型的列表不为空
}
我们使用 cons 函数将数字 100 添加到我们的 Lisp 类型列表中,所以它不为空。
如何确定合约的状态是否为空
假设我们有一个 counter
,用于存储交易次数。在智能合约状态的第一次交易中,这个变量不可用,因为状态为空,因此需要处理这种情况。如果状态为空,我们创建一个变量 counter
并保存它。
;; `get_data()` 将从合约状态返回数据 cell
cell contract_data = get_data();
slice cs = contract_data.begin_parse();
if (cs.slice_empty?()) {
;; 合约数据为空,所以我们创建 counter 并保存
int counter = 1;
;; 创建 cell,添加 counter 并保存在合约状态中
set_data(begin_cell().store_uint(counter, 32).end_cell());
}
else {
;; 合约数据不为空,所以我们获取我们的 counter,增加它并保存
;; 我们应该指定 counter 的正确的位长度
int counter = cs~load_uint(32) + 1;
set_data(begin_cell().store_uint(counter, 32).end_cell());
}
💡 注意
我们可以通过确定 cell 是否为空 来确定合约的状态是否为空。
💡 有用的链接
如何构建内部消息 cell
如果我们希望合约发送一个内部消息,我们应该首先正确地创建它为一个 cell,指定技术标志位、接收地址和其余数据。
;; 我们使用字面量 `a` 从包含地址的字符串中获取有效地址的 slice
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
int amount = 1000000000;
;; 我们使用 `op` 来识别操作
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) ;; 默认消息 header 部(参见发送消息页面)
.store_uint(op, 32)
.end_cell();
send_raw_message(msg, 3); ;; 模式 3 - 分别支付费用并忽略错误
💡 注意
在这个例子中,我们使用字面量
a
获取地址。你可以在文档中找到更多关于字符串字面量的信息。
💡 注意
💡 有用的链接
如何在内部消息 cell 中包含 body 作为 ref
在跟着标志位和其他技术数据的消息体中,我们可以发送 int
、slice
和 cell
。在后者的情况下,在 store_ref()
之前必须将位设置为 1
,以表明 cell
将继续传输。
如果我们确信有足够的空间,我们也可以在与 header 相同的 cell
中发送消息体。在这种情况下,我们需要将位设置为 0
。
;; 我们使用字面量 `a` 从包含地址的字符串中获取有效地址的 slice
slice addr = "EQArzP5prfRJtDM5WrMNWyr9yUTAi0c9o6PfR4hkWy9UQXHx"a;
int amount = 1000000000;
int op = 0;
cell message_body = begin_cell() ;; 创建包含消息的 cell
.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) ;; 默认消息 header 部(参见发送消息页面)
.store_uint(1, 1) ;; 设置位为 1,表明 cell 将继续传输
.store_ref(message_body)
.end_cell();
send_raw_message(msg, 3); ;; mode 3 - 分别支付费用并忽略错误
💡 注意
在这个例子中,我们使用字面量
a
获取地址。你可以在文档中找到更多关于字符串字面量的信息。
💡 注意
在这个例子中,我们使用node 3 接收进来的 tons 并发送确切的指定金额(amount),同时从合约余额中支付佣金并忽略错误。mode 64 用于返回所有接收到的 tons,扣除佣金,mode 128 将发送整个余额。
💡 注意
我们正在构建消息,但单独添加消息体。
💡 有用的链接
如何在内部消息 cell 中包含 body 作为 slice
发送消息时,消息体可以作为 cell
或 slice
发送。在这个例子中,我们将消息体放在 slice
内部发送。
;; 我们使用字面量 `a` 从包含地址的字符串中获取有效地址的 slice
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) ;; 默认消息 header 部(参见发送消息页面)
.store_uint(op, 32)
.store_slice(message_body)
.end_cell();
send_raw_message(msg, 3); ;;
mode 3 - 分别支付费用并忽略错误
💡 注意
在这个例子中,我们使用字面量
a
获取地址。你可以在文档中找到更多关于字符串字面量的信息。
💡 注意
在这个例子中,我们使用 mode 3 接收进来的 tons 并发送确切的指定金额(amount),同时从合约余额中支付佣金并忽略错误。mode 64 用于返回所有接收到的 tons,扣除佣金,mode 128 将发送整个余额。
💡 注意
我们正在构建消息,但将消息作为 slice 添加。
如何迭代 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);
;; 使用 x 做一些事情
i = i + 1;
}
i = len - 1;
while (i >= 0) {
int x = t.at(i);
;; 使用 x 做一些事情
i = i - 1;
}
}
💡 注意
我们声明了
tlen
汇编函数。你可以在这里阅读更多,并查看所有汇编指令列表。我们还声明了
to_tuple
函数。它只是改变任何输入的数据类型为 tuple,因此在使用时要小心。
如何使用 asm
关键字编写自己的函数
当使用任何功能时,实际上我们使用的是为我们预先准备好的 stdlib.fc
中的方法。但事实上,我们有更多的机会可以使用,我们需要学会自己编写它们。
例如,我们有 tpush
方法,它可以向 tuple
中添加元素,但没有 tpop
。在这种情况下,我们应该这样做:
;; ~ 表示它是修改方法
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
如果我们想知道 tuple
的长度以进行迭代,我们应该使用 TLEN
汇编指令编写一个新函数:
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 个 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";
;; 定义全局变量
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; ;; 重置 max_value;
iterate_tuple(t); ;; 迭代 tuple 并找到最大值
~dump(max_value); ;; 6
}
💡 有用的链接
基本的 tuple 操作
(int) tlen (tuple t) asm "TLEN";
forall X -> (tuple, X) ~tpop (tuple t) asm "TPOP";
() main () {
;; 创建一个空的 tuple
tuple names = empty_tuple();
;; 添加新项目
names~tpush("Naito Narihira");
names~tpush("Shiraki Shinichi");
names~tpush("Akamatsu Hachemon");
names~tpush("Takaki Yuichi");
;; 弹出最后一项
slice last_name = names~tpop();
;; 获取第一项
slice first_name = names.first();
;; 按索引获取项
slice best_name = names.at(2);
;; 获取列表长度
int number_names = names.tlen();
}
解析类型 X
下面的示例检查 tuple 中是否包含某个值,但 tuple 包含值 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 是类型 X,由于我们不知道确切的值是什么 - 我们需要检查值然后转换它
if (is_null(value)) {
;; 对 null 做一些事情
}
elseif (is_int(value)) {
int valueAsInt = cast_to_int(value);
;; 对 int 做一些事情
}
elseif (is_slice(value)) {
slice valueAsSlice = cast_to_slice(value);
;; 对 slice 做一些事情
}
elseif (is_cell(value)) {
cell valueAsCell = cast_to_cell(value);
;; 对 cell 做一些事情
}
elseif (is_tuple(value)) {
tuple valueAsTuple = cast_to_tuple(value);
;; 对 tuple 做一些事情
}
}
() main () {
;; 创建一个空的 tuple
tuple stack = empty_tuple();
;; 假设我们有一个 tuple 并且不知道它们的确切类型
stack~tpush("Some text");
stack~tpush(4);
;; 我们使用 var 因为我们不知道值的类型
var value = stack~tpop();
resolve_type(value);
}
💡 有用的链接
如何获取当前时间
int current_time = now();
if (current_time > 1672080143) {
;; 做一些事情
}
如何生成随机数
请注意,这种生成随机数的方法不安全。
待办事项:添加关于生成随机数的文章链接
randomize_lt(); ;; 只需做一次
int a = rand(10);
int b = rand(1000000);
int c = random();
模运算
例如,假设我们想对所有 256 个数字运行以下计算:(xp + zp)*(xp-zp)
。由于这些操作大多用于密码学,在下面的示例中,我们使用模运算符进行蒙哥马利曲线(montogomery curves)。注意 xp+zp 是一个有效的变量名(没有空格)。
(int) modulo_operations (int xp, int zp) {
;; 2^255 - 19 是蒙哥马利曲线的素数,意味着所有操作都应该对其素数进行
int prime = 57896044618658097711785492504343953926634992332820282019728792003956564819949;
;; muldivmod 自身处理以下两行
;; 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); ;; 只有当数字大于 50 时才会触发错误
throw_unless(39, number == 198); ;; 只有当数字不等于 198 时才会触发错误
throw(36); ;; 无论如何都会触发错误
反转 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]
}
💡 有用的链接
如何从列表中移除特定索引的项
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 汇编指令。
```func
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";
;; 我们使用字面量 `a` 来从包含地址的字符串中获取切片的有效地址
b = "EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF"a;
~dump(are_slices_equal_2?(a, b)); ;; 0 = false
}
💡 有用的链接
判断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
}
💡 有用的链接
判断元组是否相等
一个更高级的示例是迭代并比较每个元组的值。由于它们是 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; ;; 初始值为 true
if (t1.tuple_length() != t2.tuple_length()) {
;; 如果元组长度不同,它们就不能相等
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)) {
;; 递归地判断嵌套元组
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()
来获取工作链 ID。你可以在文档中找到更多关于工作链 ID 的信息。
💡 有用的链接
生成外部地址
我们使用 block.tlb 中的 TL-B 方案来理解我们如何以这种格式创建一个地址。
(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();
}
由于我们需要确定地址占用的位数,因此还需要声明一个使用 UBITSIZE
操作码的 asm 函数,该函数将返回存储数字所需的最小位数。
💡 有用的链接
如何在本地存储中存储和加载字典
加载字典的逻辑
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());
💡 有用的链接
如何发送简单消息
我们通常发送附带评论的方式实际上是一种简单消息。要指定消息正文为 comment
,我们应在消息文本前设置 32 bits
为 0。
cell msg = begin_cell()
.store_uint(0x18, 6) ;; 标志位
.store_slice("EQBIhPuWmjT7fP-VomuTWseE8JNWv2q7QYfsVQ1IZwnMk8wL"a) ;; 目的地址
.store_coins(100) ;; 发送的nanoTons数量
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 默认消息header(参见发送消息页面)
.store_uint(0, 32) ;; 零操作码 - 表示带评论的简单转账消息
.store_slice("Hello from FunC!") ;; 评论
.end_cell();
send_raw_message(msg, 3); ;; mode 3 - 分开支付费用,忽略错误
💡 有用的链接
如何发送带有入账的消息
以下合约示例对我们有用,如果我们需要在用户和主合约之间执行一些操作,那我们就需要一个代理合约。
() recv_internal (slice in_msg_body) {
{-
这是一个代理合约的简单示例。
它将期望 in_msg_body 包含消息 mode、body 和要发送到的目的地址。
-}
int mode = in_msg_body~load_uint(8); ;; 第一个字节将包含消息 mode
slice addr = in_msg_body~load_msg_addr(); ;; 然后我们解析目的地址
slice body = in_msg_body; ;; in_msg_body 中剩余的所有内容将是我们新消息的 body
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(100) ;; 仅作示例
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 默认消息 header (参见发送消息页面)
.store_slice(body)
.end_cell();
send_raw_message(msg, mode);
}
💡 有用的链接
如何发送携带全部余额的消息
如果我们需要发送智能合约的全部余额,那么在这种情况下,我们需要使用发送 mode 128
。这样的例子可能是一个接受付款并转发给主合约的代理合约。
cell msg = begin_cell()
.store_uint(0x18, 6) ;; 标志位
.store_slice("EQBIhPuWmjT7fP-VomuTWseE8JNWv2q7QYfsVQ1IZwnMk8wL"a) ;; 目的地址
.store_coins(0) ;; 我们现在不关心这个值
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 默认消息 header (参见发送消息页面)
.store_uint(0, 32) ;; 零操作码 - 表示带评论的简单转账消息
.store_slice("Hello from FunC!") ;; 评论
.end_cell();
send_raw_message(msg, 128); ;; 模式=128 用于携带当前智能合约剩余全部余额的消息
💡 有用的链接
如何发送带有长文本评论的消息
我们知道,单个 cell
(<1023 bits) 中只能容纳 127 个字符。如果我们需要更多 - 我们需要组织蛇形cell。
{-
如果我们想发送带有非常长的评论的消息,我们应该将评论分成几个片段。
每个片段应包含 <1023 位数据(127个字符)。
每个片段应该有一个引用指向下一个,形成蛇形结构。
-}
cell body = begin_cell()
.store_uint(0, 32) ;; 零操作码 - 带评论的简单消息
.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) ;; 标志位
;; 我们使用字面量 `a` 从包含地址的字符串中获取片段内的有效地址
.store_slice("EQBIhPuWmjT7fP-VomuTWseE8JNWv2q7QYfsVQ1IZwnMk8wL"a) ;; 目的地址
.store_coins(100) ;; 发送的nanoTons数量
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1) ;; 默认消息 header (参见发送消息页面)
.store_uint(1, 1) ;; 我们希望将 body 存储为引用
.store_ref(body)
.end_cell();
send_raw_message(msg, 3); ;; mode 3 - 分开支付费用,忽略错误
💡 有用的链接
如何仅从片段中获取数据位(不包括引用)
如果我们对 slice
内的 refs
不感兴趣,那么我们可以获取单独的数据并使用它。
slice s = begin_cell()
.store_slice("Some data bits...")
.store_ref(begin_cell().end_cell()) ;; 一些引用
.store_ref(begin_cell().end_cell()) ;; 一些引用
.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); ;; 从片段中加载 8 位(一个字符)
x -= 48; ;; 字符 '0' 的代码为 48,所以我们减去它以得到数字
return (s, (x)); ;; 返回我们修改的片段和加载的数字
}
() main () {
slice s = "258";
int c1 = s~load_digit();
int c2 = s~load_digit();
int c3 = s~load_digit();
;; 这里 s 等于 "",c1 = 2,c2 = 5,c3 = 8
}
💡 有用的链接
如何计算 n 的幂
;; 未优化版本
int pow (int a, int n) {
int i = 0;
int value = a;
while (i < n - 1) {
a *= value;
i += 1;
}
return a
;
}
;; 优化版本
(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
}
如何将字符串转换为 int
slice string_number = "26052021";
int number = 0;
while (~ string_number.slice_empty?()) {
int char = string_number~load_uint(8);
number = (number * 10) + (char - 48); ;; 我们使用 ASCII 表
}
~dump(number);
如何将 int 转换为 string
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");
;; 从小到大遍历键
(int key, slice val, int flag) = d.udict_get_min?(256);
while (flag) {
;; 使用 key->val 对,做某些事情
(key, val, flag) = d.udict_get_next?(256, key);
}
💡 有用的链接
如何从字典中删除值
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() -> 表示在字典中未找到该键
如何递归遍历cell树
我们知道,一个 cell
可以存储多达 1023 bits
的数据和最多 4 refs
。要绕过这个限制,我们可以使用cell树,但为此我们需要能够迭代它,以便正确处理数据。
forall X -> int is_null (X x) asm "ISNULL";
forall X -> (tuple, ()) push_back (tuple tail, X head) asm "CONS";
forall X -> (tuple, (X)) pop_back (tuple t) asm "UNCONS";
() main () {
;; 仅作为示例的一些cell
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();
;; 创建一个没有数据的元组,充当栈的角色
tuple stack = null();
;; 将主cell放入栈中以便在循环中处理
stack~push_back(c);
;; 在栈不为空时执行
while (~ stack.is_null()) {
;; 从栈中获取cell,并将其转换为 slice 以便处理
slice s = stack~pop_back().begin_parse();
;; 对 s 数据做一些操作
;; 如果当前 slice 有任何 refs,将它们添加到栈中
repeat (s.slice_refs()) {
stack~push_back(s~load_ref());
}
}
}
💡 有用的链接
如何遍历 Lisp 类型列表
数据类型 tuple 最多可以容纳 255 个值。如果这还不够,我们应该使用 Lisp 类型的列表。我们可以将一个 tuple 放入另一个 tuple 中,从而绕过限制。
forall X -> int is_null (X x) asm "ISNULL";
forall X -> (tuple, ()) push_back (tuple tail, X head) asm "CONS";
forall X -> (tuple, (X)) pop_back (tuple t) asm "UNCONS";
() main () {
;; 一些示例列表
tuple l = null();
l~push_back(1);
l~push_back(2);
l~push_back(3);
;; 遍历元素
;; 注意这种迭代是倒序的
while (~ l.is_null()) {
var x = l~pop_back();
;; 对 x 做一些操作
}
}
💡 有用的链接
如何发送部署消息(仅使用 stateInit 或使用 stateInit 和 body)
() deploy_with_stateinit(cell message_header, cell state_init) impure {
var msg = begin_cell()
.store_slice(begin_parse(msg_header))
.store_uint(2 + 1, 2) ;; init:(Maybe (Either StateInit ^StateInit))
.store_uint(0, 1) ;; body:(Either X ^X)
.store_ref(state_init)
.end_cell();
;; mode 64 - 在新消息中携带剩余值
send_raw_message(msg, 64);
}
() deploy_with_stateinit_body(cell message_header, cell state_init, cell body) impure {
var msg = begin_cell()
.store_slice(begin_parse(msg_header))
.store_uint(2 + 1, 2) ;; init:(Maybe (Either StateInit ^StateInit))
.store_uint(1, 1) ;; body:(Either X ^X)
.store_ref(state_init)
.store_ref(body)
.end_cell();
;; mode 64 - 在新消息中携带剩余值
send_raw_message(msg, 64);
}
如何构建 stateInit cell
() build_stateinit(cell init_code, cell init_data) {
var state_init = begin_cell()
.store_uint(0, 1) ;; split_depth:(Maybe (## 5))
.store_uint(0, 1) ;; special:(Maybe TickTock)
.store_uint(1, 1) ;; (Maybe ^Cell)
.store_uint(1, 1) ;; (Maybe ^Cell)
.store_uint(0, 1) ;; (HashmapE 256 SimpleLib)
.store_ref(init_code)
.store_ref(init_data)
.end_cell();
}
如何计算合约地址(使用 stateInit)
() calc_address(cell state_init) {
var future_address = begin_cell()
.store_uint(2, 2) ;; addr_std$10
.store_uint(0, 1) ;; anycast:(Maybe Anycast)
.store_uint(0, 8) ;; workchain_id:int8
.store_uint(cell_hash(state_init), 256) ;; address:bits256
.end_cell();
}