一、说明
TON链上所有的操作都需要与合约进行消息交互,通过程序与FUNC合约进行消息的传递,然后执行消息中包含的操作行为。在FUNC对应的智能合约中不仅需要正确识别对应的操作码op_code还需要有处于对应操作行为的功能。外部程序通过传递消息给FUNC智能合约,其中对应的消息模板要包含目标输入参数。例如,如果需要给指定地址mint jetton则需要在消息中包含mint的目标地址和对应的jetton数量。这些动态参数需要包含在正确的消息模板中。
TON 中的一笔交易包括以下内容:
- 最初触发合约的传入消息(存在特殊的触发方式)
- 由传入消息引起的合约行动,例如更新合约的存储(可选)
- 发送给其他参与者的所生成的传出消息(可选)
- 技术上,合约可以通过特殊功能如 Tick-Tock 触发,但这个功能更多用于 TON 内部的区块链核心合约。
- 并非每笔交易都会导致传出消息或对合约存储的更新——这取决于合约代码所定义的操作。
二、消息类型
幸运的是,TON 的工作方式是任何内部消息都一定会被目标账户接收。消息不会在来源和目的地之间的任何地方丢失。外部消息有点不同,因为它们被接受到区块中是由验证者自行决定的,但是,一旦消息被接受进入传入消息队列,它将被传递。
内部消息:
智能合约通过发送所谓的内部消息来相互交互。当内部消息到达其预定目的地时,会代表目的地账户创建一个普通交易,并按照该账户(智能合约)的代码和持久数据的所指定的去处理内部消息。
外部消息:
外部消息是从外部发送
到 TON 区块链中的智能合约,以使它们执行特定操作。
例如,钱包智能合约期望接收包含钱包所有者签名的订单的外部消息(例如,从钱包智能合约发送的内部消息)。当这样的外部消息被钱包智能合约接收时,它首先检查签名,然后接受消息(通过运行 TVM 原语 ACCEPT
),然后执行所需的任何操作。
非弹回消息:
几乎所有在智能合约之间发送的内部消息都应该是可弹回的,即应该设置它们的“bounce”位。然后,如果目标智能合约不存在,或者在处理此消息时抛出未处理的异常,消息将被“bounced”,携带原始值的剩余部分(减去所有消息传输和gas费用)。弹回消息的主体将包含32位的0xffffffff
,紧接着是原始消息的256位,但是“bounce”标志位被清除,“bounced”标志位被设置。因此,所有智能合约都应检查所有入站消息的“bounced”标志,并且要么默默接受它们(通过立即以exit code 0终止),要么执行一些特殊处理来检测哪个出站查询失败了。弹回消息主体中包含的查询永远不应执行。
三、以下说明了消息的发送方式和消息格式模板:
消息的组成、解析和发送位于TL-B schemas、交易阶段和TVM的交汇处。
事实上,FunC有send_raw_message函数,该函数期望一个序列化消息作为参数。
由于TON是一个功能广泛的系统,支持所有这些功能的消息可能看起来相当复杂。尽管如此,大多数情况下并不使用那么多功能,消息序列化在大多数情况下可以简化为:
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(amount)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_slice(message_body)
.end_cell();
因此,开发者不用担忧,如果这份文档中的某些内容在第一次阅读时看起来难以理解,没有关系。只需把握总体思路即可。
有时文档中可能会提到**’gram’这个词,但大多是在代码示例中,它只是toncoin**的一个过时名称。
让我们深入了解!
消息布局
我们将从内部消息布局开始。
描述智能合约可以发送的消息的TL-B方案如下:
message$_ {X:Type} info:CommonMsgInfoRelaxed
init:(Maybe (Either StateInit ^StateInit))
body:(Either X ^X) = MessageRelaxed X;
让我们用语言来描述。任何消息的序列化都包括三个字段:info(某种标题,描述来源、目的地和其他元数据)、init(仅在消息初始化时需要的字段)和body(消息有效载荷)。
Maybe
、Either
和其他类型的表达式意味着以下内容:
- 当我们有字段
info:CommonMsgInfoRelaxed
时,意味着CommonMsgInfoRelaxed
的序列化直接注入到序列化cell中。 - 当我们有字段
body:(Either X ^X)
时,意味着当我们(反)序列化某种类型X
时,我们首先放置一个either
位,如果X
被序列化到同一cell,则为0
,如果它被序列化到单独的cell,则为1
。 - 当我们有字段
init:(Maybe (Either StateInit ^StateInit))
时,意味着我们首先放置0
或1
,要取决于这个字段是否为空;如果不为空,我们序列化Either StateInit ^StateInit
(再次,放置一个either
位,如果StateInit
被序列化到同一cell则为0
,如果被序列化到单独的cell则为1
)。
CommonMsgInfoRelaxed
的布局如下:
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
src:MsgAddress dest:MsgAddressInt
value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed;
ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt
created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed;
让我们现在专注于int_msg_info
。它以1位的前缀0
开始,然后有三个1位的标志位,分别表示是否禁用即时超立方路由(目前始终为真)、是否在处理过程中出错时弹回消息,以及消息本身是否是弹回的结果。然后序列化来源和目的地址,接着是消息值和四个与消息转发费用和时间有关的整数。
如果消息是从智能合约发送的,其中一些字段将被重写为正确的值。特别是,验证者将重写bounced
、src
、ihr_fee
、fwd_fee
、created_lt
和created_at
。这意味着两件事:首先,另一个智能合约在处理消息时可以信任这些字段(发送者无法伪造来源地址、bounced
标志位等);其次,在序列化时我们可以将任何有效值放入这些字段中(无论如何这些值都将被重写)。
消息的直接序列化如下所示:
var msg = begin_cell()
.store_uint(0, 1) ;; tag
.store_uint(1, 1) ;; ihr_disabled
.store_uint(1, 1) ;; allow bounces
.store_uint(0, 1) ;; not bounced itself
.store_slice(source)
.store_slice(destination)
;; serialize CurrencyCollection (see below)
.store_coins(amount)
.store_dict(extra_currencies)
.store_coins(0) ;; ihr_fee
.store_coins(fwd_value) ;; fwd_fee
.store_uint(cur_lt(), 64) ;; lt of transaction
.store_uint(now(), 32) ;; unixtime of transaction
.store_uint(0, 1) ;; no init-field flag (Maybe)
.store_uint(0, 1) ;; inplace message body flag (Either)
.store_slice(msg_body)
.end_cell();
然而,开发者通常使用快捷方式而不是逐步序列化所有字段。因此,让我们考虑如何使用elector-code中的示例从智能合约发送消息。
() send_message_back(addr, ans_tag, query_id, body, grams, mode) impure inline_ref {
;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
var msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(grams)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_uint(ans_tag, 32)
.store_uint(query_id, 64);
if (body >= 0) {
msg~store_uint(body, 32);
}
send_raw_message(msg.end_cell(), mode);
}
然后我们应该序列化值。一般来说,消息值是一个CurrencyCollection
对象,其方案如下:
nanograms$_ amount:(VarUInteger 16) = Grams;
extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32))
= ExtraCurrencyCollection;
currencies$_ grams:Grams other:ExtraCurrencyCollection
= CurrencyCollection;
添加VX或者telegram获取全程线上免费指导
评论前必须登录!
注册