EVM 存储结构

在 EVM 中实际上有六个位置可以存储数据:stack,memory,calldata,storage,code(在一个特定的地址),log(触发事件)

实际上深入研究的是前四个:stack,memory,calldata,storage

Stack

在以太坊的堆栈中,每一次 push 或 pop 的堆栈内容是一个 32 bytes 的内容。

Memory

以太坊中的 Memory 和上面的 Stack 类似,也是基于 32 bytes 的大小。但是,它不需要任何的 PUSH,POP 操作 ,我们可以写入两个特定的内存字,并准确地选择要写入的位。Memory 实际上有一个很特殊的布局

image-20240509214542175

Calldata

calldata 也可以存储任意字节,但是不同的是,calldata 只能在这个区域读取,不能编写任何内容来调用数据。因此从calldata 中读取和访问东西要便宜很多

所以对于一些需要经常读取,但是不需要进行修改的数据,我们可以放在 calldata 中读取它,而不是将他放在 Memory 中。如果有一个函数的参数,不需要修改 Meomry 的参数,我们就可以把他定义为 calldata。

Storage

Storage 是上述这些存储数据类型中最昂贵的。但是,它是这些存储类型中唯一一个永久的。所有其他的存储结构,stack,memory,calldata 在函数调用完成后都会被清除。他们是在事务处理期间存储数据的临时场所。从某种意义上来讲,我们可以把 Storage 想象成区块链的数据库。它的工作方式与 Memory 类似,我们有 32 bytes 的数据存储在 Storage 中,但是 Storage 具有 slot(插槽),也没有像 Memory 中类似于空闲内存指针的预留空白区域。

区块链的状态,大致可以按照下图的样式理解:

image-20240511175435876

EVM 这些不同领域结合起来有什么特别的?

EVM 这些不同的领域构成了 Ethereum。什么把他们聚集在一起,使得这些东西相互影响?真正可行的是 Opcodes(操作码)的想法。在以太坊黄皮书,或者EVM Code 中给出了所有的操作码,我们可以在其中看到这些 Opcode 是如何操作 EVM 的。在 Huff 入门文章中,也有相关的详细的通过 Huff 操作 Opcode 的过程。

Opcode 是如何发挥作用的?

在 Ethereum 黄皮书和 EVM Code 中我们可以看到,这些操作码实际上对应的都是十六进制值

对于一个合约的 selector 排序

在一个合约中,一般有多个函数,当外部调用该合约的函数时,在 EVM 层次上,实际上是将 calldata 中的 selector 与合约中的 selector 挨个对比(EQ opcode),然后 PUSH 要跳转到的位置,在调用 JUMPI 来判断是否进行调用。也就是说,越早的成功匹配 selector,也就能更加节省调用合约所花费的 gas 费用。

那么 selector 的是按照什么顺序排列的?

在合约编译成字节码时,函数选择器 selector 的排布是按照 selector 的 16 进制字面量大小顺序排序的,字面量越小,越靠前也就是说,在高层次上来看,调用selector 更小的函数,会比调用 selector 大的函数更节省 gas。

在我的合约中实现了四个函数:

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.20;

contract Test {
    uint256 number;

	// 0x0b3f62f5
    function first(uint256 a) external {
        number = a;
    }
	
	// 0xfc563658
    function getNumber(uint256 a) external view returns (uint256) {
        return number * a;
    }
	
	// 0x60362514
    function doubleNumber() external {
        number *= 2;
    }
    
	// 0xd6980dfd
    function number3() external {
        number *= 3;
    }
}

示例合约的布局与相应函数对应的 selector 如上,但是实际得到的字节码是如下的,selector 的排列顺序是按照 selector 的大小来的

[36]	PUSH0	
[37]	CALLDATALOAD	
[38]	PUSH1	e0
[3a]	SHR	
[3b]	DUP1	
[3c]	PUSH4	0b3f62f5
[41]	EQ	
[42]	PUSH2	004e
[45]	JUMPI	
[46]	DUP1	
[47]	PUSH4	60362514
[4c]	EQ	
[4d]	PUSH2	006a
[50]	JUMPI	
[51]	DUP1	
[52]	PUSH4	d6980dfd
[57]	EQ	
[58]	PUSH2	0074
[5b]	JUMPI	
[5c]	DUP1	
[5d]	PUSH4	fc563658
[62]	EQ	
[63]	PUSH2	007e
[66]	JUMPI	
[67]	JUMPDEST	
[68]	PUSH0	
[69]	DUP1	
[6a]	REVERT