乐趣区

关于区块链:Solidity-vs-Vyper不同的智能合约语言的优缺点

本文探讨以下问题:哪种智能合约语言更有劣势,Solidity 还是 Vyper?最近,对于哪种是“最好的”智能合约语言存在很多争执,当然了,每一种语言都有它的支持者。

这篇文章是为了答复这场答辩最基本的问题:

我应该应用哪一种智能合约语言?

为了弄清问题的实质,咱们将先探讨语言的工具和可用性,而后再思考智能合约开发者次要关怀的问题之一:gas 优化。具体来说,咱们将钻研四种 EVM 语言(能够在 Ethereum、Avalanche、Polygon 等链上运行的语言):Solidity、Vyper、Huff 和 Yul。Rust 并不在其中,它应该呈现在一篇对于非 EVM 链的文章。

但首先,剧透一下后果。

Solidity、Vyper、Huff 和 Yul 都是能够让你实现工作的优良语言。Solidity 和 Vyper 是高级语言,大多数人都会用到。然而如果你有趣味编写近乎汇编的代码,那 Yul 和 Huff 也能够胜任。

所以如果你保持抉择其中一个应用,那就抛硬币吧:因为无论你抉择哪种语言,都是能够实现我的项目的。如果你是智能合约的老手,齐全能够应用任何一种语言来开始你旅程。

此外,这些语言也始终在变动,你能够筛选特定的智能合约和数据,从而使得运行它们的不同的语言,体现进去的更好或者更差的成果。所以请留神,为了防止不主观,咱们在比拟不同语言在 gas 优化上的优劣时,都抉择了最简的智能合约作为例子,如果你有更好的例子,也请分享给咱们!

当初,如果你是这个畛域的新手,让咱们深刻理解这些语言,看看它们的细节吧。

EVM 编程语言

咱们将要钻研的四种语言如下:

  • Solidity:目前 DeFi TVL(DeFi 锁定的通证价值)占比最大的语言。是一种高级语言,相似于 JavaScript。
  • Vyper:目前 DeFi TVL 排名第二的语言。也是一种高级语言,相似于 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 晋升了智能合约的安全性和可审计性。

以后的状况

来源于 DefiLlama 语言剖析数据

依据 DefiLlama 的数据,截至目前,在 DeFi 畛域,Solidity 智能合约取得了 87% 的 TVL,而 Vyper 智能合约取得了 8%。

因而,如果你纯正基于受欢迎水平来抉择语言的话,除了 Solidity,就不须要看别的了。

比拟雷同的合约

当初让咱们理解每种语言写出的合约的是什么样的,而后比拟它们的 gas 性能。

这是用每种语言编写的四份 简直 雷同的合同。做了大致相同的事件,它们都:

  1. Storage slot 0 有一个公有变量 number (uint256)。
  2. 有一个带有 readNumber() 函数签名的函数,它读取 storage slot 0 中的内容。
  3. 容许你应用 storeNumber(uint256) 函数签名更新该变量。

这就是这个合约做的操作。

咱们用来比拟语言的所有代码都在这个 GitHub repo 中。

🐉 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 一起工作而构建的,除此以外,大多数工具对二者反对都很好。

哪一种智能合约语言更节俭 gas?

当初是重头戏。在比拟智能合约的 gas 性能时,须要牢记两点:

  1. 合约创立 gas 老本
  2. 运行时 gas 老本

你如何实现智能合约会对这些因素产生重大影响。例如,你可能在合约代码中存储大量数组,这使得部署老本昂扬但运行函数的老本更低。或者,你能够让你的函数动静生成数组,从而使合约的部署老本更低,但运行函数老本更高。

那么,让咱们看看这四个合约,并将它们的合约创立 gas 耗费与其运行时 gas 耗费进行比拟。你能够在我的 sc-language-comparison repo 中找到所有的代码,包含用于比拟它们所应用的框架和工具。

Gas 耗费比拟 – 总结

以下是咱们如何编译本节的智能合约:

vyper src/vyper/VSimpleStorage.vy

huffc src/huff/HSimpleStorage.huff -b

