A
B
begin_parse()
begin_parse()
是 FunC 中一个常用的方法,用于将 cell
或 slice
类型的数据转换为可解析的 slice
。这个方法通常用于解析数据结构,以便从中提取信息。具体到代码中,begin_parse()
在 in_msg
和 in_msg_body
上的使用是必不可少的,因为它们的类型需要被转换为 slice
以便进一步处理和解析。- 将数据转换为可解析的
slice
:begin_parse()
方法将cell
或slice
转换为可解析的slice
,以便从中提取信息。
- 解析消息内容:
在处理消息时,通常需要解析消息的不同部分(如头部和消息体)。
begin_parse()
提供了这种解析的基础。
in_msg.begin_parse()
:in_msg
是一个cell
类型,包含了整个消息的内容(头部和消息体)。in_msg.begin_parse()
将cell
转换为slice
,以便能够解析其中的内容。
in_msg_body
:in_msg_body
是一个slice
类型,直接表示消息体部分,不需要begin_parse()
。- 你可以直接对
in_msg_body
调用解析方法,如load_uint(32)
,因为它已经是一个slice
。
get_data().begin_parse()
:get_data()
返回一个cell
,表示当前合约的存储数据。get_data().begin_parse()
将这个cell
转换为slice
,以便能够解析其中的内容。
C
cur_lt()
cur_lt()
是一个函数,用来获取当前交易的生命周期标签(LT)。- 生命周期标签(LT)通常是一个单调递增的 64 位整数,用于标识和排序账户的交易。
- 在智能合约的执行过程中,LT 被用作一个唯一标识符,以确保每个交易都是唯一且可以被追踪的。
- 交易的 LT 通常用于防止重放攻击或确保交易的顺序性。
- 在一些智能合约或区块链系统中,通过存储 LT,可以帮助合约或账户保持交易的有序性和一致性。
D
E
equal_slice_bits()
equal_slice_bits()
是 FunC 中用于比较两个 slice 的位的函数。它比较两个 slice 是否相等,即两个 slice 是否包含相同的位序列。函数原型如下:
bool equal_slice_bits(slice s1, slice s2)
s1
: 第一个要比较的 slice。
s2
: 第二个要比较的 slice。
- 返回
true
表示两个 slice 相等。
- 返回
false
表示两个 slice 不相等。
在开发中,
equal_slice_bits()
常用于以下场景:- 验证数据一致性:
- 比较两个 slice 是否相同,以验证数据的一致性。例如,在存储和检索数据时,确保检索到的数据与存储的数据相同。
- 数据完整性检查:
- 确保传递的数据在传输过程中未被篡改。例如,在签名验证中,比较签名的原始数据与经过处理后的数据是否一致。
F
flags & 1
flags & 1
是用来检查消息的 bounced
标志位的最低位是否被设置为1。如果被设置,则表示这是一个弹回消息,合约可能需要忽略或采取相应措施。弹回消息的处理机制
- 当一个消息发送给目标智能合约时,如果目标合约在处理该消息时发生了解析错误(例如未能正确解析消息体中的数据)并以非零的退出码(exit code)终止执行,该消息会自动被弹回给发送方。
- 在这种情况下,弹回的消息会清除
bounce
标志位,并设置bounced
标志位,同时消息体中会包含32位的0xffffffff
以及原始消息的256位哈希。
- 合约在接收到一个内部消息时,如果
bounced
标志位被设置,它应该检查消息体中的op
字段(操作码)和query_id
(查询ID),以确认是哪一个查询失败了,并采取适当的措施。
- 简单的合约可以选择忽略所有弹回的消息,即如果
bounced
标志位被设置,则直接返回零退出码而不处理。
G
get_data()
get_data()
是 FunC 中用于获取合约存储数据的标准方法。它返回当前合约的数据存储单元(cell)。在合约执行过程中,这个方法被广泛使用来读取和解析合约的内部状态。get_data()
函数没有参数,并且返回一个 cell
类型的数据,该数据包含了合约的所有存储状态。在 FunC 合约中,合约的状态通常以 cell 的形式存储,每个 cell 可以包含任意复杂的数据结构。通过
begin_parse()
,这个 cell
可以被转换为 slice
,从而可以逐步解析和处理数据。在合约逻辑中,get_data()
和 set_data()
通常配合使用,以读取和更新合约的内部状态。get_balance()
在 FunC 中,
get_balance()
是一个用于获取当前合约余额的函数。这个函数返回合约当前持有的资金余额,通常以(nanoTONs)为单位。(int, cell) get_balance() asm "BALANCE";
- 返回一个整数值,表示当前合约的余额
- 附加的账户数据(cell)返回的 cell 可能包含账户的其他信息,如冻结的资金、合约状态等。这部分数据在大多数情况下可能不被使用,但在特定情况下会非常有用。
H
I
In_msg & In_msg_body
- 发送者地址:
- 在
in_msg
中,你可以通过解析消息头部获得发送者地址。 in_msg_body
中没有发送者地址。
- 接收者地址:
- 在
in_msg
中,接收者地址是消息头部的一部分。 in_msg_body
中没有接收者地址。
- 消息标志位(flags):
- 在
in_msg
中,消息标志位是头部的一部分。 in_msg_body
中没有标志位。
- 消息值(msg_value):
in_msg
包含了整个消息的值。in_msg_body
中没有消息值。
in_msg
代表的是整个消息的内容,包括消息头部信息和消息体。- 消息头部:包括发送者地址、接收者地址、消息的标志位(flags)、消息值等。
- 消息体:实际的数据负载,即消息携带的主要信息。
in_msg
包含了整个消息的所有信息。in_msg_body
只包含消息的主体部分,不包括头部信息。- 消息体:实际的数据负载,通常包含操作码和具体的数据内容。
in_msg_body
是从整个消息中分离出来的,只保留了主体内容。J
K
L
load_msg_addr()
load_msg_addr()
是一个从消息载体中加载和解析地址的函数。在 FunC 中,地址MsgAddressInt
类型是一个重要的数据类型,通常用于标识智能合约或账户。下面是一个示例,展示如何在
recv_internal
函数中使用 load_msg_addr()
方法来加载和处理消息地址:int main() { // 合约的初始代码 } () recv_internal(int msg_value, cell msg_body) { // 开始解析消息体 slice s = msg_body.begin_parse(); // 加载并解析地址 var addr = s~load_msg_addr(); // 打印或使用解析的地址 // 例如,可以使用 addr 对象的字段或方法 ;; addr 现在是一个包含消息中加载的地址的对象 }
var addr = s~load_msg_addr();
: 从消息体中加载地址。这个方法将 slice
中的地址数据提取出来,并返回一个 MsgAddressInt
类型的地址对象。MsgAddressInt
是 FunC 中表示地址的类型,它通常包含多个字段和方法,用于处理和操作地址数据。load_uint()
load_uint
是用于从 slice
对象中加载无符号整数的方法。这个方法允许你指定要读取的位数,然后从 slice
中提取相应大小的无符号整数。() recv_internal(int msg_value, cell msg_body) { // 开始解析消息体 slice s = msg_body.begin_parse(); // 从 slice 中读取一个 32 位的无符号整数 int value = s~load_uint(32); // 处理读取到的整数 ;; 现在可以使用读取到的整数值 }
int value = s~load_uint(32);
: 从 slice
中读取一个 32 位的无符号整数。这里的 32
表示读取的位数,可以根据需要调整这个值。读取多段整数,可以按顺序调用
load_uint
方法来依次读取。假设有一个智能合约,它在接收到内部消息时,需要从消息体中读取一个操作码(32 位整数)和两个参数(分别为 16 位和 8 位整数)。可以按照以下方式实现:() recv_internal(int msg_value, cell msg_body) { // 开始解析消息体 slice s = msg_body.begin_parse(); // 读取操作码(32 位无符号整数) int opcode = s~load_uint(32); // 读取第一个参数(16 位无符号整数) int param1 = s~load_uint(16); // 读取第二个参数(8 位无符号整数) int param2 = s~load_uint(8); // 根据操作码和参数执行相应的逻辑 if (opcode == 1) { ;; 执行操作码为 1 的逻辑,使用 param1 和 param2 } else if (opcode == 2) { ;; 执行操作码为 2 的逻辑,使用 param1 和 param2 } else { ;; 处理未知操作码 } }
load_uint
方法在 FunC 中用于从 slice
对象中加载指定大小的无符号整数。通过传递位数参数,可以读取消息体中的不同整数值,并根据读取到的值执行相应的逻辑。load_ref()
在FunC 编程语言中,
load_ref()
函数的作用是从当前的 slice
中读取一个引用(或子单元)。这种引用通常指向一个新的单元(cell),而不是当前单元的数据。- Slice(切片):在 TON 区块链中,单元(cell)是存储数据的基本单位。一个单元可以包含多个字段,其中包括其他单元的引用。通过
slice
对象,可以顺序地从一个单元中读取数据。slice
是一个视图,它允许从单元中读取数据,但不能修改数据。
- Cell(单元):单元是 TON 区块链中基本的存储单元,可以包含二进制数据和对其他单元的引用。单元可以嵌套,从而形成复杂的数据结构。
- 引用(Reference):在一个单元内,引用是指向另一个单元的指针。通过引用,一个单元可以链接到其他单元,从而形成树状结构。
load_ref()
用于从当前的slice
中读取下一个引用,并将其转换为一个新的slice
对象。读取的引用指向另一个单元,因此新创建的slice
将指向该子单元的数据。
假设你有一个多层嵌套的单元结构,比如一个根单元包含对其他子单元的引用,而每个子单元可能又包含更多数据。你可以使用
load_ref()
来遍历这些引用,并访问每个引用指向的单元中的数据。func复制代码 // 这是一个简单的 FunC 代码示例,展示了如何使用 load_ref() () recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure { // 获取当前合约的数据 slice ds = get_data().begin_parse(); // 假设这个 slice 中第一个数据是一个引用 slice ref_slice = ds~load_ref(); // 读取 ref_slice 中的某些数据 int data_from_ref = ref_slice~load_uint(32); // 更新合约的数据,可能是某种计数器或状态 set_data(begin_cell().store_uint(data_from_ref + 1, 32).end_cell()); }
ds~load_ref()
:从当前的slice ds
中加载下一个引用,并将其解析为一个新的slice ref_slice
。ref_slice
现在指向原始单元中的另一个单元。
ref_slice~load_uint(32)
:从ref_slice
中读取 32 位整数,假设这个引用单元包含一个整数数据。
- 多层数据结构:在处理复杂的数据结构时,
load_ref()
允许你逐层解析数据,从而访问嵌套在多个单元中的信息。
- 状态机:在实现状态机或复杂的智能合约逻辑时,
load_ref()
可用于导航和操作多个相关的单元。
M
N
now()
:
- 存储内容: 当前交易的 Unix 时间戳。
- 位数: 32 位。
- 用途: Unix 时间戳是一个表示从1970年1月1日00:00:00 UTC(协调世界时)起经过的秒数的整数值。它通常用于标识交易的发生时间。
- 使用场景: 记录交易的具体发生时间,可能用于验证交易是否在规定的时间窗口内发生,或用于时间敏感的逻辑,比如防止某些交易在特定时间之后执行。
- Unix 时间戳更关注交易的实际发生时间,可以用来判断交易是否在合理的时间范围内发生。
O
P
Q
R
`recv_internal
`
recv_internal
函数用于处理接收到的内部消息。内部消息是指从同一个区块链中的另一个智能合约发送到当前智能合约的消息。在 FunC 中,你需要定义
recv_internal
函数来处理接收到的内部消息。通常,recv_internal
函数的参数是 cell
类型的消息体(payload)和 msg
类型的消息信息。以下是
recv_internal
函数的基本示例:int main() { // Your contract code here } () recv_internal(int msg_value, cell msg_body) { // 处理接收到的内部消息 // 例如,读取消息体中的数据 slice s = msg_body.begin_parse(); // 执行其他逻辑 }
slice s = msg_body.begin_parse();
: 将消息体转换为 slice
类型,以便进一步解析消息内容。通过定义这个函数,你可以根据接收到的消息执行相应的逻辑,从而实现智能合约的各种功能。解析消息体、处理不同的操作码和执行特定的合约逻辑是
recv_internal
函数的主要职责。S
set_data()
set_data()
函数的主要用途是更新合约的持久化数据。合约的数据状态通常保存在一个持久化存储中,通过 set_data()
函数,合约可以将更新后的数据状态写入到存储中。这在智能合约的操作中是非常关键的一步,因为合约需要能够修改其内部状态并确保这些修改在链上被持久化。- 更新合约状态:
- 当合约执行某个操作后需要更新其内部状态时,
set_data()
被用来保存这些变化。例如,转账操作后更新余额、修改配置参数等。
- 持久化数据:
- 确保在函数调用期间修改的数据在函数返回后仍然存在。这是因为合约的状态是持久化的,需要在链上保存,以便在未来的调用中能够访问。
- 合约初始化:
- 在合约部署时,使用
set_data()
来设置初始状态。初始状态通常包括合约的配置、初始余额、管理员地址等。
通过一个示例来了解如何在 FunC 合约中使用
set_data()
来更新和持久化数据:contract Wallet { int balance; Wallet() { balance = 0; set_data(); } () deposit(int amount) { balance += amount; set_data(); } () withdraw(int amount) { if (balance >= amount) { balance -= amount; set_data(); } } int get_balance() { return balance; } }
store_uint
store_uint
是用于构建和操作 cells 的函数,它用于存储无符号整数。builder store_uint(int value, int bits)
- value: 要存储的无符号整数值。
- bits: 存储这个整数所需的位数。
例如:
begin_cell().store_uint(42, 32).end_cell()
begin_cell()
: 开始一个新的 cell 构建。
store_uint(42, 32)
: 在 cell 中存储值为 42 的无符号整数,占用 32 位。
end_cell()
: 结束 cell 构建并返回构建好的 cell。
store_slice
store_slice
用于在一个 cell 中存储 slice 类型的数据。builder store_slice(slice value)
- value: 要存储的 slice。
例如:
begin_cell().store_slice(sender_address).end_cell()
store_uint
和 store_slice
可以被结合使用,以在一个 cell 中存储多个不同类型的数据。send_raw_message
send_raw_message
是 FunC 标准库中用于发送原始消息的函数。以下是这个函数的详细解释和用法:() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG";
cell msg
:- 发送 msg 中包含的原始消息,该消息应包含正确序列化的对象 Message X,唯一的例外是源地址允许有一个虚值 addr_none(将自动替换为当前智能合约地址),ihr_fee、fwd_fee、created_lt 和 created_at 字段可以有任意值(将在当前交易的操作阶段重写为正确值)。
int mode
:- 整数参数模式包含标志。目前有 3 种mode模式和 4 个Flags信息标志。可以将单个模式与多个(可能一个都没有)标志相结合,以获得所需的模式。简单地说,组合就是获得它们的值之和。下面的表格对模式和标志进行了说明。
Mode | Description |
0 | 普通信息 |
64 | 除新信息中最初显示的价值外,携带入站信息的所有剩余价值 |
128 | 消息携带当前智能合约的所有剩余余额,而不是消息中最初指示的金额。 |
Flag | Description |
+1 | 表示发送方希望单独支付转账费用,不从消息的金额中扣除转账费用。 |
+2 | 忽略行动阶段处理该消息时出现的一些错误(请查看下文注释) |
+16 | 在操作失败的情况下--退回交易。如果使用 +2 则无影响。 |
+32 | 如果当前账户余额为零,则必须销毁该账户(通常与模式 128 一起使用) |
当设置+2
标志时,合约将忽略以下在操作阶段处理消息时产生的错误:
- Toncoins 不足:
- 传入消息的所有金额都已被消耗,导致没有足够的 Toncoins 来转移消息。
- 处理消息的资金不足:
- 合约没有足够的资金来处理消息。
- 消息中附带的金额不足以支付转发费用:
- 消息中附带的金额不足以支付消息的转发费用。
- 附带的额外货币不足:
- 消息中附带的额外货币不足。
- 支付外部消息的资金不足:
- 合约没有足够的资金来支付外部消息的费用。
- 消息太大:
- 消息的大小超过了允许的限制。
- 消息的 Merkle 深度太大:
- 消息的 Merkle 深度超过了允许的限制。
忽略的错误
这些错误一般与资金和资源相关,忽略这些错误使得合约可以尝试继续执行操作,即使这些错误发生了。不忽略的错误
尽管设置了+2
标志,合约仍然会对以下错误进行处理并中断执行:
- 消息格式无效:
- 消息的格式不正确。
- 消息模式同时包括
64
和128
模式:
- 消息的模式设置不合法,不允许同时包括
64
和128
。
- 状态初始化中的库无效:
- 出站消息在
StateInit
中包含无效的库。
- 外部消息不普通或包含
+16
或+32
标志或两者兼有:
- 外部消息的类型不被支持,或者包含无效的模式标志。
例如,如果你想发送一条普通消息并单独支付转账费用,可以使用模式 0 和标志 +1,得到 mode = 1。如果你想发送整个合约余额并立即销毁合约,可以使用模式 128 和标志 +32,得到 mode = 160。
T
throw_unless() & throw_if()
在 FunC 中,
throw_unless()
和 throw_if()
是用于条件检查并抛出异常的宏。它们通常用于确保某些条件满足,如果条件不满足,则抛出异常,以便防止错误的执行路径。throw_unless(code,condition)
宏用于在指定条件为 false
时抛出异常。condition
:一个布尔表达式。如果condition
为false
,则抛出异常。
code
:一个整数值,表示要抛出的异常代码。
示例:
int x = 10; throw_unless(100, x > 0); // 如果 x <= 0,抛出异常,异常代码为 100
在这个示例中,如果
x
的值不大于 0,程序将抛出异常,异常代码为 100。throw_if(condition, code)
宏用于在指定条件为 true
时抛出异常。throw_if(condition, code)
U
V
W
X
Y
Z
特殊类
~
~
操作符用于方法调用,它与其他一些编程语言中的点(.
)操作符类似。具体来说,在 FunC 中,~
操作符用于调用对象或结构上的方法。~
操作符的作用是将方法应用于对象或结构。例如,s~load_msg_addr()
表示调用对象 s
的 load_msg_addr
方法。()
在 FunC 语言中,函数前的
()
表示该函数是一个内部方法或构造函数,通常用于定义合约的行为。例如:contract Wallet { int balance; Wallet() { balance = 0; set_data(); } () deposit(int amount) { balance += amount; set_data(); } () withdraw(int amount) { if (balance >= amount) { balance -= amount; set_data(); } } }
- 构造函数
Wallet()
: Wallet()
是合约的构造函数,用于初始化合约的状态。在 FunC 中,构造函数名与合约名相同。
- 内部方法
() deposit(int amount)
和() withdraw(int amount)
: - 这些方法前的
()
表示它们是无返回值的内部方法。通常在定义合约的行为时使用这种语法。
- 函数定义中的
()
表示函数的参数列表。
- 函数调用中的
()
用于传递实际参数。
- 空的
()
在定义和调用无参数函数时使用。
- 函数前的
()
在 FunC 中用于表示无返回值的内部方法或构造函数。
inline
修饰符
内联的好处
:- 避免寄存器保存和恢复:由于内联函数的代码直接插入到调用点,不需要保存和恢复寄存器状态。
- 避免堆栈管理:内联函数不需要在堆栈上分配和清理局部变量和参数的空间。
- 减少指令开销:内联函数避免了调用指令和返回指令的开销。
内联的限制
:- 禁止递归调用:
- 由于内联函数的代码会在编译时被展开,递归调用(即函数调用自身)会导致无限展开。因此,内联函数中禁止使用递归调用。
- 代码大小的增加:
- 由于函数的代码被多次插入到调用处,内联函数可能会导致生成的代码体积增大。对于非常大的函数或频繁调用的函数,需要权衡性能提升和代码大小之间的关系。
频繁使用内联函数可能会增加生成的代码体积,需要在性能优化和代码大小之间进行权衡。
impure
修饰符
impure
修饰符的重要性:- 定义:
impure
修饰符表明函数可能会有一些副作用,这些副作用不应该被忽略。
- 使用情况:当函数可能修改合约存储、发送消息或在数据无效时抛出异常(例如数据验证函数)时,必须使用
impure
修饰符。
- 缺少
impure
修饰符的影响:如果没有指定impure
修饰符,并且函数调用的结果没有被使用,那么FunC编译器会删除这个函数调用。
::
双冒号
::
是用于命名空间或模块作用域解析的符号。以以下这段代码为例:const const::min_tons_for_storage = 10000000;
这意味着常量
min_tons_for_storage
定义在 const
模块或命名空间中。这样做的好处是可以避免命名冲突,并使代码组织更清晰。