注意-气体! 或我们如何进行非碳酸化智能合约


区块链和智能合约仍然是开发人员和技术人员的热门话题。 关于他们的未来,未来的发展以及未来的发展,有很多研究和讨论。 Waves Platform上的我们对应该是什么智能合约自己的看法,在本文中,我将告诉您我们是如何做到的,遇到了什么问题以及为什么它们不像其他区块链项目的智能合约(首先以太坊)。


对于那些想了解智能合约如何在Waves网络中工作,尝试编写自己的合约并熟悉开发人员已经可以使用的工具的人,本文也是一个指南。


我们如何过上这样的生活?


经常有人问我们何时获得智能合约,因为开发人员喜欢使用网络的便利性,网络的速度(感谢Waves NG )和低佣金水平。 但是,智能合约提供了更多的想象空间。


由于区块链的传播, 智能合约在最近几年变得非常流行。 那些在工作中遇到过区块链技术的人在提到智能合约时,通常会想到以太坊和Solidity。 但是,有许多具有智能合约的区块链平台,其中大多数只是简单地重复了以太坊所做的事情(虚拟机+他们自己的合约语言)。 此存储库中有一个有趣的清单,其中包含不同的语言和方法。


什么是智能合约?


从广义上讲,智能合约是一种旨在支持,验证和执行交易条款或当事方之间的合同执行的协议。 尼克·萨博(Nick Szabo)早在1996年就提出了这个想法,但是智能合约直到最近几年才变得流行起来。


从技术角度(让我们更感兴趣)来看,智能合约是一种算法(代码),它不会在任何一台服务器或计算机上执行,而是在区块链网络中的许多(或所有)节点上执行,即 分散的。


如何运作?


区块链上智能合约的第一个原型被正确地认为是比特币脚本-图灵不完整,图灵是比特币网络上基于堆栈的语言。 比特币中没有帐户概念;而是有输入和输出。 在比特币中,进行交易(创建输出)时,有必要参考接收交易(输入)。 如果您对比特币设备的技术细节感兴趣,建议您阅读本系列文章 。 由于比特币中没有帐户,因此比特币脚本确定在哪种情况下可以使用一个或另一个出口。


以太坊提供了更多的功能 提供Solidity,一种图灵完备的语言,可在每个节点内的虚拟机中运行。 强大的力量带来巨大的责任,并带来广泛的可能性-相当多的限制,我们将在后面讨论。


智能合约浪潮


就像我在上面写的那样,我们经常被问到智能合约,但是我们不想“像直播一样”或“像任何区块链名称一样”,这样做的原因很多 。 因此,我们分析了合同的现有案例以及如何在其帮助下帮助解决实际问题。


在分析了使用场景之后,我们发现通常使用智能合约可以解决两大类任务:


  1. 简单而直接的任务,例如多重签名,原子交换或托管。
  2. dApps,具有用户逻辑的成熟的去中心化应用程序。 更准确地说,这是去中心化应用程序的后端。 最引人注目的例子是Cryptokitties或Bancor。

还有第三种,最流行的合同类型-代币。 例如,在以太坊网络上,绝大多数工作合同都是ERC20标准代币。 在Waves中,无需创建智能合约就可以创建令牌,因为 它们是区块链本身的一部分,并且发行令牌(具有立即在去中心化交易所(DEX)上进行交易的能力),足以发送一种类型为发行的交易(发行交易)。


对于上述两种类型的任务(为简单起见,我们将称其为简单案例和复杂案例),对语言,合同和概念的要求是非常不同的。 是的,可以说拥有图灵完备的语言可以解决简单和复杂的问题,但是有一个重要条件:该语言应有助于避免错误。 此要求对于普通语言也很重要,对于智能合约语言也特别重要,因为 运营与财务相关,合同通常是一成不变的,因此无法快速轻松地解决错误。


考虑到上述任务的类型,我们决定逐步进行开发,并提供解决简单问题的工具作为第一步,并提供一种可以轻松实现任何用户逻辑的语言作为下一步。 结果,该系统的功能比我们在旅途开始时想象的要强大得多。


让帐户变得聪明


逐渐地,我们提出了智能帐户的概念,该帐户旨在解决主要的简单任务。 他们的想法与比特币脚本非常相似:可以将其他规则添加到帐户中,以确定外发交易的有效性。 智能帐户的主要要求是:


  1. 最大的安全性。 几乎每个月您都可以找到有关在以太坊模型合约中发现另一个漏洞的消息 。 我们想要避免这种情况。
  2. 无需加油,因此佣金是固定的。 为此,该脚本必须在可预测的时间内执行,并且具有相当严格的大小限制。

