怎样与TON链上的Func智能合约交互,修改链上数据状态

  • A+
所属分类:TheOpenNetwork(TON)

chatGPT账号

怎样与TON链上的Func智能合约交互,修改链上数据状态

一、说明

TON链上FUNC智能合约的交互和solidity智能合约交互方式类似。以Ton上的jetton合约为例通常通过操作码进行合约的交互,在FUNC合约开发过程中预先定义相关操作的操作码(op_code),通过传递操作码到FUNC合约触发相关的功能调用。jetton代币的铸造,燃烧,转账,批量空投等操作都需要通过预先定义的状态码去完成合约的交互

比较简单的交互方式是通过ton钱包客户端(tonkeeper、mytonwallet等支持ton链的web3客户端)发送十六进制或者base64格式的payload完成合约的交互触发相关的功能;另外也可以通过编写封装后的wrapper,然后调用js来实现与FUNC智能合约jetton代币的交互。

二、发送消息

消息的组成、解析和发送位于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**的一个过时名称。

让我们深入了解!

三、消息类型

有三种类型的消息:

  • 外部消息 — 从区块链外部发送到区块链内部智能合约的消息。这类消息应该在所谓的credit_gas阶段被智能合约明确接受。如果消息未被接受,节点不应该将其纳入进区块或转发给其他节点。
  • 内部消息 — 从一个区块链实体发送到另一个区块链实体的消息。与外部消息不同,这类消息可以携带一些TON并为自己支付费用。接收此类消息的智能合约可能没有接受它,在这种情况下,消息价值中的gas将被扣除。
  • 日志 — 从区块链实体发送到外部世界的消息。一般来说,没有将这类消息发送出区块链的机制。实际上,尽管网络中的所有节点对是否创建了消息达成共识,但没有关于如何处理它们的规则。日志可能被直接发送到/dev/null,记录到磁盘,保存到索引数据库,甚至通过非区块链手段(电子邮件/Telegram/短信)发送,所有这些都取决于给定节点的自行决定。

四、消息布局

我们将从内部消息布局开始。

描述智能合约可以发送的消息的TL-B方案如下:

message$_ {X:Type} info:CommonMsgInfoRelaxed 
  init:(Maybe (Either StateInit ^StateInit))
  body:(Either X ^X) = MessageRelaxed X;

让我们用语言来描述。任何消息的序列化都包括三个字段:info(某种标题,描述来源、目的地和其他元数据)、init(仅在消息初始化时需要的字段)和body(消息有效载荷)。

MaybeEither和其他类型的表达式意味着以下内容:

  • 当我们有字段info:CommonMsgInfoRelaxed时,意味着CommonMsgInfoRelaxed的序列化直接注入到序列化cell中。
  • 当我们有字段body:(Either X ^X)时,意味着当我们(反)序列化某种类型X时,我们首先放置一个either位,如果X被序列化到同一cell,则为0,如果它被序列化到单独的cell,则为1
  • 当我们有字段init:(Maybe (Either StateInit ^StateInit))时,意味着我们首先放置01,要取决于这个字段是否为空;如果不为空,我们序列化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位的标志位,分别表示是否禁用即时超立方路由(目前始终为真)、是否在处理过程中出错时弹回消息,以及消息本身是否是弹回的结果。然后序列化来源和目的地址,接着是消息值和四个与消息转发费用和时间有关的整数。

如果消息是从智能合约发送的,其中一些字段将被重写为正确的值。特别是,验证者将重写bouncedsrcihr_feefwd_feecreated_ltcreated_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);
}

首先,它将0x18值放入6位,即放入0b011000。这是什么?

  • 第一位是0 — 1位前缀,表示它是int_msg_info
  • 然后有3位110,表示即时超立方路由被禁用,消息可以在处理过程中出错时回弹,消息本身不是回弹的结果。
  • 然后应该是发送者地址,但由于它无论如何都会被重写,因此可以存储任何有效地址。最短的有效地址序列化是addr_none的序列化,它序列化为两位字符串00

因此,.store_uint(0x18, 6)是序列化标签和前4个字段的优化后的方式。

下一行序列化目的地址。

然后我们应该序列化值。一般来说,消息值是一个CurrencyCollection对象,其方案如下:

nanograms$_ amount:(VarUInteger 16) = Grams;

extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32)) 
                 = ExtraCurrencyCollection;

currencies$_ grams:Grams other:ExtraCurrencyCollection 
           = CurrencyCollection;

这个方案意味着除了TON值之外,消息可能还携带了extra-currencies的字典。然而,目前我们可以忽略它,只假设消息值被序列化为“作为变量整数的nanotons数量”和“0 - 空字典位”。

