前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >解构 Solidity 合约 #4: 函数体

解构 Solidity 合约 #4: 函数体

作者头像
Tiny熊
发布2023-01-09 17:52:09
7980
发布2023-01-09 17:52:09
举报
文章被收录于专栏:深入浅出区块链技术

译文出自:登链翻译计划[1] 译者:翻译小组[2] 校对:Tiny 熊[3]

这是解构系列另一篇。如果你没有读过前面的文章[4],请先看一下。我们正在解构一个简单的Solidity 合约[5]的EVM 字节码[6]

我们已经走过了很长的路,不是吗?首先,我们理解了合约的创建时间和运行时字节码之间的区别;接下来,我们理解了来自任何调用或交易的执行入口是如何通过函数选择器被路由到特定的函数的;最后,我们看到了传入的交易数据是如何被解包给函数使用的,以及函数产生的数据是如何通过函数包装器为用户重新打包的。在这一节中,我们将(最后)看看函数的实际执行情况,或者我们通常称为 "函数体" 的部分。

函数体正是函数包装器在解开传入的 calldata 后所跳入的部分。当一个函数体被执行时,函数的参数应该安然无恙的在堆栈中(如果数据是动态的,则在内存中),等待被使用。让我们看看balanceOf(address)函数的实际应用。这个函数应该接收一个 address,并返回这个地址相应的 uint256的余额。

让我们回到 Remix,像以前一样编译和部署合约,然后调用balanceOf函数,把部署合约时用的地址作为参数。这应该返回数字10000,因为它是最初赋值给构造函数代码中部署合约的地址的,我们在部署合约时使用了这个地址。

好了,现在让我们来调试一下这个交易。

你会注意到的第一件事是,调试器把我们放在了指令 252 处。如果你看一下解构图[7],在包装器的蓝色部分,你应该看到balanceOf函数包装器将指令 175 处重定向到 251 的JUMPDEST指令。正如我们之前多次看到的,Remix 将我们精确地放在了函数主体即将被执行的位置。

图 1. 函数包装器将执行重定向到函数体(指令 175 的蓝色虚线)

图 2. 函数体的执行,来自于函数包装器(指令 251 处的蓝色虚线)。

现在,如果你看一下堆栈,你会发现它最上面的值是我们调用balanceOf的地址。包装器已经完成了正确解包 calldata 的工作。所以我们准备通过指令 251 到 290,即balanceOf函数体。

指令 252 推送了一个 20 字节的0xffffffffffff值,并使用AND操作码将 32 字节的地址 mask(掩码)为正确的类型(记住,以太坊的地址是 20 字节的,而堆栈的操作是 32 字节的字)。

在指令 274 至 278 中:

字节码将把地址从堆栈上传到内存。它需要这个地址用于即将到来的 SHA3操作码。如果你看一下黄皮书[8]SHA3操作码有两个参数:计算哈希值的内存位置和哈希值的字节数。

但是,为什么代码会使用SHA3操作码?这个函数想从balances映射中读取。更确切地说,它想读取映射到的地址的值。如果你了解映射在存储中的布局[9],变量槽 (在这里是 1)的哈希值,因为balances被定义为第二个变量(totalSupply_是第一个变量,在槽 0),实际的键本身是地址,SHA3需要这两个值寻找的值在存储中的位置。

所以,我们已经得到了内存中的地址,但现在我们需要内存中的插槽。这就是指令 279 和 283 之间接下来发生的事情:

数字0x01被存储在内存位置0x20。现在内存保存着第一个字的地址,即内存位置0x00,和第二个字的槽,即内存位置0x20。耶! 我们准备调用SHA3

于是在指令 284 和 287 之间调用了它。

当 287 号指令调用SHA3时,堆栈包含0x00SHA3的起始位置)和0x40SHA3的长度),这基本上是告诉 EVM 在前两个 32 字节的字中对内存中的任何内容进行哈希。32 个字节的十六进制是0x20,所以0x20+0x20等于0x40

现在,SHA3在堆栈中留下了 32 字节的哈希值,这是一个非常长的十六进制数字,比以太坊地址长很多。这个哈希值是合约存储中的位置,传递给balanceOf的地址的余额就存储在这里。你可以使用 Remix 调试器中的Storage completely loaded面板来直观地看到这一点。你应该在第二个存储对象中找到一个匹配的位置。

在这个位置上存储了什么?数字10000,或者十六进制的0x2710。在第 288 条指令中,SLOAD接收了从存储位置(我们的哈希值)读取的参数,并将0x2710推到堆栈。

最后,在第 289 条指令中,SWAP1重新显示了函数包装器的JUMPDEST位置(0x70,或 112),第 290 条指令中的JUMP将我们带回函数包装器的输出部分,它将重新包装0x2710以返回给用户。

我强烈建议你回顾一下我们刚才对balanceOf的调试过程,再对totalSupplytransfer函数的进行调试。前者非常简单,而后者要复杂得多,但基本上是由相同的结构块组成的。秘密在于理解如何从映射中读取数值和写入映射。真的没有什么更多的东西了。

现在让我们回到大解构图:

图 3. 函数包装器之后的函数体。

正如我们之前所讨论的,函数体都集中在函数封装器之后。执行流从包装器中跳到它们,并在执行完每个函数的指令后返回到包装器。

如果你仔细看这张图,在函数体之后有一大块代码,叫做 "元数据哈希"。这是一个非常简单的结构,在下一篇文章我们将解析一下这个部分。

原文链接:https://blog.openzeppelin.com/deconstructing-a-solidity-contract-part-v-function-bodies-2d19d4bef8be/

参考资料

[1]

登链翻译计划: https://github.com/lbc-team/Pioneer

[2]

翻译小组: https://learnblockchain.cn/people/412

[3]

Tiny 熊: https://learnblockchain.cn/people/15

[4]

前面的文章: https://learnblockchain.cn/article/5190

[5]

Solidity合约: https://gist.github.com/ajsantander/dce951a95e7608bc29d7f5deeb6e2ecf

[6]

EVM字节码: https://gist.github.com/ajsantander/03a4a183756980ef0865825bea96d6f5

[7]

解构图: https://img.learnblockchain.cn/pics/20221214153051.svg

[8]

黄皮书: https://ethereum.github.io/yellowpaper/paper.pdf

[9]

映射在存储中的布局: https://learnblockchain.cn/docs/solidity/internals/layout_in_storage.html

Twitter : https://twitter.com/NUpchain Discord : https://discord.gg/pZxy3CU8mh

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考资料
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档