solc --strict-assembly --optimize --optimize-runs 20000
yul/YYSimpleStorage.yul --bin

solc --optimize --optimize-runs 20000 src/yulsol/YSimpleStorage.sol --bin

solc --optimize --optimize-runs 20000 src/solidity/SSimpleStorage.sol --bin

留神:我也能够为 Solidity 编译应用 –via-ir 标记。另请留神,Vyper 和 Solidity 在其合约开端增加了“metadata”。这占总 gas 老本的一小部分减少,但不足以扭转上面的排名。我将在 metadata 局部具体探讨这一点。

后果:

创立合约时各个语言所耗费的 gas 费

正如咱们所见,像 Huff 和 Yul 这样的底层语言比 Vyper 和 Solidity 的 gas 效率更高,但这是为什么呢?Vyper 仿佛比 Solidity 更高效,咱们有这个新的“Sol and Yul”局部。那是因为你实际上能够在 Solidity 中编写 Yul。Yul 是作为 Solidity 开发人员在写更靠近机器代码时而创立的。

因而,在上图中,咱们比拟了原始 Yul、原始 Solidity 和 Solidity-Yul 组合。咱们代码的 Solidity-Yul 版本如下所示:

Yul 和 Solidity 联合的合约

稍后你将看到一个示例,其中这个 inline-Yul 对 gas 耗费产生了重大影响。稍后咱们将看看为什么存在这些 gas 差别,但当初让咱们看看与 Foundry 中的单个测试相干的 gas 耗费。

咱们的测试函数

这将测试将数字 77 存储在 storage 中,而后从 storage 中读取这个数字的 gas 老本。以下是运行此测试 的后果。

SimpleStorage 读和写的 gas 比照

咱们没有 Yul 的数据,因为获取这个数据必须制作一个 Yul-Foundry 插件,我不想做 – 而且后果可能会与 Huff 类似。请记住,这是运行整个测试函数的 gas 老本,而不仅仅是单个函数。

Gas 耗费比照

好,咱们来剖析一下这个数据。咱们须要答复的第一个问题是:为什么 Huff 和 Yul 合约的创立比 Vyper 和 Solidity 的 gas 效率高得多?咱们能够通过间接查看这些合约的字节码来找到答案。

当你写智能合约时,它通常被分成两个或三个不同的局部。

  1. 合约创立代码
  2. 运行时代码
  3. Metadata (非必须)

对于这部分,理解 opcode 的基础知识很重要。OpenZeppelin 对于解构合约的博客帮忙你从零开始学习相干常识。

合约创立代码

合约创立代码是字节码的第一局部,通知 EVM 将该合约写到到链上。你通常能够通过在生成的二进制文件中查找 CODECOPY opcode (39),而后找到它在链上的地位,并应用 RETURN opcode (f3) 返回并完结调用。

Huff:
602f8060093d393df3

Yul:
603e80600c6000396000f3fe

Vyper:
61006b61000f60003961006b6000f3

Solidity:
6080604052348015600f57600080fd5b5060ac8061001e6000396000f3fe

Solidity-Yul:
608060405234801561001057600080fd5b5060bc8061001f6000396000f3fe

你还会留神到很多 fe opcode,这是 INVALID 操作码。Solidity 增加这些作为标记以显示运行时、合约创立和 metadata 代码之间的差别。
f3 是 RETURN 操作码,通常是函数或 context 的结尾。

你可能会认为,因为 Yul-Solidity 的合约创立字节码所占空间最大而 Huff 的字节码所占空间最小,所以 Huff 最便宜而 Yul-Solidity 最贵。然而当你复制整个代码库并将其发到到链上时,代码库的大小会产生很大的差别,这才是决定性因素。然而,这个合约创立代码的确让咱们理解了编译器的工作原理,即他们将如何编译合约。

怎么读取 Opcode 和 Stack

