TON 合约开发简介
这篇文章开始,我准备开始写一个 TON 合约开发的系列文章,主要是想讲讲 TON 链上的合约开发,读者最好是有一些 Solidity 的合约开发基础,这样读起来比较容易。希望在大家读完这个系列的文章之后,能对 TON 的合约开发有一个大体的了解,并能够自己上手开发项目。
目前 TON 上的合约开发语言主要有下面四个:
Fift,最底层的语言,类似汇编,一般不用学习。
FunC,官方支持的语言,目前 TON 链上主流的钱包,DEX 等都是由 FunC 编写。类似于 C 语言,上手难度稍高。
Tact,社区支持的语言,发展比较迅速,语法和文档都持续在更新。类似于 Solidity,上手容易,但目前还有一些功能可能还不太完善,例如不支持合约升级,Gas 较高等。
Tolk,官方最新推出的语言,宣称要在未来全面替代 FunC,对比 FunC 来说稍微简单一些。并且未来官方文档的所有合约示例应该都要改写为 Tolk。
这几个语言中,目前热度比较高的是 Tact 和 FunC。Tolk 刚刚发布(2024.11),后期发展要看官方生态的动作。我们这个系列主要来讲 Tact 和 FunC。Tolk 后期如果发展迅速的话也会讲讲。
这几个语言的关系如下:
由于官方支持的 FunC 难度较高,因此社区便开发支持了 Tact 语言。但其实 Tact 也是最终会编译成 FunC,Fift。而 Tolk 可以理解为 FunC 的另一个变种,它的语法相对能简单一些。最终它们都会被编译成 Fift。
Tact 和 FunC 目前来看是取决于开发者取舍的两种语言:
Tact 好学,开发起来简单,但是它还有一些功能仍在完善,如果需要实现某些特定功能,可能无法做到,并且合约消耗的 Gas 相对较高。
FunC 难学,并且在开发的时候需要考虑更多底层的问题,例如消息格式处理等,但它支持的功能也最全面,合约耗费的 Gas 也更少。
如果只是开发一些简单的合约,例如一些简单的 Token,NFT 等,Tact 就足够了。但是如果要深入 TON 的合约开发,FunC 还是有必要学习的。并且 TON 链上目前的主流钱包,DEX 等都是由 FunC 开发的。如果不懂 FunC,这些合约的代码也很难看懂,更不用说与它们进行乐高的集成了。
Tolk 目前还处在较初期,我们先暂不考虑。
TON 采用了 Masterchain - WorkChain 机制,有一条 Masterchain,最多支持创建 2^32 条 Workchain(目前只有一条)。
我们可以将其简单理解为波卡的 中继链 - 平行链,或者以太坊 L1 - L2 的关系。我们开发的合约一般都在 WorkChain(L2)上部署执行,而 Masterchain(L1) 主要负责区块链的底层操作例如出块等。
TON 链并不是常见的 EVM 架构,它是一个异步的区块链。
在 EVM 中,合约之间互相调用是一个很常见的场景,并且是原子化的,也就是说,不论一个交易中涉及到多少次合约之间的互相调用,只要有一步出错,那么整个交易都会回滚,和什么都没发生一样。并且不论多长的调用链,它们都是在一条交易中的,只要这条交易上链,那么这个调用链的执行都是在同一时刻就完成了。
但是在 TON 中,它不支持合约之间的相互调用。那合约之间怎么交互呢,需要靠消息发送机制。我们可以理解为,地球向太空中的卫星发送消息,假设有下面的场景:
在 A 时刻,地球上的人做完了一系列事情,然后向卫星发送消息。
卫星在 B 时刻接收消息,并进行一系列的处理,然后向地球回传消息。
地球在 C 时刻收到回传消息,并进行处理。
在 EVM 的场景下,我们完成这一系列操作,只需要直接调用别的合约即可。但是在 TON 中,由于我们只能采用消息发送机制,因此就只能和上面的地球卫星之间发送消息一样,合约之间也只能发送消息。而且这一系列操作也不是同一时刻就能完成的,A 时刻开始发送的消息,C 时刻才能完成。最难的是,和 EVM 的原子化不同,如果 B 时刻发生了错误,那么 A 时刻已经发生的动作是没法回滚的。如果 C 时刻发生了错误,那么 A 和 B 时刻已经发生的动作也是没法回滚的!
在这个限制下,一个在 EVM 上很简单的逻辑,在 TON 上可能就会变得很复杂。因此我们在开发 TON 合约的时候就需要更加小心,以避免更多的问题。
在 EVM 中,地址分为 EOA 和合约地址。我们平时常用的 MetaMask 所创建的钱包地址就是 EOA,这个地址自身没有代码。但是在 TON 中,所有的钱包都是智能合约。既然是合约,那就是由代码组成的。这里又引入了另一个点,就是钱包合约的实现方式不同,那么它的功能可能就有一些差距。比如说我使用的是 A 公司开发的钱包,你使用的是 B 公司开发的钱包,那咱俩的钱包的合约代码就不同,可能就会有一些功能上的差距。
TON 上有一个很重要的概念是,合约地址是由(代码 + 初始化参数)决定的,即:
也就是说,如果合约代码一致,合约初始化参数一致,那么得到的就是固定的合约地址。
TON 上钱包是靠助记词生成的,那么由于上面的原因,即使是同一份助记词,如果使用的是不同的钱包,那么得到的合约地址也是不一样的。即:
由于钱包是合约,那么我们想要使用钱包,这个合约必须存在才可以。也就是说,这个合约必须要被部署才行。一般当我们使用一个钱包产品(例如 TonKeeper)时,它会为我们提供初始的助记词,并根据这套助记词和它们的合约生成出一个地址,我们需要首先向这个地址打入一定量的 Gas,然后对其进行部署。我们不需要手动进行部署,在第一笔任何操作之前,合约会自动进行部署。
在 EVM 中,一个地址在任何链上的显示形式都是固定的。例如 0xabc123 这个地址,在任何 EVM 兼容链上,都是这个形式的地址。但是在 TON 中,一个地址有多种表达形式。例如,TON 主网上的 USDT 地址是
EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs
但是它一共有这么多种的表达形式([地址转换工具](https://ton.org/address/)):
主网和测试网的地址不相同,这里引入了一个 Bounce 的概念。
我们前面说到,合约之间需要靠发送消息来交互。如果一条消息在处理过程中发生了错误,这时消息发送方 A 已经更改了状态,TON 为此提供了一个 bounce 容错机制,使得消息可以回弹,如果 A 也同时设置的回弹消息的处理逻辑,那就可以将 A 的状态变更回之前的状态。
可能比较难理解,不着急,我们后面会随着合约开发的学习来慢慢消化。
我们前面说过,TON 上的所有地址都是合约。TON 有一个神奇的机制是,所有的合约都需要交租金。它的逻辑是,将合约类比成传统 Web2 的各种网站、APP,这些网站是需要为自己租用服务器付费的。那么 TON 上的合约也需要为自己在链上有一席之地来付费。比如,一个钱包中有 1 TON 余额,那么在 N 年后,可能就只剩 0.9 TON 的余额,消失的 0.1 TON 就是在 TON 链上缴纳的租金。如果一个合约长时间都没有余额,那么它就会被冻结,无法使用,因此我们在合约开发中也需要考虑到这点。
TON 的合约开发相较于 EVM 的合约,有很大的不同。如果一开始不习惯也很正常,这些概念需要随着学习来慢慢消化。后面的文章开始介绍 Tact 和 FunC 的开发,我们可以慢慢学习理解。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!