重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
这篇文章主要介绍“怎么使用Solidity Assembly”,在日常操作中,相信很多人在怎么使用Solidity Assembly问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么使用Solidity Assembly”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
目前成都创新互联已为近1000家的企业提供了网站建设、域名、虚拟主机、网站托管、服务器托管、企业网站设计、茂南网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。
首先,让我们将这个简单的合约粘贴到remix编辑器中:
pragma solidity ^0.5.10; contract AssemblyArrays { bytes testArray; function getLength() public view returns (uint256) { return testArray.length; } function getElement(uint256 index) public view returns (bytes1) { return testArray[index]; } function pushElement(bytes1 value) public { testArray.push(value); } function updateElement(bytes1 value, uint256 index) public { testArray[index] = value; } }
首先熟悉一下Remix编辑器。我们首先需要选择编译器版本,然后编译合约、部署合约,执行一些功能,然后调试。
现在,让我们修改getLength
函数来编写第一行汇编代码:
function getLength() public view returns (uint256) { bytes memory memoryTestArray = testArray; uint256 result; assembly { result := mload(memoryTestArray) } return result; }
上面几行代码中发生了很多事情。汇编就是这样,要实现一个非常简单的功能也需要很多代码。我们将testArray
从存储复制到内存,因为这是本文的重点。以后我们可以再谈一谈存储插槽。
在深入探讨汇编语言块之前,请注意汇编指令是对32字节的字进行操作。因此,mload指令会将 memoryTestArray指向的内存位置的32个字节压入栈。。
现在调试一下。在Remix中,你可以通过单击行号来设置一个断点。让我们在第11行上设置一个断点,所以它看起来像这样:
更新getLength
功能后,请确保已再次编译并重新部署了合约。现在,让我们调用pushElement
函数将字节0x05插入数组,然后调用getLength
,该函数应返回1。
调用getLength
后我们可以对其进行调试。在底部面板的最后一个调用中单击“调试”按钮,这将在左侧栏中打开调试器。有一个用于快进的按钮(如:fast_forward:),它跳到下一个断点。让我们单击那个。如果你使用不同的编译器或不同的设置,那么对于你来说可能并不完全相同,但是其核心将是相同的。基本的思路是在mload
执行之前获取调试器,在我的环境中是#0871指令。
现在让我们看一下调试器侧栏的栈/stack内容:
在堆栈顶部,位置0处为0x0...80。这是在内存中memoryTestArray的位置,该位置将作为mload指令的参数。
现在,让我们看一下调试器侧边栏的“ 内存”部分,从地址0x0...80开始:
这里有31个字节0x00,后跟1个字节0x01,然后是1个字节0x05,后跟31个字节0x00。这可能有点令人困惑,所以让我们退后一点,注意1个字节(8位)由2个十六进制数字表示(1个十六进制数字表示4位)。同样,0x10十六进制的十进制等于16。因此,在内存中,位置0x80保存16个字节,位置0x90(0x80+ 0x10)保存随后的16个字节,然后位置0xa0(0x90+ 0x10)保存以下16个字节,而位置0xb0保存最后的16个字节。因为汇编中的指令以32字节为单位操作,所以如果我们调用mload(0x80),它将从内存位置0x80开始取32字节放入栈。
让我们看看实际执行情况。让我们单击调试器中的“单步进入”按钮(即向下的箭头)来执行mload指令。现在看一下栈顶:
mload
指令取栈顶内容:0x0...80,然后将内存中位置0x0...1的32个字节压入,这是了解内存中的字节数组最重要的一点:前32个字节存储数组的长度。
尝试调用pushElement
函数将元素0x06插入数组。然后调用getLength
并再次调试。同样,mload
将从内存位置0x80开始载入32个字节,但是这次内存的内容为0x0...2。当我们追加新元素时,Solidity为我们更新了数组的大小。
内存中发生变化的另一件事是,现在位置0xa0是0x050600...00。因此,在内存中,一个字节数组变量在前32个字节存储其长度,然后开始存储具体的成员。首先我们压入0x05,然后又压入0x06。
尝试再压入一些元素,调用getLength
并调试,以查看内存中的新字节。如果我们将getElement
转换为汇编,这个过程将变得更加清晰:
function getElement(uint256 index) public view returns (bytes1) { uint256 length = getLength(); require(index < length); bytes memory memoryTestArray = testArray; bytes1 result; assembly { let wordIndex := div(index, 32) let initialElement := add(memoryTestArray, 32) let resultWord := mload(add(initialElement, mul(wordIndex, 32))) let indexInWord := mod(index, 32) result := shl(mul(indexInWord, 8), resultWord) } return result; }
好吧,这可能有点吓到你了!让我们慢慢地捋一下。
第一件超级重要的事情是,我们添加了require
语句来检查index
并没有超出范围。这在调用mload时是至关重要的,我们需要确保要载入的内存位置是正确的,否则可能就会泄漏调用者不应该访问的信息,这可能会让我们的合约存在严重的受攻击风险。
接下来,让我们看一下汇编代码块。由于mload
一次读取32个字节,因此仅读取1个字节并不容易。如果我们把index除以32并取整,这将得到要查找的成员所在的32字节的序号。例如:
div(0, 32) = 0 div(18, 32) = 0 div(32, 32) = 1 div(65, 32) = 2
看起来还不错。但是请记住,内存中memoryTestArray指向的内置的第一个字(32字节)是存储数组长度的。因此,我们需要加上32个字节来查找第一个数组成员。考虑到所有这些因素后,我们就可以载入包含我们需要的1个字节的字(32字节):
memoryTestArray
的内存位置加上32个字节以跳过数组长度,再加wordIndex上乘以32,因为每个字都有32个字节。
但是还没有完成。现在我们需要从这个字中恰好提取1个字节。为此,我们需要在字中找到该字节的索引。这是字索引除以32的余数部分,可以通过mod
指令获得。例如:
mod(0, 32) = 0 mod(18, 32) = 18 mod(32, 32) = 0 mod(65, 32) = 1
不错,下面让我们完成最后一步,提取该字节。为了让字节在最前面,我们向左移动需要的位数。shl
指令一次移动一位,所以为了移动指定的位数,我们要把indexInWord乘以8。
一旦我们将以这个字节开头的32个字节的字赋值给result变量,它就会删除所有其他字节,因为 我们将其类型声明为bytes1
。
到此,关于“怎么使用Solidity Assembly”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!