EVM 堆栈指令集
EVM(以太坊虚拟机)的堆栈指令是智能合约操作的基础,使用这些指令可以对堆栈中的数据进行各种操作。
一.EVM 堆栈指令集简介
1.PUSH 指令
💡PUSH1, PUSH2, ..., PUSH32: 将 1 至 32 字节的常量值推送到堆栈顶端。
💡💡操作码: 0x60(PUSH1)到 0x7F(PUSH32)
💡💡气体费用: 3 gas
2.POP 指令
💡POP: 从堆栈中弹出最顶端的一个值。
💡💡操作码: 0x50
💡💡气体费用: 2 gas
3.DUP 指令
💡DUP1, DUP2, ..., DUP16: 复制堆栈顶端第 1 至 16 个值并将其推送到堆栈顶端。
💡💡操作码: 0x80(DUP1)到 0x8F(DUP16)
💡💡气体费用: 3 gas
4.SWAP 指令
💡SWAP1, SWAP2, ..., SWAP16: 交换堆栈顶端的值与第 1 至 16 个值。
💡💡操作码: 0x90(SWAP1)到 0x9F(SWAP16)
💡💡气体费用: 3 gas
5.基本操作步骤
- PUSH 指令: 将常量值推送到堆栈顶端。
- POP 指令: 移除堆栈顶端的值。
- DUP 指令: 复制指定位置的值到堆栈顶端。
- SWAP 指令: 交换堆栈顶端的值与指定位置的值。
二. 各个指令集详解
1. Push 指令集
在 EVM(以太坊虚拟机)中,PUSH 指令用于将常量值推送到堆栈顶端。PUSH 指令有 32 种变体,从 PUSH1 到 PUSH32,分别用于推送 1 到 32 字节的常量值。以下是 PUSH 指令的详细解释:
1.1.操作码
💡名称: PUSH1, PUSH2, ..., PUSH32
💡十六进制表示:
💡💡PUSH1: 0x60
💡💡PUSH2: 0x61
💡💡...
💡💡PUSH32: 0x7F
💡气体费用: 3 gas
1.2.功能
PUSH 指令将指定字节长度的常量值推送到堆栈顶端。例如,PUSH1 将 1 字节的常量值推送到堆栈顶端,而 PUSH32 将 32 字节的常量值推送到堆栈顶端。
1.3.操作步骤
- 取出指令: EVM 取出相应的 PUSH 操作码(例如,PUSH1 为 0x60)。
- 读取常量值: EVM 从字节码中读取相应长度的常量值。
- 推送到堆栈: 将读取的常量值推送到堆栈顶端。
1.4.示例
例 1:
60 0A // PUSH1 0A
执行这段字节码的过程如下:
- 读取 PUSH1 操作码(0x60)。
- 读取下一个字节 0A 作为常量值。
- 将 0A 推送到堆栈顶端。
执行后,堆栈状态如下:
堆栈顶端 -> 0A
例 2:
61 0102 // PUSH2 0102
执行这段字节码的过程如下:
- 读取 PUSH2 操作码(0x61)。
- 读取接下来的 2 个字节 0102 作为常量值。
- 将 0102 推送到堆栈顶端。
执行后,堆栈状态如下:
堆栈顶端 -> 0102
1.5.使用场景
PUSH 指令在智能合约开发中广泛使用,尤其是在需要将常量值加载到堆栈的场景中。以下是一些常见使用场景:
- 初始化值: 将初始值推送到堆栈以进行算术或逻辑操作。
- 设置变量: 为变量分配初始值或常量值。
- 合约操作: 在调用其他合约、设置存储等操作前将常量值推送到堆栈。
1.6.复杂示例
假设我们有以下字节码段:
60 0A // PUSH1 0A
60 14 // PUSH1 14
01 // ADD
执行流程如下:
- 60 0A 将 0A 推送到堆栈。
- 60 14 将 14 推送到堆栈。
- 01 将堆栈顶端的两个值相加(0A + 14 = 1E),并将结果 1E 推送到堆栈。
执行完成后,堆栈状态如下:
堆栈顶端 -> 1E
1.7.执行示例
pragma solidity ^0.8.0;
contract Example {
function pushValue() public pure returns (uint256) {
assembly {
let value := 10
mstore(0x80, value)
return(0x80, 32)
}
}
}
上述合约的 pushValue 函数使用内联汇编(assembly)将常量值 10 推送到堆栈,并存储在内存地址 0x80 处,然后返回该值。在 EVM 字节码中,这个过程涉及 PUSH1 指令将 0A 推送到堆栈。
1.8.注意事项
- 使用 PUSH 指令时,确保提供的常量值长度与指令的要求相符。例如,PUSH1 需要 1 字节的常量值,而 PUSH2 需要 2 字节的常量值。
- PUSH 指令是 EVM 中最基本且常用的指令之一,理解其使用对编写和调试智能合约非常重要。
2.POP 指令详解
在以太坊虚拟机(EVM)中,POP 指令用于移除堆栈顶端的一个元素。这是一种清理堆栈或丢弃不再需要的值的操作。
2.1.操作码和气体费用
- 名称: POP
- 操作码: 0x50
- 气体费用: 2 gas
2.2.功能
POP 指令从堆栈顶端移除一个元素,并不将其保存或处理。这对于清理不再需要的临时值或保持堆栈的整洁非常有用。
2.3.操作步骤
- 从堆栈中弹出一个值: 从堆栈中移除顶端的一个数值。
2.4.示例
假设当前堆栈状态如下:
堆栈顶端 -> 0x03
0x02
0x01
执行 POP 指令后,堆栈状态将变为:
堆栈顶端 -> 0x02
0x01
2.5.详细操作步骤
初始堆栈状态:
堆栈顶端 -> 0x03
0x02
0x01
执行 POP 指令:
- 弹出 0x03。
- 堆栈顶端现在是 0x02。
最终堆栈状态:
堆栈顶端 -> 0x02
0x01
2.6.代码示例
以下是一个包含 POP 指令的智能合约示例,使用 Solidity 编写,并使用 EVM 字节码来演示 POP 指令的执行:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract PopExample {
function example() public pure returns (uint256) {
uint256 a = 3;
uint256 b = 2;
uint256 c = 1;
assembly {
pop(a) // This will pop the value of a from the stack
}
return b; // Returning b to verify the result
}
}
对应的 EVM 字节码:
60 03 // PUSH1 03
60 02 // PUSH1 02
60 01 // PUSH1 01
50 // POP
2.7.执行流程
60 03:将 0x03 推送到堆栈。
堆栈顶端 -> 0x03
60 02:将 0x02 推送到堆栈。
堆栈顶端 -> 0x02
0x03
60 01:将 0x01 推送到堆栈。
堆栈顶端 -> 0x01
0x02
0x03
50:弹出堆栈顶端的 0x01,堆栈顶端现在是 0x02。
堆栈顶端 -> 0x02
0x03
2.8.注意事项
- POP 指令用于清理堆栈,但不会保存或处理被移除的值。因此,使用时应确保移除的值是不再需要的。
- POP 指令消耗 2 gas,是一种低成本的堆栈管理操作。
通过理解 POP 指令的工作原理和使用场景,开发者可以更有效地管理堆栈,确保智能合约的高效和可靠运行。
3.DUP 指令集详解
在以太坊虚拟机(EVM)中,DUP 指令用于复制堆栈中的某个元素,并将其推送到堆栈顶端。DUP 指令有多个变种,从 DUP1 到 DUP16,它们分别用于复制堆栈顶端至第十六个元素中的某个元素。
3.1.操作码和气体费用
💡DUP1
💡💡操作码: 0x80
💡💡气体费用: 3 gas
💡DUP2
💡💡操作码: 0x81
💡💡气体费用: 3 gas
💡DUP3
💡💡操作码: 0x82
💡💡气体费用: 3 gas
💡DUP4
💡💡操作码: 0x83
💡💡气体费用: 3 gas
💡DUP5
💡💡操作码: 0x84
💡💡气体费用: 3 gas
💡DUP6
💡💡操作码: 0x85
💡💡气体费用: 3 gas
💡DUP7
💡💡操作码: 0x86
💡💡气体费用: 3 gas
💡DUP8
💡💡操作码: 0x87
💡💡气体费用: 3 gas
💡DUP9
💡💡操作码: 0x88
💡💡气体费用: 3 gas
💡DUP10
💡💡操作码: 0x89
💡💡气体费用: 3 gas
💡DUP11
💡💡操作码: 0x8a
💡💡气体费用: 3 gas
💡DUP12
💡💡操作码: 0x8b
💡💡气体费用: 3 gas
💡DUP13
💡💡操作码: 0x8c
💡💡气体费用: 3 gas
💡DUP14
💡💡操作码: 0x8d
💡💡气体费用: 3 gas
💡DUP15
💡💡操作码: 0x8e
💡💡气体费用: 3 gas
💡DUP16
💡💡操作码: 0x8f
💡💡气体费用: 3 gas
3.2.功能
每个 DUP 指令从堆栈中复制特定位置的元素,并将其推送到堆栈顶端。例如,DUP1 复制堆栈顶端的元素,DUP2 复制堆栈第二个位置的元素,依此类推,直到 DUP16。
3.3.操作步骤
- 读取堆栈中指定位置的值: 根据 DUP 指令的编号,读取堆栈中对应位置的值。
- 将该值推送到堆栈顶端: 将读取到的值复制一份,推送到堆栈顶端。
3.4.示例
假设当前堆栈状态如下:
堆栈顶端 -> 0x04
0x03
0x02
0x01
执行 DUP2 指令后,堆栈状态将变为:
堆栈顶端 -> 0x03
0x04
0x03
0x02
0x01
3.5.详细操作步骤
初始堆栈状态:
堆栈顶端 -> 0x04
0x03
0x02
0x01
执行 DUP2 指令:
- 读取堆栈第二个位置的值 0x03。
- 将 0x03 推送到堆栈顶端。
最终堆栈状态:
堆栈顶端 -> 0x03
0x04
0x03
0x02
0x01
3.7.代码示例
以下是一个包含 DUP 指令的智能合约示例,使用 Solidity 编写,并使用 EVM 字节码来演示 DUP 指令的执行:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract DupExample {
function duplicate() public pure returns (uint256, uint256) {
uint256 a = 4;
uint256 b = 3;
uint256 c = 2;
uint256 d = 1;
assembly {
dup2 // This will duplicate the value of b
}
return (a, b); // Returning a and b to verify the result
}
}
对应的 EVM 字节码:
60 04 // PUSH1 04
60 03 // PUSH1 03
60 02 // PUSH1 02
60 01 // PUSH1 01
81 // DUP2
执行流程
60 04:将 0x04 推送到堆栈。
堆栈顶端 -> 0x04
60 03:将 0x03 推送到堆栈。
堆栈顶端 -> 0x03
0x04
60 02:将 0x02 推送到堆栈。
堆栈顶端 -> 0x02
0x03
0x04
60 01:将 0x01 推送到堆栈。
堆栈顶端 -> 0x01
0x02
0x03
0x04
81:将堆栈第二个位置的值 0x03 复制一份,推送到堆栈顶端。
堆栈顶端 -> 0x03
0x01
0x02
0x03
0x04
3.8.注意事项
- DUP 指令用于复制堆栈中的特定元素,以便在后续操作中多次使用该值。
- DUP 指令的气体费用均为 3 gas,属于低成本操作。
- DUP 指令应合理使用,以避免不必要的堆栈操作,从而优化合约的执行效率。
通过理解 DUP 指令的工作原理和使用场景,开发者可以更高效地管理堆栈中的数据,确保智能合约的高效和可靠运行。
4.SWAP 指令详解
在以太坊虚拟机(EVM)中,SWAP 指令用于交换堆栈顶端的两个元素的位置。
4.1.操作码和气体费用
- 名称: SWAP
- 操作码范围: 0x90 - 0x9f
- 气体费用: 3 gas
4.2.功能
SWAP 指令将堆栈顶端的第一个元素与第二个元素进行位置交换。不同的 SWAP 指令(SWAP1 到 SWAP16)用于不同位置的元素交换。
4.3.操作步骤
- 交换堆栈中的两个元素: 根据 SWAP 指令的编号,交换堆栈顶端的第一个元素和第二个元素。
4.4.示例
假设当前堆栈状态如下:
堆栈顶端 -> 0x03
0x02
0x01
执行 SWAP2 指令后,堆栈状态将变为:
堆栈顶端 -> 0x01
0x03
0x02
4.5.详细操作步骤
初始堆栈状态:
堆栈顶端 -> 0x03
0x02
0x01
执行 SWAP2 指令:交换堆栈顶端的第一个元素 0x02 和第二个元素 0x01 的位置。
堆栈顶端 -> 0x01
0x03
0x02
最终堆栈状态:
堆栈顶端 -> 0x01
0x03
0x02
4.6.代码示例
以下是一个包含 SWAP 指令的智能合约示例,使用 Solidity 编写,并使用 EVM 字节码来演示 SWAP 指令的执行:
SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SwapExample {
function swap() public pure returns (uint256, uint256, uint256) {
uint256 a = 1;
uint256 b = 2;
uint256 c = 3;
assembly {
swap2 // This will swap the second and third elements on the stack
}
return (a, b, c); // Returning a, b, and c to verify the result
}
}
对应的 EVM 字节码:
60 01 // PUSH1 01
60 02 // PUSH1 02
60 03 // PUSH1 03
92 // SWAP2
执行流程
- 60 01:将 0x01 推送到堆栈。
堆栈顶端 -> 0x01
- 60 02:将 0x02 推送到堆栈。
堆栈顶端 -> 0x02
0x01
- 60 03:将 0x03 推送到堆栈。
堆栈顶端 -> 0x03
0x02
0x01
- 92:交换堆栈顶端的第二个元素 0x02 和第三个元素 0x01 的位置。
堆栈顶端 -> 0x01
0x03
0x02
4.7.注意事项
- SWAP 指令用于在堆栈操作中交换元素的位置,以便于执行后续操作。
- 不同位置的 SWAP 指令(如 SWAP1、SWAP2 等)用于交换不同位置的元素。
- SWAP 指令的气体费用均为 3 gas,是一种低成本操作。
通过理解 SWAP 指令的工作原理和使用场景,开发者可以更灵活地管理堆栈中的数据,确保智能合约的高效和可靠运行。