解构智能合约:什么是功能选择器

尚力财经 174 0

解构智能合约:什么是功能选择器-第1张图片-尚力财经

嗨,欢迎我继续一起解构智能合约。本文是系列文章的第三部分。如果没看过前面的文章,请先看看:

(1)前言:基本代码和运行方式;

(2)创建和运行时代码分析;

我们正在解构一个简单的可靠性智能合约的EVM字节码。

在上一篇文章中,我们决定把智能合约的字节码分成两部分:创建和运行,我们知道为什么这么做。在对创造部分有了深入的了解之后,是时候开始我们对运行部分的探索了。如果看解构图,我们可以先看第二个大拆分块名为BasicToken.evm(运行时)。

这可能看起来有点吓人,因为运行的代码长度至少是创建代码的四倍!但是不要担心,我们在以前的文章中开发的理解EVM代码的技巧,加上我们绝对可靠的“分而治之”策略,将使这个挑战更加系统化,甚至可能更加容易。这只是开始。我们会继续识别独立结构,继续拆分,直到分解成可解的问题。

首先,让我们回到Remix online editor,使用运行时字节码开始调试会话。我们该怎么办?上次,我们部署了智能合约,并调试了部署事务。这一次,我们将使用其中一个函数与部署的智能合约接口进行交互,并调试事务。

首先,我们回忆一下我们的智能合约:

我们启用了优化编译器的Javascript VM,版本v0.4.24,做了10000个作为初始供应。部署智能合约后,您应该会在Remix的“运行”面板的“已部署合约”部分看到它。点击它以展开界面来查看智能合同。

这是什么界面?它是智能合约中所有公共或外部方法的列表——也就是说,任何以太坊帐户或智能合约都可以与之交互。这里不会显示私有和内部方法。如何与智能合约的运行时代码的特定部分进行交互将是本文的重点。

我们可以试试,点击Remix的“运行”面板中的totalSupply按钮。您应该在按钮下方立即看到响应,这正是我们所期望的,因为我们将智能合约部署为初始令牌供应。现在,在控制台面板中,单击Debug按钮来启动这个特定事务的调试会话。请注意,控制台面板上会有多个调试按钮;确保您使用的是最新版本。

在这种情况下,我们没有将事务调试到这个00地址,正如我们在上一篇文章中看到的,我们创建了一个智能合约。相反,我们正在调试智能合约本身的事务——也就是它的运行时代码。

如果弹出“指令”面板,您应该能够验证Remix列出的指令与解构图的BasicToken.evm(运行时)部分中的指令是否相同。如果不匹配,就有问题。尝试重新开始,并确保使用上面的正确设置。

您可能注意到的第一件事是调试器将您放在指令246中,事务滑块位于字节码的60%左右。为什么?因为Remix是一个非常慷慨的程序,它直接把你带到EVM将要执行totalSupply函数体的部分。但是,在此之前,发生了很多事情,这里要注意。事实上,我们甚至不会在本文中研究函数体的执行。我们唯一关心的是Solidity生成的EVM代码如何路由传入的事务,这就是我们所理解的契约的“功能选择器”。

所以,抓住滑块,一直向左拖,这样我们就从指令零开始了。正如我们以前看到的,EVM?总是毫无例外地执行从指令0开始的代码,然后遍历其余的代码。让我们一个操作码接一个操作码地完成这个执行操作码。

出现的第一个结构就是我们之前看到的(其实我们会看到很多):

解构智能合约:什么是功能选择器-第2张图片-尚力财经尚力财经小编2022图1。自由内存指针

这是Solidity生成的EVM代码在调用之前总会执行的任何操作:在内存中保存一个指针以备后用。

我们看看接下来会发生什么:

解构智能合约:什么是功能选择器-第3张图片-尚力财经图2。呼叫数据长度检查。

如果你在调试标签中打开Remix的堆栈面板,跳过指令5到7,你会看到堆栈现在包含两次数字。如果你在阅读这些超长的数字时有问题,请注意,你调整了Remix调试面板的宽度,以便这些数字能够很好地在一行中。第一个来自常规推送,第二个是执行操作码的结果。如黄皮书所述,它没有参数,返回“当前环境下输入数据”的大小,或尚力财经小编2022者我们常说的calldata: 4 calldata size

什么是calldata?正如Solidity的文档ABI规范中所解释的,calldata是一个十六进制数字的编码块,它包含了我们要调用的智能合约函数的信息,以及它的参数或数据。简单来说,它由一个“函数id”组成,这个id是通过散列函数的签名(截断到前四个字节)然后压缩参数数据而生成的。如果有必要,您可以详细研究文档链接,但是不要担心这个包如何工作到最细微的细节。文档里有解释,但是一下子掌握有点难。用实际例子来理解会容易很多。

