本文探讨以下问题:哪种智能合约语言更有优势,Solidity还是Vyper?最近,关于哪种是“最好的”智能合约语言存在很多争论,当然了,每一种语言都有它的支持者。
这篇文章是为了回答这场辩论最根本的问题:
我应该使用哪一种智能合约语言?
为了弄清问题的本质,我们将先讨论语言的工具和可用性,然后再考虑智能合约开发者主要关心的问题之一:gas优化。具体来说,我们将研究四种EVM语言:Solidity、Vyper、Huff和Yul。Rust并不在其中,它应该出现在一篇关于非EVM链的文章。
但首先,剧透一下结果。
Solidity、Vyper、Huff和Yul都是可以让你完成工作的优秀语言。Solidity和Vyper是高级语言,大多数人都会用到。但是如果你有兴趣编写近乎汇编的代码,那Yul和Huff也可以胜任。
所以如果你坚持选择其中一个使用,那就抛硬币吧:因为无论你选择哪种语言,都是可以完成项目的。如果你是智能合约的新手,完全可以使用任何一种语言来开始你旅程。
此外,这些语言也一直在变化,你可以挑选特定的智能合约和数据,从而使得运行它们的不同的语言,表现出来的更好或者更差的效果。所以请注意,为了避免不客观,我们在比较不同语言在gas优化上的优劣时,都选择了最简的智能合约作为例子,如果你有更好的例子,也请分享给我们!
现在,如果你是这个领域的老手,让我们深入了解这些语言,看看它们的细节吧。
EVM编程语言
我们将要研究的四种语言如下:
Solidity:目前DeFiTVL占比最大的语言。是一种高级语言,类似于JavaScript。Vyper:目前DeFiTVL排名第二的语言。也是一种高级语言,类似于Python。Huff:一种类似于汇编的底层语言。Yul:一种类似于汇编的底层语言,内置于Solidity。为什么是这四个?
使用这四种语言,是因为它们都与EVM兼容,而且其中的Solidity和Vyper是迄今为止最受欢迎的两种语言。我添加了Yul,因为在不考虑Yul的情况下,与Solidity进行gas优化比较是不全面的。我们添加了Huff是因为想以一种不是Yul,但是与几乎就是在用opcode编写合约的语言作为基准。
就EVM而言,在Vyper和Solidity之后,第三、第四和第五的流行程度也越来越高。对于没有在本文中比较的语言;只是因为它们的使用度不高。然而,有许多很有前景的智能合约语言正在兴起,我期待能够在未来尝试它们。
什么是Solidity?
Solidity是一种面向对象的编程语言,用于在以太坊和其他区块链上来编写智能合约。Solidity深受C++、Python和JavaScript的影响,并且专为EVM而设计。
什么是Vyper?
Vyper是一种面向合约的类似于Python的编程语言,也是为EVM设计的。Vyper增强了可读性,并且限制了某些用法,从而改进了Solidity。理论上,Vyper提升了智能合约的安全性和可审计性。
当前的情况
数据:Blur平台过去24小时交易额超OpenSea一倍多:2月22日消息,数据显示,NFT市场Blur过去24小时交易额达到约1.08亿美元,环比增幅0.66%,超过OpenSea(4521万美元)一倍多。[2023/2/22 12:22:32]
来源于DefiLlama语言分析数据
根据DefiLlama的数据,截至目前,在DeFi领域,Solidity智能合约获得了87%的TVL,而Vyper智能合约获得了8%。
因此,如果你纯粹基于受欢迎程度来选择语言的话,除了Solidity,就不需要看别的了。
比较相同的合约
现在让我们了解每种语言写出的合约的是什么样的,然后比较它们的gas性能。
这是用每种语言编写的四份几乎相同的合同。做了大致相同的事情,它们都:
Storageslot0有一个私有变量number(uint256)。有一个带有readNumber()函数签名的函数,它读取storageslot0中的内容。允许你使用storeNumber(uint256)函数签名更新该变量。这就是这个合约做的操作。
我们用来比较语言的所有代码都在这个GitHubrepo中:
https://github.com/PatrickAlphaC/sc-language-comparison
Solidity
Vyper
Huff
Yul
开发体验
通过查看这四张图片,我们可以大概了解编写每种语言的感受。就开发人员经验而言,编写Solidity和Vyper代码要快得多。这些语言是高级语言,而Yul和Huff是更底层的语言。仅出于这个原因,就很容易理解为什么这么多人采用Vyper和Solidity。
看一下Vyper和Solidity,你可以清楚地感觉到Vyper是从Python中汲取了灵感,而Solidity是从JavaScript和Java中汲取灵感。因此,如果你对于这几种语言更熟悉的话,那就能很好地使用对应的智能合约语言。
Vyper旨在成为一种简约、易于审计的编程语言,而Solidity旨在成为一种通用的智能合约语言。编码的体验在语法层面上也是如此,但每个人肯定都有自己的主观感受。
我不会过多地讨论工具,因为大多数这些语言都有非常相似的工具。主流框架,包括Hardhat、ape、titanoboa、Brownie和Foundry,都支持Vyper和Solidity。Solidity在这大多数框架中,都被优先支持,而Vyper需要使用插件才能与Hardhat等工具一起使用。然而,titanoboa是专为与Vyper一起工作而构建的,除此以外,大多数工具对二者支持都很好。
数据:当前Solana生态总市值约为82.64亿美元:金色财经报道,据CoinGecko最新数据显示,当前Solana生态总市值约为82.64亿美元(截至发稿时为8,264,252,338美元,24小时增涨1.8%),24小时交易额约为4.9亿美元(截至发稿时为490,375,453美元)。[2023/1/2 22:20:44]
哪一种智能合约语言更节省gas?
现在是重头戏。在比较智能合约的gas性能时,需要牢记两点:
合约创建gas成本运行时gas成本
你如何实现智能合约会对这些因素产生重大影响。例如,你可能在合约代码中存储大量数组,这使得部署成本高昂但运行函数的成本更低。或者,你可以让你的函数动态生成数组,从而使合约的部署成本更低,但运行函数成本更高。
那么,让我们看看这四个合约,并将它们的合约创建gas消耗与其运行时gas消耗进行比较。你可以在我的?sc-language-comparisonrepo?中找到所有的代码,包括用于比较它们所使用的框架和工具。
sc-language-comparisonrepo:
https://github.com/PatrickAlphaC/sc-language-comparison
Gas消耗比较-总结
以下是我们如何编译本节的智能合约:
注意:我也可以为Solidity编译使用–via-ir标志。另请注意,Vyper和Solidity在其合约末尾添加了“metadata”。这占总gas成本的一小部分增加,但不足以改变下面的排名。我将在metadata部分详细讨论这一点。
结果:
创建合约时各个语言所消耗的gas费
正如我们所见,像Huff和Yul这样的底层语言比Vyper和Solidity的gas效率更高,但这是为什么呢?Vyper似乎比Solidity更高效,我们有这个新的“SolandYul”部分。那是因为你实际上可以在Solidity中编写Yul。Yul是作为Solidity开发人员在写更接近机器代码时而创建的。
因此,在上图中,我们比较了原始Yul、原始Solidity和Solidity-Yul组合。我们代码的Solidity-Yul版本如下所示:
Yul和Solidity结合的合约
稍后你将看到一个示例,其中这个inline-Yul对gas消耗产生了重大影响。稍后我们将看看为什么存在这些gas差异,但现在让我们看看与Foundry中的单个测试相关的gas消耗。
TrueFi:已向加密投资公司Blockwater发出违约通知,迄今其仍拖欠近300万美元:10月10日消息,无抵押借贷协议TrueFi发出违约通知,称韩国加密投资公司Blockwater拖欠其约340万美元贷款,占TrueFi未偿贷款总价值的约2%,TrueFi信贷小组与Blockwater委托人进行了庭外和解,包括一项贷款修正案,以提高借款利率并延长到期日,迄今为止Blockwater已完成8次付款,总额为645405美元,用于偿还贷款,违约时仍有2967458美元到期,后续将向TrueFi社区提供进一步的更新,TrueFi DAO要求的任何行动都将取决于代币持有者的投票和批准。(CoinDesk)[2022/10/10 12:52:13]
我们的测试函数
这将测试将数字77存储在storage中,然后从storage中读取这个数字的gas成本。以下是运行此测试的结果。
SimpleStorage读和写的gas对比
我们没有Yul的数据,因为获取这个数据必须制作一个Yul-Foundry插件,我不想做-而且结果可能会与Huff相似。请记住,这是运行整个测试函数的gas成本,而不仅仅是单个函数。
Gas消耗对
好,我们来分析一下这个数据。我们需要回答的第一个问题是:为什么Huff和Yul合约的创建比Vyper和Solidity的gas效率高得多?我们可以通过直接查看这些合约的字节码来找到答案。
当你写智能合约时,它通常被分成两个或三个不同的部分。
合约创建代码运行时代码Metadata(非必需)
对于这部分,了解opcode的基础知识很重要。OpenZeppelin关于解构合约的博客帮助你从零开始学习相关知识:
https://blog.openzeppelin.com/deconstructing-a-solidity-contract-part-i-introduction-832efd2d7737/
合约创建代码
合约创建代码是字节码的第一部分,告诉EVM将该合约写到到链上。你通常可以通过在生成的二进制文件中查找CODECOPYopcode(39),然后找到它在链上的位置,并使用RETURNopcode(f3)返回并结束调用。
你还会注意到很多feopcode,这是INVALID操作码。Solidity添加这些作为标记以显示运行时、合约创建和metadata代码之间的差异。f3是RETURN操作码,通常是函数或context的结尾。
BAYC系列NFT近24小时交易额增长超400%:金色财经报道,OpenSea最新数据显示,Bored Ape Yacht Club(BAYC)系列NFT过去24小时的交易额为385 ETH,增长率为427%。近24小时交易额排名位列OpenSea第3。[2022/10/7 18:41:49]
你可能会认为,因为Yul-Solidity的合约创建字节码所占空间最大而Huff的字节码所占空间最小,所以Huff最便宜而Yul-Solidity最贵。但是当你复制整个代码库并将其发到到链上时,代码库的大小会产生很大的差异,这才是决定性因素。然而,这个合约创建代码确实让我们了解了编译器的工作原理,即他们将如何编译合约。
怎么读取Opcode和Stack
目前,EVM是一个基于堆栈的机器,这意味着你所做的大部分“事情”都是从堆栈中push和pull内容。你会在左边看到我们有opcode,在右边我们有两个斜杠(//)表示它们是注释,以及在同一行执行opcode后堆栈的样子,左边是栈顶部,右边是栈底。
Huffopcode的解释
Huff合约的创建只做了它能做的最简单的事情。它获取你编写的代码,并将其返回到链上。
Yulopcode的解释
Yul做同样的事情,它使用了一些不同的opcode,但本质上,它只是将你的合约代码放在链上,使用尽可能少的操作码和一个INVALIDopcode。
Vyperopcode解释
Vyper也基本做了同样的事情。
Solidityopcode解释
现在让我们看看Solidity的opcode。
Solidity做了更多的事情。Solidity做的第一件事是创建一个叫FreeMemoryPointer的东西。为了在内存中创建动态数组,你需要记录内存的哪些部分是空闲可供使用的。我们不会在合约构造代码中使用这个FreeMemoryPointer,但这是它在背后需要做的第一件事。这是语言之间的第一个主要区别:内存管理。每种语言处理内存的方式不同。
接下来,Solidity编译器查看你的代码,并注意到你的构造函数不是payable。因此,为了确保你不会在创建合约时错误地发送了ETH,它使用CALLVALUEopcode检查以确保你没有在创建合约时发送任何通证。这是语言之间的第二个主要区别:它们各自对常见问题有不同的检查和保护。
南天信息:目前已经开发出数字人民币相关的系统、产品和解决方案:金色财经报道,南天信息在互动平台表示,公司与部分数字人民币2层和2.5层机构有较为深入的合作,目前已经开发出数字人民币相关的系统、产品和解决方案。同时在数字人民币的场景生态建设方面作了积极的探索和布局,并取得一定进展。(东方财富)[2022/9/25 7:19:23]
最后,Solidity也做了其他语言所做的事情:它将你的合约发到在链上。
我们将跳过Solidity-Yul,它的工作方式与Solidity自身类似。
检查和保护
从这个意义上说,Solidity似乎“更安全”,因为它比其他语言有更多的保护。但是,如果你要向Vyper代码添加一个构造函数然后重新编译,你会注意到一些不同之处。
Vyper语言的构造函数
编译它,你的合约创建代码看起来更像Solidity的。
它仍然没有Solidity所具有的内存管理,但是你会看到它使用构造函数检查callvalue。如果你使构造函数payable并重新编译,则该检查将消失。
因此,仅通过查看这些合约创建时的配置,我们就可以得出两个结论:
在HuffandYul中,你需要自己显性地写检查操作。而Solidity和Vyper将为你进行检查,Solidity可能会做更多的检查和保护。
这将是语言之间最大的权衡之一:它们在幕后执行哪些检查?Huff和Yul这两种语言不会在幕后做任何事情。所以你的代码会更省gas,但你会更难避免和追踪错误。
运行时代码
现在我们对幕后发生的事情有了一定的了解,我们可以看看合约的不同函数是如何执行的,以及它们为何以这种方式执行。
让我们看看调用storeNumber()函数,在每种语言中,它的值都为77。我通过使用像forgetest–debug“testStorageAndReadSol”这样的命令使用ForgeDebugFeature来获取opcode。我还使用了HuffVSCodeExtension。
Huffopcode解释
有趣的是,如果我们没有STOP操作码,我们的Huff代码实际上会添加一组opcode来返回我们刚刚存储的值,使其比Vyper代码更贵。不过这段代码看起来还是很直观的,那我们就来看看Vyper是怎么做的吧。我们暂时跳过Yul,因为结果会非常相似。
Vyperopcode解释
可以看到在存储值的同时做了一些检查:
对于functionselector来说,calldata是否有足够的字节?他们的value是通过call发送的吗?calldata的大小和functionselector+uint256的大小一样吗?所有这些检查都增加了我们的计算量,但它们也意味着我们更有可能不犯错误。
Solidityopcode解释
这里有很多东西要解释。这与Huff代码之间的一些主要区别是什么?
我们设置了一个freememorypointer。我们检查了发送的value。我们检查了functionselector的calldata大小。我们检查了uint256的大小。
Solidity和Vyper之间的主要区别是什么?
Freememorypointer的设置。Stack在某些时候要深度要大很多。这两者结合起来似乎是Vyper比Solidity便宜的原因。同样有趣的是,Solidity使用ISZEROopcode进行检查,而Vyper使用XORopcode;两者似乎都需要大约相同的gas。正是这些微小的设计差异造成所有的不同。
所以我们现在可以明白为什么Huff和Yul在gas上更便宜:它们只执行你告诉他们的操作,仅此而已,而Vyper和Solidity试图保护你不犯错误。
FreeMemoryPointer
那么这个freememorypointer有什么用呢?Solidity与Vyper之间的gas消耗似乎存在很大差异。freememorypointer是一个控制内存管理的特性——任何时候你添加一些东西到你的内存数组,你的freememorypointer都只是指向它的末尾,就像这样:
这很有用,因为我们可能需要将动态数组等数据结构加载到内存中。对于动态数组,我们不知道它有多大,所以我们需要知道内存在哪里结束。
在Vyper中,因为没有动态的数据结构,你不得不说出像数组这样的对象到底有多大。知道这一点,Vyper可以在编译时分配内存,并且没有freememorypointer。
这意味着在内存管理方面,Vyper可以比Solidity进行更多的gas优化。缺点是使用Vyper你需要明确说明你的数据结构的大小并且不能有动态内存。然而,Vyper团队实际上将此视为一个优势。
动态数组
暂且不谈内存问题,使用Vyper确实必须声明数组的边界。在Solidity中,你可以声明一个没有大小的数组。在Vyper中,你可以有一个动态数组,但它必须是“有界的”。
这对开发人员体验很不好,但是,在Web3中,这也可以被视为针对拒绝服务攻击的保护措施,并防止你的函数中产生大量gas成本。
如果你的数组变得太大,并且你对其进行遍历,则可能会消耗大量gas。但是,如果你显性地声明数组的边界,你将知道最坏情况。
Solidityvs.Yulvs.SolYul
看看我上面的图表,使用Solidity和Yul似乎是最糟糕的选择,因为合约创建代码要贵得多。这可能适用于较小的项目,因为Solidity做了一些操作来让Yul运行,但大规模呢?
以Solidity版本和SolYul版本编写的最受欢迎的项目之一是Seaport项目。
Seaport项目Logo
使用这些语言的最佳方面之一是你可以运行命令来直接从源代码测试每个合约的gas使用效率。我们添加了一个PR来帮助测试纯Solidity合约的gas消耗的命令,因为Sol-Yul合约已经进行了测试。结果非常惊人,你可以在gas-report.txt和gas-report-reference.txt中看到所有数据。
Seaport中合约创建gas消耗的差别
Seaport中函数调用gas消耗的差别
平均而言,函数调用在SolYul版本上的性能提高了25%,而合约创建的性能提高了40%。
这节省了大量的gas。我想知道他们在纯粹的Yul中可以节省多少?我想知道他们在Vypervs.Sol-Yul中会节省多少?
Metadata
最后,Metadata。Vyper和Solidity都在合约末尾附加了一些额外的“Metadata”。虽然数量很少,但我们在这里的比较中基本上会忽略它。你可以手动将其删除,但Solidity团队也在建一个PR,你可以在编译时将其删除。
总结
以下是我对这些语言的看法:
如果你正在编写智能合约,请使用Vyper或Solidity。它们都是高级语言,有检查和保护,比如说检查调用数据大小以及你是否在不应该的情况下不小心发送了ETH。它们都是很棒的语言,所以选择其中一个并慢慢学习。如果你需要性能特别的高的代码,Yul和Huff是很棒的工具。虽然我不建议大多数人用这些语言编程,但它们还是值得学习和理解,会让你更好地了解EVM。Solidity和Vyper之间gas成本的主要区别之一是Solidity中的freememorypointer-一旦你达到高级水平并希望了解工具之间的潜在差异之一,请记住这一点。LookingForward
这些语言将继续发展,我们也可能会看到更多的语言出现,比如Reachprogramminglanguage和fe。
Solidity和Vyper团队致力于开发intermediaterepresentationcompilationstep。Solidity团队有一个–via-ir的flag,这将有助于优化Solidity代码,Vyper团队也有他们的venom作为intermediaterepresentation。
无论你选择哪种语言,你都可以编写一些很棒的智能合约。祝编码愉快!
郑重声明: 本文版权归原作者所有, 转载文章仅为传播更多信息之目的, 如作者信息标记有误, 请第一时间联系我们修改或删除, 多谢。