目前,EVM 是一个基于堆栈的机器,这意味着你所做的大部分“事件”都是从堆栈中 push 和 pull 内容。你会在右边看到咱们有 opcode,在左边咱们有两个斜杠 (//) 示意它们是正文,以及在同一行执行 opcode 后堆栈的样子,右边是栈顶部,左边是栈底。

Huff opcode 的解释

Huff 合约的创立只做了它能做的最简略的事件。它获取你编写的代码,并将其返回到链上。

PUSH 0x2f        // [2f]
DUP1             // [2f, 2f]
PUSH 0x09        // [09, 2f, 2f]
RETURNDATASIZE   // [0, 09, 2f, 2f]
CODECOPY         // [2f]
RETURNDATASIZE   // [0, 2f]
RETURN           // []

Yul opcode 的解释

Yul 做同样的事件,它应用了一些不同的 opcode,但实质上,它只是将你的合约代码放在链上,应用尽可能少的操作码和一个 INVALID opcode。

PUSH 0x3e  // [3e]
DUP1       // [3e, 3e]
PUSH 0x0c  // [0c, 3e, 3e]
PUSH 0x0   // [0, 0c, 3e, 3e]
CODECOPY   // [3e]
PUSH 0x0   // [0, e3]
RETURN     // []
INVALID    // []

Vyper opcode 解释

Vyper 也根本做了同样的事件。

PUSH2 0x06B  // [06B]
PUSH2 0x0F   // [0F, 06B]
PUSH1 0x0    // [0, 0F, 06B]
CODECOPY     // []
PUSH2 0x06B  // [06B]
PUSH1 0x0    // [0, 06B]
RETURN       // []

Solidity opcode 解释

当初让咱们看看 Solidity 的 opcode。

// Free Memory Pointer
PUSH1 0x80   // [80]
PUSH1 0x40   // [40]
MSTORE       // []

// Check msg.value
CALLVALUE    // [msg.value]
DUP1         // [msg.value, msg.value]
ISZERO       // [msg.value == 0, msg.value]
PUSH1 0xF    // [F, msg.value == 0, msg.value]
JUMPI        // [msg.value] Jump to JUMPDEST if value is not sent

// We only reach this part if msg.value has value
PUSH1 0x0    // [0, msg.value]
DUP1         // [0, 0, msg.value]
REVERT       // [msg.value]

// Finally, put our code on-chain
JUMPDEST     // [msg.value]
POP          // []
PUSH1 0xAC   // [AC]
DUP1         // [AC, AC]
PUSH2 0x1E   // [1E, AC, AC]
PUSH1 0x0    // [0, 1E, AC, AC]
CODECOPY     // [AC]
PUSH1 0x0    // [0, AC]
RETURN       // []
INVALID      // []

Solidity 做了更多的事件。Solidity 做的第一件事是创立一个叫 Free Memory Pointer 的货色。为了在内存中创立动静数组,你须要记录内存的哪些局部是闲暇可供使用的。咱们不会在合约结构代码中应用这个 Free Memory Pointer,但这是它在背地须要做的第一件事。这是语言之间的第一个次要区别:内存治理。每种语言解决内存的形式不同。

接下来,Solidity 编译器查看你的代码,并留神到你的构造函数不是 payable。因而,为了确保你不会在创立合约时谬误地发送了 ETH,它应用 CALLVALUE opcode 查看以确保你没有在创立合约时发送任何通证。这是语言之间的第二个次要区别:它们各自对常见问题有不同的检查和爱护。

最初,Solidity 也做了其余语言所做的事件:它将你的合约发到在链上。

咱们将跳过 Solidity-Yul,它的工作形式与 Solidity 本身相似。

检查和爱护

从这个意义上说,Solidity 仿佛“更平安”,因为它比其余语言有更多的爱护。然而,如果你要向 Vyper 代码增加一个构造函数而后从新编译,你会留神到一些不同之处。

Vyper 语言的构造函数

编译它,你的合约创立代码看起来更像 Solidity 的。

// First, we check the callvalue, and jump to a JUMPDEST much later in the opcodes
CALLVALUE
PUSH2 0x080
JUMPI
// This part is identical to the original compilation
PUSH2 0x06B
PUSH2 0x014
PUSH1 0x0
CODECOPY
PUSH2 0x06B
PUSH1 0x0
RETURN

它依然没有 Solidity 所具备的内存治理,然而你会看到它应用构造函数查看 callvalue。如果你使构造函数 payable 并从新编译,则该查看将隐没。

因而,仅通过查看这些合约创立时的配置,咱们就能够得出两个论断:

  1. 在 Huff and Yul 中,你须要本人显性地写查看操作。
  2. 而 Solidity 和 Vyper 将为你进行查看,Solidity 可能会做更多的检查和爱护。

这将是语言之间最大的衡量之一:它们在幕后执行哪些查看?Huff 和 Yul 这两种语言不会在幕后做任何事件。所以你的代码会更省 gas,但你会更难防止和追踪谬误。

运行时代码

当初咱们对幕后产生的事件有了肯定的理解,咱们能够看看合约的不同函数是如何执行的,以及它们为何以这种形式执行。

让咱们看看调用 storeNumber() 函数,在每种语言中,它的值都为 77。我通过应用像 forge test –debug“testStorageAndReadSol” 这样的命令应用 Forge Debug Feature 来获取 opcode。我还应用了 Huff VSCode Extension。

Huff opcode 解释

// First, we get the function selector of the call and jump to the code for our storeNumber function
PUSH 0x0         // [0]                                                                                                                                              
CALLDATALOAD     // [b6339418] The function selector for storing                                                                                                                                   
PUSH 0xe         // [e, b6339418]                                                                                   
SHR              // [b6339418]                                                                                                                                               
DUP1             // [b6339418, b6339418]                                                                                                                                              
PUSH 0xb6339418  // [b6339418, b6339418, b6339418]                                                                                      
EQ               // [true, b6339418]                                                                                                                                              
PUSH 0x1c        // [1c, true, b6339418]                                                                                  
JUMPI            // [b6339418]
                                                                                                                                           
// We skip a bunch of opcodes since we jumped
// We place the 77 in storage, and end the call
JUMPDEST         // [b6339418]                                                                                                                                           
PUSH 0x4         // [4, b6339418]                                                                                
CALLDATALOAD     // [4d, b6339418] We load 77 from the calldata                                                                                                                                             
PUSH 0x0         // [0, 4d, b6339418]                                                                                                                                          
SSTORE           // [b6339418] Place the 77 in storage 
STOP             // [b6339418] End call

乏味的是,如果咱们没有 STOP 操作码,咱们的 Huff 代码实际上会增加一组 opcode 来返回咱们刚刚存储的值,使其比 Vyper 代码更贵。不过这段代码看起来还是很直观的,那咱们就来看看 Vyper 是怎么做的吧。咱们临时跳过 Yul,因为后果会十分类似。

Vyper opcode 解释

// First, we do a check on the calldata size to make sure we have at least 4 bytes for a function selector
PUSH 0x3        // [3]
CALLDATASIZE    // [3, 24]
GT              // [true]
PUSH 0x000c     // [000c, true]
JUMPI           // []
// Then, we jump to our location, and get the function selector
JUMPDEST
PUSH 0x0        // [0]
CALLDATALOAD    // [b6339418]
PUSH 0xe        // [e, b6339418]
SHR             // [b6339418]
// And we do a check for sending value
CALLVALUE       // [0, b6339418]
PUSH 0x0059     // [59, 0, b6339418]
JUMPI           // [b6339418]
// Value looks good, so we compare selectors, and jump if the selector is something else
PUSH 0xb6339418 // [b6339418, b6339418]
DUP2            // [b6339418, b6339418, b6339418]
XOR             // [0, b6339418]
PUSH 0x0032     // [32, 0, b6339418]
JUMPI           // [b6339418]
// We do a check to make sure the calldata size is big enough for a function selector and a uint256
PUSH 0x24       // [24, b6339418]
CALLDATASIZE    // [24, 24, b6339418]
XOR             // [0, b6339418]
PUSH 0x0059     // [59, 0, b6339418]
JUMPI           // [b6339418]
// Then, we store the variable and end the call
PUSH 0x04       // [4, b6339418]
CALLDATALOAD    // [4d, b6339418]
PUSH 0x0        // [0, 4d, b6339418]
SSTORE          // [b6339418]
STOP

能够看到在存储值的同时做了一些查看:

  1. 对于 function selector 来说,calldata 是否有足够的字节?
  2. 他们的 value 是通过 call 发送的吗?
  3. calldata 的大小和 function selector + uint256 的大小一样吗?

所有这些查看都减少了咱们的计算量,但它们也意味着咱们更有可能不犯错误。

Solidity opcode 解释

// Free Memory Pointer
PUSH 0x80        // [80]
PUSH 0x40        // [40,80]
MSTORE           // []
// msg.value check, jump to function, revert otherwise
CALLVALUE        // [0]
DUP1             // [0,0]
ISZERO           // [true, 0]
PUSH 0x0f        // [0f, true, 0]
JUMPI            // [0]
// Skip reverting code
// We do a check to make sure the calldata size is big enough for a function selector and a uint256
JUMPDEST         // [0]
POP              // []
PUSH 0x04        // [4]
CALLDATASIZE     // [24, 4]
LT               // [false]
PUSH 0x32        // [32, false]
JUMPI            // []
// Find the function selector and jump to it's code
PUSH 0x00        // [0]
CALLDATALOAD     // [b6339418]
PUSH 0xe0        // [e0, b6339418]
SHR              // [b6339418]
DUP1             // [b6339418, b6339418]
PUSH 0xb6339418  // [b6339418, b6339418, b6339418]
EQ               // [true, b6339418]
PUSH 0x37        // [37, true, b6339418]
JUMPI            // [b6339418]
// Setup the function by checking the calldata size, and setup the stack for the function
JUMPDEST
PUSH 0x47        // [47, b6339418]
PUSH 0x42        // [42, 47, b6339418]
CALLDATASIZE     // [24, 42, 47, b6339418]
PUSH 0x04        // [4, 24, 42, 47, b6339418]
PUSH 0x5e        // [5e, 4, 24, 42, 47, b6339418]
JUMP             // [4, 24, 42, 47, b6339418]
JUMPDEST         // [4, 24, 42, 47, b6339418]
PUSH 0x00        // [0, 4, 24, 42, 47, b6339418]
PUSH 0x20        // [20, 0, 4, 24, 42, 47, b6339418]
DUP3             // [4, 20, 0, 4, 24, 42, 47, b6339418]
DUP5             // [24, 4, 20, 0, 4, 24, 42, 47, b6339418]
SUB              // [20, 20, 0, 4, 24, 42, 47, b6339418]
// See if the calldatasize minus the function selector size is smaller than 32 bytes
SLT              // [false(0), 0, 4, 24, 42, 47, b6339418]
ISZERO           // [true, 0, 4, 24, 42, 47, b6339418]
PUSH 0x6f        // [6f, true, 0, 4, 24, 42, 47, b6339418]
JUMPI            // [0, 4, 24, 42, 47, b6339418]
// Get the 77 value, and jump to the function selector code
JUMPDEST
POP              // [24, 42, 47, b6339418]
CALLDATALOAD     // [4d, 24, 42, 47, b6339418]
SWAP2            // [42, 24, 4d, 47, b6339418]
SWAP1            // [24, 42, 4d, 47, b6339418]
POP              // [42, 4d, 47, b6339418]
JUMP             // [4d, 47, b6339418]
JUMPDEST         // [4d, 47, b6339418]
// Store our 77 value to storage and end the function call
PUSH 0x00        // [0, 4d, 47, b6339418]
SSTORE           // [47, b6339418]
JUMP             // [b6339418]
JUMPDEST         // [b6339418]
STOP

这里有很多货色要解释。这与 Huff 代码之间的一些次要区别是什么?

  1. 咱们设置了一个 free memory pointer。
  2. 咱们查看了发送的 value。
  3. 咱们查看了 function selector 的 calldata 大小。
  4. 咱们查看了 uint256 的大小。

Solidity 和 Vyper 之间的次要区别是什么?

  1. Free memory pointer 的设置。
  2. Stack 在某些时候要深度要大很多。

这两者联合起来仿佛是 Vyper 比 Solidity 便宜的起因。同样乏味的是,Solidity 应用 ISZERO opcode 进行查看,而 Vyper 应用 XOR opcode;两者仿佛都须要大概雷同的 gas。正是这些渺小的设计差别造成所有的不同。

所以咱们当初能够明确为什么 Huff 和 Yul 在 gas 上更便宜:它们只执行你通知他们的操作,仅此而已,而 Vyper 和 Solidity 试图爱护你不犯错误。

Free Memory Pointer

那么这个 free memory pointer 有什么用呢?Solidity 与 Vyper 之间的 gas 耗费仿佛存在很大差别。free memory pointer 是一个管制内存治理的个性——任何时候你增加一些货色到你的内存数组,你的 free memory pointer 都只是指向它的开端,就像这样:

这很有用,因为咱们可能须要将动静数组等数据结构加载到内存中。对于动静数组,咱们不晓得它有多大,所以咱们须要晓得内存在哪里完结。

在 Vyper 中,因为没有动静的数据结构,你不得不说出像数组这样的对象到底有多大。晓得这一点,Vyper 能够在编译时分配内存,并且没有 free memory pointer。

这意味着在内存治理方面,Vyper 能够比 Solidity 进行更多的 gas 优化。毛病是应用 Vyper 你须要明确阐明你的数据结构的大小并且不能有动态内存。然而,Vyper 团队实际上将此视为一个劣势。

动静数组

暂且不谈内存问题,应用 Vyper 的确必须申明数组的边界。在 Solidity 中,你能够申明一个没有大小的数组。在 Vyper 中,你能够有一个动静数组,但它必须是“有界的”。

这对开发人员体验很不好,然而,在 Web3 中,这也能够被视为针对拒绝服务(DOS)攻打的保护措施,并避免你的函数中产生大量 gas 老本。

如果你的数组变得太大,并且你对其进行遍历,则可能会耗费大量 gas。然而,如果你显性地申明数组的边界,你将晓得最坏状况。

Solidity vs. Yul vs. 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 中能够节俭多少?我想晓得他们在 Vyper vs. Sol-Yul 中会节俭多少?

Metadata

最初,Metadata。Vyper 和 Solidity 都在合约开端附加了一些额定的“Metadata”。尽管数量很少,但咱们在这里的比拟中基本上会疏忽它。你能够手动将其删除(并依据你的 Solidity 代码的长度调整标记),但 Solidity 团队也在建一个 PR,你能够在编译时将其删除。

总结

以下是我对这些语言的认识:

  1. 如果你正在编写智能合约,请应用 Vyper 或 Solidity。它们都是高级语言,有检查和爱护,比如说查看调用数据大小以及你是否在不应该的状况下不小心发送了 ETH。它们都是很棒的语言,所以抉择其中一个并缓缓学习。
  2. 如果你须要性能特地的高的代码,Yul 和 Huff 是很棒的工具。尽管我不倡议大多数人用这些语言编程,但它们还是值得学习和了解,会让你更好地理解 EVM。
  3. Solidity 和 Vyper 之间 gas 老本的次要区别之一是 Solidity 中的 free memory pointer - 一旦你达到高级程度并心愿理解工具之间的潜在差别之一,请记住这一点。

Looking Forward

这些语言将持续倒退,咱们也可能会看到更多的语言呈现,比方 Reach programming language 和 fe。

Solidity 和 Vyper 团队致力于开发 intermediate representation compilation step。Solidity 团队有一个 –via-ir 的 flag,这将有助于优化 Solidity 代码,Vyper 团队也有他们的 venom 作为 intermediate representation。

无论你抉择哪种语言,你都能够编写一些很棒的智能合约。祝编码欢快!

这篇文章中表白的观点仅代表作者,并不反映 Chainlink。

欢送关注 Chainlink 预言机并且私信退出开发者社区,有大量对于智能合约的学习材料以及对于区块链的话题!

退出移动版