让我们看看这个调用数据是什么。在Remix的调试器中打开“调用数据”面板查看:0x18160ddd。这是通过将keccak256函数签名上的算法作为字符串应用而精确生成的四字节“totalSupply()”吗?并执行截断。因为这个函数没有参数,所以它只是:一个四字节的函数id。当调用CALLDATASIZE时,它只是将第二个4推送到堆栈上。

然后,指令8 LT用于验证calldata大小是否小于4。如果是,接下来两条指令执行JUMPI指令86(00056)。这小于四个字节,所以在这种情况下不会有跳转,执行流将继续执行指令13。但在我们这样做之前,让我们假设我们用空calldata调用我们的智能合约——即00而不是0x18160ddd。使用Remix btw无法做到这一点,但可以手动建立交易。

在这种情况下,我们最终将进入指令86,它基本上将几个零推到堆栈上,并将它们提供给REVERT操作码。为什么?好吧,因为这个智能合约没有备份功能。如果字节码没有识别出传入的数据,它会将流传递给回滚函数。如果这个结构没有一个“capture”调用,这个恢复结构的执行就会终止,绝对没有回滚。如果没什么可回的,那就没什么可做的,通话完全恢复。

现在,让我们做一些更有趣的事情。回到Remix的Run标签页,复制账户地址,作为参数调用balanceOf而不是totalSupply调试交易。这是一个全新的调试会话;让我们暂时忘记totalSupply。导航到指令8,CALLDATASIZE现在将36(024)压入堆栈。如果你看一下调用数据,它是0x7008231000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005。为什么是32字节?如果以太坊地址只有20字节长,好奇的读者可能会问?ABI总是使用32字节的“字”或“槽”来保存函数调用中使用的参数。

继续我们的平衡通话环境。当堆栈中没有任何内容时,让我们从指令13停止的地方开始。然后,指令13将0xffffffff推入堆栈,下一条指令将29字节的0 00000001000 … 000位推入堆栈。我们很快就会明白为什么。现在,请注意0,其中一个包含四个字节,另一个包含四个字节。

接下来,CALLDATALOAD接受一个参数(在指令48处被推送到堆栈的参数),并从该位置的calldata中读取一个32字节的块。这种情况下会是:

calldataload(0)

基本上是把我的整个calldata推到堆栈里。现在有趣的部分来了。DIV从堆栈中消费两个参数,获取calldata并除以000000001000…000的奇数,有效过滤calldata中除函数签名以外的所有内容并将其留在堆栈中:0000…000070a08231。下一条指令使用AND,这也消耗了堆栈中的两个元素:我们的函数id和四个字节的数字F。这是为了确保签名哈希的长度正好是8个字节,如果存在其他内容,其他内容将被阻止。在我看来,Solidity使用的安全措施。

简而言之,我们只是检查calldata是否太短,如果是,就恢复它,然后稍微改进一下,这样堆栈里就有我们的函数了,

另外,我们也差不多完成了。接下来的部分就好理解了:

图3。函数选择器

在指令53处,代码将18160ddd(函数id totalSuppy)压入堆栈,然后使用DUP2复制当前存在于堆栈第二个位置的传入calldata值70a08231。为什么要复制?因为EQ指令59处的操作码会消耗堆栈中的两个值,而我们想保留70a08231的值,因为我们经历过从calldata中提取它的麻烦。解构智能合约:什么是功能选择器-第4张图片-尚力财经

代码现在将尝试将calldata中的函数id与一个已知的函数id进行匹配。当70a08231进入时,它将不匹配18160ddd,跳过JUMPIat指令63。但是它将在下一次检查中匹配,并跳转到指令74中的jump 1。

让我们花点时间来观察一下智能契约上这些等式检查的每个公共或外部函数。这是函数选择器的核心:充当某种switch语句,简单地将执行路由到代码的正确部分。这是我们的“中心”。

因此,由于最后一个案例是匹配的,执行过程将我们带到JUMPDESTat位置130,正如我们将在本系列的下一部分中看到的,balanceOf函数的ABI“包装器”。正如我们将看到的,这个包装器将负责解包事务数据,以供函数体使用。

继续尝试转移这个调试功能。函数选择器真的没有什么玄机。这是一个简单而有效的结构,位于每个契约(至少是所有从Solidity编译的契约)的门口,并将执行重定向到代码中适当的位置。这就是Solidity如何为smart contract的字节码提供模拟多个入口点的能力,所以它也是接口。

看一下解构图,就是我们刚刚解构的:

图4。智能合约运行时代码的函数选择器和主入口点。

总而言之,伙计们,不知不觉中,你们比大多数人更熟悉坚实的底层代码。坚持下来,完全可以打开。解构智能合约:什么是功能选择器-第5张图片-尚力财经

*本文由亚历杭德罗桑坦德(Alejandro Santander)首次发表于medium,由猎豹区块链安全翻译整理*

标签: 288 2022

抱歉,评论功能暂时关闭!

微信号已复制,请打开微信添加咨询详情!