导语
在智能合约世界中,“以太坊虚拟机(EVM)”及其算法和数据结构是首要原则。我们创建的智能合约就是建立在这个基础之上的。不管是想要成为一名出色的 Solidity 智能合约开发人员还是安全人员都必须对 EVM 有深入的了解。
此系列我们将引介翻译 noxx 的文章(https://noxx.substack.com/),深入探讨 EVM 的基础知识。
基础知识:Solidity → 字节码 → 操作码
在阅读本篇文章之前,你需要了解一些智能合约相关基础知识以及如何将智能合约代码部署到以太坊链上。正如我们所知,智能合约在部署到以太坊网络之前需要先将 Solidity 代码编译成字节码,EVM 会根据编译后的字节码执行相应的操作。本篇重点介绍编译后的字节码以及其如何被 EVM 执行的。
智能合约被部署后编译生成的字节码代表了整个合约的内容,其中存在多个可调用的函数。那么 EVM 是如何知道不同函数所对应的字节码是哪个呢?下面我们将通过一个 Solidity 智能合约及其字节码和操作码来向大家演示 EVM 在执行代码时是如何在字节码中选择对应的函数的。
1_Storage.sol Breakdown
我们使用在线 Solidity IDE 工具 Remix 来编译 Storage 合约。
此合约中存在两个函数 store() 和 retrieve(),在进行函数调用时 EVM 需要判断我们调用的是哪个函数。我们可以通过 remix 看到整个合约编译后的字节码。
Acala在主网上线Acala EVM+,为波卡DeFi应用链带来完整的EVM+Substrate兼容性:2月8日消息,Acala宣布在主网上线Acala EVM+,允许 DApp 开发人员在 Acala 的 DeFi 应用链和波卡流动性中心上推出与以太坊兼容的 DApp。Acala 表示,任何 DApp 团队现在都可以将他们基于 EVM 的智能合约部署到 Acala EVM+ 上,只需进行最小的更改,同时利用他们在以太坊环境(MetaMask、Truffle、Waffle 等)中使用的相同工具。[2023/2/8 11:53:04]
下面这段字节码是我们需要重点关注的,这段就是 EVM 判断被调用函数的选择器。与其对应的是 EVM 操作码及输入值。
我们可以通过 Ethervm.io 来查看 EVM 操作码列表。一个操作码长度为 1 个字节(byte),这使得它可以存在 256 种不同的操作码。但 EVM 仅使用其中的 140 个操作码。
下面是我们将上述字节码解析成与其对应的操作码。这些操作码会由 EVM 在调用栈上按顺序执行。
智能合约函数调用
在深入研究操作码之前,我们需要快速了解如何调用合约中的函数。调用智能合约中的函数有以下方式:
abi.encode(...) returns (bytes):计算参数的 ABI 编码。
NeonLabs:因第三方业务流程放缓,Neon EVM的主网推迟启动:12月13日消息,Solana的EVM兼容网络开发商Neon Labs今日宣布,因为近期充满挑战的市场环境,许多涉及第三方的业务流程放缓,尽管没有直接受到FTX崩溃的影响,但基金会仍在确定与相关方的文件,从而导致Neon EVM的发布延迟,但NeonEVM的底层代码和机制从技术角度来看已经准备就绪。此前报道,NeonLabs计划12月12日在Solana上推出EVM兼容网络主网。(Medium)[2022/12/13 21:41:03]
abi.encodePacked(...) returns (bytes):计算参数的紧密打包编码。
abi. encodeWithSelector(bytes4 selector, ...) returns (bytes):计算函数选择器和参数的 ABI 编码。
abi.encodeWithSignature(string signature, ...) returns (bytes):等价于?abi.encodeWithSelector(bytes4(keccak256(signature), ...)。
abi.encodeCall(function functionPointer, (...)) returns (bytes memory):使用 tuple 类型参数 ABI 编码调用 functionPointer()。执行完整的类型检查,确保类型匹配函数签名。结果和?abi.encodeWithSelector(functionPointer.selector, (...)) 一致。
这里我们以第四种为例,调用 store() 并传入参数 10:
Messari:Solana基本指标在非EVM链中仍优于其他竞争对手:金色财经报道,据区块链研究公司Messari最新非EVM区块链报告显示,Solana 在最基本的指标上均优于其竞争对手,对于 Cardano、Algorand 和 Tezos 来说,开发活动仍然是关键,不过Algorand链上锁仓量在本轮熊市中依然增长,另外市场对Cardano即将到来的分叉升级也较为期待。[2022/8/8 12:08:36]
下面是通过?abi.encodeWithSignature (" store (uint256)",10)?编码后的内容:
这段数据就是编码后的函数签名。
我们可以使用在线工具(“https://emn178.github.io/online-tools/keccak_256.html”)来查看?store(uint256) 和?retrieve() 哈希后的结果。
也可以通过以太坊函数签名数据库(https://www.4byte.directory/signatures/)进行反查。
HyperPay新版本发布,自管钱包EVM全系兼容Wallet Connect:据官方消息,HyperPay钱包发布V5.0.5版本,在该版本中,HyperPay自管钱包新增支持EVM全系兼容Wallet Connect链接功能;新增自管身份钱包在创建/导入助记词时支持EVM系生成相同地址,提供整个生态中相对较低的手续费消耗,同时,HyperPay自管钱包新增空投版块和DApp浏览器支持钱包切换功能,托管钱包优化了HyperCard申请流程,申请更便捷,帮助用户获得更好的钱包使用体验。
HyperPay钱包成立于2017年,是集托管理财钱包、去中心化自管钱包、HyperMate硬件钱包、共管钱包于一体的多生态数字资产钱包,为用户提供资产存管、理财增值、消费支付等一站式服务。迄今,HyperPay钱包用户逾百万,资管规模超10亿美元,转账超3.1亿次,托管钱包公链支持57+,自管钱包公链支持33+,HyperMate硬件钱包支持公链17+。[2022/7/15 2:15:40]
再回到上面的那组函数签名数据,其中前 4 个字节对应的是 store(uint256)。而剩余的 32 个字节则对应的是一个十六进制的值 “a”,也就是我们调用函数时传入的 uint256 类型的 10。
这里我们可以得到一个结论,通过?abi.encodeWithSignature()?编码后得到的数据,共 36 个字节。这 36 个字节的数据就是函数签名,其中前 4 个字节为函数选择器,它将指引 EVM 去选择我们调用的目标函数,后 32 个字节的数据则是我们调用函数时传入的参数。
Terraform Labs成员发布提案建议为Evmos上UST LP提供流动性激励:4月28日消息,Terraform Labs成员发布提案建议为Evmos提供UST流动性并为UST LP提供流动性激励,支持的协议包括Diffusion Finance、Exswap、Saddle Finance、Kinesis、Midas Capital、Coslend、Cronus Finance。提案建议提供500万美元的UST,以及300万美元UST的流动性激励,其中130万美元在提案通过后支付,剩余170万美元将视TVL而定。[2022/4/28 2:36:53]
操作码和调用栈
这里相信大家已经大致了解了智能合约中函数调用的原理了,下面我们将通过解读每个操作码的作用及其对栈调用的影响。如果你不熟悉栈数据结构的工作原理,可以观看此视频来快速入门:https://www.youtube.com/watch?v=FNZ5o9S9prU
我们将得到的字节码分解成相对应的操作码后依次开始分析。
?PUSH1 操作,将一个?1 字节的值压入栈,它会告诉 EVM 将下一个数据字节 0x00(也是十进制的 0)?压入栈中。
接下来是 CALLDATALOAD,其作用是从消息数据中读取 32 个字节的值,其中使用 “输入” 值作为偏移量将 calldata 加载到栈中。栈项大小为 32 字节,但是当前我们的 calldata 有 36 个字节。推送的值是 msg.data[i:i+32] 其中 “i” 就是这个输入值。此操作确保只有 32 个字节被推送到栈,同时也能保证我们能够访问 calldata 中的任何部分。
当前输入值为 0 也就是没有偏移量(从栈中弹出的值是前一个 PUSH1 的值 0),因此 calldata 的前 32 个字节会被推送到调用栈。
还记得之前所获取到的函数签名吗?如果要传入这 36 个字节,这就意味着后面的 4 个字节“0000000a”将会丢失。如果想访问这个 uint256 类型的参数,需要设置 4 的偏移量来省略函数签名,这样就可以保证参数的完整性。
第二次进行 PUSH1 的操作将传入十六进制的数据 0xe0,也就是十进制的 224。我们上面提到过,函数签名是 4 个字节也就是 32 位。我们加载的 calldata 是 32 个字节也就是 256 位,而 256 - 32 =224 正好满足。
SHR,是向右移位指令。它从栈中获取第一项 224 表示要位移的位数,从栈中获取第二项?(0x6057361d0…00)?表示需要移位的内容。在这个操作之后调用栈上有了 4 个字节的函数选择器。
如果对于位移的工作原理不熟悉的小伙伴,可以查看这个视频了解:https://www.youtube.com/watch?v=fDKUq38H2jk&t=176s
接下来的操作码, DUP1,它用来获取并复制栈顶部的值。
PUSH4 将?retrieve() (0x2e64cec1) 的 4 个字节函数签名推入调用栈。
如果你好奇是这个值是如何获得的,那是因为 solidity 代码被编译成字节码中。编译器可以从字节码中获取所有函数名称和参数类型的信息。
EQ 用于判断从栈中弹出的 2 个值,在当前事例中为 0x2e64cec1 和 0x6057361d 并检查它们是否相等。如果相等,则将 1 推回栈,如果不相等则为 0。
PUSH2 将 2 字节的十六进制数据 0x003b,十进制值为 59,推送到调用栈中。
调用栈中有一个叫做程序计数器的东西,它会指定下一个执行命令在字节码中的位置。这里的 59,是通过 retrieve()?字节码的开始位置所得到的。
JUMPI 代表“如果条件为真,则跳转”,它从栈中弹出 2 个值作为输入,第一个 59 表示的是跳转位置,第二个 0 是是否应该执行此跳转条件的布尔值。其中 1 为真,0 为假。
如果条件为真,程序计数器将被更新,执行将跳转到该位置。但我们的例子中条件为假的,程序计数器没有改变并且继续执行。
再次进行DUP1。
PUSH4 将 store(uint256) (0x6057361d)?的 4 字节函数签名推送到调用栈上。
再次进行 EQ,但这次结果为真,因为函数签名相同。
PUSH2 推送 2 个字节的十六进制数据?0x0059 也就是十进制的 89, 到 store(uint256) 字节码的程序计数器位置。
执行 JUMPI,此次 bool 值为真,执行跳转。因此会将程序计数器更新为 89,这会将执行移动到字节码的不同部分。在这个位置,会有一个 JUMPDEST 操作码,如果没有这个操作码在这里的话,JUMPI 操作就会失败。
有了它,在执行此操作码后,将被带到 store(uint256)?对应的字节码的位置,并且函数的执行将继续。虽然这个合约只有 2 个函数,但基础原理都是相同的。
通过上面的例子我们知道了 EVM 是如何根据合约函数调用来确定它需要执行的函数字节码的位置。简单来说就是由合约中每个函数及其跳转位置所组成的一组简单的“if 语句”。
EVM Playground
这是一个 EVM Playground(https://www.evm.codes/playground)测试平台,在平台上我们可以设置刚刚运行的字节码。就能够通过交互方式来查看栈的变化,并且传入 JUMPDEST(注:可能跳转的目标元数据),可以看到 JUMPI 之后会发生什么。
敬请期待《EVM 深入探讨-Part 2》,让我们共同探索合约内存是什么以及它在 EVM 下的工作方式。
慢雾科技
个人专栏
阅读更多
金色早8点
Bress
链捕手
财经法学
PANews
成都链安
Odaily星球日报
自金融概念出现以来,就有了构建对应的金融“市场”,这一概念最早可以追溯到 17 世纪的香料交易中,中间商通过买卖香料份额向投资者提供更高的流动性,但如今的做市商已经发生了巨大的变化.
1900/1/1 0:00:00终极版VR,长这样?! 作者:夏舍予 说起VR(虚拟现实)领域的前沿研究进展,全球最火爆的VR设备供应商Meta,绝对是引领市场风向的存在。Meta的CEO马克·扎克伯格最近在访谈中透露了Meta在元宇宙领域的最新布局.
1900/1/1 0:00:00什么是 Rollup? Optimism 的发展简史Optimism 如何对以太坊进行扩展Optimism Collection 对未来的展望 更多关于 Optimism 的资源 在铁子们了解 Optimism 和 Optimisti.
1900/1/1 0:00:00原文标题:《Move:Web3 的 Javascript》原文来源:Bixin Ventures环顾 Twitter 帖子和加密出版物,Sui 和 Aptos 的名字总是出现.
1900/1/1 0:00:00原文标题:《HIP 70: Helium Core Team Proposes to Migrate to Solana》作者:Helium Foundation编译:Jordan,PANews8月底.
1900/1/1 0:00:00原文标题:《环境金融化:一文了解 Web3 碳信用市场和 ReFi 再生金融》(Financializing the Environment: The Emerging Web3 Carbon Credit Markets)撰文:0x.
1900/1/1 0:00:00