面向开发者:Yul 与 Solidity 合约比较
概述
在本文中,我将使用Remix IDE,并将提供一些带有完整源代码的要点。虽然我将解释本文中使用的每个操作码,但最好阅读文档并在手边准备一个操作码表。
Yul
Yul是EVM操作码语言上的一个薄抽象层,可以在独立的Yul合约文件中使用,也可以通过一个assembly块在Solidity合约文件中使用。
function readWithYul() public view returns (uint256 data) {
assembly {
data := sload(0)
}
}
Yul的赋值操作符是:=,而不是Solidity中的=。每个操作码都有一组唯一的参数,这些参数可以通过操作码表找到。
然而,Yul不是一个可执行的二进制文件,这意味着它仍然需要编译才能在EVM中运行。在写这篇文章的时候,Yul可以编译成EVM操作码语言和eWASM, eWASM是未来几个月提出的用于ETH 2.0的操作码语言。
字节>类型
理解EVM使用32字节是很重要的,而Solidity类型只是在这些之上的一个抽象。Yul只定义了u256类型,表示256位无符号整数或32字节的字。
函数选择器
一个函数可以通过它的选择器访问。当我们通过区块链客户端库(如web3.js或ether .js)调用合约上的函数时,在底层会发生一些事情。
首先,生成函数签名。这是一个字符串,包含函数名称,后跟参数类型,逗号分隔,括在括号中。
// function declaration
function myFunc(uint256 param1, address param2) {}
// function signature
string signature = "myFunc(uint256,address)"
接下来,使用keccak256对函数签名进行哈希处理,并将其剪切到最左边的4个字节,以生成函数选择器。
// selector = 0x67adc20f
bytes4 selector = bytes4(keccak256(“myFunc(uint256,address)”));
因此,如果我们要调用这个函数,将数字42作为第一个参数,并将地址0x5B38…c4(缩短)作为第二个参数,那么编码后的有效负载可能看起来像是这样
0x67adc20f0000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4
如果我们把它拆开,我们会看到:
// hexadecimal prefix
0x
// selector for myFunc(uint256,address)
67adc20f
// number 42, padded to 32 bytes
0000000000000000000000000000000000000000000000000000000000000002a
// 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 padded to 32 bytes
0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4
这在以后访问不同的函数时非常重要。
调用数据、堆栈、内存和存储
接下来,我们应该理解调用数据、堆栈、内存和存储。
当调用一个函数时,如上所述,编码的有效负载就是调用数据。我们可以访问calldata大小,即calldatasize(),我们可以阅读32个字节调用数据,calldataload(offset)其中offset是要读取calldata 中的起始位置,我们可以将调用数据复制到内存里,calldatacopy(destOffset, offset, size)其中destOffset是要复制内存中的起始位置,offset是要复制调用数据中的起始位置,复制的调用size数据中的字节数。
该堆栈包含32个字节字,堆栈深度为1024,函数与任何其他堆栈一样。我们可以像在任何其他堆栈上一样,推送、弹出、交换和执行算术运算。Yul为我们处理了很多这方面的工作,因此通过push和pop操作码直接与堆栈交互超出了本文的讨论范围。
内存是线性的,可以暂时存储数据,但在消息调用之间会被清除。可以使用mstore8(offset, value),以 8 位为增量写入内存,其中offset是内存中的起始位置,value是要存储的 8 位值,或者以 256 位为增量,使用mstore(offset, value),其中offset是内存中的起始位置,value是要存储的 256 位值。想从内存中读取,可以通过内存中的mload(offset),其中offset是起始位置。从内存中读取总是会返回到从偏移量开始的256位。与内存交互的gas成本与存储在内存中的数据的大小成二次方关系。
存储在函数调用之间是持久的,也称为状态。存储由键/值对组成,其中键和值都是256位字。写入状态是通过sstore(key, value)完成的,其中key是存储的位置,value是存储在给定键位置的256位值。从存储中读取数据可以用sload(key)来完成,其中key是存储所需值的位置。从gas的角度来说,读取和写入存储是最昂贵的。更多的细节可以在下面的EVM操作码链接中找到。
链接
交互式操作码表:https://www.evm.codes/
Yul Doc:https://docs.soliditylang.org/en/v0.8.11/yul.html
合约
在本文的范围内,我们将编写一个合约,其中有一个状态变量、一个读取函数、一个写入函数、一个回退函数,以及在写入状态变量时发出的单个事件。
SolidityContract.sol
对于Solidity开发人员来说,这将是不言自明的,但是为了清晰起见,我们将简要地回顾一下代码。像往常一样&#