事实上,在上面的选举人代码中,我们通过.store_coins(toncoins)序列化代币数量,但接着只放置了长度等于1 + 4 + 4 + 64 + 32 + 1 + 1的零字符串。这代表着什么?

  • 第一个位表示空的extra-currencies字典。
  • 然后我们有两个长度为4位的字段。它们以VarUInteger 16编码为0。事实上,由于ihr_feefwd_fee将被重写,我们同样可以在那里放置零。
  • 然后我们将零放入created_ltcreated_at字段。这些字段也将被重写;然而,与费用不同,这些字段有固定长度,因此被编码为64位和32位长的字符串。
  • (我们已经序列化了消息头并传递到init/body)
  • 接下来的零位表示没有init字段。
  • 最后一个零位表示消息体将就地序列化。
  • 之后,消息体(具有任意布局)就完成了编码。

这样,我们执行了4个序列化原语,而不是单独序列化了14个参数。

五、完整方案

消息布局和所有构成字段的完整方案(以及TON中所有对象的方案)在block.tlb中呈现。

消息大小

cell大小

请注意,任何Cell最多可包含1023位。如果您需要存储更多数据,您应该将其分割成块并存储在引用cell中。

例如,如果您的消息体大小为900位长,您无法将其存储在与消息头相同的cell中。实际上,除了消息头字段外,cell的总大小将超过1023位,在序列化过程中将出现cell溢出异常。在这种情况下,原本代表“就地消息体标志位(Either)”的0应该变成1,消息体应该存储在引用cell中。

由于某些字段具有可变大小,因此应小心处理这些事项。

例如,MsgAddress可以由四个构造器表示:addr_noneaddr_stdaddr_externaddr_var,长度从2位(对于addr_none)到586位(对于最大形式的addr_var)。nanotons的数量也是如此,它被序列化为VarUInteger 16。这意味着,4位指示整数的字节长度,然后指示整数本身的较前面的字节。这样,0 nanotons将被序列化为0b0000(4位编码着零字节长度字符串,然后是零字节),而100,000,000 TON(或100000000000000000 nanotons)将被序列化为0b100000000001011000110100010101111000010111011000101000000000000000000b1000表示8个字节长度,然后是8个字节其本身)。

消息大小

更多配置参数及其值可在 这里 找到。

六、消息模式

如您可能已经注意到,我们使用send_raw_message发送消息,除了消耗消息本身外,还接受mode(模式)。要了解最适合您需求的模式,请查看以下表格:

Mode 描述
0 普通消息
64 除了新消息中最初指示的值之外,携带来自入站消息的所有剩余值
128 携带当前智能合约的所有余额,而不是消息中最初指示的值
Flag 描述
+1 单独支付转账费用
+2 忽略在 Action Phase 处理该信息时出现的一些错误(请查看下面的注释)
+16 在action失败的情况下 - 弹回交易。如果使用了+2则无效。
+32 如果当前账户的结果余额为零,则必须销毁该账户(通常与Mode 128一起使用)

+2 flag

  1. Toncoins 不足:
    • 没有足够的值与消息一起传送(所有入站消息值都已消耗)。
    • 没有足够的资金来处理消息。
    • 没有足够的信息附加值来支付转发费用。
    • 没有足够的额外货币与消息一起发送。
    • 没有足够的资金支付出站外部消息。
  2. 消息过大(请查看 消息大小 以获取更多信息)。
  3. 消息的 Merkle 深度太大。

但在以下情况下,它不会忽略错误:

  1. 消息的格式无效。
  2. 消息模式包括 64 和128 modes。
  3. 出站消息在 StateInit 中有无效的库。
  4. 外部消息不是普通消息,或包含 +16 或 +32 标志,或两者兼有。:::

要为send_raw_message构建一个模式,您只需通过将Mode和Flag结合来组合它们。例如,如果您想发送常规消息并单独支付转账费用,请使用Mode0和Flag+1得到mode = 1。如果您想发送整个合约余额并立即销毁它,请使用Mode128和Flag+32得到mode = 160

至此,完成怎样与TON链上的Func智能合约交互,修改链上数据状态的所有操作流程。

添加VX或者telegram获取全程线上免费指导

怎样与TON链上的Func智能合约交互,修改链上数据状态

免责声明

发文时比特币价格:$71249

当前比特币价格:[crypto coins=”BTC” type=”text” show=”price”]

当前比特币涨幅:[crypto coins=”BTC” type=”text” show=”percent”]

免责声明:

本文不代表路远网立场,且不构成投资建议,请谨慎对待。用户由此造成的损失由用户自行承担,与路远网没有任何关系;

路远网不对网站所发布内容的准确性,真实性等任何方面做任何形式的承诺和保障;

网站内所有涉及到的区块链(衍生)项目,路远网对项目的真实性,准确性等任何方面均不做任何形式的承诺和保障;

网站内所有涉及到的区块链(衍生)项目,路远网不对其构成任何投资建议,用户由此造成的损失由用户自行承担,与路远网没有任何关系;

路远区块链研究院声明:路远区块链研究院内容由路远网发布,部分来源于互联网和行业分析师投稿收录,内容为路远区块链研究院加盟专职分析师独立观点,不代表路远网立场。

本文是全系列中第52 / 292篇:行业技术

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的电报
  • 这是我的电报扫一扫
  • weinxin
chatGPT账号
路远

发表评论

您必须登录才能发表评论!