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 指令的工作原理和使用场景,开发者可以更灵活地管理堆栈中的数据,确保智能合约的高效和可靠运行。

留言
全部评论(0)