在继续执行合同的实现和撰写技术细节之前,我们概述了Waves区块链的一些特征,这些特征对于进一步理解至关重要:


  1. Waves区块链目前有13种不同类型的交易。


  1. 在Waves区块链中,不是输入和输出(例如在比特币中),而是帐户(例如在Nxt中)。 代表一个特定帐户执行交易。
  2. 默认情况下,交易的正确性取决于区块链的当前状态以及代表交易发送的签名的有效性。 事务的JSON表示看起来很简单:


由于我们在区块链中已经具有不同类型的交易,因此我们决定不将一个单独的实体作为智能账户,而是添加一个将常规账户转变为智能账户的新交易。 任何帐户都可以成为具有更改的交易验证规则的智能帐户,为此,该帐户应仅发送SetScriptTransaction类型的交易,其中包含已编译的合同。


对于智能帐户,合同是每笔_外向交易的验证规则。


那气体呢?


我们为自己设定的主要任务之一是为简单操作省掉汽油。 这并不意味着将不收取任何佣金。 需要使矿工对执行脚本感兴趣。 我们从实际角度解决了这个问题,并决定进行性能测试并计算各种操作的速度。 为此,开发了使用JMH的基准。 结果可以在这里看到。 由此产生的限制是:


  1. 该脚本的运行速度应比20个签名验证操作快,这意味着对智能帐户的检查不会比对常规帐户的检查慢20倍。 脚本的大小不应超过8 KB。
  2. 为了使矿工履行智能合约有利可图,我们将智能帐户的最低额外佣金设置为0.004 WAVES。 在Waves网络中,交易的最小佣金为0.001 WAVES(对于智能帐户)为0.005 WAVES。

智能合约的语言


最困难的任务之一是创建自己的智能合约语言。 采取任何现有的图灵完整的语言并适应(trim)完成我们的任务似乎是从麻雀上的一门大炮射击:除此之外,在区块链项目中依赖别人的代码库是极度冒险的


让我们尝试想象一下什么是智能合约的理想语言。 我认为,任何编程语言都应强制编写“正确”且安全的代码,即 理想情况下,应该有一种正确的方法。 是的,如果您愿意,可以用任何语言编写完全不可读且不受支持的代码,但这比正确编写代码(Hello PHP和JavaScript)要困难得多。 同时,该语言应便于开发。 由于该语言在网络的所有节点上运行,因此必须尽可能高效-延迟执行可以节省很多资源。 我还希望在语言中拥有一个强大的类型系统,最好是代数语言,因为它有助于尽可能清晰地描述合同并更接近“代码就是法律”的梦想。 如果我们进一步规范化需求,我们将获得以下语言参数:


  1. 严格和静态输入。 强类型自动消除了许多潜在的程序员错误。
  2. 拥有强大的打字系统,很难踩到自己的脚。
  3. 懒惰,这样您就不会浪费宝贵的处理器周期。
  4. 标准库中具有用于处理区块链的特定功能,例如哈希。 同时,不应重载标准语言库,因为总应该有一种正确的方法。
  5. 运行时没有异常。

在我们的RIDE语言中,我们尝试考虑这些重要功能,并且由于我们在Scala上进行了大量开发,并且像函数式编程一样,因此该语言在某些方面类似于Scala和F#。


实际上,实现中的最大问题是最后一个要求,因为例如,如果您在语言中没有例外,那么加法操作将必须返回Option ,这将需要检查是否溢出,这对于开发人员来说绝对不便。 异常是一种折衷方案,但无法捕获它们-如果存在异常,则交易无效。 另一个问题是将我们在区块链中拥有的所有数据模型转换为语言。 我已经描述过,在Waves中必须使用该语言支持13种不同类型的事务,并且必须访问它们的所有字段。


有关RIDE中可用操作和数据类型的完整信息,请参见语言描述页面 。 在该语言的有趣功能中,我们还可以强调以下事实:该语言是基于表达式的,也就是说,一切都是表达式,并且存在模式匹配,这使您可以方便地描述不同类型的交易的条件:


 match tx { case t:TransferTransaction => t.recepient case t:MassTransferTransaction => t.transfers case _ => throw } 

