TON 区块链的六个独特方面,将让 Solidity 开发者感到惊讶
TON 是一个非常现代化的区块链,带来了一些对智能合约开发的激进新想法。 它是在以太坊推出之后设计的,得以借鉴以太坊虚拟机(EVM)模型中的有效经验,并改进不足之处。
如果你有一些智能合约开发经验,你可能已经熟悉以太坊的 Solidity 语言及其 EVM。在学习 TON 开发时,你应该意识到某些设计差异,这些差异使得 TON 上的某些行为与预期大不相同。本文的目的是强调这些差异,并为你提供一些它们形成原因的总体思路。
从数据到大数据的转变
了解 TON 的关键是认识到它旨在将区块链带到地球上的每个人手中。这意味着大规模的用户群体——每天有数十亿用户发送数十亿次交易。
可以将这看作是从“数据”向“大数据”的转变。当你需要存储一家餐厅的菜单时,SQL 数据库是一个很好的选择——它可以运行强大且灵活的查询,因为所有数据都是现成可用的。但当你需要存储全人类的 Facebook 帖子时,SQL 数据库可能不是最佳选择。这种“大数据”必须进行积极的分片——从而限制你可以运行的查询的灵活性。不同的目的需要不同的权衡。
TON 区块链的六个独特方面,可能会让大多数 Solidity 开发者感到惊讶:
1、你的智能合约需要支付租金并向用户收费
区块链作为数据的不可变和永恒存储概念在纸面上很好,但实际扩展起来却相当不切实际。以太坊的收费模式是受银行启发的。你想转账,就需要支付银行交易费用。谁负责支付费用?是发起转账的用户。那么在区块链上存储数据,例如部署新的智能合约字节码呢?以太坊模型规定,发送部署交易的人支付费用。但这笔费用只支付一次,然而,如果链上的数据是永恒的,矿工们将不得不持续支付基础设施成本来保存这些数据,费用经济性不成立,如果你尝试将它扩展到数十亿用户,最终将会崩溃。从银行转向即时通讯
TON 的费用模型完全不同。它不是模仿银行账户,而是受到了即时通讯应用的启发。谁在支付 Facebook Messenger 上发送消息的成本?显然不是发起传输的人。实际上,是应用开发者 Facebook Inc(或 Meta Inc,我不跟随潮流,我自己用 Telegram)在承担这些成本,并且如何回收这些成本以及为自己融资就取决于 Facebook Inc 了。相应地,在 TON 中,dapp 本身需要支付其资源成本。每个智能合约都持有 TON 代币余额,并用此余额支付租金。如果智能合约没钱了,它最终会被删除(不用担心,所有数据都是可恢复的)。请注意,支付链上存储费用不是一次性的,租金支付是持续进行的。如果你只持有数据一段时间,你支付的费用会显著减少。这种费用经济性更符合矿工的成本,因此更容易扩展。与 Facebook Inc 非常相似,TON 中的合约开发者有很大的自由度来选择如何为其运营提供资金。开发者可以用自己的 TON 代币为合约提供资金,并为用户提供补贴;或者可以向用户收取不同操作的 Gas 费用,并将这些费用存入余额用于未来的租金支付。
2、智能合约之间的调用是异步且非原子的
以太坊上强大的 DeFi 生态系统的一个重要推动力是合约的无缝组合性。在一次交易中,你可以使用 WBTC 作为抵押品与 Compound 合约进行操作,使用它借入 USDC,再用 USDC 与 Uniswap 合约进行交易以获得更多的 WBTC,从而杠杆化你的 WBTC 头寸。整个过程甚至是原子的——如果任何一个步骤失败,即使是最后一步,整个交易也会回滚,好像从未发生过一样。当你的智能合约调用另一个智能合约的方法时,该调用将在同一交易中立即处理。在这方面,以太坊非常类似于在单个服务器上运行整个后端。后端的每个部分都可以同步访问其他部分——这种方法很容易理解。它只有一个缺点,即它只能在一个地方扩展。从单个服务器到微服务集群
如果你将以太坊想象为单一服务器上的单体架构,那么 TON 更类似于微服务集群。想象每个智能合约可能在不同的机器上运行。如果两个智能合约想要相互调用,就像两个微服务进行通信一样,它们可以通过网络发送消息。这条消息需要一些时间来传输,因此通信突然变成了异步的!这意味着当你的智能合约调用另一个智能合约的方法时,该调用将在交易结束后、未来的某个区块中处理。这种情况更难理解。如果条件在消息发送和接收之间发生变化怎么办?例如,调用合约的余额有一个值,但在第二个合约处理调用时,余额发生了变化。维护一致性更加困难,并且可能出现漏洞。原子性又如何?如果你链式调用了三个合约而只有最后一个失败了怎么办?如果你需要回滚所有更改,你必须手动进行。
3、你的智能合约不能在其他合约上运行 getter 方法
这是前一个项目的另一种表现形式。在以太坊上,合约之间的调用是同步的,从另一个智能合约读取数据是直接的。假设我的合约有一个 USDC 的余额。由于 USDC 也是一个合约,为了知道自己的余额,我的合约必须调用 USDC 合约的 getBalance 方法。还记得在单一服务器上运行的单体架构吗?这种方法的一个巨大优势是,每个服务都可以直接读取其他服务的状态内存。从单个服务器到微服务集群
当我们在不同机器上运行独立的微服务时,跨服务读取状态内存变得不可能。TON 上的智能合约只能通过异步消息进行通信。如果你需要立即查询另一个合约的数据,你将很不走运。事情甚至变得更奇怪了。如果你在 TON 智能合约上看到 getter 方法,比如 getBalance——这些方法对其他智能合约是不可访问的。Getter 方法只能由链下客户端调用,类似于以太坊钱包如何使用完整节点(如 Infura)查询任何智能合约状态。
4、智能合约代码不是不可变的,可以轻松修改
以太坊上的 dapp 的初衷是受法律文件启发——因此得名“智能合约”。开发者将法律合同的条款编程为代码,而代码,如你所知,即是法律。当现实世界中的两方签订合同时,合同是不可变的。如果任何一方想要改变合同条款,他们将起草一个新合同。与这种方法一致,以太坊上智能合约的代码设计为不可变且永不修改。多年来,开发者社区学会了克服这一限制,并生成了一些依赖于代理合约指向不同合约以实现升级的繁琐模式。从律师转向软件工程师
与律师不同,软件工程师被教导每段代码都有漏洞。即使永远不会出现错误,需求仍然会随着时间的推移而改变,代码也经常需要升级和修改。在 TON 下,合约应当不可变的假设被完全放弃。智能合约可以像写入任何其他状态变量一样自由地修改其代码。如果一个合约写入了代码变量,它是可变的,如果它没有写入,它就是不可变的。这在实践中并没有太大改变,它只是使得繁琐的代理模式变得多余。
5、你不应该在合约状态中使用无限增长的数据结构
这是一个棘手的问题,理解它需要一些时间,但它可以解释为什么 TON 上的一些智能合约会设计成现在的样子。无限增长的数据结构是智能合约中的状态变量,它们可以无限增长。考虑实现 USDC 代币的 ERC20 合约。这个合约需要维护一个按用户地址分配的余额映射。不同的 USDC 持有者数量可以无限增长,因为 USDC 可以大量铸造并分成小块。换句话说,映射中的键数量可以大到你想要的程度。如果攻击者试图通过添加更多条目来垃圾轰炸合约会发生什么?他们会导致某种 DoS 攻击并阻止其他诚实用户使用这个合约吗?以太坊为智能合约开发者巧妙地解决了这个问题。以太坊的费用模型规定,写入新状态数据的用户为此数据支付费用。这意味着我们的攻击者将不得不为其垃圾轰炸支付高昂的费用。此外,以太坊上对映射的写入 gas 费用是固定的,与映射中包含的数据量无关,这意味着其他用户不会因垃圾轰炸而受苦。底线是,在以太坊上垃圾轰炸映射在经济上不可行,系统提供了保护。从无限制映射到无限制合约
不幸的是,对于 TON 智能合约开发者来说,系统无法保护合约状态中无限增长的数据结构免受垃圾轰炸。TON 的 gas 费用模型规定,写入的费用不是固定的,费用通常与数据结构中存在的数据量成正比。这种行为源于 TON 依赖于“Bag of Cells”架构——合约状态被划分为 1023 位的块,称为“单元”,开发者需要维护这些单元。映射实现为一个单元树,向树叶写入需要沿整个高度写入新的哈希。如果攻击者向映射中垃圾轰炸键,一些用户的余额将被推到树的底部,更新它们将超出 gas 限制。因此,TON 的最佳实践是避免在状态中使用无限增长的数据结构。这将保护合约免受巧妙的 DoS 漏洞攻击。这个主题可能需要独立的博客文章来详细解释,但简短的解决方案是依赖
合约分片。如果我们在 USDC 合约中有无限数量的用户余额,我们应该将单个合约分为多个子合约——每个子合约只持有单个用户的余额。
这应该可以解释为什么 TON 上的 NFT 集合合约将每件物品放置在单独的合约中(物品数量可以无限制);为什么 TON 上的可替代代币合约将每个用户的余额放置在单独的合约中。
我们通常会解释为什么 TON 是这样设计的。以太坊的 gas 费用模型中,映射写入的费用是固定的,并且与映射大小无关,这种方式过于简单化。实际上,随着映射大小的增长,矿工需要更多的努力来改变其内容。只要映射较小,这种额外的努力是微不足道的,但当映射可以增长到数十亿条目时,情况不再如此。
6、钱包是合约,一个公钥可以部署多个钱包
在以太坊上,用户的钱包与其地址是同义词,地址直接从公钥(及其对应的私钥)派生。这是一种 1:1 的关系,每个公钥只有一个地址,每个地址只有一个公钥。只要用户知道他们的私钥,他们就永远不会丢失钱包。此外,以太坊上的用户不需要做任何特殊操作来拥有钱包。以太坊地址就是钱包。地址可以持有原生货币 ETH,地址可以持有 ERC20 代币和 NFT,地址可以直接向其他智能合约发送和签署交易。从地址到合约
在 TON 上,钱包不是隐含的,它们是独立的智能合约,必须像其他智能合约一样部署。当一个新用户想要开始使用 TON 区块链时,他们的第一步是在线部署一个钱包。该钱包的地址由钱包合约代码和各种初始化参数(如用户的公钥)派生。这意味着用户可以部署多个钱包,每个钱包都有自己的地址。钱包可以在其代码上有所不同(基金会不时发布不同的官方代码版本)或其初始化参数上有所不同(其中一个参数通常是序列号)。这也意味着用户即使知道他们的私钥,仍然必须努力记住他们的钱包地址(或用于构建钱包的初始化参数)。向 TON 上的某个 dapp 发送交易涉及使用用户的私钥签署消息。与以太坊不同的是,这笔交易不会直接发送到 dapp 智能合约,而是发送到用户的钱包合约,然后由钱包合约将消息转发到 dapp 智能合约。这种设计方法为 TON 上的钱包提供了新的灵活性维度。随着时间的推移,社区可以发明新的钱包合约,例如考虑一个没有 nonce(交易序列号)的钱包,它允许多个交易从不同的客户端同时发送而无需事先同步。此外,特殊的钱包如多签钱包(在以太坊上也需要部署一个特殊的智能合约),其行为与普通钱包类似。
翻译自:https://blog.ton.org/six-unique-aspects-of-ton-blockchain-that-will-surprise-solidity-developers