如果您有兴趣学习如何安排RIDE代码的使用方式,则应该查看白皮书 ,该白皮书描述了使用合同的所有阶段:解析,编译,反序列化,计算脚本复杂性和执行。 前两个阶段-解析和编译是在链外执行的,只有在base64中编译的合同才进入区块链。 反序列化,复杂性计算和执行是在链上完成的,并且在不同的阶段进行了多次:


  1. 当您收到一笔交易并将其添加到UTX时,否则将存在区块链节点接受该交易(例如通过REST API)但永远不会进入该区块的情况。
  2. 当形成一个块时,挖掘节点将验证事务,并且需要脚本。
  3. 当非挖掘节点接收到一个区块并验证其中包含的交易时。

由于在许多网络节点上执行了多次优化,因此处理合同的每个优化都变得很有价值。 现在,Waves节点可以在DigitalOcean上以15美元的价格在虚拟机上安静地运行,尽管发布智能帐户后工作量有所增加。


结果如何?


现在,让我们看看Waves带来了什么。 我们将编写我们自己的第一份合同,让它成为标准的multisig 2 of 3合同。 要编写合同,可以使用在线IDE (语言调整-另一篇文章的主题)。 创建一个新的空合同(新建→空合同)。


首先,我们将宣布将控制帐户的Alice,Bob和Cooper的公钥。 您将需要3个签名中的2个:


 let alicePubKey = base58'B1Yz7fH1bJ2gVDjyJnuyKNTdMFARkKEpV' let bobPubKey = base58'7hghYeWtiekfebgAcuCg9ai2NXbRreNzc' let cooperPubKey = base58'BVqYXrapgJP9atQccdBPAgJPwHDKkh6A8' 

文档描述sigVerify函数,该函数使您可以验证事务签名:



该函数的参数是事务的主体,经过验证的签名和公钥。 全局范围内的合同中有一个tx对象,其中存储了交易信息。 该对象有一个tx.bodyBytes字段,其中包含要发送的事务的字节。 还有一个tx.proofs数组, tx.proofs可存储8个签名。值得注意的是,实际上,您不仅可以将签名发送到tx.proofs ,而且还可以将合同可以使用的任何其他信息发送给您。


我们可以使用3条简单的代码行来确保所有签名均正确显示,并且顺序正确:


 let aliceSigned = if(sigVerify(tx.bodyBytes, tx.proofs[0], alicePubKey )) then 1 else 0 let bobSigned = if(sigVerify(tx.bodyBytes, tx.proofs[1], bobPubKey )) then 1 else 0 let cooperSigned = if(sigVerify(tx.bodyBytes, tx.proofs[2], cooperPubKey )) then 1 else 0 

好吧,最后一步将是验证至少提交了2个签名。


 aliceSigned + bobSigned + cooperSigned >= 2 

3个多重签名合同中的全部2个看起来像这样:


 #    let alicePubKey = base58'B1Yz7fH1bJ2gVDjyJnuyKNTdMFARkKEpV' let bobPubKey = base58'7hghYeWtiekfebgAcuCg9ai2NXbRreNzc' let cooperPubKey = base58'BVqYXrapgJP9atQccdBPAgJPwHDKkh6A8' #       let aliceSigned = if(sigVerify(tx.bodyBytes, tx.proofs[0], alicePubKey )) then 1 else 0 let bobSigned = if(sigVerify(tx.bodyBytes, tx.proofs[1], bobPubKey )) then 1 else 0 let cooperSigned = if(sigVerify(tx.bodyBytes, tx.proofs[2], cooperPubKey )) then 1 else 0 # ,      2   aliceSigned + bobSigned + cooperSigned >= 2 

请注意:代码中没有诸如return之类的关键字,因为执行的最后一行被视为脚本的结果,这就是为什么它应始终返回truefalse


相比之下,常见的以太坊多重签名合同看起来要复杂得多 。 甚至相对简单的变体也看起来像这样:


  pragma solidity ^0.4.22; contract SimpleMultiSig { uint public nonce; // (only) mutable state uint public threshold; // immutable state mapping (address => bool) isOwner; // immutable state address[] public ownersArr; // immutable state // Note that owners_ must be strictly increasing, in order to prevent duplicates constructor(uint threshold_, address[] owners_) public { require(owners_.length <= 10 && threshold_ <= owners_.length && threshold_ >= 0); address lastAdd = address(0); for (uint i = 0; i < owners_.length; i++) { require(owners_[i] > lastAdd); isOwner[owners_[i]] = true; lastAdd = owners_[i]; } ownersArr = owners_; threshold = threshold_; } // Note that address recovered from signatures must be strictly increasing, in order to prevent duplicates function execute(uint8[] sigV, bytes32[] sigR, bytes32[] sigS, address destination, uint value, bytes data) public { require(sigR.length == threshold); require(sigR.length == sigS.length && sigR.length == sigV.length); // Follows ERC191 signature scheme: https://github.com/ethereum/EIPs/issues/191 bytes32 txHash = keccak256(byte(0x19), byte(0), this, destination, value, data, nonce); address lastAdd = address(0); // cannot have address(0) as an owner for (uint i = 0; i < threshold; i++) { address recovered = ecrecover(txHash, sigV[i], sigR[i], sigS[i]); require(recovered > lastAdd && isOwner[recovered]); lastAdd = recovered; } // If we make it here all signatures are accounted for. // The address.call() syntax is no longer recommended, see: // https://github.com/ethereum/solidity/issues/2884 nonce = nonce + 1; bool success = false; assembly { success := call(gas, destination, value, add(data, 0x20), mload(data), 0, 0) } require(success); } function () payable public {} } 

IDE具有内置控制台,可让您立即编译合同,进行部署,创建交易并查看执行结果。 如果您想认真对待合同,建议您查看不同语言Visual Studio Code插件


如果您的手发痒,那么在本文结尾处,有最重要的链接可以开始潜水。


系统比语言更强大


Waves区块链具有用于存储数据的特殊数据类型- 数据事务 。 它们用作与帐户关联的键值存储,也就是说,这就是帐户的状态。



交易日期可以包含字符串,数字,布尔值和每个键最多32 KB的字节数组。 一个使用数据事务的示例,它允许您仅在帐户的键值存储中已经在键上包含数字42的情况下发送事务:


 let keyName = "key" match (tx) { case tx:DataTransaction => let x = extract(getInteger(tx.sender, keyName)) x == 42 case _ => false } 

得益于数据交易,智能帐户已成为功能非常强大的工具,使您可以使用oracle,管理状态并方便地描述行为。


本文介绍如何使用数据事务和控制状态的智能合约来实现NFT(不可替代令牌)。 因此,该帐户的样式将包含以下形式的条目:


 +------------+-----------------------------------------------+ | Token Name | Owner Publc Key | +------------+-----------------------------------------------+ | "Token #1" | "6iQaHazE9NVAJfAjMpHifDXMfr1euWcy8fmW6rNcdhr" | | "Token #2" | "3tNLxyJnyxLzDkMkqiZmUjRqXe1UuwFeSyQ14GRYnGL" | | "Token #3" | "3wH7rENpbS78uohErXHq77yKzQwRyKBYhzCR9nKU17q" | | "Token #4" | "6iQaHazE9NVAJfAjMpHifDXMfr1euWcy8fmW6rNcdhr" | | "Token #5" | "6iQaHazE9NVAJfAjMpHifDXMfr1euWcy8fmW6rNcdhr" | +------------+-----------------------------------------------+ 

NFT合同本身看起来非常简单:


 match tx { case dt: DataTransaction => let oldOwner = extract(getString(dt.sender, dt.data[0].key)) let newOwner = getBinary(dt.data, 0) size(dt.data) == 1 && sigVerify(dt.bodyBytes, dt.proofs[0], fromBase58String(oldOwner)) case _ => false } 

接下来是什么?


Waves智能合约的进一步开发是Ride4DApps ,它将允许在其他帐户上调用合约,以及一种图灵完备的语言(或系统),它将允许您解决所有类型的任务,触发其他任务等。


在Waves生态系统中开发智能合约的另一个有趣方向是智能资产,其工作原理相似-与令牌相关的不完整的图灵合约。 合同控制可以完成令牌交易的条件。 例如,在他们的帮助下,有可能将代币冻结到一定的区块链高度或禁止p2p代币交易。 您可以在博客中阅读有关智能资产的更多信息。


好吧,最后,我将为您提供开始使用Waves网络上的智能合约所需的清单。


  1. 该文件
  2. 带控制台的IDE
  3. 最好奇的白皮书

Source: https://habr.com/ru/post/zh-CN440702/


All Articles