├── .gitignore ├── 01_HelloOpcodes ├── img │ ├── 1-1.png │ ├── 1-2.png │ ├── 1-3.png │ ├── 1-4.png │ ├── 1-5.png │ └── 1-6.png └── readme.md ├── 02_Categories ├── img │ ├── 2-1.png │ ├── 2-2.png │ └── 2-3.png └── readme.md ├── 03_StackOp ├── img │ ├── 3-1.png │ └── 3-2.png ├── readme.md └── stackOp.ipynb ├── 04_ArithmeticOp ├── ArithmeticOp.ipynb └── readme.md ├── 05_ComparisonOp ├── ComparisonOp.ipynb └── readme.md ├── 06_BitwiseOp ├── BitwiseOp.ipynb └── readme.md ├── 07_MemoryOp ├── MemoryOp.ipynb ├── img │ └── 7-1.png └── readme.md ├── 08_StorageOp ├── StorageOp.ipynb ├── img │ └── 8-1.png └── readme.md ├── 09_FlowOp ├── FlowOp.ipynb └── readme.md ├── 10_BlockOp ├── BlockOp.ipynb └── readme.md ├── 11_StackOp2 ├── StackOp2.ipynb └── readme.md ├── 12_SHA3 ├── SHA3Op.ipynb ├── img │ └── 12-1.png └── readme.md ├── 13_AccountOp ├── AccountOp.ipynb ├── img │ └── 13-1.png └── readme.md ├── 14_TxOp ├── TxOp.ipynb ├── img │ ├── 14-1.png │ └── 14-2.png └── readme.md ├── 15_LogOp ├── LogOp.ipynb └── readme.md ├── 16_ReturnOp ├── ReturnOp.ipynb └── readme.md ├── 17_RevertOp ├── RevertOp.ipynb └── readme.md ├── 18_CallOp ├── CallOp.ipynb ├── img │ └── 18-1.png └── readme.md ├── 19_DelegatecallOp ├── DelegatecallOp.ipynb ├── img │ └── 19-1.png └── readme.md ├── 20_StaticcallOp ├── StaticcallOp.ipynb └── readme.md ├── 21_Create ├── CreateOp.ipynb └── readme.md ├── 22_Create2 ├── Create2Op.ipynb └── readme.md ├── 23_SelfdestructOp ├── SelfdestructOp.ipynb └── readme.md ├── 24_GasOp ├── GasOp.ipynb └── readme.md ├── 25_MinimalProxy ├── Clone0Factory.sol ├── img │ ├── 25-1.gif │ ├── 25-2.png │ ├── 25-3.png │ └── 25-4.png └── readme.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | **.DS_Store 2 | .DS_Store 3 | node_modules 4 | cache 5 | typechain-types 6 | artifacts 7 | .idea 8 | .history 9 | **node_modules** 10 | **cache** 11 | **artifacts** -------------------------------------------------------------------------------- /01_HelloOpcodes/img/1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/01_HelloOpcodes/img/1-1.png -------------------------------------------------------------------------------- /01_HelloOpcodes/img/1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/01_HelloOpcodes/img/1-2.png -------------------------------------------------------------------------------- /01_HelloOpcodes/img/1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/01_HelloOpcodes/img/1-3.png -------------------------------------------------------------------------------- /01_HelloOpcodes/img/1-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/01_HelloOpcodes/img/1-4.png -------------------------------------------------------------------------------- /01_HelloOpcodes/img/1-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/01_HelloOpcodes/img/1-5.png -------------------------------------------------------------------------------- /01_HelloOpcodes/img/1-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/01_HelloOpcodes/img/1-6.png -------------------------------------------------------------------------------- /01_HelloOpcodes/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 1. Hello Opcodes 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们将介绍Opcodes和EVM基础,为之后的课程做准备。 14 | 15 | ## Opcodes 简介 16 | 17 | Opcodes(操作码)是以太坊智能合约的基本单元。大家写的Solidity智能合约会被编译为字节码(bytecode),然后才能在EVM(以太坊虚拟机)上运行。而字节码就是由一系列Opcodes组成的。当用户在EVM中调用这个智能合约的函数时,EVM会解析并执行这些Opcodes,以实现合约逻辑。 18 | 19 | 例如,我们看一下几个常见的Opcodes: 20 | - `PUSH1`: 将一个字节的数据压入堆栈。例如,`PUSH1 0x60` 就是将 0x60 压入堆栈。 21 | - `DUP1`: 复制堆栈顶部的一个元素。 22 | - `SWAP1`: 交换堆栈顶部的前两个元素。 23 | 24 | 下面是一个简单的Solidity智能合约,它只有一个`add()`函数,计算`1+1`的结果并返回。 25 | 26 | ```solidity 27 | // SPDX-License-Identifier: MIT 28 | pragma solidity ^0.8.20; 29 | 30 | contract Add { 31 | function add() public pure returns (uint256 result) { 32 | result = 1+1; 33 | } 34 | } 35 | ``` 36 | 37 | 将合约编译后,我们可以得到合约对应的bytecode: 38 | 39 | ``` 40 | 60806040523480156100... 41 | ``` 42 | 43 | 通过bytecode,我们可以得到合约对应的opcodes为: 44 | 45 | ``` 46 | PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ... 47 | ``` 48 | 49 | 如果你想理解这些opcodes在做什么,那么这个教程就适合你,那我们现在开始吧。 50 | 51 | ## EVM 基础 52 | 53 | 由于Opcodes直接操作EVM的资源,比如堆栈、内存、存储,因此了解EVM基础很重要。 54 | 55 | 类似Java的JVM,以太坊智能合约的运行时环境就是EVM。EVM的基本架构主要包括堆栈,内存,存储,EVM字节码,和燃料费,下面我们逐个讲解: 56 | 57 | ![](./img/1-1.png) 58 | 59 | ### 1. 堆栈 Stack 60 | 61 | EVM是基于堆栈的,这意味着它处理数据的方式是使用堆栈数据结构进行大多数计算。堆栈是一种“后进先出”(LIFO)的数据结构,高效而简洁。你可以把它想像成一叠盘子,当你需要添加一个盘子时,你只能把它放在堆栈的最上面,我们把这个动作叫压入`PUSH`;而当你需要取一个盘子时,你只能取最上面的那一个,我们称之为弹出`POP`。许多操作码涉及将数据压入堆栈或从堆栈弹出数据。 62 | 63 | 在堆栈中,每个元素长度为256位(32字节),最大深度为1024元素,但是每个操作只能操作堆栈顶的16个元素。这也是为什么有时Solidity会报`Stack too deep`错误。 64 | 65 | ![](./img/1-2.png) 66 | 67 | ### 2. 内存 Memory 68 | 69 | 堆栈虽然计算高效,但是存储能力有限,因此EVM使用内存来支持交易执行期间的数据存储和读取。EVM的内存是一个线性寻址存储器,你可以把它理解为一个动态字节数组,可以根据需要动态扩展。它支持以8或256 bit写入(`MSTORE8`/`MSTORE`),但只支持以256 bit读取(`MLOAD`)。 70 | 71 | 需要注意的是,EVM的内存是“易失性”的:交易开始时,所有内存位置的值均为0;交易执行期间,值被更新;交易结束时,内存中的所有数据都会被清除,不会被持久化。如果需要永久保存数据,就需要使用EVM的存储 72 | 73 | ![](./img/1-3.png) 74 | 75 | ### 3. 存储 Storage 76 | 77 | EVM的账户存储(Account Storage)是一种映射(mapping,键值对存储),每个键和值都是256 bit的数据,它支持256 bit的读和写。这种存储在每个合约账户上都存在,并且是持久的,它的数据会保持在区块链上,直到被明确地修改。 78 | 79 | 对存储的读取(`SLOAD`)和写入(`SSTORE`)都需要gas,并且比内存操作更昂贵。这样设计可以防止滥用存储资源,因为所有的存储数据都需要在每个以太坊节点上保存。 80 | 81 | ![](./img/1-4.png) 82 | 83 | ### 4. EVM 字节码 84 | 85 | 我们之前提到,Solidity智能合约会被编译为EVM字节码,然后才能在EVM上运行。这个字节码是由一系列的Opcodes组成的,通常表现为一串十六进制的数字。EVM字节码在执行的时候,会按照顺序一个一个地读取并执行每个Opcode。 86 | 87 | 例如,字节码`6001600101`可以被解码为: 88 | 89 | ``` 90 | PUSH1 0x01 91 | PUSH1 0x01 92 | ADD 93 | ``` 94 | 95 | 这段Opcodes的含义是将两个1相加,得到结果2。 96 | 97 | 98 | ### 5. Gas 99 | 100 | Gas是以太坊中执行交易和运行合约的"燃料"。每个交易或合约调用都需要消耗一定数量的Gas,这个数量取决于它们进行的计算的复杂性和数据存储的大小。 101 | 102 | EVM上每笔交易的gas是如何计算的呢?其实是通过opcodes。以太坊规定了每个opcode的gas消耗,复杂度越高的opcodes消耗越多的gas,比如: 103 | - `ADD`操作消耗3 gas 104 | - `SSTORE`操作消耗20000 gas 105 | - `SLOAD`操作消耗200 Gas 106 | 107 | 一笔交易的gas消耗等于其中所有opcodes的gas成本总和。当你调用一个合约函数时,你需要预估这个函数执行所需要的Gas,并在交易中提供足够的Gas。如果提供的Gas不够,那么函数执行会在中途停止,已经消耗的Gas不会退回。 108 | 109 | ![](./img/1-5.png) 110 | 111 | ### 6. 执行模型 112 | 113 | 最后,咱们串联一下以上的内容,介绍EVM的执行模型。它可以概括为以下步骤: 114 | 115 | 1. 当一个交易被接收并准备执行时,以太坊会初始化一个新的执行环境并加载合约的字节码。 116 | 117 | 2. 字节码被翻译成Opcode,被逐一执行。每个Opcodes代表一种操作,比如算术运算、逻辑运算、存储操作或者跳转到其他操作码。 118 | 119 | 3. 每执行一个Opcodes,都要消耗一定数量的Gas。如果Gas耗尽或者执行出错,执行就会立即停止,所有的状态改变(除了已经消耗的Gas)都会被回滚。 120 | 121 | 4. 执行完成后,交易的结果会被记录在区块链上,包括Gas的消耗、交易日志等信息。 122 | 123 | ![](./img/1-6.png) 124 | 125 | 126 | ## 总结 127 | 128 | 这一讲,我们介绍了EVM和Opcodes的基础知识,在之后的教程中,我们将继续学习Opcodes! 129 | -------------------------------------------------------------------------------- /02_Categories/img/2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/02_Categories/img/2-1.png -------------------------------------------------------------------------------- /02_Categories/img/2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/02_Categories/img/2-2.png -------------------------------------------------------------------------------- /02_Categories/img/2-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/02_Categories/img/2-3.png -------------------------------------------------------------------------------- /02_Categories/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 2. Opcodes分类 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们将介绍Opcodes的分类,对Opcodes进行分类,并使用这些操作码来创建一个简单的程序,执行`1+1`的计算。 14 | 15 | ## Opcodes分类 16 | 17 | Opcodes可以根据功能分为以下几类: 18 | 19 | - **堆栈(Stack)指令**: 这些指令直接操作EVM堆栈。这包括将元素压入堆栈(如`PUSH1`)和从堆栈中弹出元素(如`POP`)。 20 | 21 | - **算术(Arithmetic)指令**: 这些指令用于在EVM中执行基本的数学运算,如加法(`ADD`)、减法(`SUB`)、乘法(`MUL`)和除法(`DIV`)。 22 | 23 | - **比较(Comparison)指令**: 这些指令用于比较堆栈顶部的两个元素。例如,大于(`GT`)和小于(`LT`)。 24 | 25 | - **位运算(Bitwise)指令**: 这些指令用于在位级别上操作数据。例如,按位与(`AND`)和按位或(`OR`)。 26 | 27 | - **内存(Memory)指令**: 这些指令用于操作EVM的内存。例如,将内存中的数据读取到堆栈(`MLOAD`)和将堆栈中的数据存储到内存(`MSTORE`)。 28 | 29 | - **存储(Storage)指令**: 这些指令用于操作EVM的账户存储。例如,将存储中的数据读取到堆栈(`SLOAD`)和将堆栈中的数据保存到存储(`SSTORE`)。这类指令的gas消耗比内存指令要大。 30 | 31 | - **控制流(Control Flow)指令**: 这些指令用于EVM的控制流操作,比如跳转`JUMP`和跳转目标`JUMPDEST`。 32 | 33 | - **上下文(Context)指令**: 这些指令用于获取交易和区块的上下文信息。例如,获取msg.sender(`CALLER`)和当前可用的gas(`GAS`)。 34 | 35 | ## evm.codes 36 | 37 | 我们在WTF-Opcodes教程的入门部分会使用[evm.codes](https://www.evm.codes/?fork=shanghai)来运行Opcodes程序。 38 | 39 | ### 1. Opcode列表 40 | 41 | evm.codes提供了完整的Opcodes列表,这对于学习Opcodes非常有用。它包括每个Opcode的编号(例如,`ADD`的编号是`0x01`)、名称、gas消耗、堆栈输入和输出以及一个简短的描述。 42 | 43 | ![](./img/2-1.png) 44 | 45 | ### 2. Playground 46 | 47 | evm.codes还提供了一个在线的Opcodes[playground](https://www.evm.codes/playground),你可以在这里运行Opcodes代码。Playground分为三部分:左上角的编辑器,右上角的执行界面,以及右下角的状态界面,它们分别显示你的代码、代码的执行过程和执行结果。 48 | 49 | 50 | ![](./img/2-2.png) 51 | 52 | 53 | ## 示例: 1+1 54 | 55 | 我们现在来用Opcodes编写一个简单的程序,这个程序将在堆栈中计算1+1,并将结果保存到内存中。代码如下: 56 | 57 | ```go 58 | PUSH1 0x01 59 | PUSH1 0x01 60 | ADD 61 | PUSH0 62 | MSTORE 63 | ``` 64 | 65 | 我们来逐行分析这个程序,同时展示每行指令执行后堆栈和内存的状态: 66 | 67 | 1. 第1-2行:`PUSH1`指令将一个长度为1字节的数据压入堆栈顶部。 68 | 69 | ```go 70 | PUSH1 0x01 71 | // stack: [1] 72 | PUSH1 0x01 73 | // stack: [1, 1] 74 | ``` 75 | 76 | 2. 第3行:`ADD`指令会弹出堆栈顶部的两个元素,计算它们的和,然后将结果压入堆栈。 77 | 78 | ```go 79 | ADD 80 | // stack: [2] 81 | ``` 82 | 83 | 3. 第4行: `PUSH0`指令将0压入堆栈。 84 | 85 | ```go 86 | PUSH0 87 | // stack: [0, 2] 88 | ``` 89 | 90 | 4. 第5行: `MSTORE` 属于内存指令,它会弹出堆栈顶的两个数据 `[offset, value]`(偏移量和值),然后将`value`(长度为32字节)保存到内存索引(偏移量)为`offset`的位置。 91 | 92 | ```go 93 | MSTORE 94 | // stack: [] 95 | // memory: [0: 2] 96 | ``` 97 | 98 | 你可以在evm.codes中验证执行过程和结果。 99 | 100 | ![](./img/2-3.png) 101 | 102 | ## 总结 103 | 104 | 这一讲,我们介绍了Opcodes的功能分类,并使用opcodes写了第一个程序,计算`1+1`。接下来,我们会按分类介绍所有的opcodes。 -------------------------------------------------------------------------------- /03_StackOp/img/3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/03_StackOp/img/3-1.png -------------------------------------------------------------------------------- /03_StackOp/img/3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/03_StackOp/img/3-2.png -------------------------------------------------------------------------------- /03_StackOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 3. 堆栈指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们介绍EVM中的程序计数器(Program Counter)和堆栈指令,同时用Python实现一个简化版的EVM,可以执行`PUSH`和`POP`指令。 14 | 15 | ## 程序计数器 16 | 17 | 在EVM中,程序计数器(通常缩写为 PC)是一个用于跟踪当前执行指令位置的寄存器。每执行一条指令(opcode),程序计数器的值会自动增加,以指向下一个待执行的指令。但是,这个过程并不总是线性的,在执行跳转指令(`JUMP`和`JUMPI`)时,程序计数器会被设置为新的值。 18 | 19 | 下面我们使用Python创建一个简单的EVM程序计数器: 20 | 21 | ```python 22 | class EVM: 23 | # 初始化 24 | def __init__(self, code): 25 | self.code = code # 初始化字节码,bytes对象 26 | self.pc = 0 # 初始化程序计数器为0 27 | self.stack = [] # 堆栈初始为空 28 | 29 | # 获取当前指令 30 | def next_instruction(self): 31 | op = self.code[self.pc] # 获取当前指令 32 | self.pc += 1 # 递增 33 | return op 34 | 35 | def run(self): 36 | while self.pc < len(self.code): 37 | op = self.next_instruction() # 获取当前指令 38 | ``` 39 | 40 | 上面的示例代码很简单,它的功能就是利用程序计数器遍历字节码中的opcode,在接下来的部分,我们将为它添加更多的功能。 41 | 42 | ```python 43 | code = b"\x01\x02\x03" 44 | evm = EVM(code) 45 | evm.run() 46 | ``` 47 | 48 | ## 堆栈指令 49 | 50 | EVM是基于堆栈的,堆栈遵循 LIFO(后入先出)原则,最后一个被放入堆栈的元素将是第一个被取出的元素。PUSH和POP指令就是用来操作堆栈的。 51 | 52 | ### PUSH 53 | 54 | 在EVM中,PUSH是一系列操作符,共有32个(在以太坊上海升级前),从`PUSH1`,`PUSH2`,一直到`PUSH32`,操作码范围为`0x60`到`0x7F`。它们将一个字节大小为1到32字节的值从字节码压入堆栈(堆栈中每个元素的长度为32字节),每种指令的gas消耗都是3。 55 | 56 | 以`PUSH1`为例,它的操作码为`0x60`,它会将字节码中的下一个字节压入堆栈。例如,字节码`0x6001`就表示把`0x01`压入堆栈。`PUSH2`就是将字节码中的下两个字节压入堆栈,例如,`0x610101`就是把`0x0101`压入堆栈。其他的PUSH指令类似。 57 | 58 | 以太坊上海升级新加入了`PUSH0`,操作码为`0x5F`(即`0x60`的前一位),用于将`0`压入堆栈,gas消耗为2,比其他的PUSH指令更省gas。 59 | 60 | 下面我们用python实现`PUSH0`到`PUSH32`,主要逻辑见`push()`和`run()`函数: 61 | 62 | ```python 63 | PUSH0 = 0x5F 64 | PUSH1 = 0x60 65 | PUSH32 = 0x7F 66 | 67 | class EVM: 68 | def __init__(self, code): 69 | self.code = code # 初始化字节码,bytes对象 70 | self.pc = 0 # 初始化程序计数器为0 71 | self.stack = [] # 堆栈初始为空 72 | 73 | def next_instruction(self): 74 | op = self.code[self.pc] # 获取当前指令 75 | self.pc += 1 # 递增 76 | return op 77 | 78 | def push(self, size): 79 | data = self.code[self.pc:self.pc + size] # 按照size从code中获取数据 80 | value = int.from_bytes(data, 'big') # 将bytes转换为int 81 | self.stack.append(value) # 压入堆栈 82 | self.pc += size # pc增加size单位 83 | 84 | def run(self): 85 | while self.pc < len(self.code): 86 | op = self.next_instruction() 87 | 88 | if PUSH1 <= op <= PUSH32: 89 | size = op - PUSH1 + 1 90 | self.push(size) 91 | elif op == PUSH0: 92 | self.stack.append(0) 93 | ``` 94 | 95 | 字节码`0x60016001`(PUSH1 1 PUSH1 1)会将两个1压入堆栈,下面我们执行一下: 96 | 97 | ```python 98 | code = b"\x60\x01\x60\x01" 99 | evm = EVM(code) 100 | evm.run() 101 | print(evm.stack) 102 | # output: [1, 1] 103 | ``` 104 | 105 | 你也可以在evm.codes上验证(注意要把字节码开头的`0x`去掉): 106 | 107 | ![](./img/3-1.png) 108 | 109 | ### POP 110 | 111 | 在EVM中,`POP`指令(操作码`0x50`,gas消耗`2`)用于移除栈顶元素;如果当前堆栈为空,就抛出一个异常。 112 | 113 | 下面我们将`POP`指令加入到之前的代码中,主要逻辑见`pop()`和`run()`函数: 114 | 115 | ```python 116 | PUSH0 = 0x5F 117 | PUSH1 = 0x60 118 | PUSH32 = 0x7F 119 | POP = 0x50 120 | 121 | class EVM: 122 | def __init__(self, code): 123 | self.code = code # 初始化字节码,bytes对象 124 | self.pc = 0 # 初始化程序计数器为0 125 | self.stack = [] # 堆栈初始为空 126 | 127 | def next_instruction(self): 128 | op = self.code[self.pc] # 获取当前指令 129 | self.pc += 1 # 递增 130 | return op 131 | 132 | def push(self, size): 133 | data = self.code[self.pc:self.pc + size] # 按照size从code中获取数据 134 | value = int.from_bytes(data, 'big') # 将bytes转换为int 135 | self.stack.append(value) # 压入堆栈 136 | self.pc += size # pc增加size单位 137 | 138 | def pop(self): 139 | if len(self.stack) == 0: 140 | raise Exception('Stack underflow') 141 | return self.stack.pop() # 弹出堆栈 142 | 143 | def run(self): 144 | while self.pc < len(self.code): 145 | op = self.next_instruction() 146 | 147 | if PUSH1 <= op <= PUSH32: # 如果为PUSH1-PUSH32 148 | size = op - PUSH1 + 1 149 | self.push(size) 150 | elif op == PUSH0: # 如果为PUSH0 151 | self.stack.append(0) 152 | elif op == POP: # 如果为POP 153 | self.pop() 154 | ``` 155 | 156 | 字节码`0x6001600150`(PUSH1 1 PUSH1 1 POP)会将两个1压入堆栈,然后再弹出一个1。下面我们执行一下: 157 | 158 | ```python 159 | code = b"\x60\x01\x60\x01\x50" 160 | evm = EVM(code) 161 | evm.run() 162 | evm.stack 163 | # output: [1] 164 | ``` 165 | 166 | 你也可以在evm.codes上验证(注意要把字节码开头的`0x`去掉): 167 | 168 | ![](./img/3-2.png) 169 | 170 | ## 总结 171 | 172 | 这一讲,我们主要介绍了EVM中的程序计数器和堆栈指令,特别是`PUSH`和`POP`指令。并且参考[evm-from-scratch](https://github.com/w1nt3r-eth/evm-from-scratch),我们使用Python实现了一个简化版的EVM,能够处理`PUSH`和`POP`指令。在后续的教程中,我们将继续探索更多的opcodes,从而进一步完善我们的EVM实现。 173 | -------------------------------------------------------------------------------- /03_StackOp/stackOp.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 71, 6 | "id": "2b96b12e", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "PUSH0 = 0x5F\n", 11 | "PUSH1 = 0x60\n", 12 | "PUSH32 = 0x7F\n", 13 | "POP = 0x50\n", 14 | "\n", 15 | "class EVM:\n", 16 | " def __init__(self, code):\n", 17 | " self.code = code # 初始化字节码,bytes对象\n", 18 | " self.pc = 0 # 初始化程序计数器为0\n", 19 | " self.stack = [] # 堆栈初始为空\n", 20 | "\n", 21 | " def next_instruction(self):\n", 22 | " op = self.code[self.pc] # 获取当前指令\n", 23 | " self.pc += 1 # 递增\n", 24 | " return op\n", 25 | "\n", 26 | " def push(self, size):\n", 27 | " data = self.code[self.pc:self.pc + size] # 按照size从code中获取数据\n", 28 | " value = int.from_bytes(data, 'big') # 将bytes转换为int\n", 29 | " self.stack.append(value) # 压入堆栈\n", 30 | " self.pc += size # pc增加size单位\n", 31 | "\n", 32 | " def pop(self):\n", 33 | " if len(self.stack) == 0:\n", 34 | " raise Exception('Stack underflow')\n", 35 | " return self.stack.pop() # 弹出堆栈\n", 36 | "\n", 37 | " def run(self):\n", 38 | " while self.pc < len(self.code):\n", 39 | " op = self.next_instruction()\n", 40 | "\n", 41 | " if PUSH1 <= op <= PUSH32: # 如果为PUSH1-PUSH32\n", 42 | " size = op - PUSH1 + 1\n", 43 | " self.push(size)\n", 44 | " elif op == PUSH0: # 如果为PUSH0\n", 45 | " self.stack.append(0)\n", 46 | " elif op == POP: # 如果为POP\n", 47 | " self.pop()\n" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 72, 53 | "id": "3f377c2d", 54 | "metadata": {}, 55 | "outputs": [ 56 | { 57 | "name": "stdout", 58 | "output_type": "stream", 59 | "text": [ 60 | "[1, 1]\n" 61 | ] 62 | } 63 | ], 64 | "source": [ 65 | "# PUSH1\n", 66 | "code = b\"\\x60\\x01\\x60\\x01\"\n", 67 | "evm = EVM(code)\n", 68 | "evm.run()\n", 69 | "print(evm.stack)\n", 70 | "# output: [1, 1]" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 73, 76 | "id": "7a70f0ea", 77 | "metadata": {}, 78 | "outputs": [ 79 | { 80 | "data": { 81 | "text/plain": [ 82 | "[1]" 83 | ] 84 | }, 85 | "execution_count": 73, 86 | "metadata": {}, 87 | "output_type": "execute_result" 88 | } 89 | ], 90 | "source": [ 91 | "# PUSH and POP\n", 92 | "code = b\"\\x60\\x01\\x60\\x01\\x50\"\n", 93 | "evm = EVM(code)\n", 94 | "evm.run()\n", 95 | "evm.stack\n", 96 | "# output: [1]" 97 | ] 98 | } 99 | ], 100 | "metadata": { 101 | "kernelspec": { 102 | "display_name": "Python 3 (ipykernel)", 103 | "language": "python", 104 | "name": "python3" 105 | }, 106 | "language_info": { 107 | "codemirror_mode": { 108 | "name": "ipython", 109 | "version": 3 110 | }, 111 | "file_extension": ".py", 112 | "mimetype": "text/x-python", 113 | "name": "python", 114 | "nbconvert_exporter": "python", 115 | "pygments_lexer": "ipython3", 116 | "version": "3.7.7" 117 | } 118 | }, 119 | "nbformat": 4, 120 | "nbformat_minor": 5 121 | } 122 | -------------------------------------------------------------------------------- /04_ArithmeticOp/ArithmeticOp.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "2b96b12e", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "ADD = 0x01\n", 11 | "MUL = 0x02\n", 12 | "SUB = 0x03\n", 13 | "DIV = 0x04\n", 14 | "SDIV = 0x05\n", 15 | "MOD = 0x06\n", 16 | "SMOD = 0x07\n", 17 | "ADDMOD = 0x08\n", 18 | "MULMOD = 0x09\n", 19 | "EXP = 0x0A\n", 20 | "SIGNEXTEND = 0x0B\n", 21 | "PUSH0 = 0x5F\n", 22 | "PUSH1 = 0x60\n", 23 | "PUSH32 = 0x7F\n", 24 | "POP = 0x50\n", 25 | "\n", 26 | "class EVM:\n", 27 | " def __init__(self, code):\n", 28 | " self.code = code # 初始化字节码,bytes对象\n", 29 | " self.pc = 0 # 初始化程序计数器为0\n", 30 | " self.stack = [] # 堆栈初始为空\n", 31 | "\n", 32 | " def next_instruction(self):\n", 33 | " op = self.code[self.pc] # 获取当前指令\n", 34 | " self.pc += 1 # 递增\n", 35 | " return op\n", 36 | "\n", 37 | " def push(self, size):\n", 38 | " data = self.code[self.pc:self.pc + size] # 按照size从code中获取数据\n", 39 | " value = int.from_bytes(data, 'big') # 将bytes转换为int\n", 40 | " self.stack.append(value) # 压入堆栈\n", 41 | " self.pc += size # pc增加size单位\n", 42 | "\n", 43 | " def pop(self):\n", 44 | " if len(self.stack) == 0:\n", 45 | " raise Exception('Stack underflow')\n", 46 | " return self.stack.pop() # 弹出堆栈\n", 47 | "\n", 48 | " def add(self):\n", 49 | " if len(self.stack) < 2:\n", 50 | " raise Exception('Stack underflow')\n", 51 | " a = self.stack.pop()\n", 52 | " b = self.stack.pop()\n", 53 | " res = (a + b) % (2**256) # 加法结果需要模2^256,防止溢出\n", 54 | " self.stack.append(res)\n", 55 | " \n", 56 | " def mul(self):\n", 57 | " if len(self.stack) < 2:\n", 58 | " raise Exception('Stack underflow')\n", 59 | " a = self.stack.pop()\n", 60 | " b = self.stack.pop()\n", 61 | " res = (a * b) % (2**256) # 乘法结果需要模2^256,防止溢出\n", 62 | " self.stack.append(res)\n", 63 | "\n", 64 | " def sub(self):\n", 65 | " if len(self.stack) < 2:\n", 66 | " raise Exception('Stack underflow')\n", 67 | " a = self.stack.pop()\n", 68 | " b = self.stack.pop()\n", 69 | " res = (a - b) % (2**256) # 结果需要模2^256,防止溢出\n", 70 | " self.stack.append(res)\n", 71 | "\n", 72 | " def div(self):\n", 73 | " if len(self.stack) < 2:\n", 74 | " raise Exception('Stack underflow')\n", 75 | " a = self.stack.pop()\n", 76 | " b = self.stack.pop()\n", 77 | " if b == 0:\n", 78 | " res = 0\n", 79 | " else:\n", 80 | " res = (a // b) % (2**256)\n", 81 | " self.stack.append(res)\n", 82 | "\n", 83 | " def sdiv(self):\n", 84 | " if len(self.stack) < 2:\n", 85 | " raise Exception('Stack underflow')\n", 86 | " a = self.stack.pop()\n", 87 | " b = self.stack.pop()\n", 88 | " res = a//b % (2**256) if b!=0 else 0\n", 89 | " self.stack.append(res)\n", 90 | "\n", 91 | " def mod(self):\n", 92 | " if len(self.stack) < 2:\n", 93 | " raise Exception('Stack underflow')\n", 94 | " a = self.stack.pop()\n", 95 | " b = self.stack.pop()\n", 96 | " res = a % b if b != 0 else 0\n", 97 | " self.stack.append(res)\n", 98 | "\n", 99 | " def smod(self):\n", 100 | " if len(self.stack) < 2:\n", 101 | " raise Exception('Stack underflow')\n", 102 | " a = self.stack.pop()\n", 103 | " b = self.stack.pop()\n", 104 | " res = a % b if b != 0 else 0\n", 105 | " self.stack.append(res)\n", 106 | "\n", 107 | " def addmod(self):\n", 108 | " if len(self.stack) < 3:\n", 109 | " raise Exception('Stack underflow')\n", 110 | " a = self.stack.pop()\n", 111 | " b = self.stack.pop()\n", 112 | " n = self.stack.pop()\n", 113 | " res = (a + b) % n if n != 0 else 0\n", 114 | " self.stack.append(res)\n", 115 | "\n", 116 | " def mulmod(self):\n", 117 | " if len(self.stack) < 3:\n", 118 | " raise Exception('Stack underflow')\n", 119 | " a = self.stack.pop()\n", 120 | " b = self.stack.pop()\n", 121 | " n = self.stack.pop()\n", 122 | " res = (a * b) % n if n != 0 else 0\n", 123 | " self.stack.append(res)\n", 124 | "\n", 125 | " def exp(self):\n", 126 | " if len(self.stack) < 2:\n", 127 | " raise Exception('Stack underflow')\n", 128 | " a = self.stack.pop()\n", 129 | " b = self.stack.pop()\n", 130 | " res = pow(a, b) % (2**256)\n", 131 | " self.stack.append(res)\n", 132 | " \n", 133 | " def signextend(self):\n", 134 | " if len(self.stack) < 2:\n", 135 | " raise Exception('Stack underflow')\n", 136 | " b = self.stack.pop()\n", 137 | " x = self.stack.pop()\n", 138 | " if b < 32: # 如果b>=32,则不需要扩展\n", 139 | " sign_bit = 1 << (8 * b - 1) # b 字节的最高位(符号位)对应的掩码值,将用来检测 x 的符号位是否为1\n", 140 | " x = x & ((1 << (8 * b)) - 1) # 对 x 进行掩码操作,保留 x 的前 b+1 字节的值,其余字节全部置0\n", 141 | " if x & sign_bit: # 检查 x 的符号位是否为1\n", 142 | " x = x | ~((1 << (8 * b)) - 1) # 将 x 的剩余部分全部置1\n", 143 | " self.stack.append(x)\n", 144 | " \n", 145 | " def run(self):\n", 146 | " while self.pc < len(self.code):\n", 147 | " op = self.next_instruction()\n", 148 | "\n", 149 | " if PUSH1 <= op <= PUSH32: # 如果为PUSH1-PUSH32\n", 150 | " size = op - PUSH1 + 1\n", 151 | " self.push(size)\n", 152 | " elif op == PUSH0: # 如果为PUSH0\n", 153 | " self.stack.append(0)\n", 154 | " self.pc += size\n", 155 | " elif op == POP: # 如果为POP\n", 156 | " self.pop()\n", 157 | " elif op == ADD: # 处理ADD指令\n", 158 | " self.add()\n", 159 | " elif op == MUL: # 处理MUL指令\n", 160 | " self.mul()\n", 161 | " elif op == SUB: # 处理SUB指令\n", 162 | " self.sub()\n", 163 | " elif op == DIV: # 处理DIV指令\n", 164 | " self.div()\n", 165 | " elif op == SDIV:\n", 166 | " self.sdiv()\n", 167 | " elif op == MOD:\n", 168 | " self.mod()\n", 169 | " elif op == SMOD:\n", 170 | " self.smod()\n", 171 | " elif op == ADDMOD:\n", 172 | " self.addmod()\n", 173 | " elif op == MULMOD:\n", 174 | " self.mulmod()\n", 175 | " elif op == EXP:\n", 176 | " self.exp()\n", 177 | " elif op == SIGNEXTEND:\n", 178 | " self.signextend()\n", 179 | " else:\n", 180 | " raise Exception('Invalid opcode')\n" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": 2, 186 | "id": "3f377c2d", 187 | "metadata": {}, 188 | "outputs": [ 189 | { 190 | "name": "stdout", 191 | "output_type": "stream", 192 | "text": [ 193 | "[5]\n" 194 | ] 195 | } 196 | ], 197 | "source": [ 198 | "# ADD\n", 199 | "code = b\"\\x60\\x02\\x60\\x03\\x01\"\n", 200 | "evm = EVM(code)\n", 201 | "evm.run()\n", 202 | "print(evm.stack)\n", 203 | "# output: [5]" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": 3, 209 | "id": "7a70f0ea", 210 | "metadata": {}, 211 | "outputs": [ 212 | { 213 | "name": "stdout", 214 | "output_type": "stream", 215 | "text": [ 216 | "[6]\n" 217 | ] 218 | } 219 | ], 220 | "source": [ 221 | "# MUL\n", 222 | "code = b\"\\x60\\x02\\x60\\x03\\x02\"\n", 223 | "evm = EVM(code)\n", 224 | "evm.run()\n", 225 | "print(evm.stack)\n", 226 | "# output: [6]" 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": 4, 232 | "id": "6b0fbaa1", 233 | "metadata": {}, 234 | "outputs": [ 235 | { 236 | "name": "stdout", 237 | "output_type": "stream", 238 | "text": [ 239 | "[1]\n" 240 | ] 241 | } 242 | ], 243 | "source": [ 244 | "# SUB\n", 245 | "code = b\"\\x60\\x02\\x60\\x03\\x03\"\n", 246 | "evm = EVM(code)\n", 247 | "evm.run()\n", 248 | "print(evm.stack)\n", 249 | "# output: [1]" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": 6, 255 | "id": "0f712b49", 256 | "metadata": {}, 257 | "outputs": [ 258 | { 259 | "name": "stdout", 260 | "output_type": "stream", 261 | "text": [ 262 | "[2]\n" 263 | ] 264 | } 265 | ], 266 | "source": [ 267 | "# DIV\n", 268 | "code = b\"\\x60\\x03\\x60\\x06\\x04\"\n", 269 | "evm = EVM(code)\n", 270 | "evm.run()\n", 271 | "print(evm.stack)\n", 272 | "# output: [2]" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": null, 278 | "id": "a21d9191", 279 | "metadata": {}, 280 | "outputs": [], 281 | "source": [] 282 | } 283 | ], 284 | "metadata": { 285 | "kernelspec": { 286 | "display_name": "Python 3 (ipykernel)", 287 | "language": "python", 288 | "name": "python3" 289 | }, 290 | "language_info": { 291 | "codemirror_mode": { 292 | "name": "ipython", 293 | "version": 3 294 | }, 295 | "file_extension": ".py", 296 | "mimetype": "text/x-python", 297 | "name": "python", 298 | "nbconvert_exporter": "python", 299 | "pygments_lexer": "ipython3", 300 | "version": "3.7.7" 301 | } 302 | }, 303 | "nbformat": 4, 304 | "nbformat_minor": 5 305 | } 306 | -------------------------------------------------------------------------------- /04_ArithmeticOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 4. 算数指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们将介绍EVM中用于基础算术运算的11个指令,包括`ADD`(加法),`MUL`(乘法),`SUB`(减法),和`DIV`(除法)。并且,我们将在用Python写的极简版EVM中添加对他们的支持。 14 | 15 | ## ADD (加法) 16 | 17 | `ADD`指令从堆栈中弹出两个元素,将它们相加,然后将结果推入堆栈。如果堆栈元素不足两个,那么会抛出异常。这个指令的操作码是`0x01`,gas消耗为`3`。 18 | 19 | 我们可以将`ADD`指令的实现添加到我们的EVM模拟器中: 20 | 21 | ```python 22 | def add(self): 23 | if len(self.stack) < 2: 24 | raise Exception('Stack underflow') 25 | a = self.stack.pop() 26 | b = self.stack.pop() 27 | res = (a + b) % (2**256) # 加法结果需要模2^256,防止溢出 28 | self.stack.append(res) 29 | ``` 30 | 31 | 我们在`run()`函数中添加对`ADD`指令的处理: 32 | 33 | ```python 34 | def run(self): 35 | while self.pc < len(self.code): 36 | op = self.next_instruction() 37 | 38 | if PUSH1 <= op <= PUSH32: 39 | size = op - PUSH1 + 1 40 | self.push(size) 41 | elif op == PUSH0: 42 | self.stack.append(0) 43 | self.pc += size 44 | elif op == POP: 45 | self.pop() 46 | elif op == ADD: # 处理ADD指令 47 | self.add() 48 | ``` 49 | 50 | 现在,我们可以尝试运行一个包含`ADD`指令的字节码:`0x6002600301`(PUSH1 2 PUSH1 3 ADD)。这个字节码将`2`和`3`推入堆栈,然后将它们相加。 51 | 52 | ```python 53 | code = b"\x60\x02\x60\x03\x01" 54 | evm = EVM(code) 55 | evm.run() 56 | print(evm.stack) 57 | # output: [5] 58 | ``` 59 | 60 | ## MUL (乘法) 61 | 62 | `MUL`指令和`ADD`类似,但是它将堆栈的顶部两个元素相乘。操作码是`0x02`,gas消耗为`5`。 63 | 64 | 我们将`MUL`指令的实现添加到EVM模拟器: 65 | 66 | ```python 67 | def mul(self): 68 | if len(self.stack) < 2: 69 | raise Exception('Stack underflow') 70 | a = self.stack.pop() 71 | b = self.stack.pop() 72 | res = (a * b) % (2**256) # 乘法结果需要模2^256,防止溢出 73 | self.stack.append(res) 74 | ``` 75 | 76 | 我们在`run()`函数中添加对`MUL`指令的处理: 77 | 78 | ```python 79 | def run(self): 80 | while self.pc < len(self.code): 81 | op = self.next_instruction() 82 | 83 | if PUSH1 <= op <= PUSH32: 84 | size = op - PUSH1 + 1 85 | self.push(size) 86 | elif op == PUSH0: 87 | self.stack.append(0) 88 | self.pc += size 89 | elif op == POP: 90 | self.pop() 91 | elif op == ADD: 92 | self.add() 93 | elif op == MUL: # 处理MUL指令 94 | self.mul() 95 | ``` 96 | 97 | 现在,我们可以尝试运行一个包含`MUL`指令的字节码:`0x6002600302`(PUSH1 2 PUSH1 3 MUL)。这个字节码将`2`和`3`推入堆栈,然后将它们相乘。 98 | 99 | ```python 100 | code = b"\x60\x02\x60\x03\x02" 101 | evm = EVM(code) 102 | evm.run() 103 | print(evm.stack) 104 | # output: [6] 105 | ``` 106 | 107 | ## SUB (减法) 108 | 109 | `SUB`指令从堆栈顶部弹出两个元素,然后计算第一个元素减去第二个元素,最后将结果推入堆栈。这个指令的操作码是`0x03`,gas消耗为`3`。 110 | 111 | 我们将`SUB`指令的实现添加到EVM模拟器: 112 | 113 | ```python 114 | def sub(self): 115 | if len(self.stack) < 2: 116 | raise Exception('Stack underflow') 117 | a = self.stack.pop() 118 | b = self.stack.pop() 119 | res = (a - b) % (2**256) # 结果需要模2^256,防止溢出 120 | self.stack.append(res) 121 | ``` 122 | 123 | 我们在`run()`函数中添加对`SUB`指令的处理: 124 | 125 | ```python 126 | def run(self): 127 | while self.pc < len(self.code): 128 | op = self.next_instruction() 129 | 130 | if PUSH1 <= op <= PUSH32: 131 | size = op - PUSH1 + 1 132 | self.push(size) 133 | elif op == PUSH0: 134 | self.stack.append(0) 135 | self.pc += size 136 | elif op == POP: 137 | self.pop() 138 | elif op == ADD: 139 | self.add() 140 | elif op == MUL: 141 | self.mul() 142 | elif op == SUB: # 处理SUB指令 143 | self.sub() 144 | ``` 145 | 146 | 现在,我们可以尝试运行一个包含`SUB`指令的字节码:`0x6002600303`(PUSH1 2 PUSH1 3 SUB)。这个字节码将`2`和`3`推入堆栈,然后将它们相减(`3-2`)。 147 | 148 | ```python 149 | code = b"\x60\x02\x60\x03\x03" 150 | evm = EVM(code) 151 | evm.run() 152 | print(evm.stack) 153 | # output: [1] 154 | ``` 155 | 156 | ## DIV (除法) 157 | 158 | `DIV`指令从堆栈顶部弹出两个元素,然后将第一个元素除以第二个元素,最后将结果推入堆栈。如果第二个元素(除数)为0,则将0推入堆栈。这个指令的操作码是`0x04`,gas消耗为`5`。 159 | 160 | 我们将`DIV`指令的实现添加到EVM模拟器: 161 | 162 | ```python 163 | def div(self): 164 | if len(self.stack) < 2: 165 | raise Exception('Stack underflow') 166 | a = self.stack.pop() 167 | b = self.stack.pop() 168 | if b == 0: 169 | res = 0 170 | else: 171 | res = (a // b) % (2**256) 172 | self.stack.append(res) 173 | ``` 174 | 175 | 我们在`run()`函数中添加对`DIV`指令的处理: 176 | 177 | ```python 178 | def run(self): 179 | while self.pc < len(self.code): 180 | op = self.next_instruction() 181 | 182 | if PUSH1 <= op <= PUSH32: 183 | size = op - PUSH1 + 1 184 | self.push(size) 185 | elif op == PUSH0: 186 | self.stack.append(0) 187 | self.pc += size 188 | elif op == POP: 189 | self.pop() 190 | elif op == ADD: 191 | self.add() 192 | elif op == MUL: 193 | self.mul() 194 | elif op == SUB: 195 | self.sub() 196 | elif op == DIV: # 处理DIV指令 197 | self.div() 198 | ``` 199 | 200 | 现在,我们可以尝试运行一个包含`DIV`指令的字节码:`0x6003600604`(PUSH1 3 PUSH1 6 DIV)。这个字节码将`3`和`6`推入堆栈,然后将它们相除(`6//3`)。 201 | 202 | ```python 203 | code = b"\x60\x03\x60\x06\x04" 204 | evm = EVM(code) 205 | evm.run() 206 | print(evm.stack) 207 | # output: [2] 208 | ``` 209 | 210 | ## 其他算数指令 211 | 212 | 1. **SDIV**: 带符号整数的除法指令。与`DIV`类似,这个指令会从堆栈中弹出两个元素,然后将第一个元素除以第二个元素,结果带有符号。如果第二个元素(除数)为0,结果为0。它的操作码是`0x05`,gas消耗为5。要注意,EVM字节码中的负数是用二进制补码(two’s complement)形式,比如`-1`表示为`0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`,它加一等于0。 213 | 214 | ```python 215 | def sdiv(self): 216 | if len(self.stack) < 2: 217 | raise Exception('Stack underflow') 218 | a = self.stack.pop() 219 | b = self.stack.pop() 220 | res = a//b % (2**256) if b!=0 else 0 221 | self.stack.append(res) 222 | ``` 223 | 224 | 2. **MOD**: 取模指令。这个指令会从堆栈中弹出两个元素,然后将第一个元素除以第二个元素的余数推入堆栈。如果第二个元素(除数)为0,结果为0。它的操作码是`0x06`,gas消耗为5。 225 | 226 | ```python 227 | def mod(self): 228 | if len(self.stack) < 2: 229 | raise Exception('Stack underflow') 230 | a = self.stack.pop() 231 | b = self.stack.pop() 232 | res = a % b if b != 0 else 0 233 | self.stack.append(res) 234 | ``` 235 | 236 | 3. **SMOD**: 带符号的取模指令。这个指令会从堆栈中弹出两个元素,然后将第一个元素除以第二个元素的余数推入堆栈,结果带符号。如果第一个元素(除数)为0,结果为0。它的操作码是`0x07`,gas消耗为5。 237 | 238 | ```python 239 | def smod(self): 240 | if len(self.stack) < 2: 241 | raise Exception('Stack underflow') 242 | a = self.stack.pop() 243 | b = self.stack.pop() 244 | res = a % b if b != 0 else 0 245 | self.stack.append(res) 246 | ``` 247 | 248 | 4. **ADDMOD**: 模加法指令。这个指令会从堆栈中弹出三个元素,将前两个元素相加,然后对第三个元素取模,将结果推入堆栈。如果第三个元素(模数)为0,结果为0。它的操作码是`0x08`,gas消耗为8。 249 | 250 | ```python 251 | def addmod(self): 252 | if len(self.stack) < 3: 253 | raise Exception('Stack underflow') 254 | a = self.stack.pop() 255 | b = self.stack.pop() 256 | n = self.stack.pop() 257 | res = (a + b) % n if n != 0 else 0 258 | self.stack.append(res) 259 | ``` 260 | 261 | 5. **MULMOD**: 模乘法指令。这个指令会从堆栈中弹出三个元素,将前两个元素相乘,然后对第三个元素取模,将结果推入堆栈。如果第三个元素(模数)为0,结果为0。它的操作码是`0x09`,gas消耗为5。 262 | 263 | ```python 264 | def mulmod(self): 265 | if len(self.stack) < 3: 266 | raise Exception('Stack underflow') 267 | a = self.stack.pop() 268 | b = self.stack.pop() 269 | n = self.stack.pop() 270 | res = (a * b) % n if n != 0 else 0 271 | self.stack.append(res) 272 | ``` 273 | 274 | 6. **EXP**: 指数运算指令。这个指令会从堆栈中弹出两个元素,将第一个元素作为底数,第二个元素作为指数,进行指数运算,然后将结果推入堆栈。它的操作码是`0x0A`,gas消耗为10。 275 | 276 | ```python 277 | def exp(self): 278 | if len(self.stack) < 2: 279 | raise Exception('Stack underflow') 280 | a = self.stack.pop() 281 | b = self.stack.pop() 282 | res = pow(a, b) % (2**256) 283 | self.stack.append(res) 284 | ``` 285 | 286 | 7. **SIGNEXTEND**: 符号位扩展指令,即在保留数字的符号(正负性)及数值的情况下,增加二进制数字位数的操作。举个例子,若计算机使用8位二进制数表示数字“0000 1010”,且此数字需要将字长符号扩充至16位,则扩充后的值为“0000 0000 0000 1010”。此时,数值与符号均保留了下来。`SIGNEXTEND`指令会从堆栈中弹出两个元素,对第二个元素进行符号扩展,扩展的位数由第一个元素决定,然后将结果推入堆栈。它的操作码是`0x0B`,gas消耗为5。 287 | 288 | ```python 289 | def signextend(self): 290 | if len(self.stack) < 2: 291 | raise Exception('Stack underflow') 292 | b = self.stack.pop() 293 | x = self.stack.pop() 294 | if b < 32: # 如果b>=32,则不需要扩展 295 | sign_bit = 1 << (8 * b - 1) # b 字节的最高位(符号位)对应的掩码值,将用来检测 x 的符号位是否为1 296 | x = x & ((1 << (8 * b)) - 1) # 对 x 进行掩码操作,保留 x 的前 b+1 字节的值,其余字节全部置0 297 | if x & sign_bit: # 检查 x 的符号位是否为1 298 | x = x | ~((1 << (8 * b)) - 1) # 将 x 的剩余部分全部置1 299 | self.stack.append(x) 300 | ``` 301 | 302 | ## 总结 303 | 304 | 这一讲,我们介绍了EVM中的11个算数指令,并在极简版EVM中添加了对他们的支持。课后习题: 写出`0x60036004600209`对应的指令形式,并给出运行后的堆栈状态。 -------------------------------------------------------------------------------- /05_ComparisonOp/ComparisonOp.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 4, 6 | "id": "2b96b12e", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "ADD = 0x01\n", 11 | "MUL = 0x02\n", 12 | "SUB = 0x03\n", 13 | "DIV = 0x04\n", 14 | "SDIV = 0x05\n", 15 | "MOD = 0x06\n", 16 | "SMOD = 0x07\n", 17 | "ADDMOD = 0x08\n", 18 | "MULMOD = 0x09\n", 19 | "EXP = 0x0A\n", 20 | "SIGNEXTEND = 0x0B\n", 21 | "LT = 0x10\n", 22 | "GT = 0x11\n", 23 | "SLT = 0x12\n", 24 | "SGT = 0x13\n", 25 | "EQ = 0x14\n", 26 | "ISZERO = 0x15\n", 27 | "PUSH0 = 0x5F\n", 28 | "PUSH1 = 0x60\n", 29 | "PUSH32 = 0x7F\n", 30 | "POP = 0x50\n", 31 | "\n", 32 | "class EVM:\n", 33 | " def __init__(self, code):\n", 34 | " self.code = code # 初始化字节码,bytes对象\n", 35 | " self.pc = 0 # 初始化程序计数器为0\n", 36 | " self.stack = [] # 堆栈初始为空\n", 37 | "\n", 38 | " def next_instruction(self):\n", 39 | " op = self.code[self.pc] # 获取当前指令\n", 40 | " self.pc += 1 # 递增\n", 41 | " return op\n", 42 | "\n", 43 | " def push(self, size):\n", 44 | " data = self.code[self.pc:self.pc + size] # 按照size从code中获取数据\n", 45 | " value = int.from_bytes(data, 'big') # 将bytes转换为int\n", 46 | " self.stack.append(value) # 压入堆栈\n", 47 | " self.pc += size # pc增加size单位\n", 48 | "\n", 49 | " def pop(self):\n", 50 | " if len(self.stack) == 0:\n", 51 | " raise Exception('Stack underflow')\n", 52 | " return self.stack.pop() # 弹出堆栈\n", 53 | "\n", 54 | " def add(self):\n", 55 | " if len(self.stack) < 2:\n", 56 | " raise Exception('Stack underflow')\n", 57 | " a = self.stack.pop()\n", 58 | " b = self.stack.pop()\n", 59 | " res = (a + b) % (2**256) # 加法结果需要模2^256,防止溢出\n", 60 | " self.stack.append(res)\n", 61 | " \n", 62 | " def mul(self):\n", 63 | " if len(self.stack) < 2:\n", 64 | " raise Exception('Stack underflow')\n", 65 | " a = self.stack.pop()\n", 66 | " b = self.stack.pop()\n", 67 | " res = (a * b) % (2**256) # 乘法结果需要模2^256,防止溢出\n", 68 | " self.stack.append(res)\n", 69 | "\n", 70 | " def sub(self):\n", 71 | " if len(self.stack) < 2:\n", 72 | " raise Exception('Stack underflow')\n", 73 | " a = self.stack.pop()\n", 74 | " b = self.stack.pop()\n", 75 | " res = (a - b) % (2**256) # 结果需要模2^256,防止溢出\n", 76 | " self.stack.append(res)\n", 77 | "\n", 78 | " def div(self):\n", 79 | " if len(self.stack) < 2:\n", 80 | " raise Exception('Stack underflow')\n", 81 | " a = self.stack.pop()\n", 82 | " b = self.stack.pop()\n", 83 | " if a == 0:\n", 84 | " res = 0\n", 85 | " else:\n", 86 | " res = (a // b) % (2**256)\n", 87 | " self.stack.append(res)\n", 88 | "\n", 89 | " def sdiv(self):\n", 90 | " if len(self.stack) < 2:\n", 91 | " raise Exception('Stack underflow')\n", 92 | " a = self.stack.pop()\n", 93 | " b = self.stack.pop()\n", 94 | " res = a//b % (2**256) if a!=0 else 0\n", 95 | " self.stack.append(res)\n", 96 | "\n", 97 | " def mod(self):\n", 98 | " if len(self.stack) < 2:\n", 99 | " raise Exception('Stack underflow')\n", 100 | " a = self.stack.pop()\n", 101 | " b = self.stack.pop()\n", 102 | " res = a % b if a != 0 else 0\n", 103 | " self.stack.append(res)\n", 104 | "\n", 105 | " def smod(self):\n", 106 | " if len(self.stack) < 2:\n", 107 | " raise Exception('Stack underflow')\n", 108 | " a = self.stack.pop()\n", 109 | " b = self.stack.pop()\n", 110 | " res = a % b if a != 0 else 0\n", 111 | " self.stack.append(res)\n", 112 | "\n", 113 | " def addmod(self):\n", 114 | " if len(self.stack) < 3:\n", 115 | " raise Exception('Stack underflow')\n", 116 | " a = self.stack.pop()\n", 117 | " b = self.stack.pop()\n", 118 | " n = self.stack.pop()\n", 119 | " res = (a + b) % n if n != 0 else 0\n", 120 | " self.stack.append(res)\n", 121 | "\n", 122 | " def mulmod(self):\n", 123 | " if len(self.stack) < 3:\n", 124 | " raise Exception('Stack underflow')\n", 125 | " a = self.stack.pop()\n", 126 | " b = self.stack.pop()\n", 127 | " n = self.stack.pop()\n", 128 | " res = (a * b) % n if n != 0 else 0\n", 129 | " self.stack.append(res)\n", 130 | "\n", 131 | " def exp(self):\n", 132 | " if len(self.stack) < 2:\n", 133 | " raise Exception('Stack underflow')\n", 134 | " a = self.stack.pop()\n", 135 | " b = self.stack.pop()\n", 136 | " res = pow(a, b) % (2**256)\n", 137 | " self.stack.append(res)\n", 138 | "\n", 139 | " def signextend(self):\n", 140 | " if len(self.stack) < 2:\n", 141 | " raise Exception('Stack underflow')\n", 142 | " b = self.stack.pop()\n", 143 | " x = self.stack.pop()\n", 144 | " if b < 32: # 如果b>=32,则不需要扩展\n", 145 | " sign_bit = 1 << (8 * b - 1) # b 字节的最高位(符号位)对应的掩码值,将用来检测 x 的符号位是否为1\n", 146 | " x = x & ((1 << (8 * b)) - 1) # 对 x 进行掩码操作,保留 x 的前 b+1 字节的值,其余字节全部置0\n", 147 | " if x & sign_bit: # 检查 x 的符号位是否为1\n", 148 | " x = x | ~((1 << (8 * b)) - 1) # 将 x 的剩余部分全部置1\n", 149 | " self.stack.append(x)\n", 150 | " \n", 151 | " def lt(self):\n", 152 | " if len(self.stack) < 2:\n", 153 | " raise Exception('Stack underflow')\n", 154 | " a = self.stack.pop()\n", 155 | " b = self.stack.pop()\n", 156 | " self.stack.append(int(b < a)) # 注意这里的比较顺序\n", 157 | "\n", 158 | " def gt(self):\n", 159 | " if len(self.stack) < 2:\n", 160 | " raise Exception('Stack underflow')\n", 161 | " a = self.stack.pop()\n", 162 | " b = self.stack.pop()\n", 163 | " self.stack.append(int(b > a)) # 注意这里的比较顺序\n", 164 | "\n", 165 | " def slt(self):\n", 166 | " if len(self.stack) < 2:\n", 167 | " raise Exception('Stack underflow')\n", 168 | " a = self.stack.pop()\n", 169 | " b = self.stack.pop()\n", 170 | " self.stack.append(int(b < a)) # 极简evm stack中的值已经是以有符号整数存储了,所以和lt一样实现\n", 171 | "\n", 172 | " def sgt(self):\n", 173 | " if len(self.stack) < 2:\n", 174 | " raise Exception('Stack underflow')\n", 175 | " a = self.stack.pop()\n", 176 | " b = self.stack.pop()\n", 177 | " self.stack.append(int(b > a)) # 极简evm stack中的值已经是以有符号整数存储了,所以和gt一样实现\n", 178 | "\n", 179 | " def eq(self):\n", 180 | " if len(self.stack) < 2:\n", 181 | " raise Exception('Stack underflow')\n", 182 | " a = self.stack.pop()\n", 183 | " b = self.stack.pop()\n", 184 | " self.stack.append(int(a == b))\n", 185 | "\n", 186 | " def iszero(self):\n", 187 | " if len(self.stack) < 1:\n", 188 | " raise Exception('Stack underflow')\n", 189 | " a = self.stack.pop()\n", 190 | " self.stack.append(int(a == 0))\n", 191 | "\n", 192 | " def run(self):\n", 193 | " while self.pc < len(self.code):\n", 194 | " op = self.next_instruction()\n", 195 | "\n", 196 | " if PUSH1 <= op <= PUSH32: # 如果为PUSH1-PUSH32\n", 197 | " size = op - PUSH1 + 1\n", 198 | " self.push(size)\n", 199 | " elif op == PUSH0: # 如果为PUSH0\n", 200 | " self.stack.append(0)\n", 201 | " elif op == POP: # 如果为POP\n", 202 | " self.pop()\n", 203 | " elif op == ADD: # 处理ADD指令\n", 204 | " self.add()\n", 205 | " elif op == MUL: # 处理MUL指令\n", 206 | " self.mul()\n", 207 | " elif op == SUB: # 处理SUB指令\n", 208 | " self.sub()\n", 209 | " elif op == DIV: # 处理DIV指令\n", 210 | " self.div()\n", 211 | " elif op == SDIV:\n", 212 | " self.sdiv()\n", 213 | " elif op == MOD:\n", 214 | " self.mod()\n", 215 | " elif op == SMOD:\n", 216 | " self.smod()\n", 217 | " elif op == ADDMOD:\n", 218 | " self.addmod()\n", 219 | " elif op == MULMOD:\n", 220 | " self.mulmod()\n", 221 | " elif op == EXP:\n", 222 | " self.exp()\n", 223 | " elif op == SIGNEXTEND:\n", 224 | " self.signextend()\n", 225 | " elif op == LT:\n", 226 | " self.lt()\n", 227 | " elif op == GT:\n", 228 | " self.gt()\n", 229 | " elif op == SLT:\n", 230 | " self.slt()\n", 231 | " elif op == SGT:\n", 232 | " self.sgt()\n", 233 | " elif op == EQ:\n", 234 | " self.eq()\n", 235 | " elif op == ISZERO:\n", 236 | " self.iszero()\n", 237 | " else:\n", 238 | " raise Exception('Invalid opcode')\n" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": 5, 244 | "id": "3f377c2d", 245 | "metadata": {}, 246 | "outputs": [ 247 | { 248 | "name": "stdout", 249 | "output_type": "stream", 250 | "text": [ 251 | "[1]\n" 252 | ] 253 | } 254 | ], 255 | "source": [ 256 | "# LT\n", 257 | "code = b\"\\x60\\x02\\x60\\x03\\x10\"\n", 258 | "evm = EVM(code)\n", 259 | "evm.run()\n", 260 | "print(evm.stack)\n", 261 | "# output: [1]" 262 | ] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "execution_count": 6, 267 | "id": "7a70f0ea", 268 | "metadata": {}, 269 | "outputs": [ 270 | { 271 | "name": "stdout", 272 | "output_type": "stream", 273 | "text": [ 274 | "[0]\n" 275 | ] 276 | } 277 | ], 278 | "source": [ 279 | "# GT\n", 280 | "code = b\"\\x60\\x02\\x60\\x03\\x11\"\n", 281 | "evm = EVM(code)\n", 282 | "evm.run()\n", 283 | "print(evm.stack)\n", 284 | "# output: [0]" 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": 7, 290 | "id": "9a4d2684", 291 | "metadata": {}, 292 | "outputs": [ 293 | { 294 | "name": "stdout", 295 | "output_type": "stream", 296 | "text": [ 297 | "[0]\n" 298 | ] 299 | } 300 | ], 301 | "source": [ 302 | "# EQ\n", 303 | "code = b\"\\x60\\x02\\x60\\x03\\x14\"\n", 304 | "evm = EVM(code)\n", 305 | "evm.run()\n", 306 | "print(evm.stack)\n", 307 | "# output: [0]" 308 | ] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "execution_count": 8, 313 | "id": "448bbff5", 314 | "metadata": {}, 315 | "outputs": [ 316 | { 317 | "name": "stdout", 318 | "output_type": "stream", 319 | "text": [ 320 | "[1]\n" 321 | ] 322 | } 323 | ], 324 | "source": [ 325 | "# ISZERO\n", 326 | "code = b\"\\x60\\x00\\x15\"\n", 327 | "evm = EVM(code)\n", 328 | "evm.run()\n", 329 | "print(evm.stack)\n", 330 | "# output: [1]" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": null, 336 | "id": "bb5bd8f9", 337 | "metadata": {}, 338 | "outputs": [], 339 | "source": [] 340 | } 341 | ], 342 | "metadata": { 343 | "kernelspec": { 344 | "display_name": "Python 3 (ipykernel)", 345 | "language": "python", 346 | "name": "python3" 347 | }, 348 | "language_info": { 349 | "codemirror_mode": { 350 | "name": "ipython", 351 | "version": 3 352 | }, 353 | "file_extension": ".py", 354 | "mimetype": "text/x-python", 355 | "name": "python", 356 | "nbconvert_exporter": "python", 357 | "pygments_lexer": "ipython3", 358 | "version": "3.7.7" 359 | } 360 | }, 361 | "nbformat": 4, 362 | "nbformat_minor": 5 363 | } 364 | -------------------------------------------------------------------------------- /05_ComparisonOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 5. 比较指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们将介绍EVM中用于比较运算的6个指令,包括`LT`(小于),`GT`(大于),和`EQ`(相等)。并且,我们将在用Python写的极简版EVM中添加对他们的支持。 14 | 15 | ## LT (小于) 16 | 17 | `LT`指令从堆栈中弹出两个元素,比较第二个元素是否小于第一个元素。如果是,那么将`0`推入堆栈,否则将`1`推入堆栈。如果堆栈元素不足两个,那么会抛出异常。这个指令的操作码是`0x10`,gas消耗为`3`。 18 | 19 | 我们可以将`LT`指令的实现添加到我们的极简EVM中: 20 | 21 | ```python 22 | def lt(self): 23 | if len(self.stack) < 2: 24 | raise Exception('Stack underflow') 25 | a = self.stack.pop() 26 | b = self.stack.pop() 27 | self.stack.append(int(b < a)) # 注意这里的比较顺序 28 | ``` 29 | 30 | 我们在`run()`函数中添加对`LT`指令的处理: 31 | 32 | ```python 33 | def run(self): 34 | while self.pc < len(self.code): 35 | op = self.next_instruction() 36 | 37 | # ... 其他指令的处理 ... 38 | 39 | elif op == LT: # 处理LT指令 40 | self.lt() 41 | ``` 42 | 43 | 现在,我们可以尝试运行一个包含`LT`指令的字节码:`0x6002600310`(PUSH1 2 PUSH1 3 LT)。这个字节码将`2`和`3`推入堆栈,然后比较`2`是否小于`3`。 44 | 45 | ```python 46 | code = b"\x60\x02\x60\x03\x10" 47 | evm = EVM(code) 48 | evm.run() 49 | print(evm.stack) 50 | # output: [0] 51 | ``` 52 | 53 | ## GT (大于) 54 | 55 | `GT`指令和`LT`指令非常类似,不过它比较的是第二个元素是否大于第一个元素。如果是,那么将`0`推入堆栈,否则将`1`推入堆栈。如果堆栈元素不足两个,那么会抛出异常。这个指令的操作码是`0x10`,gas消耗为`3`。操作码是`0x11`,gas消耗为`3`。 56 | 57 | 我们将`GT`指令的实现添加到极简EVM: 58 | 59 | ```python 60 | def gt(self): 61 | if len(self.stack) < 2: 62 | raise Exception('Stack underflow') 63 | a = self.stack.pop() 64 | b = self.stack.pop() 65 | self.stack.append(int(b > a)) # 注意这里的比较顺序 66 | ``` 67 | 68 | 我们在`run()`函数中添加对`GT`指令的处理: 69 | 70 | ```python 71 | def run(self): 72 | while self.pc < len(self.code): 73 | op = self.next_instruction() 74 | 75 | # ... 其他指令的处理 ... 76 | 77 | elif op == GT: # 处理GT指令 78 | self.gt() 79 | ``` 80 | 81 | 现在,我们可以运行一个包含`GT`指令的字节码:`0x6002600311`(PUSH1 2 PUSH1 3 GT)。这个字节码将`2`和`3`推入堆栈,然后比较`2`是否大于`3`。 82 | 83 | ```python 84 | code = b"\x60\x02\x60\x03\x11" 85 | evm = EVM(code) 86 | evm.run() 87 | print(evm.stack) 88 | # output: [1] 89 | ``` 90 | 91 | ## EQ (等于) 92 | 93 | `EQ`指令从堆栈中弹出两个元素,如果两个元素相等,那么将`1`推入堆栈,否则将`0`推入堆栈。该指令的操作码是`0x14`,gas消耗为`3`。 94 | 95 | 我们将`EQ`指令的实现添加到极简EVM: 96 | 97 | ```python 98 | def eq(self): 99 | if len(self.stack) < 2: 100 | raise Exception('Stack underflow') 101 | a = self.stack.pop() 102 | b = self.stack.pop() 103 | self.stack.append(int(a == b)) 104 | ``` 105 | 106 | 我们在`run()`函数中添加对`EQ`指令的处理: 107 | 108 | ```python 109 | elif op == EQ: 110 | self.eq() 111 | ``` 112 | 113 | 现在,我们可以运行一个包含`EQ`指令的字节码:`0x6002600314`(PUSH1 2 PUSH1 3 EQ)。这个字节码将`2`和`3`推入堆栈,然后比较两者是否相等。 114 | 115 | ```python 116 | code = b"\x60\x02\x60\x03\x14" 117 | evm = EVM(code) 118 | evm.run() 119 | print(evm.stack) 120 | # output: [0] 121 | ``` 122 | 123 | ## ISZERO (是否为零) 124 | 125 | `ISZERO`指令从堆栈中弹出一个元素,如果元素为0,那么将`1`推入堆栈,否则将`0`推入堆栈。该指令的操作码是`0x15`,gas消耗为`3`。 126 | 127 | 我们将`ISZERO`指令的实现添加到极简EVM: 128 | 129 | ```python 130 | def iszero(self): 131 | if len(self.stack) < 1: 132 | raise Exception('Stack underflow') 133 | a = self.stack.pop() 134 | self.stack.append(int(a == 0)) 135 | ``` 136 | 137 | 我们在`run()`函数中添加对`ISZERO`指令的处理: 138 | 139 | ```python 140 | elif op == ISZERO: 141 | self.iszero() 142 | ``` 143 | 144 | 现在,我们可以运行一个包含`ISZERO`指令的字节码:`0x600015`(PUSH1 0 ISZERO)。这个字节码将`0`推入堆栈,然后检查其是否为0。 145 | 146 | ```python 147 | code = b"\x60\x00\x15" 148 | evm = EVM(code) 149 | evm.run() 150 | print(evm.stack) 151 | # output: [1] 152 | ``` 153 | 154 | ## 其他比较指令 155 | 156 | 1. **SLT (有符号小于)**: 这个指令会从堆栈中弹出两个元素,然后比较第二个元素是否小于第一个元素,结果以有符号整数形式返回。如果第二个元素小于第一个元素,将`0`推入堆栈,否则将`1`推入堆栈。它的操作码是`0x12`,gas消耗为`3`。 157 | 158 | ```python 159 | def slt(self): 160 | if len(self.stack) < 2: 161 | raise Exception('Stack underflow') 162 | a = self.stack.pop() 163 | b = self.stack.pop() 164 | self.stack.append(int(b < a)) # 极简evm stack中的值已经是以有符号整数存储了,所以和lt一样实现 165 | ``` 166 | 167 | 2. **SGT (有符号大于)**: 这个指令会从堆栈中弹出两个元素,然后比较第二个元素是否大于第一个元素,结果以有符号整数形式返回。如果第二个元素大于第一个元素,将`0`推入堆栈,否则将`1`推入堆栈。它的操作码是`0x13`,gas消耗为`3`。 168 | 169 | ```python 170 | def sgt(self): 171 | if len(self.stack) < 2: 172 | raise Exception('Stack underflow') 173 | a = self.stack.pop() 174 | b = self.stack.pop() 175 | self.stack.append(int(b > a)) # 极简evm stack中的值已经是以有符号整数存储了,所以和gt一样实现 176 | ``` 177 | 178 | ## 总结 179 | 180 | 这一讲,我们介绍了EVM中的6个比较指令,并在极简版EVM中添加了对他们的支持。课后习题: 写出`0x6003600414`对应的指令形式,并给出运行后的堆栈状态。 -------------------------------------------------------------------------------- /06_BitwiseOp/BitwiseOp.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 62, 6 | "id": "2b96b12e", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "ADD = 0x01\n", 11 | "MUL = 0x02\n", 12 | "SUB = 0x03\n", 13 | "DIV = 0x04\n", 14 | "SDIV = 0x05\n", 15 | "MOD = 0x06\n", 16 | "SMOD = 0x07\n", 17 | "ADDMOD = 0x08\n", 18 | "MULMOD = 0x09\n", 19 | "EXP = 0x0A\n", 20 | "SIGNEXTEND = 0x0B\n", 21 | "LT = 0x10\n", 22 | "GT = 0x11\n", 23 | "SLT = 0x12\n", 24 | "SGT = 0x13\n", 25 | "EQ = 0x14\n", 26 | "ISZERO = 0x15\n", 27 | "AND = 0x16\n", 28 | "OR = 0x17\n", 29 | "XOR = 0x18\n", 30 | "NOT = 0x19\n", 31 | "BYTE = 0x1A\n", 32 | "SHL = 0x1B\n", 33 | "SHR = 0x1C\n", 34 | "SAR = 0x1D\n", 35 | "PUSH0 = 0x5F\n", 36 | "PUSH1 = 0x60\n", 37 | "PUSH32 = 0x7F\n", 38 | "POP = 0x50\n", 39 | "\n", 40 | "class EVM:\n", 41 | " def __init__(self, code):\n", 42 | " self.code = code # 初始化字节码,bytes对象\n", 43 | " self.pc = 0 # 初始化程序计数器为0\n", 44 | " self.stack = [] # 堆栈初始为空\n", 45 | "\n", 46 | " def next_instruction(self):\n", 47 | " op = self.code[self.pc] # 获取当前指令\n", 48 | " self.pc += 1 # 递增\n", 49 | " return op\n", 50 | "\n", 51 | " def push(self, size):\n", 52 | " data = self.code[self.pc:self.pc + size] # 按照size从code中获取数据\n", 53 | " value = int.from_bytes(data, 'big') # 将bytes转换为int\n", 54 | " self.stack.append(value) # 压入堆栈\n", 55 | " self.pc += size # pc增加size单位\n", 56 | "\n", 57 | " def pop(self):\n", 58 | " if len(self.stack) == 0:\n", 59 | " raise Exception('Stack underflow')\n", 60 | " return self.stack.pop() # 弹出堆栈\n", 61 | "\n", 62 | " def sub(self):\n", 63 | " if len(self.stack) < 2:\n", 64 | " raise Exception('Stack underflow')\n", 65 | " a = self.stack.pop()\n", 66 | " b = self.stack.pop()\n", 67 | " res = (a - b) % (2**256) # 结果需要模2^256,防止溢出\n", 68 | " self.stack.append(res)\n", 69 | "\n", 70 | " def div(self):\n", 71 | " if len(self.stack) < 2:\n", 72 | " raise Exception('Stack underflow')\n", 73 | " a = self.stack.pop()\n", 74 | " b = self.stack.pop()\n", 75 | " if a == 0:\n", 76 | " res = 0\n", 77 | " else:\n", 78 | " res = (a // b) % (2**256)\n", 79 | " self.stack.append(res)\n", 80 | "\n", 81 | " def sdiv(self):\n", 82 | " if len(self.stack) < 2:\n", 83 | " raise Exception('Stack underflow')\n", 84 | " a = self.stack.pop()\n", 85 | " b = self.stack.pop()\n", 86 | " res = a//b % (2**256) if a!=0 else 0\n", 87 | " self.stack.append(res)\n", 88 | "\n", 89 | " def mod(self):\n", 90 | " if len(self.stack) < 2:\n", 91 | " raise Exception('Stack underflow')\n", 92 | " a = self.stack.pop()\n", 93 | " b = self.stack.pop()\n", 94 | " res = a % b if a != 0 else 0\n", 95 | " self.stack.append(res)\n", 96 | "\n", 97 | " def smod(self):\n", 98 | " if len(self.stack) < 2:\n", 99 | " raise Exception('Stack underflow')\n", 100 | " a = self.stack.pop()\n", 101 | " b = self.stack.pop()\n", 102 | " res = a % b if a != 0 else 0\n", 103 | " self.stack.append(res)\n", 104 | "\n", 105 | " def addmod(self):\n", 106 | " if len(self.stack) < 3:\n", 107 | " raise Exception('Stack underflow')\n", 108 | " a = self.stack.pop()\n", 109 | " b = self.stack.pop()\n", 110 | " n = self.stack.pop()\n", 111 | " res = (a + b) % n if n != 0 else 0\n", 112 | " self.stack.append(res)\n", 113 | "\n", 114 | " def mulmod(self):\n", 115 | " if len(self.stack) < 3:\n", 116 | " raise Exception('Stack underflow')\n", 117 | " a = self.stack.pop()\n", 118 | " b = self.stack.pop()\n", 119 | " n = self.stack.pop()\n", 120 | " res = (a * b) % n if n != 0 else 0\n", 121 | " self.stack.append(res)\n", 122 | "\n", 123 | " def exp(self):\n", 124 | " if len(self.stack) < 2:\n", 125 | " raise Exception('Stack underflow')\n", 126 | " a = self.stack.pop()\n", 127 | " b = self.stack.pop()\n", 128 | " res = pow(a, b) % (2**256)\n", 129 | " self.stack.append(res)\n", 130 | " \n", 131 | " def signextend(self):\n", 132 | " if len(self.stack) < 2:\n", 133 | " raise Exception('Stack underflow')\n", 134 | " b = self.stack.pop()\n", 135 | " x = self.stack.pop()\n", 136 | " if b < 32: # 如果b>=32,则不需要扩展\n", 137 | " sign_bit = 1 << (8 * b - 1) # b 字节的最高位(符号位)对应的掩码值,将用来检测 x 的符号位是否为1\n", 138 | " x = x & ((1 << (8 * b)) - 1) # 对 x 进行掩码操作,保留 x 的前 b+1 字节的值,其余字节全部置0\n", 139 | " if x & sign_bit: # 检查 x 的符号位是否为1\n", 140 | " x = x | ~((1 << (8 * b)) - 1) # 将 x 的剩余部分全部置1\n", 141 | " self.stack.append(x)\n", 142 | " \n", 143 | " def lt(self):\n", 144 | " if len(self.stack) < 2:\n", 145 | " raise Exception('Stack underflow')\n", 146 | " a = self.stack.pop()\n", 147 | " b = self.stack.pop()\n", 148 | " self.stack.append(int(b < a)) # 注意这里的比较顺序\n", 149 | "\n", 150 | " def gt(self):\n", 151 | " if len(self.stack) < 2:\n", 152 | " raise Exception('Stack underflow')\n", 153 | " a = self.stack.pop()\n", 154 | " b = self.stack.pop()\n", 155 | " self.stack.append(int(b > a)) # 注意这里的比较顺序\n", 156 | "\n", 157 | " def slt(self):\n", 158 | " if len(self.stack) < 2:\n", 159 | " raise Exception('Stack underflow')\n", 160 | " a = self.stack.pop()\n", 161 | " b = self.stack.pop()\n", 162 | " self.stack.append(int(b < a)) # 极简evm stack中的值已经是以有符号整数存储了,所以和lt一样实现\n", 163 | "\n", 164 | " def sgt(self):\n", 165 | " if len(self.stack) < 2:\n", 166 | " raise Exception('Stack underflow')\n", 167 | " a = self.stack.pop()\n", 168 | " b = self.stack.pop()\n", 169 | " self.stack.append(int(b > a)) # 极简evm stack中的值已经是以有符号整数存储了,所以和gt一样实现\n", 170 | "\n", 171 | " def eq(self):\n", 172 | " if len(self.stack) < 2:\n", 173 | " raise Exception('Stack underflow')\n", 174 | " a = self.stack.pop()\n", 175 | " b = self.stack.pop()\n", 176 | " self.stack.append(int(a == b))\n", 177 | "\n", 178 | " def iszero(self):\n", 179 | " if len(self.stack) < 1:\n", 180 | " raise Exception('Stack underflow')\n", 181 | " a = self.stack.pop()\n", 182 | " self.stack.append(int(a == 0))\n", 183 | "\n", 184 | " def and_op(self):\n", 185 | " if len(self.stack) < 2:\n", 186 | " raise Exception('Stack underflow')\n", 187 | " a = self.stack.pop()\n", 188 | " b = self.stack.pop()\n", 189 | " self.stack.append(a & b)\n", 190 | "\n", 191 | " def or_op(self):\n", 192 | " if len(self.stack) < 2:\n", 193 | " raise Exception('Stack underflow')\n", 194 | " a = self.stack.pop()\n", 195 | " b = self.stack.pop()\n", 196 | " self.stack.append(a | b)\n", 197 | "\n", 198 | " def xor_op(self):\n", 199 | " if len(self.stack) < 2:\n", 200 | " raise Exception('Stack underflow')\n", 201 | " a = self.stack.pop()\n", 202 | " b = self.stack.pop()\n", 203 | " self.stack.append(a ^ b)\n", 204 | "\n", 205 | " def not_op(self):\n", 206 | " if len(self.stack) < 1:\n", 207 | " raise Exception('Stack underflow')\n", 208 | " a = self.stack.pop()\n", 209 | " self.stack.append(~a % (2**256)) # 按位非操作的结果需要模2^256,防止溢出\n", 210 | "\n", 211 | " def byte_op(self):\n", 212 | " if len(self.stack) < 2:\n", 213 | " raise Exception('Stack underflow')\n", 214 | " position = self.stack.pop()\n", 215 | " value = self.stack.pop()\n", 216 | " if position >= 32:\n", 217 | " res = 0\n", 218 | " else:\n", 219 | " res = (value // pow(256, 31 - position)) & 0xFF\n", 220 | " self.stack.append(res)\n", 221 | "\n", 222 | " def shl(self):\n", 223 | " if len(self.stack) < 2:\n", 224 | " raise Exception('Stack underflow')\n", 225 | " a = self.stack.pop()\n", 226 | " b = self.stack.pop()\n", 227 | " self.stack.append((b << a) % (2**256)) # 左移位操作的结果需要模2^256\n", 228 | " \n", 229 | " def shr(self):\n", 230 | " if len(self.stack) < 2:\n", 231 | " raise Exception('Stack underflow')\n", 232 | " a = self.stack.pop()\n", 233 | " b = self.stack.pop()\n", 234 | " self.stack.append(b >> a) # 右移位操作\n", 235 | " \n", 236 | " def sar(self):\n", 237 | " if len(self.stack) < 2:\n", 238 | " raise Exception('Stack underflow')\n", 239 | " a = self.stack.pop()\n", 240 | " b = self.stack.pop()\n", 241 | " self.stack.append(b >> a) # 右移位操作\n", 242 | "\n", 243 | " def run(self):\n", 244 | " while self.pc < len(self.code):\n", 245 | " op = self.next_instruction()\n", 246 | "\n", 247 | " if PUSH1 <= op <= PUSH32: # 如果为PUSH1-PUSH32\n", 248 | " size = op - PUSH1 + 1\n", 249 | " self.push(size)\n", 250 | " elif op == PUSH0: # 如果为PUSH0\n", 251 | " self.stack.append(0)\n", 252 | " elif op == POP: # 如果为POP\n", 253 | " self.pop()\n", 254 | " elif op == ADD: # 处理ADD指令\n", 255 | " self.add()\n", 256 | " elif op == MUL: # 处理MUL指令\n", 257 | " self.mul()\n", 258 | " elif op == SUB: # 处理SUB指令\n", 259 | " self.sub()\n", 260 | " elif op == DIV: # 处理DIV指令\n", 261 | " self.div()\n", 262 | " elif op == SDIV:\n", 263 | " self.sdiv()\n", 264 | " elif op == MOD:\n", 265 | " self.mod()\n", 266 | " elif op == SMOD:\n", 267 | " self.smod()\n", 268 | " elif op == ADDMOD:\n", 269 | " self.addmod()\n", 270 | " elif op == MULMOD:\n", 271 | " self.mulmod()\n", 272 | " elif op == EXP:\n", 273 | " self.exp()\n", 274 | " elif op == SIGNEXTEND:\n", 275 | " self.signextend()\n", 276 | " elif op == LT:\n", 277 | " self.lt()\n", 278 | " elif op == GT:\n", 279 | " self.gt()\n", 280 | " elif op == SLT:\n", 281 | " self.slt()\n", 282 | " elif op == SGT:\n", 283 | " self.sgt()\n", 284 | " elif op == EQ:\n", 285 | " self.eq()\n", 286 | " elif op == ISZERO:\n", 287 | " self.iszero()\n", 288 | " elif op == AND: # 处理AND指令\n", 289 | " self.and_op()\n", 290 | " elif op == OR: # 处理AND指令\n", 291 | " self.or_op()\n", 292 | " elif op == XOR: # 处理AND指令\n", 293 | " self.xor_op()\n", 294 | " elif op == NOT: # 处理AND指令\n", 295 | " self.not_op()\n", 296 | " elif op == BYTE: # 处理AND指令\n", 297 | " self.byte_op()\n", 298 | " elif op == SHL: # 处理AND指令\n", 299 | " self.shl()\n", 300 | " elif op == SHR: # 处理AND指令\n", 301 | " self.shr()\n", 302 | " elif op == SAR: # 处理AND指令\n", 303 | " self.sar()\n", 304 | " else:\n", 305 | " raise Exception('Invalid opcode')\n" 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": 63, 311 | "id": "3f377c2d", 312 | "metadata": {}, 313 | "outputs": [ 314 | { 315 | "name": "stdout", 316 | "output_type": "stream", 317 | "text": [ 318 | "[2]\n" 319 | ] 320 | } 321 | ], 322 | "source": [ 323 | "# AND\n", 324 | "code = b\"\\x60\\x02\\x60\\x03\\x16\"\n", 325 | "evm = EVM(code)\n", 326 | "evm.run()\n", 327 | "print(evm.stack)\n", 328 | "# output: [2]" 329 | ] 330 | }, 331 | { 332 | "cell_type": "code", 333 | "execution_count": 64, 334 | "id": "7a70f0ea", 335 | "metadata": {}, 336 | "outputs": [ 337 | { 338 | "name": "stdout", 339 | "output_type": "stream", 340 | "text": [ 341 | "[3]\n" 342 | ] 343 | } 344 | ], 345 | "source": [ 346 | "# OR\n", 347 | "code = b\"\\x60\\x02\\x60\\x03\\x17\"\n", 348 | "evm = EVM(code)\n", 349 | "evm.run()\n", 350 | "print(evm.stack)\n", 351 | "# output: [3]" 352 | ] 353 | }, 354 | { 355 | "cell_type": "code", 356 | "execution_count": 65, 357 | "id": "9a4d2684", 358 | "metadata": {}, 359 | "outputs": [ 360 | { 361 | "name": "stdout", 362 | "output_type": "stream", 363 | "text": [ 364 | "[1]\n" 365 | ] 366 | } 367 | ], 368 | "source": [ 369 | "# XOR\n", 370 | "code = b\"\\x60\\x02\\x60\\x03\\x18\"\n", 371 | "evm = EVM(code)\n", 372 | "evm.run()\n", 373 | "print(evm.stack)\n", 374 | "# output: [1]" 375 | ] 376 | }, 377 | { 378 | "cell_type": "code", 379 | "execution_count": 66, 380 | "id": "448bbff5", 381 | "metadata": {}, 382 | "outputs": [ 383 | { 384 | "name": "stdout", 385 | "output_type": "stream", 386 | "text": [ 387 | "[115792089237316195423570985008687907853269984665640564039457584007913129639933]\n" 388 | ] 389 | } 390 | ], 391 | "source": [ 392 | "# NOT\n", 393 | "code = b\"\\x60\\x02\\x19\"\n", 394 | "evm = EVM(code)\n", 395 | "evm.run()\n", 396 | "print(evm.stack)\n", 397 | "# output: [很大的数] (0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd)" 398 | ] 399 | }, 400 | { 401 | "cell_type": "code", 402 | "execution_count": 67, 403 | "id": "bb5bd8f9", 404 | "metadata": {}, 405 | "outputs": [ 406 | { 407 | "name": "stdout", 408 | "output_type": "stream", 409 | "text": [ 410 | "[1]\n" 411 | ] 412 | } 413 | ], 414 | "source": [ 415 | "# BYTE\n", 416 | "code = b\"\\x60\\x01\\x60\\x1F\\x1A\"\n", 417 | "evm = EVM(code)\n", 418 | "evm.run()\n", 419 | "print(evm.stack)\n", 420 | "# output: [18] (取出 0x01 的第 31 个字节 => 0x01)\n" 421 | ] 422 | }, 423 | { 424 | "cell_type": "code", 425 | "execution_count": 68, 426 | "id": "228ff25f", 427 | "metadata": {}, 428 | "outputs": [ 429 | { 430 | "name": "stdout", 431 | "output_type": "stream", 432 | "text": [ 433 | "[16]\n" 434 | ] 435 | } 436 | ], 437 | "source": [ 438 | "# SHL\n", 439 | "code = b\"\\x60\\x02\\x60\\x03\\x1B\"\n", 440 | "evm = EVM(code)\n", 441 | "evm.run()\n", 442 | "print(evm.stack)\n", 443 | "# output: [16] (0x000000010 << 3 => 0x00010000)\n" 444 | ] 445 | }, 446 | { 447 | "cell_type": "code", 448 | "execution_count": 69, 449 | "id": "f1900e03", 450 | "metadata": {}, 451 | "outputs": [ 452 | { 453 | "name": "stdout", 454 | "output_type": "stream", 455 | "text": [ 456 | "[2]\n" 457 | ] 458 | } 459 | ], 460 | "source": [ 461 | "# SHR\n", 462 | "code = b\"\\x60\\x10\\x60\\x03\\x1C\"\n", 463 | "evm = EVM(code)\n", 464 | "evm.run()\n", 465 | "print(evm.stack)\n", 466 | "# output: [2] (0x00010000 >> 3 => 0x000000010)\n" 467 | ] 468 | }, 469 | { 470 | "cell_type": "code", 471 | "execution_count": null, 472 | "id": "19caeaad", 473 | "metadata": {}, 474 | "outputs": [], 475 | "source": [] 476 | } 477 | ], 478 | "metadata": { 479 | "kernelspec": { 480 | "display_name": "Python 3 (ipykernel)", 481 | "language": "python", 482 | "name": "python3" 483 | }, 484 | "language_info": { 485 | "codemirror_mode": { 486 | "name": "ipython", 487 | "version": 3 488 | }, 489 | "file_extension": ".py", 490 | "mimetype": "text/x-python", 491 | "name": "python", 492 | "nbconvert_exporter": "python", 493 | "pygments_lexer": "ipython3", 494 | "version": "3.7.7" 495 | } 496 | }, 497 | "nbformat": 4, 498 | "nbformat_minor": 5 499 | } 500 | -------------------------------------------------------------------------------- /06_BitwiseOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes 极简入门: 6. 位级指令 2 | 3 | 我最近在重新学以太坊 opcodes,也写一个“WTF EVM Opcodes 极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在 github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | --- 12 | 13 | 这一讲,我们将介绍 EVM 中用于位级运算的 8 个指令,包括`AND`(与),`OR`(或),和`XOR`(异或)。并且,我们将在用 Python 写的极简版 EVM 中添加对他们的支持。 14 | 15 | ## AND (与) 16 | 17 | `AND`指令从堆栈中弹出两个元素,对它们进行位与运算,并将结果推入堆栈。操作码是`0x16`,gas 消耗为`3`。 18 | 19 | 我们将`AND`指令的实现添加到我们的 EVM 模拟器中: 20 | 21 | ```python 22 | def and_op(self): 23 | if len(self.stack) < 2: 24 | raise Exception('Stack underflow') 25 | a = self.stack.pop() 26 | b = self.stack.pop() 27 | self.stack.append(a & b) 28 | ``` 29 | 30 | 我们在`run()`函数中添加对`AND`指令的处理: 31 | 32 | ```python 33 | def run(self): 34 | while self.pc < len(self.code): 35 | op = self.next_instruction() 36 | 37 | # ... 其他指令的处理 ... 38 | 39 | elif op == AND: # 处理AND指令 40 | self.and_op() 41 | ``` 42 | 43 | 现在,我们可以尝试运行一个包含`AND`指令的字节码:`0x6002600316`(PUSH1 2 PUSH1 3 AND)。这个字节码将`2`(0000 0010)和`3`(0000 0011)推入堆栈,然后进行位级与运算,结果应该为`2`(0000 0010)。 44 | 45 | ```python 46 | code = b"\x60\x02\x60\x03\x16" 47 | evm = EVM(code) 48 | evm.run() 49 | print(evm.stack) 50 | # output: [2] 51 | ``` 52 | 53 | ## OR (或) 54 | 55 | `OR`指令与`AND`指令类似,但执行的是位或运算。操作码是`0x17`,gas 消耗为`3`。 56 | 57 | 我们将`OR`指令的实现添加到 EVM 模拟器: 58 | 59 | ```python 60 | def or_op(self): 61 | if len(self.stack) < 2: 62 | raise Exception('Stack underflow') 63 | a = self.stack.pop() 64 | b = self.stack.pop() 65 | self.stack.append(a | b) 66 | ``` 67 | 68 | 我们在`run()`函数中添加对`OR`指令的处理: 69 | 70 | ```python 71 | def run(self): 72 | while self.pc < len(self.code): 73 | op = self.next_instruction() 74 | 75 | # ... 其他指令的处理 ... 76 | 77 | elif op == OR: # 处理OR指令 78 | self.or_op() 79 | ``` 80 | 81 | 现在,我们可以尝试运行一个包含`OR`指令的字节码:`0x6002600317`(PUSH1 2 PUSH1 3 OR)。这个字节码将`2`(0000 0010)和`3`(0000 0011)推入堆栈,然后进行位级与运算,结果应该为`3`(0000 0011)。 82 | 83 | ```python 84 | code = b"\x60\x02\x60\x03\x17" 85 | evm = EVM(code) 86 | evm.run() 87 | print(evm.stack) 88 | # output: [3] 89 | ``` 90 | 91 | ## XOR (异或) 92 | 93 | `XOR`指令与`AND`和`OR`指令类似,但执行的是异或运算。操作码是`0x18`,gas 消耗为`3`。 94 | 95 | 我们将`XOR`指令的实现添加到 EVM 模拟器: 96 | 97 | ```python 98 | def xor_op(self): 99 | if len(self.stack) < 2: 100 | raise Exception('Stack underflow') 101 | a = self.stack.pop() 102 | b = self.stack.pop() 103 | self.stack.append(a ^ b) 104 | ``` 105 | 106 | 我们在`run()`函数中添加对`XOR`指令的处理: 107 | 108 | ```python 109 | def run(self): 110 | while self.pc < len(self.code): 111 | op = self.next_instruction() 112 | 113 | # ... 其他指令的处理 ... 114 | 115 | elif op == XOR: # 处理XOR指令 116 | self.xor_op() 117 | ``` 118 | 119 | 现在,我们可以尝试运行一个包含`XOR`指令的字节码:`0x6002600318`(PUSH1 2 PUSH1 3 XOR)。这个字节码将`2`(0000 0010)和`3`(0000 0011)推入堆栈,然后进行位级与运算,结果应该为`1`(0000 0001)。 120 | 121 | ```python 122 | code = b"\x60\x02\x60\x03\x18" 123 | evm = EVM(code) 124 | evm.run() 125 | print(evm.stack) 126 | # output: [1] 127 | ``` 128 | 129 | ## NOT 130 | 131 | `NOT` 指令执行按位非操作,取栈顶元素的补码,然后将结果推回栈顶。它的操作码是`0x19`,gas 消耗为`3`。 132 | 133 | 我们将`NOT`指令的实现添加到 EVM 模拟器: 134 | 135 | ```python 136 | def not_op(self): 137 | if len(self.stack) < 1: 138 | raise Exception('Stack underflow') 139 | a = self.stack.pop() 140 | self.stack.append(~a % (2**256)) # 按位非操作的结果需要模2^256,防止溢出 141 | ``` 142 | 143 | 在`run()`函数中添加对`NOT`指令的处理: 144 | 145 | ```python 146 | elif op == NOT: # 处理NOT指令 147 | self.not_op() 148 | ``` 149 | 150 | 现在,我们可以尝试运行一个包含`NOT`指令的字节码:`0x600219`(PUSH1 2 NOT)。这个字节码将`2`(0000 0010)推入堆栈,然后进行位级非运算,结果应该为`很大的数`(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd)。 151 | 152 | ```python 153 | # NOT 154 | code = b"\x60\x02\x19" 155 | evm = EVM(code) 156 | evm.run() 157 | print(evm.stack) 158 | # output: [很大的数] (fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd) 159 | ``` 160 | 161 | ## SHL 162 | 163 | `SHL`指令执行左移位操作,从堆栈中弹出两个元素,将第二个元素左移第一个元素位数,然后将结果推回栈顶。它的操作码是`0x1B`,gas 消耗为`3`。 164 | 165 | 我们将`SHL`指令的实现添加到 EVM 模拟器: 166 | 167 | ```python 168 | def shl(self): 169 | if len(self.stack) < 2: 170 | raise Exception('Stack underflow') 171 | a = self.stack.pop() 172 | b = self.stack.pop() 173 | self.stack.append((b << a) % (2**256)) # 左移位操作的结果需要模2^256 174 | ``` 175 | 176 | 在`run()`函数中添加对`SHL`指令的处理: 177 | 178 | ```python 179 | elif op == SHL: # 处理SHL指令 180 | self.shl() 181 | ``` 182 | 183 | 现在,我们可以尝试运行一个包含`XOR`指令的字节码:`0x600260031B`(PUSH1 2 PUSH1 3 SHL)。这个字节码将`2`(0000 0010)和`3`(0000 0011)推入堆栈,然后将`2`左移`3`位,结果应该为`16`(0001 0000)。 184 | 185 | ```python 186 | code = b"\x60\x02\x60\x03\x1B" 187 | evm = EVM(code) 188 | evm.run() 189 | print(evm.stack) 190 | # output: [16] (0x000000010 << 3 => 0x00010000) 191 | ``` 192 | 193 | ## SHR 194 | 195 | `SHR`指令执行右移位操作,从堆栈中弹出两个元素,将第二个元素右移第一个元素位数,然后将结果推回栈顶。它的操作码是`0x1C`,gas 消耗为`3`。 196 | 197 | 我们将`SHR`指令的实现添加到 EVM 模拟器: 198 | 199 | ```python 200 | def shr(self): 201 | if len(self.stack) < 2: 202 | raise Exception('Stack underflow') 203 | a = self.stack.pop() 204 | b = self.stack.pop() 205 | self.stack.append(b >> a) # 右移位操作 206 | ``` 207 | 208 | 在`run()`函数中添加对`SHR`指令的处理: 209 | 210 | ```python 211 | elif op == SHR: # 处理SHR指令 212 | self.shr() 213 | ``` 214 | 215 | 现在,我们可以尝试运行一个包含`XOR`指令的字节码:`0x601060031C`(PUSH1 16 PUSH1 3 SHL)。这个字节码将`16`(0001 0000)和`3`(0000 0011)推入堆栈,然后将`16`右移`3`位,结果应该为`2`(0000 0010)。 216 | 217 | ```python 218 | code = b"\x60\x10\x60\x03\x1C" 219 | evm = EVM(code) 220 | evm.run() 221 | print(evm.stack) 222 | # output: [2] (0x00010000 >> 3 => 0x000000010) 223 | ``` 224 | 225 | ## 其他位级指令 226 | 227 | 1. **BYTE**: `BYTE`指令从堆栈中弹出两个元素(`a`和`b`),将第二个元素(`b`)看作一个`32字节`的数组,不够位数补 0,并返回该字节数组中从高位开始的第`a`个索引的字节,即(`b[31-a]`),并压入堆栈。如果索引`a`大于或等于 32,返回`0`,否则返回`b[31-a]`。 操作码是`0x1a`,gas 消耗为`3`。 228 | 229 | ```python 230 | def byte_op(self): 231 | if len(self.stack) < 2: 232 | raise Exception('Stack underflow') 233 | position = self.stack.pop() 234 | value = self.stack.pop() 235 | if position >= 32: 236 | res = 0 237 | else: 238 | res = (value // pow(256, 31 - position)) & 0xFF 239 | self.stack.append(res) 240 | ``` 241 | 242 | 2. **SAR**: `SAR`指令执行算术右移位操作,与`SHR`类似,但考虑符号位:如果我们对一个负数进行算术右移,那么在右移的过程中,最左侧(符号位)会被填充`F`以保持数字的负值。它从堆栈中弹出两个元素,将第二个元素以符号位填充的方式右移第一个元素位数,然后将结果推回栈顶。它的操作码是`0x1D`,gas 消耗为`3`。由于 Python 的`>>`操作符已经是算术右移,我们可以直接复用`shr`函数的代码。 243 | 244 | ```python 245 | def sar(self): 246 | if len(self.stack) < 2: 247 | raise Exception('Stack underflow') 248 | a = self.stack.pop() 249 | b = self.stack.pop() 250 | self.stack.append(b >> a) # 右移位操作 251 | ``` 252 | 253 | ## 总结 254 | 255 | 这一讲,我们介绍了 EVM 中的 8 个位级指令,并在极简版 EVM 中添加了对他们的支持。课后习题: 写出`0x6002600160011B1B`对应的指令形式,并给出运行后的堆栈状态。 256 | -------------------------------------------------------------------------------- /07_MemoryOp/MemoryOp.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 6, 6 | "id": "2b96b12e", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "ADD = 0x01\n", 11 | "MUL = 0x02\n", 12 | "SUB = 0x03\n", 13 | "DIV = 0x04\n", 14 | "SDIV = 0x05\n", 15 | "MOD = 0x06\n", 16 | "SMOD = 0x07\n", 17 | "ADDMOD = 0x08\n", 18 | "MULMOD = 0x09\n", 19 | "EXP = 0x0A\n", 20 | "SIGNEXTEND = 0x0B\n", 21 | "LT = 0x10\n", 22 | "GT = 0x11\n", 23 | "SLT = 0x12\n", 24 | "SGT = 0x13\n", 25 | "EQ = 0x14\n", 26 | "ISZERO = 0x15\n", 27 | "AND = 0x16\n", 28 | "OR = 0x17\n", 29 | "XOR = 0x18\n", 30 | "NOT = 0x19\n", 31 | "BYTE = 0x1A\n", 32 | "SHL = 0x1B\n", 33 | "SHR = 0x1C\n", 34 | "SAR = 0x1D\n", 35 | "PUSH0 = 0x5F\n", 36 | "PUSH1 = 0x60\n", 37 | "PUSH32 = 0x7F\n", 38 | "POP = 0x50\n", 39 | "MLOAD = 0x51\n", 40 | "MSTORE = 0x52\n", 41 | "MSTORE8 = 0x53\n", 42 | "MSIZE = 0x59\n", 43 | "class EVM:\n", 44 | " def __init__(self, code):\n", 45 | " self.code = code # 初始化字节码,bytes对象\n", 46 | " self.pc = 0 # 初始化程序计数器为0\n", 47 | " self.stack = [] # 堆栈初始为空\n", 48 | " self.memory = bytearray() # 内存初始化为空\n", 49 | "\n", 50 | " def next_instruction(self):\n", 51 | " op = self.code[self.pc] # 获取当前指令\n", 52 | " self.pc += 1 # 递增\n", 53 | " return op\n", 54 | "\n", 55 | " def push(self, size):\n", 56 | " data = self.code[self.pc:self.pc + size] # 按照size从code中获取数据\n", 57 | " value = int.from_bytes(data, 'big') # 将bytes转换为int\n", 58 | " self.stack.append(value) # 压入堆栈\n", 59 | " self.pc += size # pc增加size单位\n", 60 | "\n", 61 | " def pop(self):\n", 62 | " if len(self.stack) == 0:\n", 63 | " raise Exception('Stack underflow')\n", 64 | " return self.stack.pop() # 弹出堆栈\n", 65 | "\n", 66 | " def add(self):\n", 67 | " if len(self.stack) < 2:\n", 68 | " raise Exception('Stack underflow')\n", 69 | " a = self.stack.pop()\n", 70 | " b = self.stack.pop()\n", 71 | " res = (a + b) % (2**256) # 加法结果需要模2^256,防止溢出\n", 72 | " self.stack.append(res)\n", 73 | " \n", 74 | " def mul(self):\n", 75 | " if len(self.stack) < 2:\n", 76 | " raise Exception('Stack underflow')\n", 77 | " a = self.stack.pop()\n", 78 | " b = self.stack.pop()\n", 79 | " res = (a * b) % (2**256) # 乘法结果需要模2^256,防止溢出\n", 80 | " self.stack.append(res)\n", 81 | "\n", 82 | " def sub(self):\n", 83 | " if len(self.stack) < 2:\n", 84 | " raise Exception('Stack underflow')\n", 85 | " a = self.stack.pop()\n", 86 | " b = self.stack.pop()\n", 87 | " res = (a - b) % (2**256) # 结果需要模2^256,防止溢出\n", 88 | " self.stack.append(res)\n", 89 | "\n", 90 | " def div(self):\n", 91 | " if len(self.stack) < 2:\n", 92 | " raise Exception('Stack underflow')\n", 93 | " a = self.stack.pop()\n", 94 | " b = self.stack.pop()\n", 95 | " if a == 0:\n", 96 | " res = 0\n", 97 | " else:\n", 98 | " res = (a // b) % (2**256)\n", 99 | " self.stack.append(res)\n", 100 | "\n", 101 | " def sdiv(self):\n", 102 | " if len(self.stack) < 2:\n", 103 | " raise Exception('Stack underflow')\n", 104 | " a = self.stack.pop()\n", 105 | " b = self.stack.pop()\n", 106 | " res = a//b % (2**256) if a!=0 else 0\n", 107 | " self.stack.append(res)\n", 108 | "\n", 109 | " def mod(self):\n", 110 | " if len(self.stack) < 2:\n", 111 | " raise Exception('Stack underflow')\n", 112 | " a = self.stack.pop()\n", 113 | " b = self.stack.pop()\n", 114 | " res = a % b if a != 0 else 0\n", 115 | " self.stack.append(res)\n", 116 | "\n", 117 | " def smod(self):\n", 118 | " if len(self.stack) < 2:\n", 119 | " raise Exception('Stack underflow')\n", 120 | " a = self.stack.pop()\n", 121 | " b = self.stack.pop()\n", 122 | " res = a % b if a != 0 else 0\n", 123 | " self.stack.append(res)\n", 124 | "\n", 125 | " def addmod(self):\n", 126 | " if len(self.stack) < 3:\n", 127 | " raise Exception('Stack underflow')\n", 128 | " a = self.stack.pop()\n", 129 | " b = self.stack.pop()\n", 130 | " n = self.stack.pop()\n", 131 | " res = (a + b) % n if n != 0 else 0\n", 132 | " self.stack.append(res)\n", 133 | "\n", 134 | " def mulmod(self):\n", 135 | " if len(self.stack) < 3:\n", 136 | " raise Exception('Stack underflow')\n", 137 | " a = self.stack.pop()\n", 138 | " b = self.stack.pop()\n", 139 | " n = self.stack.pop()\n", 140 | " res = (a * b) % n if n != 0 else 0\n", 141 | " self.stack.append(res)\n", 142 | "\n", 143 | " def exp(self):\n", 144 | " if len(self.stack) < 2:\n", 145 | " raise Exception('Stack underflow')\n", 146 | " a = self.stack.pop()\n", 147 | " b = self.stack.pop()\n", 148 | " res = pow(a, b) % (2**256)\n", 149 | " self.stack.append(res)\n", 150 | "\n", 151 | " def signextend(self):\n", 152 | " if len(self.stack) < 2:\n", 153 | " raise Exception('Stack underflow')\n", 154 | " b = self.stack.pop()\n", 155 | " x = self.stack.pop()\n", 156 | " if b < 32: # 如果b>=32,则不需要扩展\n", 157 | " sign_bit = 1 << (8 * b - 1) # b 字节的最高位(符号位)对应的掩码值,将用来检测 x 的符号位是否为1\n", 158 | " x = x & ((1 << (8 * b)) - 1) # 对 x 进行掩码操作,保留 x 的前 b+1 字节的值,其余字节全部置0\n", 159 | " if x & sign_bit: # 检查 x 的符号位是否为1\n", 160 | " x = x | ~((1 << (8 * b)) - 1) # 将 x 的剩余部分全部置1\n", 161 | " self.stack.append(x)\n", 162 | " \n", 163 | " def lt(self):\n", 164 | " if len(self.stack) < 2:\n", 165 | " raise Exception('Stack underflow')\n", 166 | " a = self.stack.pop()\n", 167 | " b = self.stack.pop()\n", 168 | " self.stack.append(int(b < a)) # 注意这里的比较顺序\n", 169 | "\n", 170 | " def gt(self):\n", 171 | " if len(self.stack) < 2:\n", 172 | " raise Exception('Stack underflow')\n", 173 | " a = self.stack.pop()\n", 174 | " b = self.stack.pop()\n", 175 | " self.stack.append(int(b > a)) # 注意这里的比较顺序\n", 176 | "\n", 177 | " def slt(self):\n", 178 | " if len(self.stack) < 2:\n", 179 | " raise Exception('Stack underflow')\n", 180 | " a = self.stack.pop()\n", 181 | " b = self.stack.pop()\n", 182 | " self.stack.append(int(b < a)) # 极简evm stack中的值已经是以有符号整数存储了,所以和lt一样实现\n", 183 | "\n", 184 | " def sgt(self):\n", 185 | " if len(self.stack) < 2:\n", 186 | " raise Exception('Stack underflow')\n", 187 | " a = self.stack.pop()\n", 188 | " b = self.stack.pop()\n", 189 | " self.stack.append(int(b > a)) # 极简evm stack中的值已经是以有符号整数存储了,所以和gt一样实现\n", 190 | "\n", 191 | " def eq(self):\n", 192 | " if len(self.stack) < 2:\n", 193 | " raise Exception('Stack underflow')\n", 194 | " a = self.stack.pop()\n", 195 | " b = self.stack.pop()\n", 196 | " self.stack.append(int(a == b))\n", 197 | "\n", 198 | " def iszero(self):\n", 199 | " if len(self.stack) < 1:\n", 200 | " raise Exception('Stack underflow')\n", 201 | " a = self.stack.pop()\n", 202 | " self.stack.append(int(a == 0))\n", 203 | "\n", 204 | " def and_op(self):\n", 205 | " if len(self.stack) < 2:\n", 206 | " raise Exception('Stack underflow')\n", 207 | " a = self.stack.pop()\n", 208 | " b = self.stack.pop()\n", 209 | " self.stack.append(a & b)\n", 210 | "\n", 211 | " def or_op(self):\n", 212 | " if len(self.stack) < 2:\n", 213 | " raise Exception('Stack underflow')\n", 214 | " a = self.stack.pop()\n", 215 | " b = self.stack.pop()\n", 216 | " self.stack.append(a | b)\n", 217 | "\n", 218 | " def xor_op(self):\n", 219 | " if len(self.stack) < 2:\n", 220 | " raise Exception('Stack underflow')\n", 221 | " a = self.stack.pop()\n", 222 | " b = self.stack.pop()\n", 223 | " self.stack.append(a ^ b)\n", 224 | "\n", 225 | " def not_op(self):\n", 226 | " if len(self.stack) < 1:\n", 227 | " raise Exception('Stack underflow')\n", 228 | " a = self.stack.pop()\n", 229 | " self.stack.append(~a % (2**256)) # 按位非操作的结果需要模2^256,防止溢出\n", 230 | "\n", 231 | " def byte_op(self):\n", 232 | " if len(self.stack) < 2:\n", 233 | " raise Exception('Stack underflow')\n", 234 | " position = self.stack.pop()\n", 235 | " value = self.stack.pop()\n", 236 | " if position >= 32:\n", 237 | " res = 0\n", 238 | " else:\n", 239 | " res = (value // pow(256, 31 - position)) & 0xFF\n", 240 | " self.stack.append(res)\n", 241 | "\n", 242 | " def shl(self):\n", 243 | " if len(self.stack) < 2:\n", 244 | " raise Exception('Stack underflow')\n", 245 | " a = self.stack.pop()\n", 246 | " b = self.stack.pop()\n", 247 | " self.stack.append((b << a) % (2**256)) # 左移位操作的结果需要模2^256\n", 248 | " \n", 249 | " def shr(self):\n", 250 | " if len(self.stack) < 2:\n", 251 | " raise Exception('Stack underflow')\n", 252 | " a = self.stack.pop()\n", 253 | " b = self.stack.pop()\n", 254 | " self.stack.append(b >> a) # 右移位操作\n", 255 | " \n", 256 | " def sar(self):\n", 257 | " if len(self.stack) < 2:\n", 258 | " raise Exception('Stack underflow')\n", 259 | " a = self.stack.pop()\n", 260 | " b = self.stack.pop()\n", 261 | " self.stack.append(b >> a) # 右移位操作\n", 262 | "\n", 263 | " def mstore(self):\n", 264 | " if len(self.stack) < 2:\n", 265 | " raise Exception('Stack underflow')\n", 266 | " offset = self.stack.pop()\n", 267 | " value = self.stack.pop()\n", 268 | " while len(self.memory) < offset + 32:\n", 269 | " self.memory.append(0) # 内存扩展\n", 270 | " self.memory[offset:offset+32] = value.to_bytes(32, 'big')\n", 271 | "\n", 272 | " def mstore8(self):\n", 273 | " if len(self.stack) < 2:\n", 274 | " raise Exception('Stack underflow')\n", 275 | " offset = self.stack.pop()\n", 276 | " value = self.stack.pop()\n", 277 | " while len(self.memory) < offset + 32:\n", 278 | " self.memory.append(0) # 内存扩展\n", 279 | " self.memory[offset] = value & 0xFF # 取最低有效字节\n", 280 | "\n", 281 | " def mload(self):\n", 282 | " if len(self.stack) < 1:\n", 283 | " raise Exception('Stack underflow')\n", 284 | " offset = self.stack.pop()\n", 285 | " while len(self.memory) < offset + 32:\n", 286 | " self.memory.append(0) # 内存扩展\n", 287 | " value = int.from_bytes(self.memory[offset:offset+32], 'big')\n", 288 | " self.stack.append(value)\n", 289 | "\n", 290 | " def msize(self):\n", 291 | " self.stack.append(len(self.memory))\n", 292 | " \n", 293 | " def run(self):\n", 294 | " while self.pc < len(self.code):\n", 295 | " op = self.next_instruction()\n", 296 | "\n", 297 | " if PUSH1 <= op <= PUSH32: # 如果为PUSH1-PUSH32\n", 298 | " size = op - PUSH1 + 1\n", 299 | " self.push(size)\n", 300 | " elif op == PUSH0: # 如果为PUSH0\n", 301 | " self.stack.append(0)\n", 302 | " elif op == POP: # 如果为POP\n", 303 | " self.pop()\n", 304 | " elif op == ADD: # 处理ADD指令\n", 305 | " self.add()\n", 306 | " elif op == MUL: # 处理MUL指令\n", 307 | " self.mul()\n", 308 | " elif op == SUB: # 处理SUB指令\n", 309 | " self.sub()\n", 310 | " elif op == DIV: # 处理DIV指令\n", 311 | " self.div()\n", 312 | " elif op == SDIV:\n", 313 | " self.sdiv()\n", 314 | " elif op == MOD:\n", 315 | " self.mod()\n", 316 | " elif op == SMOD:\n", 317 | " self.smod()\n", 318 | " elif op == ADDMOD:\n", 319 | " self.addmod()\n", 320 | " elif op == MULMOD:\n", 321 | " self.mulmod()\n", 322 | " elif op == EXP:\n", 323 | " self.exp()\n", 324 | " elif op == SIGNEXTEND:\n", 325 | " self.signextend()\n", 326 | " elif op == LT:\n", 327 | " self.lt()\n", 328 | " elif op == GT:\n", 329 | " self.gt()\n", 330 | " elif op == SLT:\n", 331 | " self.slt()\n", 332 | " elif op == SGT:\n", 333 | " self.sgt()\n", 334 | " elif op == EQ:\n", 335 | " self.eq()\n", 336 | " elif op == ISZERO:\n", 337 | " self.iszero()\n", 338 | " elif op == AND: # 处理AND指令\n", 339 | " self.and_op()\n", 340 | " elif op == OR: # 处理AND指令\n", 341 | " self.or_op()\n", 342 | " elif op == XOR: # 处理AND指令\n", 343 | " self.xor_op()\n", 344 | " elif op == NOT: # 处理AND指令\n", 345 | " self.not_op()\n", 346 | " elif op == BYTE: # 处理AND指令\n", 347 | " self.byte_op()\n", 348 | " elif op == SHL: # 处理AND指令\n", 349 | " self.shl()\n", 350 | " elif op == SHR: # 处理AND指令\n", 351 | " self.shr()\n", 352 | " elif op == SAR: # 处理AND指令\n", 353 | " self.sar()\n", 354 | " elif op == MLOAD: # 处理MLOAD指令\n", 355 | " self.mload()\n", 356 | " elif op == MSTORE: # 处理MSTORE指令\n", 357 | " self.mstore()\n", 358 | " elif op == MSTORE8: # 处理MSTORE8指令\n", 359 | " self.mstore8()\n", 360 | " elif op == MSIZE: # 处理MSIZE指令\n", 361 | " self.msize()\n", 362 | " else:\n", 363 | " raise Exception('Invalid opcode')\n" 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": 9, 369 | "id": "3f377c2d", 370 | "metadata": {}, 371 | "outputs": [ 372 | { 373 | "name": "stdout", 374 | "output_type": "stream", 375 | "text": [ 376 | "bytearray(b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02')\n" 377 | ] 378 | } 379 | ], 380 | "source": [ 381 | "# MSTORE\n", 382 | "code = b\"\\x60\\x02\\x60\\x20\\x52\"\n", 383 | "evm = EVM(code)\n", 384 | "evm.run()\n", 385 | "print(evm.memory[0x20:0x40]) \n", 386 | "# 输出: [0, 0, 0, ..., 0, 2]" 387 | ] 388 | }, 389 | { 390 | "cell_type": "code", 391 | "execution_count": 13, 392 | "id": "7a70f0ea", 393 | "metadata": {}, 394 | "outputs": [ 395 | { 396 | "name": "stdout", 397 | "output_type": "stream", 398 | "text": [ 399 | "bytearray(b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02')\n", 400 | "[2]\n" 401 | ] 402 | } 403 | ], 404 | "source": [ 405 | "# MLOAD\n", 406 | "code = b\"\\x60\\x02\\x60\\x20\\x52\\x60\\x20\\x51\"\n", 407 | "evm = EVM(code)\n", 408 | "evm.run()\n", 409 | "print(evm.memory[0x20:0x40]) \n", 410 | "# 输出: [0, 0, 0, ..., 0, 2]\n", 411 | "print(evm.stack) \n", 412 | "# output: [2]" 413 | ] 414 | }, 415 | { 416 | "cell_type": "code", 417 | "execution_count": null, 418 | "id": "19caeaad", 419 | "metadata": {}, 420 | "outputs": [], 421 | "source": [] 422 | } 423 | ], 424 | "metadata": { 425 | "kernelspec": { 426 | "display_name": "Python 3 (ipykernel)", 427 | "language": "python", 428 | "name": "python3" 429 | }, 430 | "language_info": { 431 | "codemirror_mode": { 432 | "name": "ipython", 433 | "version": 3 434 | }, 435 | "file_extension": ".py", 436 | "mimetype": "text/x-python", 437 | "name": "python", 438 | "nbconvert_exporter": "python", 439 | "pygments_lexer": "ipython3", 440 | "version": "3.7.7" 441 | } 442 | }, 443 | "nbformat": 4, 444 | "nbformat_minor": 5 445 | } 446 | -------------------------------------------------------------------------------- /07_MemoryOp/img/7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/07_MemoryOp/img/7-1.png -------------------------------------------------------------------------------- /07_MemoryOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 7. 内存指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 在这一讲,我们将介绍EVM中用于内存(Memory)操作的4个指令,包括`MSTORE`,`MSTORE8`,`MLOAD`,和`MSIZE`。我们将在用Python写的极简版EVM中添加对这些操作的支持。 14 | 15 | ## EVM中的内存 16 | 17 | 我们在[第一讲](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/01_HelloOpcodes/readme.md)介绍了EVM的内存,它是一个线性寻址存储器,类似一个动态的字节数组,可以根据需求动态扩展。它的另一个特点就是易失性,交易结束时所有数据都会被清零。它支持以8或256 bit写入(`MSTORE8`/`MSTORE`),但只支持以256 bit取(`MLOAD`)。 18 | 19 | ![](./img/7-1.png) 20 | 21 | 我们可以用Python内置的`bytearray`来代表内存: 22 | 23 | ```python 24 | def __init__(self, code): 25 | self.code = code 26 | self.pc = 0 27 | self.stack = [] 28 | self.memory = bytearray() # 内存初始化为空 29 | ``` 30 | 31 | 内存的读写比存储(Storage)的读写要便宜的多,每次读写有固定费用3 gas,另外如果首次访问了新的内存位置(内存拓展),则需要付额外的费用(由当前偏移量和历史最大偏移量决定),计算方法见[链接](https://www.evm.codes/about#accesssets)。 32 | 33 | ## MSTORE (内存写) 34 | 35 | `MSTORE`指令用于将一个256位(32字节)的值存储到内存中。它从堆栈中弹出两个元素,第一个元素为内存的地址(偏移量 offset),第二个元素为存储的值(value)。操作码是`0x52`,gas消耗根据实际内存使用情况计算(3+X)。 36 | 37 | ```python 38 | def mstore(self): 39 | if len(self.stack) < 2: 40 | raise Exception('Stack underflow') 41 | offset = self.stack.pop() 42 | value = self.stack.pop() 43 | while len(self.memory) < offset + 32: 44 | self.memory.append(0) # 内存扩展 45 | self.memory[offset:offset+32] = value.to_bytes(32, 'big') 46 | ``` 47 | 48 | 我们在`run()`函数中添加对`MSTORE`指令的处理: 49 | 50 | ```python 51 | def run(self): 52 | while self.pc < len(self.code): 53 | op = self.next_instruction() 54 | 55 | # ... 其他指令的处理 ... 56 | 57 | elif op == MSTORE: # 处理MSTORE指令 58 | self.mstore() 59 | ``` 60 | 61 | 现在,我们可以尝试运行一个包含`MSTORE`指令的字节码:`0x6002602052`(PUSH1 2 PUSH1 0x20 MSTORE)。这个字节码将`2`和`0x20`(32)推入堆栈,然后进行`MSTORE`,将`2`存到偏移量为`0x20`的地方。 62 | 63 | ```python 64 | # MSTORE 65 | code = b"\x60\x02\x60\x20\x52" 66 | evm = EVM(code) 67 | evm.run() 68 | print(evm.memory[0x20:0x40]) 69 | # 输出: [0, 0, 0, ..., 0, 2] 70 | ``` 71 | 72 | ## MSTORE8 (内存8位写) 73 | 74 | `MSTORE8`指令用于将一个8位(1字节)的值存储到内存中。与`MSTORE`类似,但只使用最低8位。操作码是`0x53`,gas消耗根据实际内存使用情况计算(3+X)。 75 | 76 | ```python 77 | def mstore8(self): 78 | if len(self.stack) < 2: 79 | raise Exception('Stack underflow') 80 | offset = self.stack.pop() 81 | value = self.stack.pop() 82 | while len(self.memory) < offset + 32: 83 | self.memory.append(0) # 内存扩展 84 | self.memory[offset] = value & 0xFF # 取最低有效字节 85 | ``` 86 | 87 | 我们在`run()`函数中添加对`MSTORE8`指令的处理: 88 | 89 | ```python 90 | elif op == MSTORE8: # 处理MSTORE8指令 91 | self.mstore8() 92 | ``` 93 | 94 | 现在,我们可以尝试运行一个包含`MSTORE8`指令的字节码:`0x6002602053`(PUSH1 2 PUSH1 0x20 MSTORE8)。这个字节码将`2`和`0x20`(32)推入堆栈,然后进行`MSTORE8`,将`2`存到偏移量为`0x20`的地方。 95 | 96 | ```python 97 | # MSTORE8 98 | code = b"\x60\x02\x60\x20\x53" 99 | evm = EVM(code) 100 | evm.run() 101 | print(evm.memory[0x20:0x40]) 102 | # 输出: [2, 0, 0, ..., 0, 0] 103 | ``` 104 | 105 | ## MLOAD (内存读) 106 | 107 | `MLOAD`指令从内存中加载一个256位的值并推入堆栈。它从堆栈中弹出一个元素,从该元素表示的内存地址中加载32字节,并将其推入堆栈。操作码是`0x51`,gas消耗根据实际内存使用情况计算(3+X)。 108 | 109 | ```python 110 | def mload(self): 111 | if len(self.stack) < 1: 112 | raise Exception('Stack underflow') 113 | offset = self.stack.pop() 114 | while len(self.memory) < offset + 32: 115 | self.memory.append(0) # 内存扩展 116 | value = int.from_bytes(self.memory[offset:offset+32], 'big') 117 | self.stack.append(value) 118 | ``` 119 | 120 | 我们在`run()`函数中添加对`MLOAD`指令的处理: 121 | 122 | ```python 123 | elif op == MLOAD: 124 | self.mload() 125 | ``` 126 | 127 | 现在,我们可以尝试运行一个包含`MLOAD`指令的字节码:`0x6002602052602051`(PUSH1 2 PUSH1 0x20 MSTORE PUSH1 0x20 MLOAD)。这个字节码将`2`和`0x20`(32)推入堆栈,然后进行`MSTORE`,将`2`存到偏移量为`0x20`的地方;然后将`0x20`推入堆栈,然后进行`MLOAD`,将刚才存储在内存的值读取出来。 128 | 129 | ```python 130 | # MSTORE 131 | code = b"\x60\x02\x60\x20\x52\x60\x20\x51" 132 | evm = EVM(code) 133 | evm.run() 134 | print(evm.memory[0x20:0x40]) 135 | # 输出: [0, 0, 0, ..., 0, 2] 136 | print(evm.stack) 137 | # output: [2] 138 | ``` 139 | 140 | ## MSIZE (内存大小) 141 | 142 | `MSIZE`指令将当前的内存大小(以字节为单位)压入堆栈。操作码是`0x59`,gas消耗为2。 143 | 144 | ```python 145 | def msize(self): 146 | self.stack.append(len(self.memory)) 147 | ``` 148 | 149 | ## 总结 150 | 151 | 这一讲,我们介绍了EVM中的内存操作指令,并在极简版EVM中添加了对它们的支持。这些操作允许我们在EVM的内存中存储和读取值,为更复杂的合约逻辑提供基础。 152 | 153 | 课后习题: 写出`0x6002602053602051`对应的指令形式,并给出运行后的堆栈和内存状态。 154 | -------------------------------------------------------------------------------- /08_StorageOp/StorageOp.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 15, 6 | "id": "2b96b12e", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "ADD = 0x01\n", 11 | "MUL = 0x02\n", 12 | "SUB = 0x03\n", 13 | "DIV = 0x04\n", 14 | "SDIV = 0x05\n", 15 | "MOD = 0x06\n", 16 | "SMOD = 0x07\n", 17 | "ADDMOD = 0x08\n", 18 | "MULMOD = 0x09\n", 19 | "EXP = 0x0A\n", 20 | "SIGNEXTEND = 0x0B\n", 21 | "LT = 0x10\n", 22 | "GT = 0x11\n", 23 | "SLT = 0x12\n", 24 | "SGT = 0x13\n", 25 | "EQ = 0x14\n", 26 | "ISZERO = 0x15\n", 27 | "AND = 0x16\n", 28 | "OR = 0x17\n", 29 | "XOR = 0x18\n", 30 | "NOT = 0x19\n", 31 | "BYTE = 0x1A\n", 32 | "SHL = 0x1B\n", 33 | "SHR = 0x1C\n", 34 | "SAR = 0x1D\n", 35 | "PUSH0 = 0x5F\n", 36 | "PUSH1 = 0x60\n", 37 | "PUSH32 = 0x7F\n", 38 | "POP = 0x50\n", 39 | "MLOAD = 0x51\n", 40 | "MSTORE = 0x52\n", 41 | "MSTORE8 = 0x53\n", 42 | "SLOAD = 0x54\n", 43 | "SSTORE = 0x55\n", 44 | "MSIZE = 0x59\n", 45 | "\n", 46 | "class EVM:\n", 47 | " def __init__(self, code):\n", 48 | " self.code = code # 初始化字节码,bytes对象\n", 49 | " self.pc = 0 # 初始化程序计数器为0\n", 50 | " self.stack = [] # 堆栈初始为空\n", 51 | " self.memory = bytearray() # 内存初始化为空\n", 52 | " self.storage = {} # 存储初始化为空字典\n", 53 | "\n", 54 | " def next_instruction(self):\n", 55 | " op = self.code[self.pc] # 获取当前指令\n", 56 | " self.pc += 1 # 递增\n", 57 | " return op\n", 58 | "\n", 59 | " def push(self, size):\n", 60 | " data = self.code[self.pc:self.pc + size] # 按照size从code中获取数据\n", 61 | " value = int.from_bytes(data, 'big') # 将bytes转换为int\n", 62 | " self.stack.append(value) # 压入堆栈\n", 63 | " self.pc += size # pc增加size单位\n", 64 | "\n", 65 | " def pop(self):\n", 66 | " if len(self.stack) == 0:\n", 67 | " raise Exception('Stack underflow')\n", 68 | " return self.stack.pop() # 弹出堆栈\n", 69 | "\n", 70 | " def add(self):\n", 71 | " if len(self.stack) < 2:\n", 72 | " raise Exception('Stack underflow')\n", 73 | " a = self.stack.pop()\n", 74 | " b = self.stack.pop()\n", 75 | " res = (a + b) % (2**256) # 加法结果需要模2^256,防止溢出\n", 76 | " self.stack.append(res)\n", 77 | " \n", 78 | " def mul(self):\n", 79 | " if len(self.stack) < 2:\n", 80 | " raise Exception('Stack underflow')\n", 81 | " a = self.stack.pop()\n", 82 | " b = self.stack.pop()\n", 83 | " res = (a * b) % (2**256) # 乘法结果需要模2^256,防止溢出\n", 84 | " self.stack.append(res)\n", 85 | "\n", 86 | " def sub(self):\n", 87 | " if len(self.stack) < 2:\n", 88 | " raise Exception('Stack underflow')\n", 89 | " a = self.stack.pop()\n", 90 | " b = self.stack.pop()\n", 91 | " res = (a - b) % (2**256) # 结果需要模2^256,防止溢出\n", 92 | " self.stack.append(res)\n", 93 | "\n", 94 | " def div(self):\n", 95 | " if len(self.stack) < 2:\n", 96 | " raise Exception('Stack underflow')\n", 97 | " a = self.stack.pop()\n", 98 | " b = self.stack.pop()\n", 99 | " if a == 0:\n", 100 | " res = 0\n", 101 | " else:\n", 102 | " res = (a // b) % (2**256)\n", 103 | " self.stack.append(res)\n", 104 | "\n", 105 | " def sdiv(self):\n", 106 | " if len(self.stack) < 2:\n", 107 | " raise Exception('Stack underflow')\n", 108 | " a = self.stack.pop()\n", 109 | " b = self.stack.pop()\n", 110 | " res = a//b % (2**256) if a!=0 else 0\n", 111 | " self.stack.append(res)\n", 112 | "\n", 113 | " def mod(self):\n", 114 | " if len(self.stack) < 2:\n", 115 | " raise Exception('Stack underflow')\n", 116 | " a = self.stack.pop()\n", 117 | " b = self.stack.pop()\n", 118 | " res = a % b if a != 0 else 0\n", 119 | " self.stack.append(res)\n", 120 | "\n", 121 | " def smod(self):\n", 122 | " if len(self.stack) < 2:\n", 123 | " raise Exception('Stack underflow')\n", 124 | " a = self.stack.pop()\n", 125 | " b = self.stack.pop()\n", 126 | " res = a % b if a != 0 else 0\n", 127 | " self.stack.append(res)\n", 128 | "\n", 129 | " def addmod(self):\n", 130 | " if len(self.stack) < 3:\n", 131 | " raise Exception('Stack underflow')\n", 132 | " a = self.stack.pop()\n", 133 | " b = self.stack.pop()\n", 134 | " n = self.stack.pop()\n", 135 | " res = (a + b) % n if n != 0 else 0\n", 136 | " self.stack.append(res)\n", 137 | "\n", 138 | " def mulmod(self):\n", 139 | " if len(self.stack) < 3:\n", 140 | " raise Exception('Stack underflow')\n", 141 | " a = self.stack.pop()\n", 142 | " b = self.stack.pop()\n", 143 | " n = self.stack.pop()\n", 144 | " res = (a * b) % n if n != 0 else 0\n", 145 | " self.stack.append(res)\n", 146 | "\n", 147 | " def exp(self):\n", 148 | " if len(self.stack) < 2:\n", 149 | " raise Exception('Stack underflow')\n", 150 | " a = self.stack.pop()\n", 151 | " b = self.stack.pop()\n", 152 | " res = pow(a, b) % (2**256)\n", 153 | " self.stack.append(res)\n", 154 | " \n", 155 | " def signextend(self):\n", 156 | " if len(self.stack) < 2:\n", 157 | " raise Exception('Stack underflow')\n", 158 | " b = self.stack.pop()\n", 159 | " x = self.stack.pop()\n", 160 | " if b < 32: # 如果b>=32,则不需要扩展\n", 161 | " sign_bit = 1 << (8 * b - 1) # b 字节的最高位(符号位)对应的掩码值,将用来检测 x 的符号位是否为1\n", 162 | " x = x & ((1 << (8 * b)) - 1) # 对 x 进行掩码操作,保留 x 的前 b+1 字节的值,其余字节全部置0\n", 163 | " if x & sign_bit: # 检查 x 的符号位是否为1\n", 164 | " x = x | ~((1 << (8 * b)) - 1) # 将 x 的剩余部分全部置1\n", 165 | " self.stack.append(x)\n", 166 | " \n", 167 | " def lt(self):\n", 168 | " if len(self.stack) < 2:\n", 169 | " raise Exception('Stack underflow')\n", 170 | " a = self.stack.pop()\n", 171 | " b = self.stack.pop()\n", 172 | " self.stack.append(int(b < a)) # 注意这里的比较顺序\n", 173 | "\n", 174 | " def gt(self):\n", 175 | " if len(self.stack) < 2:\n", 176 | " raise Exception('Stack underflow')\n", 177 | " a = self.stack.pop()\n", 178 | " b = self.stack.pop()\n", 179 | " self.stack.append(int(b > a)) # 注意这里的比较顺序\n", 180 | "\n", 181 | " def slt(self):\n", 182 | " if len(self.stack) < 2:\n", 183 | " raise Exception('Stack underflow')\n", 184 | " a = self.stack.pop()\n", 185 | " b = self.stack.pop()\n", 186 | " self.stack.append(int(b < a)) # 极简evm stack中的值已经是以有符号整数存储了,所以和lt一样实现\n", 187 | "\n", 188 | " def sgt(self):\n", 189 | " if len(self.stack) < 2:\n", 190 | " raise Exception('Stack underflow')\n", 191 | " a = self.stack.pop()\n", 192 | " b = self.stack.pop()\n", 193 | " self.stack.append(int(b > a)) # 极简evm stack中的值已经是以有符号整数存储了,所以和gt一样实现\n", 194 | "\n", 195 | " def eq(self):\n", 196 | " if len(self.stack) < 2:\n", 197 | " raise Exception('Stack underflow')\n", 198 | " a = self.stack.pop()\n", 199 | " b = self.stack.pop()\n", 200 | " self.stack.append(int(a == b))\n", 201 | "\n", 202 | " def iszero(self):\n", 203 | " if len(self.stack) < 1:\n", 204 | " raise Exception('Stack underflow')\n", 205 | " a = self.stack.pop()\n", 206 | " self.stack.append(int(a == 0))\n", 207 | "\n", 208 | " def and_op(self):\n", 209 | " if len(self.stack) < 2:\n", 210 | " raise Exception('Stack underflow')\n", 211 | " a = self.stack.pop()\n", 212 | " b = self.stack.pop()\n", 213 | " self.stack.append(a & b)\n", 214 | "\n", 215 | " def or_op(self):\n", 216 | " if len(self.stack) < 2:\n", 217 | " raise Exception('Stack underflow')\n", 218 | " a = self.stack.pop()\n", 219 | " b = self.stack.pop()\n", 220 | " self.stack.append(a | b)\n", 221 | "\n", 222 | " def xor_op(self):\n", 223 | " if len(self.stack) < 2:\n", 224 | " raise Exception('Stack underflow')\n", 225 | " a = self.stack.pop()\n", 226 | " b = self.stack.pop()\n", 227 | " self.stack.append(a ^ b)\n", 228 | "\n", 229 | " def not_op(self):\n", 230 | " if len(self.stack) < 1:\n", 231 | " raise Exception('Stack underflow')\n", 232 | " a = self.stack.pop()\n", 233 | " self.stack.append(~a % (2**256)) # 按位非操作的结果需要模2^256,防止溢出\n", 234 | "\n", 235 | " def byte_op(self):\n", 236 | " if len(self.stack) < 2:\n", 237 | " raise Exception('Stack underflow')\n", 238 | " position = self.stack.pop()\n", 239 | " value = self.stack.pop()\n", 240 | " if position >= 32:\n", 241 | " res = 0\n", 242 | " else:\n", 243 | " res = (value // pow(256, 31 - position)) & 0xFF\n", 244 | " self.stack.append(res)\n", 245 | "\n", 246 | " def shl(self):\n", 247 | " if len(self.stack) < 2:\n", 248 | " raise Exception('Stack underflow')\n", 249 | " a = self.stack.pop()\n", 250 | " b = self.stack.pop()\n", 251 | " self.stack.append((b << a) % (2**256)) # 左移位操作的结果需要模2^256\n", 252 | " \n", 253 | " def shr(self):\n", 254 | " if len(self.stack) < 2:\n", 255 | " raise Exception('Stack underflow')\n", 256 | " a = self.stack.pop()\n", 257 | " b = self.stack.pop()\n", 258 | " self.stack.append(b >> a) # 右移位操作\n", 259 | " \n", 260 | " def sar(self):\n", 261 | " if len(self.stack) < 2:\n", 262 | " raise Exception('Stack underflow')\n", 263 | " a = self.stack.pop()\n", 264 | " b = self.stack.pop()\n", 265 | " self.stack.append(b >> a) # 右移位操作\n", 266 | "\n", 267 | " def mstore(self):\n", 268 | " if len(self.stack) < 2:\n", 269 | " raise Exception('Stack underflow')\n", 270 | " offset = self.stack.pop()\n", 271 | " value = self.stack.pop()\n", 272 | " while len(self.memory) < offset + 32:\n", 273 | " self.memory.append(0) # 内存扩展\n", 274 | " self.memory[offset:offset+32] = value.to_bytes(32, 'big')\n", 275 | "\n", 276 | " def mstore8(self):\n", 277 | " if len(self.stack) < 2:\n", 278 | " raise Exception('Stack underflow')\n", 279 | " offset = self.stack.pop()\n", 280 | " value = self.stack.pop()\n", 281 | " while len(self.memory) < offset + 32:\n", 282 | " self.memory.append(0) # 内存扩展\n", 283 | " self.memory[offset] = value & 0xFF # 取最低有效字节\n", 284 | "\n", 285 | " def mload(self):\n", 286 | " if len(self.stack) < 1:\n", 287 | " raise Exception('Stack underflow')\n", 288 | " offset = self.stack.pop()\n", 289 | " while len(self.memory) < offset + 32:\n", 290 | " self.memory.append(0) # 内存扩展\n", 291 | " value = int.from_bytes(self.memory[offset:offset+32], 'big')\n", 292 | " self.stack.append(value)\n", 293 | "\n", 294 | " def sload(self):\n", 295 | " if len(self.stack) < 1:\n", 296 | " raise Exception('Stack underflow')\n", 297 | " key = self.stack.pop()\n", 298 | " value = self.storage.get(key, 0) # 如果键不存在,返回0\n", 299 | " self.stack.append(value)\n", 300 | "\n", 301 | " def sstore(self):\n", 302 | " if len(self.stack) < 2:\n", 303 | " raise Exception('Stack underflow')\n", 304 | " key = self.stack.pop()\n", 305 | " value = self.stack.pop()\n", 306 | " self.storage[key] = value\n", 307 | "\n", 308 | " def msize(self):\n", 309 | " self.stack.append(len(self.memory))\n", 310 | "\n", 311 | " def run(self):\n", 312 | " while self.pc < len(self.code):\n", 313 | " op = self.next_instruction()\n", 314 | "\n", 315 | " if PUSH1 <= op <= PUSH32: # 如果为PUSH1-PUSH32\n", 316 | " size = op - PUSH1 + 1\n", 317 | " self.push(size)\n", 318 | " elif op == PUSH0: # 如果为PUSH0\n", 319 | " self.stack.append(0)\n", 320 | " elif op == POP: # 如果为POP\n", 321 | " self.pop()\n", 322 | " elif op == ADD: # 处理ADD指令\n", 323 | " self.add()\n", 324 | " elif op == MUL: # 处理MUL指令\n", 325 | " self.mul()\n", 326 | " elif op == SUB: # 处理SUB指令\n", 327 | " self.sub()\n", 328 | " elif op == DIV: # 处理DIV指令\n", 329 | " self.div()\n", 330 | " elif op == SDIV:\n", 331 | " self.sdiv()\n", 332 | " elif op == MOD:\n", 333 | " self.mod()\n", 334 | " elif op == SMOD:\n", 335 | " self.smod()\n", 336 | " elif op == ADDMOD:\n", 337 | " self.addmod()\n", 338 | " elif op == MULMOD:\n", 339 | " self.mulmod()\n", 340 | " elif op == EXP:\n", 341 | " self.exp()\n", 342 | " elif op == SIGNEXTEND:\n", 343 | " self.signextend()\n", 344 | " elif op == LT:\n", 345 | " self.lt()\n", 346 | " elif op == GT:\n", 347 | " self.gt()\n", 348 | " elif op == SLT:\n", 349 | " self.slt()\n", 350 | " elif op == SGT:\n", 351 | " self.sgt()\n", 352 | " elif op == EQ:\n", 353 | " self.eq()\n", 354 | " elif op == ISZERO:\n", 355 | " self.iszero()\n", 356 | " elif op == AND: # 处理AND指令\n", 357 | " self.and_op()\n", 358 | " elif op == OR: # 处理AND指令\n", 359 | " self.or_op()\n", 360 | " elif op == XOR: # 处理AND指令\n", 361 | " self.xor_op()\n", 362 | " elif op == NOT: # 处理AND指令\n", 363 | " self.not_op()\n", 364 | " elif op == BYTE: # 处理AND指令\n", 365 | " self.byte_op()\n", 366 | " elif op == SHL: # 处理AND指令\n", 367 | " self.shl()\n", 368 | " elif op == SHR: # 处理AND指令\n", 369 | " self.shr()\n", 370 | " elif op == SAR: # 处理AND指令\n", 371 | " self.sar()\n", 372 | " elif op == MLOAD: # 处理MLOAD指令\n", 373 | " self.mload()\n", 374 | " elif op == MSTORE: # 处理MSTORE指令\n", 375 | " self.mstore()\n", 376 | " elif op == MSTORE8: # 处理MSTORE8指令\n", 377 | " self.mstore8()\n", 378 | " elif op == SLOAD: \n", 379 | " self.sload()\n", 380 | " elif op == SSTORE: # 处理SSTORE指令\n", 381 | " self.sstore()\n", 382 | " elif op == MSIZE: # 处理MSIZE指令\n", 383 | " self.msize()\n", 384 | " else:\n", 385 | " raise Exception('Invalid opcode')\n" 386 | ] 387 | }, 388 | { 389 | "cell_type": "code", 390 | "execution_count": 16, 391 | "id": "3f377c2d", 392 | "metadata": {}, 393 | "outputs": [ 394 | { 395 | "name": "stdout", 396 | "output_type": "stream", 397 | "text": [ 398 | "{0: 2}\n" 399 | ] 400 | } 401 | ], 402 | "source": [ 403 | "# SSTORE\n", 404 | "code = b\"\\x60\\x02\\x60\\x00\\x55\"\n", 405 | "evm = EVM(code)\n", 406 | "evm.run()\n", 407 | "print(evm.storage) \n", 408 | "# Output: {0: 2}" 409 | ] 410 | }, 411 | { 412 | "cell_type": "code", 413 | "execution_count": 19, 414 | "id": "7a70f0ea", 415 | "metadata": {}, 416 | "outputs": [ 417 | { 418 | "name": "stdout", 419 | "output_type": "stream", 420 | "text": [ 421 | "{0: 2}\n", 422 | "[2]\n" 423 | ] 424 | } 425 | ], 426 | "source": [ 427 | "# SLOAD\n", 428 | "code = b\"\\x60\\x02\\x60\\x00\\x55\\x60\\x00\\x54\"\n", 429 | "evm = EVM(code)\n", 430 | "evm.run()\n", 431 | "print(evm.storage) \n", 432 | "# 输出: {0: 2}\n", 433 | "print(evm.stack) \n", 434 | "# 输出: [2]" 435 | ] 436 | }, 437 | { 438 | "cell_type": "code", 439 | "execution_count": null, 440 | "id": "19caeaad", 441 | "metadata": {}, 442 | "outputs": [], 443 | "source": [] 444 | } 445 | ], 446 | "metadata": { 447 | "kernelspec": { 448 | "display_name": "Python 3 (ipykernel)", 449 | "language": "python", 450 | "name": "python3" 451 | }, 452 | "language_info": { 453 | "codemirror_mode": { 454 | "name": "ipython", 455 | "version": 3 456 | }, 457 | "file_extension": ".py", 458 | "mimetype": "text/x-python", 459 | "name": "python", 460 | "nbconvert_exporter": "python", 461 | "pygments_lexer": "ipython3", 462 | "version": "3.7.7" 463 | } 464 | }, 465 | "nbformat": 4, 466 | "nbformat_minor": 5 467 | } 468 | -------------------------------------------------------------------------------- /08_StorageOp/img/8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/08_StorageOp/img/8-1.png -------------------------------------------------------------------------------- /08_StorageOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 8. 存储指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 在这一讲,我们将介绍EVM中用于存储(Storage)操作的2个指令:`SSTORE`和`SLOAD`。并且,我们将在用Python写的极简版EVM中添加对这些操作的支持。 14 | 15 | ## EVM中的存储 16 | 17 | 我们在[第一讲](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/01_HelloOpcodes/readme.md)介绍了EVM的存储,和内存不同,它是一种持久化存储空间,存在存储中的数据在交易之间可以保持。它是EVM的状态存储的一部分,支持以256 bit为单位的读写。 18 | 19 | ![](./img/8-1.png) 20 | 21 | 由于存储使用键值对存储数据,每个键和值都是256 bit,因此我们可以用Python内置的`dict`(字典)来代表存储: 22 | 23 | ```python 24 | def __init__(self, code): 25 | self.code = code 26 | self.pc = 0 27 | self.stack = [] 28 | self.memory = bytearray() # 内存初始化为空 29 | self.storage = {} # 存储初始化为空字典 30 | ``` 31 | 32 | 对存储的读取(`SLOAD`)和写入(`SSTORE`)都需要gas,并且比内存操作更昂贵。这样设计可以防止滥用存储资源,因为所有的存储数据都需要在每个以太坊节点上保存。 33 | 34 | ## SSTORE (存储写) 35 | 36 | `SSTORE`指令用于将一个256位(32字节)的值写入到存储。它从堆栈中弹出两个元素,第一个元素为存储的地址(key),第二个元素为存储的值(value)。操作码是`0x55`,gas消耗根据实际改变的数据计算(下面给出)。 37 | 38 | ```python 39 | def sstore(self): 40 | if len(self.stack) < 2: 41 | raise Exception('Stack underflow') 42 | key = self.stack.pop() 43 | value = self.stack.pop() 44 | self.storage[key] = value 45 | ``` 46 | 47 | 我们在`run()`函数中添加对`SSTORE`指令的处理: 48 | 49 | ```python 50 | def run(self): 51 | while self.pc < len(self.code): 52 | op = self.next_instruction() 53 | 54 | # ... 其他指令的处理 ... 55 | 56 | elif op == SSTORE: # 处理SSTORE指令 57 | self.sstore() 58 | ``` 59 | 60 | 现在,我们可以尝试运行一个包含`SSTORE`指令的字节码:`0x6002600055`(PUSH1 2 PUSH1 0 SSTORE)。这个字节码将`2`和`0`推入堆栈,然后进行`SSTORE`,将`2`存到键为`0x0`的存储槽。 61 | 62 | ```python 63 | # SSTORE 64 | code = b"\x60\x02\x60\x00\x55" 65 | evm = EVM(code) 66 | evm.run() 67 | print(evm.storage) 68 | # Output: {0: 2} 69 | ``` 70 | 71 | ## SLOAD (存储读) 72 | 73 | `SLOAD`指令从存储中读取一个256位(32字节)的值并推入堆栈。它从堆栈中弹出一个元素,从该元素表示的存储槽中加载值,并将其推入堆栈。操作码是`0x54`,gas消耗后面给出。 74 | 75 | ```python 76 | def sload(self): 77 | if len(self.stack) < 1: 78 | raise Exception('Stack underflow') 79 | key = self.stack.pop() 80 | value = self.storage.get(key, 0) # 如果键不存在,返回0 81 | self.stack.append(value) 82 | ``` 83 | 84 | 我们在`run()`函数中添加对`SLOAD`指令的处理: 85 | 86 | ```python 87 | elif op == SLOAD: 88 | self.sload() 89 | ``` 90 | 91 | 现在,我们可以尝试运行一个包含`SLOAD`指令的字节码:`0x6002600055600054`(PUSH1 2 PUSH1 0 SSTORE PUSH1 0 SLOAD)。这个字节码将`2`和`0`推入堆栈,然后进行`SSTORE`,将`2`存到键为`0`的地方;然后将`0`推入堆栈,然后进行`SLOAD`,将刚才写入`0x0`存储槽的值读取出来。 92 | 93 | ```python 94 | # SLOAD 95 | code = b"\x60\x02\x60\x00\x55\x60\x00\x54" 96 | evm = EVM(code) 97 | evm.run() 98 | print(evm.storage) 99 | # 输出: {0: 2} 100 | print(evm.stack) 101 | # 输出: [2] 102 | ``` 103 | 104 | ## 访问集 EIP-2929 105 | 106 | 访问集(Access Sets)是[EIP-2929](https://eips.ethereum.org/EIPS/eip-2929)提出的一种新概念,它的引入有助于优化Gas计费和以太坊的网络性能。访问集是在每个外部交易中定义的,并且在交易过程中会跟踪和记录每个交易访问过的合约地址和存储槽(slot)。 107 | 108 | - 合约地址:在执行交易过程中,任何被访问到的地址都会被添加到访问集中。 109 | - 存储槽:这个列表包含了一个交易在执行过程中访问过的所有存储槽。 110 | 111 | 如果一个地址或存储槽在访问集中,我们称它为"warm",否则称之为"cold"。一个地址或存储槽在一次交易中首次被访问时,它会从"cold"变为"warm"。在交易执行期间,如果一个指令需要访问一个"cold"的地址或存储槽,那么这个指令的Gas消耗会更高。而对 "warm" 的地址或存储槽的访问,则会有较低的 Gas 消耗,因为相关数据已经被缓存了。 112 | 113 | ### Gas Cost 114 | 115 | 对于`SLOAD`(存储读),如果读取的存储槽为"cold"(即这是交易中首次访问),那么`SLOAD`的gas消耗为2100 gas;如果为 "warm"(即在交易中已经访问过),那么`SLOAD`的gas消耗为100 gas。 116 | 117 | 对于`SSTORE`(存储写),gas计算公式更为复杂,分为gas消耗和gas返还两部分。 118 | 119 | 1. `SSTORE`的gas消耗:简单来说,如果存储槽为`cold`,则需要多花2100 gas;如果存储槽初始值为0,那么将它改为非0值的gas消耗最大,为22100 gas。 120 | 121 | 具体计算公式如下: 122 | 123 | ```python 124 | static_gas = 0 125 | 126 | if value == current_value 127 | base_dynamic_gas = 100 128 | else if current_value == original_value 129 | if original_value == 0 130 | base_dynamic_gas = 20000 131 | else 132 | base_dynamic_gas = 2900 133 | else 134 | base_dynamic_gas = 100 135 | 136 | if key is not warm 137 | base_dynamic_gas += 2100 138 | ``` 139 | 140 | 其中`value`为要存储的新值,`current_value`为存储槽当前值,`original_value`为交易开始时存储槽的原始值,`base_dynamic_gas`为gas消耗。 141 | 142 | 2. `SSTORE`的gas返还:当要存储的新值不等于存储槽的当前值时,可能触发gas返还。简单来说,将存储槽的非0值改为0,返还的gas最多高达 19900 gas。 143 | 144 | ```python 145 | if value != current_value 146 | if current_value == original_value 147 | if original_value != 0 and value == 0 148 | gas_refunds += 4800 149 | else 150 | if original_value != 0 151 | if current_value == 0 152 | gas_refunds -= 4800 153 | else if value == 0 154 | gas_refunds += 4800 155 | if value == original_value 156 | if original_value == 0 157 | gas_refunds += 19900 158 | else 159 | if key is warm 160 | gas_refunds += 5000 - 2100 - 100 161 | else 162 | gas_refunds += 4900 163 | ``` 164 | 165 | 其中`value`为要存储的新值,`current_value`为存储槽当前值,`original_value`为交易开始时存储槽的原始值,`gas_refunds`为gas返还。 166 | 167 | ## 总结 168 | 169 | 这一讲,我们介绍了EVM中的存储操作指令(`SSTORE`和`SLOAD`),并在极简版EVM中添加了对它们的支持。这些操作允许我们在EVM的存储中写入和读取值,为更复杂的合约逻辑提供基础。 170 | 171 | 课后习题: 写出`0x6002602055602054`对应的指令形式,并给出运行后的堆栈和存储的状态。 172 | -------------------------------------------------------------------------------- /09_FlowOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 9. 控制流指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 在这一讲,我们将介绍EVM中用于控制流的5个指令,包括`STOP`,`JUMP`,`JUMPI`,`JUMPDEST`,和`PC`。我们将在用Python写的极简版EVM中添加对这些操作的支持。 14 | 15 | ## EVM中的控制流 16 | 17 | 我们在[第三讲](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/03_StackOp/readme.md)介绍了程序计数器(Program Counter,PC),而EVM的控制流是由跳转指令(`JUMP`,`JUMPI`,`JUMPDEST`)控制PC指向新的指令位置而实现的,这允许合约进行条件执行和循环执行。 18 | 19 | ## STOP(停止) 20 | 21 | `STOP`是EVM的停止指令,它的作用是停止当前上下文的执行,并成功退出。它的操作码是`0x00`,gas消耗为0。 22 | 23 | 将`STOP`操作作码设为`0x00`有一个好处:当一个调用被执行到一个没有代码的地址(EOA),并且EVM尝试读取代码数据时,系统会返回一个默认值0,这个默认值对应的就是`STOP`指令,程序就会停止执行。 24 | 25 | 下面,让我们在`run()`函数中添加对`STOP`指令的处理: 26 | 27 | ```python 28 | def run(self): 29 | while self.pc < len(self.code): 30 | op = self.next_instruction() 31 | 32 | # ... 其他指令的处理 ... 33 | 34 | elif op == STOP: # 处理STOP指令 35 | print('Program has been stopped') 36 | break # 停止执行 37 | ``` 38 | 39 | 现在,我们可以尝试运行一个包含`STOP`指令的字节码: 40 | 41 | ```python 42 | # STOP 43 | code = b"\x00" 44 | evm = EVM(code) 45 | evm.run() 46 | # output: Program has been stopped 47 | ``` 48 | 49 | ## JUMPDEST(跳转目标) 50 | 51 | `JUMPDEST`指令标记一个有效的跳转目标位置,不然无法使用`JUMP`和`JUMPI`进行跳转。它的操作码是`0x5b`,gas消耗为1。 52 | 53 | 但是`0x5b`有时会作为`PUSH`的参数(详情可看[黄皮书](https://ethereum.github.io/yellowpaper/paper.pdf)中的9.4.3. Jump Destination Validity),所以需要在运行代码前,筛选字节码中有效的`JUMPDEST`指令,使用`ValidJumpDest` 来存储有效的`JUMPDEST`指令所在位置。 54 | 55 | ```python 56 | def findValidJumpDestinations(self): 57 | pc = 0 58 | 59 | while pc < len(self.code): 60 | op = self.code[pc] 61 | if op == JUMPDEST: 62 | self.validJumpDest[pc] = True 63 | elif op >= PUSH1 and op <= PUSH32: 64 | pc += op - PUSH1 + 1 65 | pc += 1 66 | ``` 67 | 68 | ```python 69 | def jumpdest(self): 70 | pass 71 | ``` 72 | 73 | ## JUMP(跳转) 74 | 75 | `JUMP`指令用于无条件跳转到一个新的程序计数器位置。它从堆栈中弹出一个元素,将这个元素设定为新的程序计数器(`pc`)的值。操作码是`0x56`,gas消耗为8。 76 | 77 | ```python 78 | def jump(self): 79 | if len(self.stack) < 1: 80 | raise Exception('Stack underflow') 81 | destination = self.stack.pop() 82 | if destination not in self.validJumpDest: 83 | raise Exception('Invalid jump destination') 84 | else: self.pc = destination 85 | ``` 86 | 87 | 我们在`run()`函数中添加对`JUMP`和`JUMPDEST`指令的处理: 88 | 89 | ```python 90 | elif op == JUMP: 91 | self.jump() 92 | elif op == JUMPDEST: 93 | self.jumpdest() 94 | ``` 95 | 96 | 现在,我们可以尝试运行一个包含`JUMP`和`JUMPDEST`指令的字节码:`0x600456005B`(PUSH1 4 JUMP STOP JUMPDEST)。这段字节码将`4`推入堆栈,然后进行`JUMP`,跳转到`pc = 4`的位置,该位置正好是`JUMPDEST`指令,跳转成功,程序没有被`STOP`指令中断。 97 | 98 | ```python 99 | # JUMP 100 | code = b"\x60\x04\x56\x00\x5b" 101 | evm = EVM(code) 102 | evm.run() 103 | print(evm.pc) 104 | # output: 5 105 | ``` 106 | 107 | ## JUMPI(条件跳转) 108 | 109 | `JUMPI`指令用于条件跳转,它从堆栈中弹出两个元素,如果第二个元素(条件,`condition`)不为0,那么将第一个元素(目标,`destination`)设定为新的`pc`的值。操作码是`0x57`,gas消耗为10。 110 | 111 | ```python 112 | def jumpi(self): 113 | if len(self.stack) < 2: 114 | raise Exception('Stack underflow') 115 | destination = self.stack.pop() 116 | condition = self.stack.pop() 117 | if condition != 0: 118 | if destination not in self.validJumpDest: 119 | raise Exception('Invalid jump destination') 120 | else: self.pc = destination 121 | ``` 122 | 123 | 我们在`run()`函数中添加对`JUMPI`指令的处理: 124 | 125 | ```python 126 | elif op == JUMPI: 127 | self.jumpi() 128 | ``` 129 | 130 | 现在,我们可以尝试运行一个包含`JUMPI`和`JUMPDEST`指令的字节码:`0x6001600657005B`(PUSH1 01 PUSH1 6 JUMPI STOP JUMPDEST)。这个字节码将`1`和`6`推入堆栈,然后进行`JUMPI`,由于条件不为`0`,执行跳转到`pc = 6`的位置,该位置正好是`JUMPDEST`指令,跳转成功,程序没有被`STOP`指令中断。 131 | 132 | ```python 133 | # JUMPI 134 | code = b"\x60\x01\x60\x06\x57\x00\x5b" 135 | evm = EVM(code) 136 | evm.run() 137 | print(evm.pc) 138 | # output: 7 程序没有被中断 139 | ``` 140 | 141 | ## PC(程序计数器) 142 | 143 | `PC`指令将当前的程序计数器(`pc`)的值压入堆栈。操作码为`0x58`,gas消耗为2。 144 | 145 | ```python 146 | def pc(self): 147 | self.stack.append(self.pc) 148 | ``` 149 | 150 | ## 总结 151 | 152 | 这一讲,我们介绍了EVM中的控制流程指令,并在极简版EVM中添加了对它们的支持。这些操作为合约提供了控制流程的能力,为编写更复杂的合约逻辑提供了可能。 153 | 154 | 课后习题: 写出`0x5F600557005B`对应的指令形式,并给出运行后的堆栈和程序计数器状态。 155 | -------------------------------------------------------------------------------- /10_BlockOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 10. 区块信息指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 在这一讲,我们将介绍EVM中用于查询区块信息的9个指令,包括`BLOCKHASH`,`COINBASE`,`PREVRANDAO`等。我们将在用Python写的极简版EVM中添加对这些操作的支持。 14 | 15 | ## 区块信息 16 | 17 | 我们在写智能合约时经常会用到区块链信息,比如生成[伪随机数](https://github.com/AmazingAng/WTF-Solidity/blob/main/39_Random/Random.sol)时我们会使用`blockhash`,`block.number`,和`block.timestamp`: 18 | 19 | ```solidity 20 | /** 21 | * 链上伪随机数生成 22 | * keccak256(abi.encodePacked())中填上一些链上的全局变量/自定义变量 23 | * 返回时转换成uint256类型 24 | */ 25 | function getRandomOnchain() public view returns(uint256){ 26 | /* 27 | * 本例链上随机只依赖区块哈希,调用者地址,和区块时间, 28 | * 想提高随机性可以再增加一些属性比如nonce等,但是不能根本上解决安全问题 29 | */ 30 | bytes32 randomBytes = keccak256(abi.encodePacked(blockhash(block.number-1), msg.sender, block.timestamp)); 31 | return uint256(randomBytes); 32 | } 33 | ``` 34 | 35 | EVM提供了一系列指令让智能合约访问当前或历史区块的信息,包括区块哈希、时间戳、coinbase等。 36 | 37 | 这些信息一般保存在区块头(`Header`)中,但我们可以为在极简EVM中添加`current_block`属性来模拟这些区块信息: 38 | 39 | ```python 40 | def __init__(self, code): 41 | self.code = code 42 | self.pc = 0 43 | self.stack = [] 44 | self.memory = bytearray() 45 | self.current_block = { 46 | "blockhash": 0x7527123fc877fe753b3122dc592671b4902ebf2b325dd2c7224a43c0cbeee3ca, 47 | "coinbase": 0x388C818CA8B9251b393131C08a736A67ccB19297, 48 | "timestamp": 1625900000, 49 | "number": 17871709, 50 | "prevrandao": 0xce124dee50136f3f93f19667fb4198c6b94eecbacfa300469e5280012757be94, 51 | "gaslimit": 30, 52 | "chainid": 1, 53 | "selfbalance": 100, 54 | "basefee": 30, 55 | } 56 | ``` 57 | 58 | ## 区块信息指令 59 | 60 | 下面,我们介绍这些区块信息指令: 61 | 62 | 1. `BLOCKHASH`: 查询特定区块(最近的256个区块,不包括当前区块)的hash,它的操作码为`0x40`,gas消耗为20。。它从堆栈中弹出一个值作为区块高度(block number),然后将该区块的hash压入堆栈,如果它不属于最近的256个区块,则返回0(你可以使用`NUMBER`指令查询当前区块高度)。但是为了简化,我们在这里只考虑当前块。 63 | 64 | ```python 65 | def blockhash(self): 66 | if len(self.stack) < 1: 67 | raise Exception('Stack underflow') 68 | number = self.stack.pop() 69 | # 在真实场景中, 你会需要访问历史的区块hash 70 | if number == self.current_block["number"]: 71 | self.stack.append(self.current_block["blockhash"]) 72 | else: 73 | self.stack.append(0) # 如果不是当前块,返回0 74 | ``` 75 | 76 | 77 | 2. `COINBASE`: 将当前区块的coinbase(矿工/受益人)地址压入堆栈,它的操作码为`0x41`,gas消耗为2。 78 | 79 | ```python 80 | def coinbase(self): 81 | self.stack.append(self.current_block["coinbase"]) 82 | ``` 83 | 84 | 3. `TIMESTAMP`: 将当前区块的时间戳压入堆栈,它的操作码为`0x42`,gas消耗为2。 85 | 86 | ```python 87 | def timestamp(self): 88 | self.stack.append(self.current_block["timestamp"]) 89 | ``` 90 | 91 | 4. `NUMBER`: 将当前区块高度压入堆栈,它的操作码为`0x43`,gas消耗为2。 92 | 93 | ```python 94 | def number(self): 95 | self.stack.append(self.current_block["number"]) 96 | ``` 97 | 98 | 5. `PREVRANDAO`: 替代了原先的`DIFFICULTY`(0x44) 操作码,其返回值是beacon链随机性信标的输出。此变更允许智能合约在以太坊转向权益证明(PoS)后继续从原本的`DIFFICULTY`操作码处获得随机性。它的操作码为`0x44`,gas消耗为2。 99 | 100 | ```python 101 | def prevrandao(self): 102 | self.stack.append(self.current_block["prevrandao"]) 103 | ``` 104 | 105 | 6. `GASLIMIT`: 将当前区块的gas限制压入堆栈,它的操作码为`0x45`,gas消耗为2。 106 | 107 | ```python 108 | def gaslimit(self): 109 | self.stack.append(self.current_block["gaslimit"]) 110 | ``` 111 | 112 | 7. `CHAINID`: 将当前的[链ID](https://chainlist.org/)压入堆栈,它的操作码为`0x46`,gas消耗为2。 113 | 114 | ```python 115 | def chainid(self): 116 | self.stack.append(self.current_block["chainid"]) 117 | ``` 118 | 119 | 8. `SELFBALANCE`: 将合约的当前余额压入堆栈,它的操作码为`0x47`,gas消耗为5。 120 | 121 | ```python 122 | def selfbalance(self): 123 | self.stack.append(self.current_block["selfbalance"]) 124 | ``` 125 | 126 | 9. `BASEFEE`: 将当前区块的[基础费](https://ethereum.org/zh/developers/docs/gas/#base-fee)(base fee)压入堆栈,它的操作码`0x48`,gas消耗为2。 127 | 128 | ```python 129 | def basefee(self): 130 | self.stack.append(self.current_block["basefee"]) 131 | ``` 132 | 133 | 下面,我们在极简EVM中添加对这些操作码的支持: 134 | 135 | ```python 136 | BLOCKHASH = 0x40 137 | COINBASE = 0x41 138 | TIMESTAMP = 0x42 139 | NUMBER = 0x43 140 | PREVRANDAO = 0x44 141 | GASLIMIT = 0x45 142 | CHAINID = 0x46 143 | SELFBALANCE = 0x47 144 | BASEFEE = 0x48 145 | 146 | def run(self): 147 | while self.pc < len(self.code): 148 | op = self.next_instruction() 149 | 150 | # ... 其他指令的处理 ... 151 | 152 | elif op == BLOCKHASH: 153 | self.blockhash() 154 | elif op == COINBASE: 155 | self.coinbase() 156 | elif op == TIMESTAMP: 157 | self.timestamp() 158 | elif op == NUMBER: 159 | self.number() 160 | elif op == PREVRANDAO: 161 | self.prevrandao() 162 | elif op == GASLIMIT: 163 | self.gaslimit() 164 | elif op == CHAINID: 165 | self.chainid() 166 | elif op == SELFBALANCE: 167 | self.selfbalance() 168 | elif op == BASEFEE: 169 | self.basefee() 170 | ``` 171 | 172 | ## 总结 173 | 174 | 这一讲,我们介绍了EVM中与区块链信息相关的指令,这些指令允许智能合约访问与其所在区块链相关的信息。这些信息有很多用途,比如判断交易是否超时,或者检查合约的余额。 175 | 176 | 课后习题: 请尝试写出一段字节码,该字节码会先压入当前区块链的高度,然后获取它的区块哈希。 177 | -------------------------------------------------------------------------------- /11_StackOp2/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 11. 堆栈指令2 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 之前,我们介绍了堆栈指令中的`PUSH`和`POP`,这一讲我们将介绍另外两个指令:`DUP`和`SWAP`。 14 | 15 | ## DUP 16 | 17 | 在EVM中,`DUP`是一系列的指令,总共有16个,从`DUP1`到`DUP16`,操作码范围为`0x80`到`0x8F`,gas消耗均为3。这些指令用于复制(Duplicate)堆栈上的指定元素(根据指令的序号)到堆栈顶部。例如,`DUP1`复制栈顶元素,`DUP2`复制距离栈顶的第二个元素,以此类推。 18 | 19 | 我们可以在极简EVM中增加对`DUP`指令的支持: 20 | 21 | ```python 22 | DUP1 = 0x80 23 | DUP16 = 0x8F 24 | 25 | def dup(self, position): 26 | if len(self.stack) < position: 27 | raise Exception('Stack underflow') 28 | value = self.stack[-position] 29 | self.stack.append(value) 30 | 31 | def run(self): 32 | while self.pc < len(self.code): 33 | op = self.next_instruction() 34 | 35 | # ... 其他指令的实现 ... 36 | 37 | elif DUP1 <= op <= DUP16: # 如果是DUP1-DUP16 38 | position = op - DUP1 + 1 39 | self.dup(position) 40 | ``` 41 | 42 | 现在,我们可以尝试运行一个包含`DUP1`指令的字节码:`0x6001600280`(PUSH1 1 PUSH1 2 DUP1)。这个字节码将`1`和`2`推入堆栈,然后进行`DUP1`复制栈顶元素(2),堆栈最后会变为[1, 2, 2]。 43 | 44 | ```python 45 | # DUP1 46 | code = b"\x60\x01\x60\x02\x80" 47 | evm = EVM(code) 48 | evm.run() 49 | print(evm.stack) 50 | # output: [1, 2, 2] 51 | ``` 52 | 53 | ## SWAP 54 | 55 | `SWAP`指令用于交换堆栈顶部的两个元素。与`DUP`类似,`SWAP`也是一系列的指令,从`SWAP1`到`SWAP16`共16个,操作码范围为`0x90`到`0x9F`,gas消耗均为3。`SWAP1`交换堆栈的顶部和次顶部的元素,`SWAP2`交换顶部和第三个元素,以此类推。 56 | 57 | 让我们在极简EVM中增加对`SWAP`指令的支持: 58 | 59 | ```python 60 | SWAP1 = 0x90 61 | SWAP16 = 0x9F 62 | 63 | def swap(self, position): 64 | if len(self.stack) < position + 1: 65 | raise Exception('Stack underflow') 66 | idx1, idx2 = -1, -position - 1 67 | self.stack[idx1], self.stack[idx2] = self.stack[idx2], self.stack[idx1] 68 | 69 | def run(self): 70 | while self.pc < len(self.code): 71 | op = self.next_instruction() 72 | 73 | # ... 其他指令的实现 ... 74 | 75 | elif SWAP1 <= op <= SWAP16: # 如果是SWAP1-SWAP16 76 | position = op - SWAP1 + 1 77 | self.swap(position) 78 | ``` 79 | 80 | 现在,我们可以尝试运行一个包含`SWAP1`指令的字节码:`0x6001600290`(PUSH1 1 PUSH1 2 SWAP1)。这个字节码将`1`和`2`推入堆栈,然后进行`SWAP1`交换这两个元素,堆栈最后会变为[2, 1]。 81 | 82 | ```python 83 | # SWAP1 84 | code = b"\x60\x01\x60\x02\x90" 85 | evm = EVM(code) 86 | evm.run() 87 | print(evm.stack) 88 | # output: [2, 1] 89 | ``` 90 | 91 | ## 总结 92 | 93 | 这讲过后,我们已经介绍了EVM中最基础的四种堆栈操作:`PUSH`,`POP`,`DUP`,和`SWAP`,理解它们有助于更深入地理解EVM的工作原理。我们写的极简EVM也已经支持了111/144个操作码。 -------------------------------------------------------------------------------- /12_SHA3/img/12-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/12_SHA3/img/12-1.png -------------------------------------------------------------------------------- /12_SHA3/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 12. SHA3指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们将介绍EVM唯一内置的密码学指令--`SHA3`,你可以用它计算`keccak-256`哈希。 14 | 15 | ## SHA3指令 16 | 17 | 在EVM中,计算数据的哈希是一个常见的[操作](https://github.com/AmazingAng/WTF-Solidity/tree/main/28_Hash)。以太坊使用Keccak算法([SHA-3](https://en.wikipedia.org/wiki/SHA-3))计算数据的哈希,并提供了一个专门的操作码`SHA3`,Solidity中的`keccak256()`函数就是建立在它之上的。 18 | 19 | `SHA3(offset, size)`指令从堆栈中取出两个参数,起始位置`offset`和长度`size`(以字节为单位),然后它从内存中读取起始位置`offset`开始的`size`长度的数据,计算这段数据的Keccak-256哈希,并将结果(一个32字节的值)压入堆栈。它的操作码为`0x20`,gas消耗为`30 + 6*数据的字节长度 + 扩展内存成本`。 20 | 21 | 在Python中,我们可以使用`pysha3`库来实现`keccak-256`哈希计算。首先你需要安装这个库: 22 | 23 | ```bash 24 | pip install pysha3 25 | ``` 26 | 27 | 下面,让我们在极简EVM中支持SHA3指令: 28 | 29 | ```python 30 | import sha3 31 | 32 | SHA3 = 0x20 33 | 34 | def sha3(self): 35 | if len(self.stack) < 2: 36 | raise Exception('Stack underflow') 37 | 38 | offset = self.pop() 39 | size = self.pop() 40 | data = self.memory[offset:offset+size] # 从内存中获取数据 41 | hash_value = int.from_bytes(sha3.keccak_256(data).digest(), 'big') # 计算哈希值 42 | self.stack.append(hash_value) # 将哈希值压入堆栈 43 | 44 | def run(self): 45 | while self.pc < len(self.code): 46 | op = self.next_instruction() 47 | 48 | # ... 其他指令的实现 ... 49 | 50 | elif op == SHA3: # 如果为SHA3 51 | self.sha3() 52 | ``` 53 | 54 | 我们可以尝试运行一个包含`SHA3`指令的字节码:`0x5F5F20`(PUSH0 PUSH0 SHA3)。这个字节码将两个`0`推入堆栈,然后使用`SHA3`指令计算`0`的哈希。 55 | 56 | ```python 57 | # SHA3 58 | code = b"\x5F\x5F\x20" 59 | evm = EVM(code) 60 | evm.run() 61 | print(hex(evm.stack[-1])) # 打印出0的keccak256 hash 62 | # output: 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 63 | ``` 64 | 65 | 我们可以在evm.codes上验证结果: 66 | 67 | ![](./img/12-1.png) 68 | 69 | ## 总结 70 | 71 | 这一讲,我们介绍了EVM中重要的操作符`SHA3`,它为我们提供了计算数据哈希的功能,这在验证数据或身份时非常重要。目前,我们写的极简EVM已经支持了112/144个操作码,剩下的不多了! -------------------------------------------------------------------------------- /13_AccountOp/img/13-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/13_AccountOp/img/13-1.png -------------------------------------------------------------------------------- /13_AccountOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 13. 账户指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 在这一讲,我们将探索EVM中与账户(Account)相关的4个指令,包括`BALANCE`, `EXTCODESIZE`, `EXTCODECOPY`, 以及 `EXTCODEHASH`。我们能利用这些指令获取以太坊账户的信息。 14 | 15 | ## 以太坊账户结构 16 | 17 | 以太坊上的账户分两类:外部账户(Externally Owned Accounts,EOA)和合约账户。EOA是用户在以太坊网络上的代表,它们可以拥有ETH、发送交易并与合约互动;而合约账户是存储和执行智能合约代码的实体,它们也可以拥有和发送ETH,但不能主动发起交易。 18 | 19 | ![](./img/13-1.png) 20 | 21 | 以太坊上的账户结构非常简单,你可以它理解为地址到账户状态的映射。账户地址是20字节(160位)的数据,可以用40位的16进制表示,比如`0x9bbfed6889322e016e0a02ee459d306fc19545d8`。而账户的状态具有4种属性: 22 | 23 | - **Balance**:这是账户持有的ETH数量,用Wei表示(1 ETH = 10^18 Wei)。 24 | 25 | - **Nonce**:对于外部账户(EOA),这是该账户发送的交易数。对于合约账户,它是该账户创建的合约数量。 26 | 27 | - **Storage**:每个合约账户都有与之关联的存储空间,其中包含状态变量的值。 28 | 29 | - **Code**:合约账户的字节码。 30 | 31 | 也就是说,只有合约账户拥有`Storage`和`Code`,EOA没有。 32 | 33 | 为了让极简EVM支持账户相关的指令,我们利用`dict`做一个简单账户数据库: 34 | 35 | ```python 36 | account_db = { 37 | '0x9bbfed6889322e016e0a02ee459d306fc19545d8': { 38 | 'balance': 100, # wei 39 | 'nonce': 1, 40 | 'storage': {}, 41 | 'code': b'\x60\x00\x60\x00' # Sample bytecode (PUSH1 0x00 PUSH1 0x00) 42 | }, 43 | # ... 其他账户数据 ... 44 | } 45 | ``` 46 | 47 | 下面,我们将介绍账户相关指令。 48 | 49 | ## BALANCE 50 | 51 | `BALANCE` 指令用于返回某个账户的余额。它从堆栈中弹出一个地址,然后查询该地址的余额并压入堆栈。它的操作码是`0x31`,gas为2600(cold address)或100(warm address)。 52 | 53 | ```python 54 | def balance(self): 55 | if len(self.stack) < 1: 56 | raise Exception('Stack underflow') 57 | addr_int = self.stack.pop() 58 | # 将stack中的int转换为bytes,然后再转换为十六进制字符串,用于在账户数据库中查询 59 | addr_str = '0x' + addr_int.to_bytes(20, byteorder='big').hex() 60 | self.stack.append(account_db.get(addr_str, {}).get('balance', 0)) 61 | ``` 62 | 63 | 我们可以尝试运行一个包含`BALANCE`指令的字节码:`739bbfed6889322e016e0a02ee459d306fc19545d831`(PUSH20 9bbfed6889322e016e0a02ee459d306fc19545d8 BALANCE)。这个字节码使用`PUSH20`将一个地址推入堆栈,然后使用`BALANCE`指令查询该地址的余额。 64 | 65 | ```python 66 | # BALANCE 67 | code = b"\x73\x9b\xbf\xed\x68\x89\x32\x2e\x01\x6e\x0a\x02\xee\x45\x9d\x30\x6f\xc1\x95\x45\xd8\x31" 68 | evm = EVM(code) 69 | evm.run() 70 | print(evm.stack) 71 | # output: 100 72 | ``` 73 | 74 | ## EXTCODESIZE 75 | 76 | `EXTCODESIZE` 指令用于返回某个账户的代码长度(以字节为单位)。它从堆栈中弹出一个地址,然后查询该地址的代码长度并压入堆栈。如果账户不存在或没有代码,返回0。他的操作码为`0x3B`,gas为2600(cold address)或100(warm address)。 77 | 78 | ```python 79 | def extcodesize(self): 80 | if len(self.stack) < 1: 81 | raise Exception('Stack underflow') 82 | addr_int = self.stack.pop() 83 | # 将stack中的int转换为bytes,然后再转换为十六进制字符串,用于在账户数据库中查询 84 | addr_str = '0x' + addr_int.to_bytes(20, byteorder='big').hex() 85 | self.stack.append(len(account_db.get(addr_str, {}).get('code', b''))) 86 | ``` 87 | 88 | 我们可以尝试运行一个包含`EXTCODESIZE`指令的字节码:`739bbfed6889322e016e0a02ee459d306fc19545d83B`(PUSH20 9bbfed6889322e016e0a02ee459d306fc19545d8 EXTCODESIZE)。这个字节码使用`PUSH20`将一个地址推入堆栈,然后使用`EXTCODESIZE`指令查询该地址的代码长度。 89 | 90 | ```python 91 | # EXTCODESIZE 92 | code = b"\x73\x9b\xbf\xed\x68\x89\x32\x2e\x01\x6e\x0a\x02\xee\x45\x9d\x30\x6f\xc1\x95\x45\xd8\x3B" 93 | evm = EVM(code) 94 | evm.run() 95 | print(evm.stack) 96 | # output: 4 97 | ``` 98 | 99 | ## EXTCODECOPY 100 | 101 | `EXTCODECOPY` 指令用于将某个账户的部分代码复制到EVM的内存中。它会从堆栈中弹出4个参数(addr, mem_offset, code_offset, length),分别对应要查询的地址,写到内存的偏移量,读取代码的偏移量和长度。它的操作码是`0x3C`,gas由读取代码长度、内存扩展成本和地址是否为cold这三部分决定。 102 | 103 | ```python 104 | def extcodecopy(self): 105 | # 确保堆栈中有足够的数据 106 | if len(self.stack) < 4: 107 | raise Exception('Stack underflow') 108 | addr = self.stack.pop() 109 | mem_offset = self.stack.pop() 110 | code_offset = self.stack.pop() 111 | length = self.stack.pop() 112 | 113 | code = account_db.get(addr, {}).get('code', b'')[code_offset:code_offset+length] 114 | 115 | while len(self.memory) < mem_offset + length: 116 | self.memory.append(0) 117 | 118 | self.memory[mem_offset:mem_offset+length] = code 119 | ``` 120 | 121 | 我们可以尝试运行一个包含`EXTCODECOPY`指令的字节码:`60045F5F739bbfed6889322e016e0a02ee459d306fc19545d83C`(PUSH1 4 PUSH0 PUSH0 PUSH20 9bbfed6889322e016e0a02ee459d306fc19545d8 EXTCODECOPY)。这个字节码将4(`length`), 0(`code_offset`), 0(`mem_offset`), 地址(`addr`)分别推入堆栈,然后,然后使用`EXTCODECOPY`指令将代码复制到内存中。 122 | 123 | ```python 124 | # EXTCODECOPY 125 | code = b"\x60\x04\x5F\x5F\x73\x9b\xbf\xed\x68\x89\x32\x2e\x01\x6e\x0a\x02\xee\x45\x9d\x30\x6f\xc1\x95\x45\xd8\x3C" 126 | evm = EVM(code) 127 | evm.run() 128 | print(evm.memory.hex()) 129 | # output: 60006000 130 | ``` 131 | 132 | ## EXTCODEHASH 133 | 134 | `EXTCODEHASH` 指令返回某个账户的代码的Keccak256哈希值。它从堆栈中弹出一个地址,然后查询该地址代码的哈希并压入堆栈。它的操作码是`0x3F`,gas为2600(cold address)或100(warm address)。 135 | 136 | ```python 137 | import sha3 138 | 139 | def extcodehash(self): 140 | if len(self.stack) < 1: 141 | raise Exception('Stack underflow') 142 | addr_int = self.stack.pop() 143 | # 将stack中的int转换为bytes,然后再转换为十六进制字符串,用于在账户数据库中查询 144 | addr_str = '0x' + addr_int.to_bytes(20, byteorder='big').hex() 145 | code = account_db.get(addr_str, {}).get('code', b'') 146 | code_hash = int.from_bytes(sha3.keccak_256(code).digest(), 'big') # 计算哈希值 147 | self.stack.append(code_hash) 148 | ``` 149 | 150 | 我们可以尝试运行一个包含`EXTCODEHASH`指令的字节码:`739bbfed6889322e016e0a02ee459d306fc19545d83F`(PUSH20 9bbfed6889322e016e0a02ee459d306fc19545d8 EXTCODEHASH)。这个字节码使用`PUSH20`将一个地址推入堆栈,然后使用`EXTCODEHASH`指令查询该地址的代码哈希。 151 | 152 | ```python 153 | # EXTCODEHASH 154 | code = b"\x73\x9b\xbf\xed\x68\x89\x32\x2e\x01\x6e\x0a\x02\xee\x45\x9d\x30\x6f\xc1\x95\x45\xd8\x3F" 155 | evm = EVM(code) 156 | evm.run() 157 | print(hex(evm.stack[-1])) 158 | # output: 0x5e3ce470a8506d55e59815db7232a08774174ae0c7fdb2fbc81a49e4e242b0d6 159 | ``` 160 | 161 | ## 总结 162 | 163 | 这一讲,我们简单介绍了以太坊账户结构,并学习了与账户相关的一系列指令。这些指令使得合约可以与以太坊上的其他账户交互并获取相关信息,为合约间的交互提供了基础。目前,我们已经学习了144个操作码中的116个! 164 | -------------------------------------------------------------------------------- /14_TxOp/img/14-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/14_TxOp/img/14-1.png -------------------------------------------------------------------------------- /14_TxOp/img/14-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/14_TxOp/img/14-2.png -------------------------------------------------------------------------------- /14_TxOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 14. 交易指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 在这一讲,我们将探索EVM中与交易(Transaction)上下文相关的4个指令,包括`ADDRESS`, `ORIGIN`, `CALLER`等。我们能利用这些指令访问当前交易或调用者的信息。 14 | 15 | 16 | ## 交易的基本结构 17 | 18 | ![](./img/14-1.png) 19 | 20 | 在深入学习这些指令之前,让我们先了解以太坊交易的基本结构。每一笔以太坊交易都包含以下属性: 21 | 22 | - `nonce`:一个与发送者账户相关的数字,表示该账户已发送的交易数。 23 | - `gasPrice`:交易发送者愿意支付的单位gas价格。 24 | - `gasLimit`:交易发送者为这次交易分配的最大gas数量。 25 | - `to`:交易的接收者地址。当交易为合约创建时,这一字段为空。 26 | - `value`:以wei为单位的发送金额。 27 | - `data`:附带的数据,通常为合约调用的输入数据(calldata)或新合约的初始化代码(initcode)。 28 | ![](./img/14-2.png) 29 | - `v, r, s`:与交易签名相关的三个值。 30 | 31 | 在此基础上,我们可以在极简EVM中添加一个交易类,除了上述的信息以外,我们还把一些交易上下文分信息包含其中,包含当前调用者`caller`,原始发送者`origin`(签名者),和执行合约地址,`thisAddr`(Solidity中的`address(this)`): 32 | 33 | ```python 34 | class Transaction: 35 | def __init__(self, to = '', value = 0, data = '', caller='0x00', origin='0x00', thisAddr='0x00', gasPrice=1, gasLimit=21000, nonce=0, v=0, r=0, s=0): 36 | self.nonce = nonce 37 | self.gasPrice = gasPrice 38 | self.gasLimit = gasLimit 39 | self.to = to 40 | self.value = value 41 | self.data = data 42 | self.caller = caller 43 | self.origin = origin 44 | self.thisAddr = thisAddr 45 | self.v = v 46 | self.r = r 47 | self.s = s 48 | ``` 49 | 50 | 当初始化evm对象时,需要传入`Transaction`对象: 51 | 52 | ```python 53 | class EVM: 54 | def __init__(self, code, txn = None): 55 | 56 | # 初始化其他变量... 57 | 58 | self.txn = txn 59 | 60 | # 示例 61 | code = b"\x73\x9b\xbf\xed\x68\x89\x32\x2e\x01\x6e\x0a\x02\xee\x45\x9d\x30\x6f\xc1\x95\x45\xd8\x31" 62 | txn = Transaction(to='0x9bbfed6889322e016e0a02ee459d306fc19545d8', value=10, data='', caller='0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', origin='0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045') 63 | vm = EVM(code, txn) 64 | ``` 65 | 66 | ## 交易指令 67 | 68 | ### 1. ADDRESS 69 | 70 | - 操作码:`0x30` 71 | - gas消耗: 2 72 | - 功能:将当前执行合约的地址压入堆栈。 73 | - 使用场景:当合约需要知道自己的地址时使用。 74 | 75 | ```python 76 | def address(self): 77 | self.stack.append(self.txn.thisAddr) 78 | ``` 79 | 80 | ### 2. ORIGIN 81 | 82 | - 操作码:`0x32` 83 | - gas消耗: 2 84 | - 功能:将交易的原始发送者(即签名者)地址压入堆栈。 85 | - 使用场景:区分合约调用者与交易发起者。 86 | 87 | ```python 88 | def origin(self): 89 | self.stack.append(self.txn.origin) 90 | ``` 91 | 92 | ### 3. CALLER 93 | 94 | - 操作码:`0x33` 95 | - gas消耗: 2 96 | - 功能:将直接调用当前合约的地址压入堆栈。 97 | - 使用场景:当合约需要知道是谁调用了它时使用。 98 | 99 | ```python 100 | def caller(self): 101 | self.stack.append(self.txn.caller) 102 | ``` 103 | 104 | ### 4. CALLVALUE 105 | 106 | - 操作码:`0x34` 107 | - gas消耗: 2 108 | - 功能:将发送给合约的ether的数量(以wei为单位)压入堆栈。 109 | - 使用场景:当合约需要知道有多少以太币被发送时使用。 110 | 111 | ```python 112 | def callvalue(self): 113 | self.stack.append(self.txn.value) 114 | ``` 115 | 116 | ### 5. CALLDATALOAD 117 | 118 | - 操作码:`0x35` 119 | - gas消耗: 3 120 | - 功能:从交易或合约调用的`data`字段加载数据。它从堆栈中弹出calldata的偏移量(`offset`),然后从calldata的`offset`位置读取32字节的数据并压入堆栈。如果calldata剩余不足32字节,则补0。 121 | - 使用场景:读取传入的数据。 122 | 123 | ```python 124 | def calldataload(self): 125 | if len(self.stack) < 1: 126 | raise Exception('Stack underflow') 127 | offset = self.stack.pop() 128 | # 从字符形式转换为bytes数组 129 | calldata_bytes = bytes.fromhex(self.txn.data[2:]) # 假设由 '0x' 开头 130 | data = bytearray(32) 131 | # 复制calldata 132 | for i in range(32): 133 | if offset + i < len(calldata_bytes): 134 | data[i] = calldata_bytes[offset + i] 135 | self.stack.append(int.from_bytes(data, 'big')) 136 | ``` 137 | 138 | ### 6. CALLDATASIZE 139 | 140 | - 操作码:`0x36` 141 | - gas消耗:2 142 | - 功能:获取交易或合约调用的`data`字段的字节长度,并压入堆栈。 143 | - 使用场景:在读取数据之前检查大小。 144 | 145 | ```python 146 | def calldatasize(self): 147 | # Assuming calldata is a hex string with a '0x' prefix 148 | size = (len(self.transaction.data) - 2) // 2 149 | self.stack.append(size) 150 | ``` 151 | 152 | ### 7. CALLDATACOPY 153 | 154 | - 操作码:`0x37` 155 | - gas消耗:3 + 3 * 数据长度 + 内存扩展成本 156 | - 功能:将`data`中的数据复制到内存中。它会从堆栈中弹出3个参数(mem_offset, calldata_offset, length),分别对应写到内存的偏移量,读取calldata的偏移量和长度。 157 | - 使用场景:将输入数据复制到内存。 158 | 159 | ```python 160 | def calldatacopy(self): 161 | # 确保堆栈中有足够的数据 162 | if len(self.stack) < 3: 163 | raise Exception('Stack underflow') 164 | mem_offset = self.stack.pop() 165 | calldata_offset = self.stack.pop() 166 | length = self.stack.pop() 167 | 168 | # 拓展内存 169 | if len(self.memory) < mem_offset + length: 170 | self.memory.extend([0] * (mem_offset + length - len(self.memory))) 171 | 172 | # 从字符形式转换为bytes数组. 173 | calldata_bytes = bytes.fromhex(self.txn.data[2:]) # Assuming it's prefixed with '0x' 174 | 175 | # 将calldata复制到内存 176 | for i in range(length): 177 | if calldata_offset + i < len(calldata_bytes): 178 | self.memory[mem_offset + i] = calldata_bytes[calldata_offset + i] 179 | ``` 180 | 181 | ### 8. CODESIZE 182 | 183 | - 操作码:`0x38` 184 | - gas消耗: 2 185 | - 功能:获取当前合约代码的字节长度,然后压入堆栈。 186 | - 使用场景:当合约需要访问自己的字节码时使用。 187 | 188 | ```python 189 | def codesize(self): 190 | addr = self.txn.thisAddr 191 | self.stack.append(len(account_db.get(addr, {}).get('code', b''))) 192 | ``` 193 | 194 | ### 9. CODECOPY 195 | 196 | - 操作码:`0x39` 197 | - gas消耗:3 + 3 * 数据长度 + 内存扩展成本 198 | - 功能:复制合约的代码到EVM的内存中。它从堆栈中弹出三个参数:目标内存的开始偏移量(`mem_offset`)、代码的开始偏移量(`code_offset`)、以及要复制的长度(`length`)。 199 | - 使用场景:当合约需要读取自己的部分字节码时使用。 200 | 201 | ```python 202 | def codecopy(self): 203 | if len(self.stack) < 3: 204 | raise Exception('Stack underflow') 205 | 206 | mem_offset = self.stack.pop() 207 | code_offset = self.stack.pop() 208 | length = self.stack.pop() 209 | 210 | # 获取当前地址的code 211 | addr = self.txn.thisAddr 212 | code = account_db.get(addr, {}).get('code', b'') 213 | 214 | # 拓展内存 215 | if len(self.memory) < mem_offset + length: 216 | self.memory.extend([0] * (mem_offset + length - len(self.memory))) 217 | 218 | # 将代码复制到内存 219 | for i in range(length): 220 | if code_offset + i < len(code): 221 | self.memory[mem_offset + i] = code[code_offset + i] 222 | ``` 223 | 224 | ### 10. GASPRICE 225 | 226 | - 操作码:`0x3A` 227 | - gas消耗:2 228 | - 功能:获取交易的gas价格,并压入堆栈。 229 | - 使用场景:当合约需要知道当前交易的gas价格时使用。 230 | 231 | ```python 232 | def gasprice(self): 233 | self.stack.append(self.txn.gasPrice) 234 | ``` 235 | 236 | ## 总结 237 | 238 | 在这一讲,我们详细介绍了EVM中与交易有关的10个指令。这些指令为智能合约提供了与其环境交互的能力,使其能够访问调用者,calldata,和代码等信息。目前,我们已经学习了144个操作码中的126个! -------------------------------------------------------------------------------- /15_LogOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 15. Log指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们将介绍EVM中与日志(Log)相关的5个指令::从`LOG0`到`LOG4`。日志是EVM中一个重要的概念,用于记录与合约交互的重要信息,是智能合约中事件(Event)的基础。这些记录永久保存在在区块链上,方便检索,但不会影响区块链的状态,是DApps和智能合约开发者的一个强大工具。 14 | 15 | 16 | ## EVM中的日志和事件 17 | 18 | 在Solidity中,我们常常使用`event`来定义和触发事件。当这些事件被触发时,它们会生成日志,将数据永久存储在区块链上。日志分为主题(`topic`)和数据(`data`)。第一个主题通常是事件签名的哈希值,后面的主题是由`indexed`修饰的事件参数。如果你对`event`不了解,推荐阅读WTF Solidity的[相应章节](https://github.com/AmazingAng/WTF-Solidity/tree/main/12_Event)。 19 | 20 | EVM中的`LOG`指令用于创建这些日志。指令`LOG0`到`LOG4`的区别在于它们包含的主题数量。例如,`LOG0`没有主题,而`LOG4`有四个主题。 21 | 22 | 为了在我们的极简EVM中支持日志功能,我们首先需要定义一个`Log`类来表示一个日志条目,他会记录发出日志的合约地址`address`,数据部分`data`,和主体部分`topics`: 23 | 24 | ```python 25 | class Log: 26 | def __init__(self, address, data, topics=[]): 27 | self.address = address 28 | self.data = data 29 | self.topics = topics 30 | 31 | def __str__(self): 32 | return f'Log(address={self.address}, data={self.data}, topics={self.topics})' 33 | ``` 34 | 35 | 然后,我们需要在EVM的初始化函数中增加一个`logs`列表,记录这些日志: 36 | 37 | ```python 38 | class EVM: 39 | def __init__(self, code, txn = None): 40 | 41 | # ... 初始化其他变量 ... 42 | 43 | self.logs = [] 44 | ``` 45 | 46 | ## LOG指令 47 | 48 | EVM中有五个`Log`指令:`LOG0`、`LOG1`、`LOG2`、`LOG3`和`LOG4`。它们的主要区别在于携带的主题数(`topics`):`LOG0`没有主题,而`LOG4`有四个。操作码从`A0`到`A4`,gas消耗由以下公式计算: 49 | 50 | ```python 51 | gas = 375 + 375 * topic数量 + 内存扩展成本 52 | ``` 53 | 54 | `Log`指令从堆栈中弹出2 + n的元素。其中前两个参数是内存开始位置`mem_offset`和数据长度`length`,n是主题的数量(取决于具体的`LOG`指令)。所以对于`LOG1`,我们会从堆栈中弹出3个元素:内存开始位置,数据长度,和一个主题。需要`mem_offset`的原因是日志的数据(`data`)部分存储在内存中,gas消耗低,而主题(`topic`)部分直接存储在堆栈上。 55 | 56 | 接下来,我们实现`LOG`指令: 57 | 58 | ```python 59 | def log(self, num_topics): 60 | if len(self.stack) < 2 + num_topics: 61 | raise Exception('Stack underflow') 62 | 63 | mem_offset = self.stack.pop() 64 | length = self.stack.pop() 65 | topics = [self.stack.pop() for _ in range(num_topics)] 66 | 67 | data = self.memory[mem_offset:mem_offset + length] 68 | log_entry = { 69 | "address": self.txn.thisAddr, 70 | "data": data.hex(), 71 | "topics": [f"0x{topic:064x}" for topic in topics] 72 | } 73 | self.logs.append(log_entry) 74 | ``` 75 | 76 | 最后,我们需要在`run`方法中为不同的`LOG`指令添加支持: 77 | 78 | ```python 79 | def run(self): 80 | while self.pc < len(self.code): 81 | op = self.next_instruction() 82 | 83 | # ... 其他指令的处理 ... 84 | 85 | elif op == LOG0: 86 | self.log(0) 87 | elif op == LOG1: 88 | self.log(1) 89 | elif op == LOG2: 90 | self.log(2) 91 | elif op == LOG3: 92 | self.log(3) 93 | elif op == LOG4: 94 | self.log(4) 95 | ``` 96 | 97 | ## 测试 98 | 99 | ### 测试`LOG0` 100 | 101 | 我们运行一个包含`LOG0`指令的字节码:`60aa6000526001601fa0`(PUSH1 aa PUSH1 0 MSTORE PUSH1 1 PUSH1 1f LOG0)。这个字节码将`aa`存在内存中,然后使用`LOG0`指令将`aa`输出到日志的数据部分。 102 | 103 | ```python 104 | # LOG0 105 | code = b"\x60\xaa\x60\x00\x52\x60\x01\x60\x1f\xa0" 106 | evm = EVM(code, txn) 107 | evm.run() 108 | print(evm.logs) 109 | # output: [{'address': '0x9bbfed6889322e016e0a02ee459d306fc19545d8', 'data': 'aa', 'topics': []}] 110 | ``` 111 | 112 | ### 测试`LOG1` 113 | 114 | 我们运行一个包含`LOG1`指令的字节码:`60aa60005260116001601fa1`(PUSH1 aa PUSH1 0 MSTORE PUSH 11 PUSH1 1 PUSH1 1f LOG1)。这个字节码将`aa`存在内存,然后将`11`压入堆栈,最后使用`LOG1`指令将`aa`输出到日志的数据部分,将`11`输出到日志的主题部分。 115 | 116 | ```python 117 | # LOG1 118 | code = b"\x60\xaa\x60\x00\x52\x60\x11\x60\x01\x60\x1f\xa1" 119 | evm = EVM(code, txn) 120 | evm.run() 121 | print(evm.logs) 122 | # output: [{'address': '0x9bbfed6889322e016e0a02ee459d306fc19545d8', 'data': 'aa', 'topics': ['0x0000000000000000000000000000000000000000000000000000000000000011']}] 123 | ``` 124 | 125 | ## 总结 126 | 127 | 这一讲,我们学习了EVM中与日志和事件相关的5个指令。这些指令在智能合约开发中扮演着关键角色,允许开发者在区块链上永久记录重要信息,同时不会影响区块链的状态。目前,我们已经学习了144个操作码中的131个! 128 | -------------------------------------------------------------------------------- /16_ReturnOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 16. Return指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们将介绍EVM中与返回数据(return)相关的3个指令: `RETURN`,`RETURNDATASIZE`,和`RETURNDATACOPY`。它们是Solidity中`return`关键字的基础。 14 | 15 | ## 返回数据 16 | 17 | EVM的返回数据,通常称为`returnData`,本质上是一个字节数组。它不遵循固定的数据结构,而是简单地表示为连续的字节。当合约函数需要返回复杂数据类型(如结构体或数组)时,这些数据将按照ABI规范被编码为字节,并存储在`returnData`中,供其他函数或合约访问。 18 | 19 | 为了支持这一特性,我们需要为我们的简化版EVM添加一个新属性以保存返回数据: 20 | 21 | ```python 22 | class EVM: 23 | def __init__(self): 24 | # ... 其他属性 ... 25 | self.returnData = bytearray() 26 | ``` 27 | 28 | ## 返回相关指令 29 | 30 | ### 1. RETURN 31 | 32 | - **操作码**:`0xF3` 33 | - **gas消耗**:内存扩展成本。 34 | - **功能**:从指定的内存位置提取数据,存储到`returnData`中,并终止当前的操作。此指令需要从堆栈中取出两个参数:内存的起始位置`mem_offset`和数据的长度`length`。 35 | - **使用场景**:当需要将数据返回给外部函数或交易时。 36 | 37 | ```python 38 | def return_op(self): 39 | if len(self.stack) < 2: 40 | raise Exception('Stack underflow') 41 | 42 | mem_offset = self.stack.pop() 43 | length = self.stack.pop() 44 | 45 | # 拓展内存 46 | if len(self.memory) < mem_offset + length: 47 | self.memory.extend([0] * (mem_offset + length - len(self.memory))) 48 | 49 | self.returnData = self.memory[offset:offset + length] 50 | ``` 51 | 52 | ### 2. RETURNDATASIZE 53 | 54 | - **操作码**:`0x3D` 55 | - **gas消耗**:2 56 | - **功能**:将`returnData`的大小推入堆栈。 57 | - **使用场景**:使用上一个调用返回的数据。 58 | 59 | ```python 60 | def returndatasize(self): 61 | self.stack.append(len(self.returnData)) 62 | ``` 63 | 64 | ### 3. RETURNDATACOPY 65 | 66 | - 操作码:`0x3E` 67 | - gas消耗: 3 + 3 * 数据长度 + 内存扩展成本 68 | - **功能**:将`returnData`中的某段数据复制到内存中。此指令需要从堆栈中取出三个参数:内存的起始位置`mem_offset`,返回数据的起始位置`return_offset`,和数据的长度`length`。 69 | - **使用场景**:使用上一个调用返回的部分数据。 70 | 71 | ```python 72 | def returndatacopy(self): 73 | if len(self.stack) < 3: 74 | raise Exception('Stack underflow') 75 | 76 | mem_offset = self.stack.pop() 77 | return_offset = self.stack.pop() 78 | length = self.stack.pop() 79 | 80 | if return_offset + length > len(self.returnData): 81 | raise Exception("Invalid returndata size") 82 | 83 | # 扩展内存 84 | if len(self.memory) < mem_offset + length: 85 | self.memory.extend([0] * (mem_offset + length - len(self.memory))) 86 | 87 | # 使用切片进行复制 88 | self.memory[mem_offset:mem_offset + length] = self.returnData[return_offset:return_offset + length] 89 | ``` 90 | 91 | ## 测试 92 | 93 | 1. **RETURN**: 我们运行一个包含`RETURN`指令的字节码:`60a26000526001601ff3`(PUSH1 a2 PUSH1 0 MSTORE PUSH1 1 PUSH1 1f RETURN)。这个字节码将`a2`存在内存中,然后使用`RETURN`指令将`a2`复制到`returnData`中。 94 | 95 | 96 | ```python 97 | # RETURN 98 | code = b"\x60\xa2\x60\x00\x52\x60\x01\x60\x1f\xf3" 99 | evm = EVM(code) 100 | evm.run() 101 | print(evm.returnData.hex()) 102 | # output: a2 103 | ``` 104 | 105 | 2. **RETURNDATASIZE**:我们将`returnData`设为`aaaa`,然后用`RETURNDATASIZE`将它的长度压入堆栈。 106 | 107 | ```python 108 | # RETURNDATASIZE 109 | code = b"\x3D" 110 | evm = EVM(code) 111 | evm.returnData = b"\xaa\xaa" 112 | evm.run() 113 | print(evm.stack) 114 | # output: 2 115 | ``` 116 | 117 | 3. **RETURNDATACOPY**:我们将`returnData`设为`aaaa`,然后运行一个包含`RETURNDATACOPY`指令的字节码:`60025F5F3E`(PUSH1 2 PUSH0 PUSH0 RETURNDATACOPY),将返回数据存入内存。 118 | 119 | ```python 120 | # RETURNDATACOPY 121 | code = b"\x60\x02\x5F\x5F\x3E" 122 | evm = EVM(code) 123 | evm.returnData = b"\xaa\xaa" 124 | evm.run() 125 | print(evm.memory.hex()) 126 | # output: aaaa 127 | `````` 128 | 129 | ## 总结 130 | 131 | 这一讲,我们学习了EVM中与返回数据相关的3个指令:`RETURN`、`RETURNDATASIZE`,和`RETURNDATACOPY`,并通过代码示例为极简EVM添加了对这些指令的支持。目前,我们已经学习了144个操作码中的134个(93%)! -------------------------------------------------------------------------------- /17_RevertOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 17. Revert指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们将介绍EVM中与异常处理相关的2个指令: `REVERT` 和 `INVALID`。当它们被触发时,交易会回滚。 14 | 15 | ## 交易状态 16 | 17 | 我们需要在咱们的极简`evm`中跟踪交易的状态`success`,默认为`True`,当交易失败回滚时变为`False`,只有当`success`为`True`时继续执行opcodes,否则结束交易: 18 | 19 | ```python 20 | class EVM: 21 | def __init__(self): 22 | # ... 其他属性 ... 23 | self.success = True 24 | 25 | def run(self): 26 | while self.pc < len(self.code) and self.success: 27 | op = self.next_instruction() 28 | # ... 指令操作 ... 29 | ``` 30 | 31 | ## REVERT 32 | 33 | 当合约运行出错,或者达到了某种条件需要终止执行并返回错误信息时,可以使用`REVERT`指令。`REVERT`指令会终止交易的执行,返回一个错误消息,并且所有状态更改(例如资金转移、存储值的更改等)都不会生效。它会从堆栈中弹出两个参数:内存中错误消息的起始位置`mem_offset`和错误消息的长度`length`。它的操作码为`0xFD`,gas消耗为内存扩展消耗。 34 | 35 | ```python 36 | def revert(self): 37 | if len(self.stack) < 2: 38 | raise Exception('Stack underflow') 39 | mem_offset = self.stack.pop() 40 | length = self.stack.pop() 41 | 42 | # 拓展内存 43 | if len(self.memory) < mem_offset + length: 44 | self.memory.extend([0] * (mem_offset + length - len(self.memory))) 45 | 46 | self.returnData = self.memory[mem_offset:mem_offset+length] 47 | self.success = False 48 | ``` 49 | 50 | ## INVALID 51 | 52 | `INVALID`是EVM中用来表示无效操作的指令。当EVM遇到无法识别的操作码时,或者在故意触发异常的情境下,它会执行`INVALID`指令,导致所有状态更改都不会生效,并且消耗掉所有的gas。它确保了当合约试图执行未定义的操作时,不会无所作为或产生不可预测的行为,而是会安全地停止执行,对EVM的安全至关重要。它的操作码为`0xFE`,gas消耗为所有剩余的gas。 53 | 54 | ```python 55 | def invalid(self): 56 | self.success = False 57 | ``` 58 | 59 | ## 测试 60 | 61 | 1. **REVERT**: 我们运行一个包含`REVERT`指令的字节码:`60aa6000526001601ffd`(PUSH1 aa PUSH1 0 MSTORE PUSH1 1 PUSH1 1f REVERT)。这个字节码将`aa`存在内存中,然后使用`REVERT`指令将交易回滚,并将`aa`复制到`returnData`中。 62 | 63 | 64 | ```python 65 | # REVERT 66 | code = b"\x60\xa2\x60\x00\x52\x60\x01\x60\x1f\xfd" 67 | evm = EVM(code) 68 | evm.run() 69 | print(evm.returnData.hex()) 70 | # output: a2 71 | ``` 72 | 73 | ## 总结 74 | 75 | 这一讲,我们学习了EVM中与异常处理相关的2个指令:`REVERT`和`INVALID`,并通过代码示例为极简EVM添加了对这些指令的支持。异常处理是任何程序或合约执行的重要部分,而这两个指令是Solidity中的`require`,`error`和`assert`关键字的基础。目前,我们已经学习了144个操作码中的136个(94%)! 76 | -------------------------------------------------------------------------------- /18_CallOp/img/18-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/18_CallOp/img/18-1.png -------------------------------------------------------------------------------- /18_CallOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 18. Call指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们介绍EVM中的`CALL`指令。`CALL`指令可以被视为以太坊的核心,它允许合约之间进行交互,让区块链上的合约不再孤立。如果你不了解`CALL`指令,请参考[WTF Solidity教程第22讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/22_Call/readme.md)。 14 | 15 | ![](./img/18-1.png) 16 | 17 | ## CALL 指令 18 | 19 | `CALL`指令会创建一个子环境来执行其他合约的部分代码,发送`ETH`,并返回数据。返回数据可以使用`RETURNDATASIZE`和`RETURNDATACOPY`获取。若执行成功,会将`1`压入堆栈;否则,则压入`0`。如果目标合约没有代码,仍将`1`压入堆栈(视为成功)。如果账户`ETH`余额小于要发送的`ETH`数量,调用失败,但当前交易不会回滚。 20 | 21 | 它从堆栈中弹出7个参数,依次为: 22 | 23 | - `gas`:为这次调用分配的gas量。 24 | - `to`:被调用合约的地址。 25 | - `value`:要发送的以太币数量,单位为`wei`。 26 | - `mem_in_start`:输入数据(calldata)在内存的起始位置。 27 | - `mem_in_size`:输入数据的长度。 28 | - `mem_out_start`:返回数据(returnData)在内存的起始位置。 29 | - `mem_out_size`:返回数据的长度。 30 | 31 | 它的操作码为`0xF1`,gas消耗比较复杂,包含内存扩展和代码执行等成本。 32 | 33 | 下面,我们在极简EVM中支持`CALL`指令。由于`CALL`指令比较复杂,我们进行了一些简化,主要包括以下几个步骤:读取calldata,更新ETH余额,根据目标地址代码创建evm子环境,执行evm子环境代码,读取返回值。 34 | 35 | ```python 36 | def call(self): 37 | if len(self.stack) < 7: 38 | raise Exception('Stack underflow') 39 | 40 | gas = self.stack.pop() 41 | to_addr = self.stack.pop() 42 | value = self.stack.pop() 43 | mem_in_start = self.stack.pop() 44 | mem_in_size = self.stack.pop() 45 | mem_out_start = self.stack.pop() 46 | mem_out_size = self.stack.pop() 47 | 48 | # 拓展内存 49 | if len(self.memory) < mem_in_start + mem_in_size: 50 | self.memory.extend([0] * (mem_in_start + mem_in_size - len(self.memory))) 51 | 52 | # 从内存中获取输入数据 53 | data = self.memory[mem_in_start: mem_in_start + mem_in_size] 54 | 55 | account_source = account_db[self.txn.caller] 56 | account_target = account_db[hex(to_addr)] 57 | 58 | # 检查caller的余额 59 | if account_source['balance'] < value: 60 | self.success = False 61 | print("Insufficient balance for the transaction!") 62 | self.stack.append(0) 63 | return 64 | 65 | # 更新余额 66 | account_source['balance'] -= value 67 | account_target['balance'] += value 68 | 69 | # 使用txn构建上下文 70 | ctx = Transaction(to=hex(to_addr), 71 | data=data, 72 | value=value, 73 | caller=self.txn.thisAddr, 74 | origin=self.txn.origin, 75 | thisAddr=hex(to_addr), 76 | gasPrice=self.txn.gasPrice, 77 | gasLimit=self.txn.gasLimit, 78 | ) 79 | 80 | # 创建evm子环境 81 | evm_call = EVM(account_target['code'], ctx) 82 | evm_call.run() 83 | 84 | # 拓展内存 85 | if len(self.memory) < mem_out_start + mem_out_size: 86 | self.memory.extend([0] * (mem_out_start + mem_out_size - len(self.memory))) 87 | 88 | self.memory[mem_out_start: mem_out_start + mem_out_size] = evm_call.returnData 89 | 90 | if evm_call.success: 91 | self.stack.append(1) 92 | else: 93 | self.stack.append(0) 94 | ``` 95 | 96 | ## 测试 97 | 98 | 测试用的以太坊账户状态: 99 | ```python 100 | account_db = { 101 | '0x9bbfed6889322e016e0a02ee459d306fc19545d8': { 102 | 'balance': 100, # wei 103 | 'nonce': 1, 104 | 'storage': {}, 105 | 'code': b'' 106 | }, 107 | '0x1000000000000000000000000000000000000c42': { 108 | 'balance': 0, # wei 109 | 'nonce': 0, 110 | 'storage': {}, 111 | 'code': b'\x60\x42\x60\x00\x52\x60\x01\x60\x1f\xf3' # PUSH1 0x42 PUSH1 0 MSTORE PUSH1 1 PUSH1 31 RETURN 112 | }, 113 | 114 | # ... 其他账户数据 ... 115 | } 116 | ``` 117 | 118 | 在测试中,我们会使用第一个地址(`0x9bbf`起始)调用第二个地址(`0x1000`起始),运行上面的代码(`PUSH1 0x42 PUSH1 0 MSTORE PUSH1 1 PUSH1 31 RETURN`),成功的话会返回`0x42`。 119 | 120 | 测试字节码为`6001601f5f5f6001731000000000000000000000000000000000000c425ff15f51`(PUSH1 1 PUSH1 31 PUSH0 PUSH0 PUSH1 1 PUSH20 1000000000000000000000000000000000000c42 PUSH0 CALL PUSH0 MLOAD),它会调用第二个地址上的代码,并发送`1 wei`的以太坊,然后将内存中的返回值`0x42`压入堆栈。 121 | 122 | ```python 123 | # Call 124 | code = b"\x60\x01\x60\x1f\x5f\x5f\x60\x01\x73\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x42\x5f\xf1\x5f\x51" 125 | evm = EVM(code, txn) 126 | evm.run() 127 | print(hex(evm.stack[-2])) 128 | # output: 0x1 (success) 129 | print(hex(evm.stack[-1])) 130 | # output: 0x42 131 | ``` 132 | 133 | ## 总结 134 | 135 | 这一讲,我们探讨了`CALL`指令,它使得EVM上的合约可以调用其他合约,实现更复杂的功能。希望这一讲对您有所帮助!目前,我们已经学习了144个操作码中的137个(95%)! 136 | -------------------------------------------------------------------------------- /19_DelegatecallOp/img/19-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/19_DelegatecallOp/img/19-1.png -------------------------------------------------------------------------------- /19_DelegatecallOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 19. Delegatecall指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们介绍EVM中的`DELEGATECALL`指令和不再建议使用的`CALLCODE`指令。他们与`CALL`指令类似,但是调用的上下文不同。如果你不了解`DELEGATECALL`指令,请参考[WTF Solidity教程第23讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/23_Delegatecall/readme.md)。 14 | 15 | ![](./img/19-1.png) 16 | 17 | ## DELEGATECALL 18 | 19 | `DELEGATECALL`指令与`CALL`有许多相似之处,但关键的区别在于调用的上下文不同,它在代理合约和可升级合约中被广泛应用。它的设计目的是允许一个合约借用其他合约的代码,但代码是在原始合约的上下文中执行。这使得一份代码可以被多个合约重复使用,而无需重新部署。使用`DELEGATECALL`时`msg.sender`和`msg.value`保持不变,修改的存储变量也是原始合约的。 20 | 21 | 它从堆栈中弹出6个参数,与`CALL`不同,它不包括`value`,因为ETH不会被发送: 22 | 23 | - `gas`:为这次调用分配的gas量。 24 | - `to`:被调用合约的地址。 25 | - `mem_in_start`:输入数据(calldata)在内存的起始位置。 26 | - `mem_in_size`:输入数据的长度。 27 | - `mem_out_start`:返回数据(returnData)在内存的起始位置。 28 | - `mem_out_size`:返回数据的长度。 29 | 30 | ```python 31 | def delegatecall(self): 32 | if len(self.stack) < 6: 33 | raise Exception('Stack underflow') 34 | 35 | gas = self.stack.pop() 36 | to_addr = self.stack.pop() 37 | mem_in_start = self.stack.pop() 38 | mem_in_size = self.stack.pop() 39 | mem_out_start = self.stack.pop() 40 | mem_out_size = self.stack.pop() 41 | 42 | # 拓展内存 43 | if len(self.memory) < mem_in_start + mem_in_size: 44 | self.memory.extend([0] * (mem_in_start + mem_in_size - len(self.memory))) 45 | 46 | # 从内存中获取输入数据 47 | data = self.memory[mem_in_start: mem_in_start + mem_in_size] 48 | 49 | account_target = account_db[hex(to_addr)] 50 | 51 | # 创建evm子环境,注意,这里的上下文是原始的调用合约,而不是目标合约 52 | evm_delegate = EVM(account_target['code'], self.txn) 53 | evm_delegate.storage = self.storage 54 | # 运行代码 55 | evm_delegate.run() 56 | 57 | # 拓展内存 58 | if len(self.memory) < mem_out_start + mem_out_size: 59 | self.memory.extend([0] * (mem_out_start + mem_out_size - len(self.memory))) 60 | 61 | self.memory[mem_out_start: mem_out_start + mem_out_size] = evm_delegate.returnData 62 | 63 | if evm_delegate.success: 64 | self.stack.append(1) 65 | else: 66 | self.stack.append(0) 67 | print("Delegatecall execution failed!") 68 | ``` 69 | 70 | 有两个关键点需要注意: 71 | 72 | 1. `DELEGATECALL`不会更改`msg.sender`和`msg.value`。 73 | 2. `DELEGATECALL`改变的存储(storage)是原始合约的存储。 74 | 3. 与`CALL`不同,`DELEGATECALL`不会传递ETH值,因此少一个`value`参数。 75 | 76 | ## CALLCODE 77 | 78 | `CALLCODE`与`DELEGATECALL`非常相似,但当修改状态变量时,它会更改调用者的合约状态而不是被调用者的合约状态。由于这个原因,`CALLCODE`在某些情况下可能会引起意料之外的行为,目前被视为已弃用。建议大家使用`DELEGATECALL`,而不是`CALLCODE`。 79 | 80 | 我们根据[EIP-2488](https://eips.ethereum.org/EIPS/eip-2488),将`CALLCODE`视为已弃用:每次调用在堆栈中压入`0`(视为调用失败)。 81 | 82 | ```python 83 | def callcode(self): 84 | self.stack.append(0) 85 | print("Callcode not support!") 86 | ``` 87 | 88 | ## 测试 89 | 90 | 在测试中,我们会使用第一个地址(`0x9bbf`起始)调用第二个地址(`0x1000`起始),运行上面的代码(`PUSH1 0x42 PUSH1 0 MSTORE PUSH1 1 PUSH1 31 RETURN`),成功的话会返回`0x42`。 91 | 92 | 测试字节码为`6001601f5f5f731000000000000000000000000000000000000c425ff45f51`(PUSH1 1 PUSH1 31 PUSH0 PUSH0 PUSH20 1000000000000000000000000000000000000c42 PUSH0 DELEGATECALL PUSH0 MLOAD),它会调用第二个地址上的代码,然后将内存中的返回值`0x42`压入堆栈。 93 | 94 | ```python 95 | # Delegatecall 96 | code = b"\x60\x01\x60\x1f\x5f\x5f\x73\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x42\x5f\xf4\x5f\x51" 97 | evm = EVM(code, txn) 98 | evm.run() 99 | print(hex(evm.stack[-2])) 100 | # output: 0x1 (success) 101 | print(hex(evm.stack[-1])) 102 | # output: 0x42 103 | ``` 104 | 105 | ## 总结 106 | 107 | 这一讲,我们探讨了`DELEGATECALL`指令,它使得EVM上的合约在不更改上下文的情况下调用其他合约,增加代码的复用性。它在代理合约和可升级合约中被广泛应用。另外,我们还介绍了已被视为弃用的`CALLCODE`指令。目前,我们已经学习了144个操作码中的139个(96%)! 108 | -------------------------------------------------------------------------------- /20_StaticcallOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 20. Staticcall指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们介绍EVM中的`STATICCALL`指令,它和`CALL`指令类似,允许合约执行其他合约的代码,但是不能改变合约状态。它是Solidity中`pure`和`view`关键字的基础。 14 | 15 | ## STATICCALL 指令 16 | 17 | `STATICCALL`指令会创建一个子环境来执行其他合约的部分代码,并返回数据。返回数据可以使用`RETURNDATASIZE`和`RETURNDATACOPY`获取。若执行成功,会将`1`压入堆栈;否则,则压入`0`。如果目标合约没有代码,仍将`1`压入堆栈(视为成功)。 18 | 19 | 和`CALL`指令的不同,`STATICCALL`不能发送`ETH`,也不能改变合约的状态。它不允许子环境执行的代码中包含以下指令: 20 | 21 | - `CREATE`, `CREATE2`, `SELFDESTRUCT` 22 | - `LOG0` - `LOG4` 23 | - `SSTORE` 24 | - `value`不为0的`CALL` 25 | 26 | 它从堆栈中弹出6个参数,依次为: 27 | 28 | - `gas`:为这次调用分配的gas量。 29 | - `to`:被调用合约的地址。 30 | - `mem_in_start`:输入数据(calldata)在内存的起始位置。 31 | - `mem_in_size`:输入数据的长度。 32 | - `mem_out_start`:返回数据(returnData)在内存的起始位置。 33 | - `mem_out_size`:返回数据的长度。 34 | 35 | 它的操作码为`0xFA`,gas消耗为:内存扩展成本+地址操作成本。 36 | 37 | 下面,我们在极简evm中实现`STATICCALL`指令。首先,我们需要检查子环境的代码是否包含`STATICCALL`不支持的指令: 38 | 39 | ```python 40 | def is_state_changing_opcode(self, opcode): # 检查static call不能包含的opcodes 41 | state_changing_opcodes = [ 42 | 0xF0, # CREATE 43 | 0xF5, # CREATE2 44 | 0xFF, # SELFDESTRUCT 45 | 0xA0, # LOG0 46 | 0xA1, # LOG1 47 | 0xA2, # LOG2 48 | 0xA3, # LOG3 49 | 0xA4, # LOG4 50 | 0x55 # SSTORE 51 | ] 52 | return opcode in state_changing_opcodes 53 | ``` 54 | 55 | 然后在`init()`函数中初始化一个`is_static`状态,当它为`true`时,意味着执行的是`STATICCALL`,需要检查不支持的指令: 56 | 57 | ```python 58 | class EVM: 59 | def __init__(self, code, is_static=False): 60 | # ... 其他初始化 ... 61 | self.is_static = is_static 62 | 63 | def run(self): 64 | while self.pc < len(self.code) and self.success: 65 | op = self.next_instruction() 66 | 67 | if self.is_static and self.is_state_changing_opcode(op): 68 | self.success = False 69 | raise Exception("State changing operation detected during STATICCALL!") 70 | ``` 71 | 72 | 此外,对于不为0的value的CALL,我们需要稍作修改: 73 | 74 | ```python 75 | def call(self): 76 | 77 | if self.is_static and value != 0: 78 | self.success = False 79 | raise Exception("State changing operation detected during STATICCALL!") 80 | 81 | # ... 其他代码 ... 82 | ``` 83 | 84 | 最后,我们可以加入`staticcall`函数: 85 | 86 | ```python 87 | def staticcall(self): 88 | if len(self.stack) < 6: 89 | raise Exception('Stack underflow') 90 | 91 | gas = self.stack.pop() 92 | to_addr = self.stack.pop() 93 | mem_in_start = self.stack.pop() 94 | mem_in_size = self.stack.pop() 95 | mem_out_start = self.stack.pop() 96 | mem_out_size = self.stack.pop() 97 | 98 | # 拓展内存 99 | if len(self.memory) < mem_in_start + mem_in_size: 100 | self.memory.extend([0] * (mem_in_start + mem_in_size - len(self.memory))) 101 | 102 | # 从内存中获取输入数据 103 | data = self.memory[mem_in_start: mem_in_start + mem_in_size] 104 | 105 | account_target = account_db[hex(to_addr)] 106 | 107 | # 使用txn构建上下文 108 | ctx = Transaction(to=hex(to_addr), 109 | data=data, 110 | value=0, 111 | caller=self.txn.thisAddr, 112 | origin=self.txn.origin, 113 | thisAddr=hex(to_addr), 114 | gasPrice=self.txn.gasPrice, 115 | gasLimit=self.txn.gasLimit, 116 | ) 117 | 118 | # 创建evm子环境 119 | evm_staticcall = EVM(account_target['code'], ctx, is_static=True) 120 | # 运行代码 121 | evm_staticcall.run() 122 | 123 | # 拓展内存 124 | if len(self.memory) < mem_out_start + mem_out_size: 125 | self.memory.extend([0] * (mem_out_start + mem_out_size - len(self.memory))) 126 | 127 | self.memory[mem_out_start: mem_out_start + mem_out_size] = evm_staticcall.returnData 128 | 129 | if evm_staticcall.success: 130 | self.stack.append(1) 131 | else: 132 | self.stack.append(0) 133 | ``` 134 | 135 | ## 测试 136 | 137 | 在测试中,我们会使用第一个地址(`0x9bbf`起始)调用第二个地址(`0x1000`起始),运行上面的代码(`PUSH1 0x42 PUSH1 0 MSTORE PUSH1 1 PUSH1 31 RETURN`),成功的话会返回`0x42`。 138 | 139 | 测试字节码为`6001601f5f5f731000000000000000000000000000000000000c425ffA5f51`(PUSH1 1 PUSH1 31 PUSH0 PUSH0 PUSH20 1000000000000000000000000000000000000c42 PUSH0 STATICCALL PUSH0 MLOAD),它会调用第二个地址上的代码,然后将内存中的返回值`0x42`压入堆栈。 140 | 141 | ```python 142 | # Staticcall 143 | code = b"\x60\x01\x60\x1f\x5f\x5f\x73\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x42\x5f\xfA\x5f\x51" 144 | evm = EVM(code, txn) 145 | evm.run() 146 | print(hex(evm.stack[-2])) 147 | # output: 0x1 (success) 148 | print(hex(evm.stack[-1])) 149 | # output: 0x42 150 | ``` 151 | 152 | ## 总结 153 | 154 | 这一讲,我们探讨了`STATICCALL`指令,它提供了一种安全的方法来执行其他合约的代码,而不修改合约状态,是Solidity中`pure`和`view`关键字的基础。目前,我们已经学习了144个操作码中的140个(97%)! 155 | -------------------------------------------------------------------------------- /21_Create/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 21. Create指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们介绍EVM中的`CREATE`指令,它可以让合约创建新的合约。 14 | 15 | ## initcode 初始代码 16 | 17 | 之前我们提到过,以太坊有两种交易,一种是合约调用,而另一种是合约创建。在合约创建的交易中,`to`字段设为空,而`data`字段应填写为合约的初始代码(`initcode`)。`initcode`也是字节码,但它只在合约创建时执行一次,目的是为新合约设置必要的状态和返回最终的合约字节码(`contract code`)。 18 | 19 | 下面,我们看一个简单的`initcode`:`63ffffffff6000526004601cf3`。它的指令形式为: 20 | ``` 21 | PUSH4 ffffffff 22 | PUSH1 00 23 | MSTORE 24 | PUSH1 04 25 | PUSH1 1c 26 | RETURN 27 | ``` 28 | 29 | 它先用`MSTORE`指令把`ffffffff`拷贝到内存中,然后用`RETURN`指令将它拷贝到返回数据中。这段`initcode`会把新合约的字节码设置为`ffffffff`。 30 | 31 | 32 | ## CREATE 33 | 34 | 在EVM中,当一个合约想要创建一个新的合约时,会使用`CREATE`指令。它的简化流程: 35 | 36 | 1. 从堆栈中弹出`value`(向新合约发送的ETH)、`mem_offset`和`length`(新合约的`initcode`在内存中的初始位置和长度)。 37 | 2. 计算新合约的地址,计算方法为: 38 | ```python 39 | address = keccak256(rlp([sender_address,sender_nonce]))[12:] 40 | ``` 41 | 3. 更新ETH余额。 42 | 4. 初始化新的EVM上下文`evm_create`,用于执行`initcode`。 43 | 5. 在`evm_create`中执行`initcode`。 44 | 6. 如果执行成功,则更新创建的账户状态:更新`balance`,将`nonce`初始化为`0`,将`code`字段设为`evm_create`的返回数据,将`storage`字段设置为`evm_create`的`storage`。 45 | 7. 如果成功,则将新合约地址推入堆栈;若失败,将`0`推入堆栈。 46 | 47 | 下面,我们在极简EVM中实现`CREATE`指令: 48 | 49 | ```python 50 | def create(self): 51 | if len(self.stack) < 3: 52 | raise Exception('Stack underflow') 53 | 54 | # 弹出堆栈数据 55 | value = self.stack.pop() 56 | mem_offset = self.stack.pop() 57 | length = self.stack.pop() 58 | 59 | # 扩展内存 60 | if len(self.memory) < mem_offset + length: 61 | self.memory.extend([0] * (mem_offset + length - len(self.memory))) 62 | 63 | # 获取初始化代码 64 | init_code = self.memory[mem_offset: mem_offset + length] 65 | 66 | # 检查创建者的余额是否足够 67 | creator_account = account_db[self.txn.thisAddr] 68 | if creator_account['balance'] < value: 69 | raise Exception('Insufficient balance to create contract!') 70 | 71 | # 为创建者扣除指定的金额 72 | creator_account['balance'] -= value 73 | 74 | # 生成新的合约地址(参考geth中的方式,使用创建者的地址和nonce) 75 | creator_nonce = creator_account['nonce'] 76 | new_contract_address_bytes = sha3.keccak_256(self.txn.thisAddr.encode() + str(creator_nonce).encode()).digest() 77 | new_contract_address = '0x' + new_contract_address_bytes[-20:].hex() # 取后20字节作为地址 78 | 79 | # 使用txn构建上下文 80 | ctx = Transaction(to=new_contract_address, 81 | data=init_code, 82 | value=value, 83 | caller=self.txn.thisAddr, 84 | origin=self.txn.origin, 85 | thisAddr=new_contract_address, 86 | gasPrice=self.txn.gasPrice, 87 | gasLimit=self.txn.gasLimit) 88 | 89 | # 创建并运行新的EVM实例 90 | evm_create = EVM(init_code, ctx) 91 | evm_create.run() 92 | 93 | # 如果EVM实例返回错误,压入0,表示创建失败 94 | if evm_create.success == False: 95 | self.stack.append(0) 96 | return 97 | 98 | # 更新创建者的nonce 99 | creator_account['nonce'] += 1 100 | 101 | # 存储合约的状态 102 | account_db[new_contract_address] = { 103 | 'balance': value, 104 | 'nonce': 0, # 新合约的nonce从0开始 105 | 'storage': evm_create.storage, 106 | 'code': evm_create.returnData 107 | } 108 | 109 | # 压入新创建的合约地址 110 | self.stack.append(int(new_contract_address, 16)) 111 | ``` 112 | 113 | ## 测试 114 | 115 | 1. 使用`CREATE`指令部署一个新合约,发送`9` wei,但不部署任何代码: 116 | ```python 117 | # CREATE (empty code, 9 wei balance) 118 | code = b"\x5f\x5f\x60\x09\xf0" 119 | evm = EVM(code, txn) 120 | evm.run() 121 | print(hex(evm.stack[-1])) 122 | # output: 0x260144093a2920f68e1ae2e26b3bd15ddd610dfe 123 | print(account_db[hex(evm.stack[-1])]) 124 | # output: {'balance': 9, 'nonce': 0, 'storage': {}, 'code': bytearray(b'')} 125 | ``` 126 | 127 | 2. 使用`CREATE`指令部署一个新合约,并将代码设置为`ffffffff`: 128 | 129 | ```python 130 | # CREATE (with 4x FF) 131 | code = b"\x6c\x63\xff\xff\xff\xff\x60\x00\x52\x60\x04\x60\x1c\xf3\x60\x00\x52\x60\x0d\x60\x13\x60\x00\xf0" 132 | # PUSH13 0x63ffffffff6000526004601cf3 PUSH1 0x00 MSTORE PUSH1 0x0d PUSH1 0x19 PUSH1 0x00 CREATE 133 | evm = EVM(code, txn) 134 | evm.run() 135 | print(hex(evm.stack[-1])) 136 | # output: 0x6dddd3288a19f0bf4eee7bfb9e168ad29e1395d0 137 | print(account_db[hex(evm.stack[-1])]) 138 | # {'balance': 0, 'nonce': 0, 'storage': {}, 'code': bytearray(b'\xff\xff\xff\xff')} 139 | ``` 140 | 141 | ## 总结 142 | 143 | 这一讲,我们介绍了`EVM`中创建合约的指令`CREATE`,通过它,合约可以创造其他合约,从而实现更为复杂的逻辑和功能。我们已经学习了144个操作码中的141个(98%)! 144 | -------------------------------------------------------------------------------- /22_Create2/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 22. Create2指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 上一讲我们介绍了`CREATE`指令,使合约有能力创建其他合约。这一讲,我们将进一步探讨`CREATE2`指令,它提供了一种新的方式来确定新合约的地址。 14 | 15 | ## CREATE vs CREATE2 16 | 17 | 传统的`CREATE`指令通过调用者的地址和nonce来确定新合约的地址,而`CREATE2`则提供了一种新的计算方法,使我们可以在合约部署之前预知它的地址。 18 | 19 | 与`CREATE`不同,`CREATE2`使用调用者地址、盐(一个自定义的256位的值)以及`initcode`的哈希来确定新合约的地址,计算方法如下: 20 | 21 | ```python 22 | address = keccak256( 0xff + sender_address + salt + keccak256(init_code))[12:] 23 | ``` 24 | 25 | 这样的好处是,只要你知道`initcode`,盐值和发送者的地址,就可以预先知道新合约的地址,而不需要现在部署它。而`CREATE`计算的地址取决于部署账户的`nonce`,也就是说,在`nonce`不确定的情况下(合约还未部署,nonce可能会增加),没法确定新合约的地址。 26 | 27 | 对`CREATE2`的更多介绍可以参考[WTF Solidity教程第25讲](https://github.com/AmazingAng/WTF-Solidity/blob/main/25_Create2/readme.md)。 28 | 29 | ## CREATE2 30 | 31 | 在EVM中,`CREATE2`指令的简化流程如下: 32 | 33 | 1. 从堆栈中弹出`value`(向新合约发送的ETH)、`mem_offset`、`length`(新合约的`initcode`在内存中的初始位置和长度)以及`salt`。 34 | 2. 使用上面的公式计算新合约的地址。 35 | 3. 之后的步骤同`CREATE`指令:初始化新的EVM上下文、执行`initcode`、更新创建的账户状态、返回新合约地址或`0`(如果失败)。 36 | 37 | 下面,我们在极简EVM中实现`CREATE2`指令: 38 | 39 | ```python 40 | def create2(self): 41 | if len(self.stack) < 4: 42 | raise Exception('Stack underflow') 43 | 44 | value = self.stack.pop() 45 | mem_offset = self.stack.pop() 46 | length = self.stack.pop() 47 | salt = self.stack.pop() 48 | 49 | # 扩展内存 50 | if len(self.memory) < mem_offset + length: 51 | self.memory.extend([0] * (mem_offset + length - len(self.memory))) 52 | 53 | # 获取初始化代码 54 | init_code = self.memory[mem_offset: mem_offset + length] 55 | 56 | # 检查创建者的余额是否足够 57 | creator_account = account_db[self.txn.thisAddr] 58 | if creator_account['balance'] < value: 59 | raise Exception('Insufficient balance to create contract!') 60 | 61 | # 为创建者扣除指定的金额 62 | creator_account['balance'] -= value 63 | 64 | # 生成新的合约地址(参考geth中的方式,使用盐和initcode的hash) 65 | init_code_hash = sha3.keccak_256(init_code).digest() 66 | data_to_hash = b'\xff' + self.txn.thisAddr.encode() + str(salt).encode() + init_code_hash 67 | new_contract_address_bytes = sha3.keccak_256(data_to_hash).digest() 68 | new_contract_address = '0x' + new_contract_address_bytes[-20:].hex() # 取后20字节作为地址 69 | 70 | # 使用txn构建上下文并执行 71 | ctx = Transaction(to=new_contract_address, 72 | data=init_code, 73 | value=value, 74 | caller=self.txn.thisAddr, 75 | origin=self.txn.origin, 76 | thisAddr=new_contract_address, 77 | gasPrice=self.txn.gasPrice, 78 | gasLimit=self.txn.gasLimit) 79 | evm_create2 = EVM(init_code, ctx) 80 | evm_create2.run() 81 | 82 | # 如果EVM实例返回错误,压入0,表示创建失败 83 | if evm_create2.success == False: 84 | self.stack.append(0) 85 | return 86 | 87 | # 更新创建者的nonce 88 | creator_account['nonce'] += 1 89 | 90 | # 存储合约的状态 91 | account_db[new_contract_address] = { 92 | 'balance': value, 93 | 'nonce': 0, 94 | 'storage': evm_create2.storage, 95 | 'code': evm_create2.returnData 96 | } 97 | 98 | # 压入新创建的合约地址 99 | self.stack.append(int(new_contract_address, 16)) 100 | ``` 101 | 102 | ## 测试 103 | 104 | 1. 使用`CREATE2`指令部署一个新合约,发送`9` wei,但不部署任何代码: 105 | ```python 106 | # CREATE2 (empty code, 9 wei balance) 107 | code = b"\x5f\x5f\x5f\x60\x09\xf5" 108 | # PUSH0 PUSH0 PUSH0 PUSH1 0x09 CREATE2 109 | evm = EVM(code, txn) 110 | evm.run() 111 | print(hex(evm.stack[-1])) 112 | # output: 0x260144093a2920f68e1ae2e26b3bd15ddd610dfe 113 | print(account_db[hex(evm.stack[-1])]) 114 | # output: {'balance': 9, 'nonce': 0, 'storage': {}, 'code': bytearray(b'')} 115 | ``` 116 | 117 | 2. 使用`CREATE2`指令部署一个新合约,并将代码设置为`ffffffff`: 118 | 119 | ```python 120 | # CREATE2 (with 4x FF) 121 | code = b"\x6c\x63\xff\xff\xff\xff\x60\x00\x52\x60\x04\x60\x1c\xf3\x60\x00\x52\x60\x00\x60\x0d\x60\x13\x60\x00\xf5" 122 | # PUSH13 0x63ffffffff6000526004601cf3 PUSH1 0x00 MSTORE PUSH1 0x00 PUSH1 0x0d PUSH1 0x13 PUSH1 0x00 CREATE2 123 | evm = EVM(code, txn) 124 | evm.run() 125 | print(hex(evm.stack[-1])) 126 | # output: 0x6dddd3288a19f0bf4eee7bfb9e168ad29e1395d0 127 | print(account_db[hex(evm.stack[-1])]) 128 | # {'balance': 0, 'nonce': 0, 'storage': {}, 'code': bytearray(b'\xff\xff\xff\xff')} 129 | ``` 130 | 131 | ## 总结 132 | 133 | 这一讲,我们介绍了`EVM`中创建合约的另一个指令,`CREATE2`,通过它,合约不仅可以创造其他合约,而且可以预知新合约的地址。`Uniswap v2`中的LP地址就是用这个方法计算的。现在,我们已经学习了144个操作码中的142个(98.6%)! 134 | -------------------------------------------------------------------------------- /23_SelfdestructOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 23. Selfdestruct指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们介绍EVM中的`SELFDESTRUCT`指令,它可以让合约自毁。这个指令可能在未来会被弃用,见[EIP-4758](https://eips.ethereum.org/EIPS/eip-4758)和[EIP-6049](https://eips.ethereum.org/EIPS/eip-6049)。 14 | 15 | ## 基本概念 16 | 17 | EVM中的`SELFDESTRUCT`指令可以让合约自行销毁,并将账户中的ETH余额发送到指定地址。这个指令一些特殊的地方: 18 | 19 | 1. 使用`SELFDESTRUCT`指令时,当前合约会被标记为待销毁。但实际的销毁操作会在整个交易完成后进行。 20 | 2. 合约的`ETH`余额会被发送到指定的地址,并且这一过程保证会成功的。 21 | 3. 如果指定的地址是一个合约,那么该合约的代码不会被执行,即不会像平常的`ETH`转账执行目标地址的`fallback`方法。 22 | 4. 如果指定的地址不存在,则会为其创建一个新的账户,并存储这些ETH。 23 | 5. 一旦合约被销毁,其代码和数据都会永久地从链上删除,无法恢复。销毁合约可能会影响到与它互动的其他合约或服务。 24 | 25 | `SELFDESTRUCT`指令的工作流程如下: 26 | 27 | 1. 从堆栈中弹出接收`ETH`的指定地址。 28 | 2. 将当前合约的余额转移到指定地址。 29 | 3. 销毁合约。 30 | 31 | 下面,我们在极简EVM中实现`SELFDESTRUCT`指令: 32 | 33 | ```python 34 | def selfdestruct(self): 35 | if len(self.stack) < 1: 36 | raise Exception('Stack underflow') 37 | 38 | # 弹出接收ETH的指定地址 39 | raw_recipient = self.stack.pop() 40 | recipient = '0x' + format(raw_recipient, '040x') # 转化为0x前缀的40个十六进制字符 41 | 42 | # 如果地址不存在,则创建它 43 | if recipient not in account_db: 44 | account_db[recipient] = {'balance': 0, 'nonce': 0, 'storage': {}, 'code': bytearray(b'')} 45 | 46 | # 将合约的余额转移到接收者账户 47 | account_db[recipient]['balance'] += account_db[self.txn.thisAddr]['balance'] 48 | 49 | # 从数据库中删除合约 50 | del account_db[self.txn.thisAddr] 51 | ``` 52 | 53 | ## 测试 54 | 55 | 1. 自毁当前合约,并将余额转移到新地址。 56 | 57 | ```python 58 | # Define Txn 59 | addr = '0x1000000000000000000000000000000000000c42' 60 | txn = Transaction(to=None, value=10, 61 | caller=addr, origin=addr, thisAddr=addr) 62 | 63 | # SELFDESTRUCT 64 | # delete account: 0x1000000000000000000000000000000000000c42 65 | print("自毁前: ", account_db) 66 | # 自毁前: {'0x9bbfed6889322e016e0a02ee459d306fc19545d8': {'balance': 100, 'nonce': 1, 'storage': {}, 'code': b''}, '0x1000000000000000000000000000000000000c42': {'balance': 10, 'nonce': 0, 'storage': {}, 'code': b'`B`\x00R`\x01`\x1f\xf3'}} 67 | 68 | code = b"\x60\x20\xff" # PUSH1 0x20 (destination address) SELFDESTRUCT 69 | evm = EVM(code, txn) 70 | evm.run() 71 | print("自毁后: ", account_db) 72 | # 自毁后: {'0x9bbfed6889322e016e0a02ee459d306fc19545d8': {'balance': 100, 'nonce': 1, 'storage': {}, 'code': b''}, '0x0000000000000000000000000000000000000020': {'balance': 10, 'nonce': 0, 'storage': {}, 'code': bytearray(b'')}} 73 | ``` 74 | 75 | ## 总结 76 | 77 | 这一讲,我们介绍了EVM中销毁合约的`SELFDESTRUCT`指令,它可以自毁合约并且将其剩余的`ETH`强行发送到另一个地址。该指令将会在未来被弃用,大家尽量不要使用。现在,我们已经学习了144个操作码中的143个(99%),仅剩一个了! -------------------------------------------------------------------------------- /24_GasOp/readme.md: -------------------------------------------------------------------------------- 1 | # WTF Opcodes极简入门: 24. Gas指令 2 | 3 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 4 | 5 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 6 | 7 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 8 | 9 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 10 | 11 | ----- 12 | 13 | 这一讲,我们介绍EVM中的`GAS`指令,并介绍以太坊的Gas机制。 14 | 15 | ## 什么是Gas 16 | 17 | 在EVM中,交易和执行智能合约需要消耗计算资源。为了防止用户恶意的滥用网络资源和补偿验证者所消耗的计算能源,以太坊引入了一种称为Gas的计费机制,使每一笔交易都有一个关联的成本。 18 | 19 | 在发起交易时,用户设定一个最大Gas数量(`gasLimit`)和每单位Gas的价格(`gasPrice`)。如果交易执行超出了`gasLimit`,交易会回滚,但已消耗的Gas不会退还。 20 | 21 | ## Gas规则 22 | 23 | 以太坊上的Gas用`gwei`衡量,它是`ETH`的子单位,`1 ETH = 10^9 gwei`。一笔交易的Gas成本等于每单位gas价格乘以交易的gas消耗,即`gasPrice * gasUsed`。gas价格会随着时间的推移而变化,具体取决于当前对区块空间的需求。gas消耗由很多因素决定,并且每个以太坊版本都会有所改动,下面总结下: 24 | 25 | 1. `calldata`大小:`calldata`中的每个字节都需要花费gas,交易数据的大小越大,gas消耗就越高。`calldata`每个零字节花费`4` Gas,每个非零字节花费`16` Gas(伊斯坦布尔硬分叉之前为 64 个)。 26 | 27 | 2. 内在gas:每笔交易的内在成本为21000 Gas。除了交易成本之外,创建合约还需要 32000 Gas。该成本是在任何操作码执行之前从交易中支付的。 28 | 29 | 3. `opcode`固定成本:每个操作码在执行时都有固定的成本,以Gas为单位。对于所有执行,该成本都是相同的。比如每个`ADD`指令消耗`3` Gas。 30 | 31 | 4. `opcode`动态成本:一些指令消耗更多的计算资源取决于其参数。因此,除了固定成本之外,这些指令还具有动态成本。比如`SHA3`指令消耗的Gas随参数长度增长。 32 | 33 | 5. 内存拓展成本:在EVM中,合约可以使用操作码访问内存。当首次访问特定偏移量的内存(读取或写入)时,内存可能会触发扩展,产生gas消耗。比如`MLOAD`或`RETURN`。 34 | 35 | 6. 访问集成本:对于每个外部交易,EVM会定义一个访问集,记录交易过程中访问过的合约地址和存储槽(slot)。访问成本根据数据是否已经被访问过(热)或是首次被访问(冷)而有所不同。 36 | 37 | 7. Gas退款:`SSTORE`的一些操作(比如清除存储)可以触发Gas退款。退款会在交易结束时执行,上限为总Gas消耗的20%(从伦敦硬分叉开始)。 38 | 39 | 更详细的Gas消耗信息可以参考[evm.codes](https://www.evm.codes/)。 40 | 41 | ## GAS指令 42 | 43 | EVM中的`GAS`指令会将当前交易的剩余`Gas`压入堆栈。它的操作码为`0x5A`,gas消耗为`2`。 44 | 45 | ```python 46 | def gas(self): 47 | self.stack.append(self.txn.gasLimit - self.gasUsed) 48 | ``` 49 | 50 | 下面,我们在极简EVM中实现`GAS`。出于教学目的,目前我们仅实现部分`opcode`的固定成本,其他的未来实现。 51 | 52 | 首先,我们需要在`EVM`中添加一个`gasUsed`属性,用于记录已经消耗的Gas: 53 | 54 | ```python 55 | class EVM: 56 | def __init__(self, ...): 57 | # ... 其他属性 ... 58 | self.gasUsed = 0 59 | 60 | ``` 61 | 62 | 接下来,我们需要定义每个指令的固定成本: 63 | 64 | ```python 65 | # 固定成本 66 | GAS_COSTS = { 67 | 'PUSH': 3, 68 | 'POP': 2, 69 | 'ADD': 3, 70 | 'MUL': 5, 71 | 'SUB': 3, 72 | # ... 其他操作码的固定成本 ... 73 | } 74 | ``` 75 | 76 | 77 | 在每一个操作码的实现中更新Gas消耗,比如`PUSH`指令: 78 | ```python 79 | def push(self, size): 80 | data = self.code[self.pc:self.pc + size] # 按照size从code中获取数据 81 | value = int.from_bytes(data, 'big') # 将bytes转换为int 82 | self.stack.append(value) # 压入堆栈 83 | self.pc += size # pc增加size单位 84 | self.gasUsed += GAS_COSTS['PUSH'] # 更新Gas消耗 85 | ``` 86 | 87 | 最后,在每个操作码执行后检查Gas是否被耗尽: 88 | 89 | ```python 90 | def run(self): 91 | while self.pc < len(self.code) and self.success: 92 | op = self.next_instruction() 93 | # ... opcode执行逻辑 ... 94 | 95 | # 检查gas是否耗尽 96 | if self.gasUsed > self.txn.gasLimit: 97 | raise Exception('Out of gas!') 98 | ``` 99 | 100 | ## 测试 101 | 102 | ```python 103 | # Define Txn 104 | addr = '0x1000000000000000000000000000000000000c42' 105 | txn = Transaction(to=None, value=10, data='', 106 | caller=addr, origin=addr, thisAddr=addr, gasLimit=100, gasPrice=1) 107 | 108 | # GAS 109 | code = b"\x60\x20\x5a" # PUSH1 0x20 GAS 110 | evm = EVM(code, txn) 111 | evm.run() 112 | print(evm.stack) 113 | # output: [32, 97] 114 | # gasLimit=100,gasUsed=3 115 | ``` 116 | 117 | ## 总结 118 | 119 | 这一讲,我们介绍了以太坊的Gas机制以及`GAS`操作码。Gas机制确保了以太坊网络的计算资源不被恶意代码滥用。通过`GAS`指令,智能合约可以实时地查询还剩下多少Gas,从而做出相应的决策。 120 | 121 | 至此,EVM中的144个操作码我们全部学习完了!相信你对EVM的理解一定有了质的飞跃,恭喜你! -------------------------------------------------------------------------------- /25_MinimalProxy/Clone0Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | pragma solidity ^0.8.20; 3 | 4 | // Note: this contract requires `PUSH0`, which is available in solidity > 0.8.20 and EVM version > Shanghai 5 | contract Clone0Factory { 6 | error FailedCreateClone(); 7 | 8 | receive() external payable {} 9 | 10 | /** 11 | * @dev Deploys and returns the address of a clone0 (Minimal Proxy Contract with `PUSH0`) that mimics the behaviour of `implementation`. 12 | * 13 | * This function uses the create opcode, which should never revert. 14 | */ 15 | function clone0(address impl) public payable returns (address addr) { 16 | // first 18 bytes of the creation code 17 | bytes memory data1 = hex"602c8060095f395ff3365f5f375f5f365f73"; 18 | // last 15 bytes of the creation code 19 | bytes memory data2 = hex"5af43d5f5f3e5f3d91602a57fd5bf3"; 20 | // complete the creation code of Clone0 21 | bytes memory _code = abi.encodePacked(data1, impl, data2); 22 | 23 | // deploy with create op 24 | assembly { 25 | // create(v, p, n) 26 | addr := create(callvalue(), add(_code, 0x20), mload(_code)) 27 | } 28 | 29 | if (addr == address(0)) { 30 | revert FailedCreateClone(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /25_MinimalProxy/img/25-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/25_MinimalProxy/img/25-1.gif -------------------------------------------------------------------------------- /25_MinimalProxy/img/25-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/25_MinimalProxy/img/25-2.png -------------------------------------------------------------------------------- /25_MinimalProxy/img/25-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/25_MinimalProxy/img/25-3.png -------------------------------------------------------------------------------- /25_MinimalProxy/img/25-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-EVM-Opcodes/23bef5cb4fb4b4d1c5b73117a77cb1fdb40482e6/25_MinimalProxy/img/25-4.png -------------------------------------------------------------------------------- /25_MinimalProxy/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 25. 优化最小代理合约 EIP-7511 3 | tags: 4 | - opcode 5 | - evm 6 | - gas 7 | - eip1677 8 | - proxy 9 | - delegatecall 10 | - push0 11 | --- 12 | 13 | # WTF Opcodes极简入门: 25. 优化最小代理合约 14 | 15 | 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 16 | 17 | 推特:[@0xAA_Science](https://twitter.com/0xAA_Science) 18 | 19 | 社区:[Discord](https://discord.gg/5akcruXrsk)|[微信群](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[官网 wtf.academy](https://wtf.academy) 20 | 21 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-Opcodes](https://github.com/WTFAcademy/WTF-Opcodes) 22 | 23 | ----- 24 | 25 | 这一讲,我们将综合应用之前所学的内容,用`PUSH0`指令优化[EIP-1167](https://eips.ethereum.org/EIPS/eip-1167)最小代理合约(Minimal Proxy Contract),减少合约长度并降低gas。 26 | 27 | ![](./img/25-1.gif) 28 | 29 | ## 最小代理合约 30 | 31 | 当人们需要反复部署同一个合约时,比如每个用户都需要部署一遍抽象账户合约,[代理合约](https://github.com/AmazingAng/WTF-Solidity/tree/main/46_ProxyContract)是最好的解决办法。在这个模式下,复杂的逻辑合约可以被重复利用,用户只需要部署一个简单的代理合约,从而降低gas成本。 32 | 33 | ![](./img/25-2.png) 34 | 35 | 由于代理合约会被用户重复部署,因此我们必须要优化它。在[WTF Solidity教程第46讲](https://github.com/AmazingAng/WTF-Solidity/tree/main/46_ProxyContract)我们用Solidity写了一个代理合约,在没有经过任何优化的情况下,它的合约`bytecode`有`573`字节。 36 | 37 | ```solidity 38 | // SPDX-License-Identifier: MIT 39 | // wtf.academy 40 | pragma solidity ^0.8.4; 41 | 42 | /** 43 | * @dev Proxy合约的所有调用都通过`delegatecall`操作码委托给另一个合约执行。后者被称为逻辑合约(Implementation)。 44 | * 45 | * 委托调用的返回值,会直接返回给Proxy的调用者 46 | */ 47 | contract Proxy { 48 | address public implementation; // 逻辑合约地址。implementation合约同一个位置的状态变量类型必须和Proxy合约的相同,不然会报错。 49 | 50 | /** 51 | * @dev 初始化逻辑合约地址 52 | */ 53 | constructor(address implementation_){ 54 | implementation = implementation_; 55 | } 56 | 57 | /** 58 | * @dev 回调函数,调用`_delegate()`函数将本合约的调用委托给 `implementation` 合约 59 | */ 60 | fallback() external payable { 61 | _delegate(); 62 | } 63 | 64 | /** 65 | * @dev 将调用委托给逻辑合约运行 66 | */ 67 | function _delegate() internal { 68 | assembly { 69 | // Copy msg.data. We take full control of memory in this inline assembly 70 | // block because it will not return to Solidity code. We overwrite the 71 | // 读取位置为0的storage,也就是implementation地址。 72 | let _implementation := sload(0) 73 | 74 | calldatacopy(0, 0, calldatasize()) 75 | 76 | // 利用delegatecall调用implementation合约 77 | // delegatecall操作码的参数分别为:gas, 目标合约地址,input mem起始位置,input mem长度,output area mem起始位置,output area mem长度 78 | // output area起始位置和长度位置,所以设为0 79 | // delegatecall成功返回1,失败返回0 80 | let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0) 81 | 82 | // 将起始位置为0,长度为returndatasize()的returndata复制到mem位置0 83 | returndatacopy(0, 0, returndatasize()) 84 | 85 | switch result 86 | // 如果delegate call失败,revert 87 | case 0 { 88 | revert(0, returndatasize()) 89 | } 90 | // 如果delegate call成功,返回mem起始位置为0,长度为returndatasize()的数据(格式为bytes) 91 | default { 92 | return(0, returndatasize()) 93 | } 94 | } 95 | } 96 | } 97 | ``` 98 | 99 | 那么经过优化后的代理合约有多大呢?`EIP-1677`提出了最小代理合约,完全用字节码写成,合约长度仅有`55`字节,能节省超过90%的gas!😱,手撸字节码就是这么强大。 100 | 101 | ``` 102 | 363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3 103 | ``` 104 | 105 | 我第一次见到这一串字节码就像见到了天书,不知所措,相信现在的你也能感同身受。但是,在我们学习完之前的章节之后,不单要看懂它,还要优化它!优化后的代理合约: 106 | 107 | 1. 使用了Shanghai升级后引入的新opcode:`PUSH0`。 108 | 2. 合约仅需`54`字节,部署时节省`200` gas,运行时节省`5` gas。 109 | 110 | 我们基于优化后的代理合约,提出一个新的[EIP-7511](https://eips.ethereum.org/EIPS/eip-7511): 使用`PUSH0`的最小代理合约。 111 | 112 | ## 从头搭建最小代理合约 113 | 114 | 代理合约中最重要的操作码是什么?对,是[DELEGATECALL](../19_DelegatecallOp/readme.md),它可以将用户对代理合约的调用委托给逻辑合约。 115 | 116 | ![](./img/25-3.png) 117 | 118 | 因此,最小代理合约的核心元素包括: 119 | 120 | 1. 使用`CALLDATACOPY`复制交易的calldata。 121 | 2. 使用`DELEGATECALL`将calldata转发到逻辑合约。 122 | 3. 将`DELEGATECALL`返回的数据复制到内存。 123 | 4. 根据`DELEGATECALL`是否成功来返回结果或回滚交易。 124 | 125 | ### 第一步:复制Calldata 126 | 127 | 为了复制calldata,我们需要为`CALLDATACOPY`操作码提供参数,这些参数是`[0, 0, cds]`,其中`cds`代表calldata的大小。 128 | 129 | | pc | op | opcode | stack | 130 | |------|--------|----------------|--------------------| 131 | | [00] | 36 | CALLDATASIZE | cds | 132 | | [01] | 5f | PUSH0 | 0 cds | 133 | | [02] | 5f | PUSH0 | 0 0 cds | 134 | | [03] | 37 | CALLDATACOPY | | 135 | 136 | ### 第二步:Delegatecall 137 | 138 | 为了将calldata转发到委托调用,我们要在堆栈中准备`DELEGATECALL`操作码所需的参数,这些参数分别是`[gas 0xbebe. 0 cds 0 0]`,其中`gas`代表剩余的gas,`0xbebe.`代表逻辑合约的地址(20字节,实际使用时需要替换成你的逻辑合约地址),`suc`代表delegatecall是否成功。 139 | 140 | | pc | op | opcode | stack | 141 | |------|--------|----------------|--------------------| 142 | | [04] | 5f | PUSH0 | 0 | 143 | | [05] | 5f | PUSH0 | 0 0 | 144 | | [06] | 36 | CALLDATASIZE | cds 0 0 | 145 | | [07] | 5f | PUSH0 | 0 cds 0 0 | 146 | | [08] | 73bebe.| PUSH20 0xbebe. | 0xbebe. 0 cds 0 0 | 147 | | [1d] | 5a | GAS | gas 0xbebe. 0 cds 0 0| 148 | | [1e] | f4 | DELEGATECALL | suc | 149 | 150 | ### 第三步:将`DELEGATECALL`返回的数据复制到内存 151 | 152 | 进行完`DELEGATECALL`之后,我们就可以处理返回的数据了。这一步,我们要使用``RETURNDATACOPY`操作码将返回的数据复制到内存,它的参数是`[0, 0, rds]`,其中`rds`代表从`DELEGATECALL`返回的数据长度。 153 | 154 | | pc | op | opcode | stack | 155 | |------|--------|----------------|--------------------| 156 | | [1f] | 3d | RETURNDATASIZE | rds suc | 157 | | [20] | 5f | PUSH0 | 0 rds suc | 158 | | [21] | 5f | PUSH0 | 0 0 rds suc | 159 | | [22] | 3e | RETURNDATACOPY | suc | 160 | 161 | ### 第四步:返回数据或回滚交易 162 | 163 | 最后,我们需要根据`DELEGATECALL`是否成功(`suc`)选择返回数据或回滚交易。因为EVM操作码中没有`if/else`,我们需要使用`JUMPI`和`JUMPDEST`。`JUMPI`的参数是`[0x2a, suc]`,其中`0x2a`是条件跳转的目的地。 164 | 165 | 我们还需要在`JUMPI`之前为`REVERT`和`RETURN`操作码准备参数`[0, rds]`,否则我们就要在返回/回滚条件下重复准备两次。另外,我们不能避免使用`SWAP`操作交换`rds`和`suc`在堆栈中的位置,因为我们只能在`DELEGATECALL`之后获得返回数据的长度`rds`。 166 | 167 | | pc | op | opcode | stack | 168 | |------|--------|----------------|--------------------| 169 | | [23] | 5f | PUSH0 | 0 suc | 170 | | [24] | 3d | RETURNDATASIZE | rds 0 suc | 171 | | [25] | 91 | SWAP2 | suc 0 rds | 172 | | [26] | 602a | PUSH1 0x2a | 0x2a suc 0 rds | 173 | | [27] | 57 | JUMPI | 0 rds | 174 | | [29] | fd | REVERT | | 175 | | [2a] | 5b | JUMPDEST | 0 rds | 176 | | [2b] | f3 | RETURN | | 177 | 178 | 希望前面的步骤你都跟上了,如果没跟上的话,可以反复看几遍。其实逻辑很简单,就是为核心的指令准备参数,然后调用它。 179 | 180 | 最后,我们就得到了带有`PUSH0`的最小代理合约的运行时代码: 181 | 182 | ``` 183 | 365f5f375f5f365f73bebebebebebebebebebebebebebebebebebebebe5af43d5f5f3e5f3d91602a57fd5bf3 184 | ``` 185 | 186 | 优化后的代码长度是`44`字节,比之前的最小代理合约少了`1`字节。此外,它用`PUSH0`替换了`RETURNDATASIZE`和`DUP`操作,节省了gas并提高了代码的可读性。总结一下,优化后的最小代理合约在部署时节省`200` gas,在运行时节省`5` gas,同时保持了与之前版本相同的功能。 187 | 188 | 你可以在[evm.codes](https://www.evm.codes/playground?fork=shanghai&unit=Wei&codeType=Bytecode&code='36z7z6y73~~~~~5af43dzey3d91602a57fd5bf3'~xxxxzyy3y5fxbe%01xyz~_)中测试下它。 189 | 190 | ![](./img/25-4.png) 191 | 192 | ## 部署最小代理合约 193 | 194 | ### 最小创建时代码 195 | 196 | 优化后的最小代理合约的创建时代码为: 197 | 198 | ``` 199 | 602c8060095f395ff3365f5f375f5f365f73bebebebebebebebebebebebebebebebebebebebe5af43d5f5f3e5f3d91602a57fd5bf3 200 | ``` 201 | 202 | 总共`53`字节,其中前`9`字节为`initcode`,你可以结合[第21讲](https://github.com/WTFAcademy/WTF-EVM-Opcodes/blob/main/21_Create/readme.md),思考它为什么长这样: 203 | 204 | ``` 205 | 602c8060095f395ff3 206 | ``` 207 | 208 | 剩余部分是我们刚才建立的代理合约的运行时代码。 209 | 210 | ### 部署合约 211 | 212 | 我们可以用下面的`Solidity`合约来部署优化后的最小代理合约: 213 | 214 | ```solidity 215 | // SPDX-License-Identifier: CC0-1.0 216 | pragma solidity ^0.8.20; 217 | 218 | // Note: this contract requires `PUSH0`, which is available in solidity > 0.8.20 and EVM version > Shanghai 219 | contract Clone0Factory { 220 | error FailedCreateClone(); 221 | 222 | receive() external payable {} 223 | 224 | /** 225 | * @dev Deploys and returns the address of a clone0 (Minimal Proxy Contract with `PUSH0`) that mimics the behaviour of `implementation`. 226 | * 227 | * This function uses the create opcode, which should never revert. 228 | */ 229 | function clone0(address impl) public payable returns (address addr) { 230 | // first 18 bytes of the creation code 231 | bytes memory data1 = hex"602c8060095f395ff3365f5f375f5f365f73"; 232 | // last 15 bytes of the creation code 233 | bytes memory data2 = hex"5af43d5f5f3e5f3d91602a57fd5bf3"; 234 | // complete the creation code of Clone0 235 | bytes memory _code = abi.encodePacked(data1, impl, data2); 236 | 237 | // deploy with create op 238 | assembly { 239 | // create(v, p, n) 240 | addr := create(callvalue(), add(_code, 0x20), mload(_code)) 241 | } 242 | 243 | if (addr == address(0)) { 244 | revert FailedCreateClone(); 245 | } 246 | } 247 | } 248 | ``` 249 | 250 | ## 总结 251 | 252 | 这一讲,我们结合了前面24讲学习的内容,从头构建了最小代理合约,并且使用`PUSH0`优化了它。优化后最小代理合约的代码长度减少了`1`字节,在部署时节省`200` gas,在运行时生生`5` gas,同时保持了与之前版本相同的功能。 253 | 254 | 相信你在学习完本教程后,对EVM,字节码,和最小代理合约的认识会有质的飞跃!如果你对本教程有疑问或建议,欢迎推特联系我们或者在GitHub上提issue。另外也欢迎你对[EIP-7511的草稿](https://ethereum-magicians.org/)给出改进建议,它是这门课程的结晶! 255 | 256 | ## 延伸阅读 257 | 258 | 1. Peter Murray (@yarrumretep), Nate Welch (@flygoing), Joe Messerman (@JAMesserman), "ERC-1167: Minimal Proxy Contract," Ethereum Improvement Proposals, no. 1167, June 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1167. 259 | 260 | 2. Alex Beregszaszi (@axic), Hugo De la cruz (@hugo-dc), Paweł Bylica (@chfast), "EIP-3855: PUSH0 instruction," Ethereum Improvement Proposals, no. 3855, February 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-3855. 261 | 262 | 3. Martin Abbatemarco, Deep dive into the Minimal Proxy contract, https://blog.openzeppelin.com/deep-dive-into-the-minimal-proxy-contract 263 | 264 | 4. 0age, The More-Minimal Proxy, https://medium.com/@0age/the-more-minimal-proxy-5756ae08ee48 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 WTF.Academy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WTF EVM Opcodes 2 | 3 | 这个教程逐个介绍了EVM的144个Opcodes,让你深入理解EVM。 4 | 5 | 进度: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 144/144 (100%)! 6 | 7 | 8 | 我最近在重新学以太坊的opcodes,巩固一下细节,也写一个“WTF EVM Opcodes极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 9 | 10 | 在学习这个教程之前,你需要先学习[WTF Solidity教程](https://github.com/AmazingAng/WTF-Solidity)。 11 | 12 | 13 | 14 | 15 | ## 入门 101 16 | 17 | **第1讲:Hello Opcodes**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/01_HelloOpcodes) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/01_HelloOpcodes/readme.md) 18 | 19 | **第2讲:Opcodes分类**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/02_Categories) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/02_Categories/readme.md) 20 | 21 | **第3讲:堆栈指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/03_StackOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/03_StackOp/readme.md) 22 | 23 | **第4讲:算数指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/04_ArithmeticOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/04_ArithmeticOp/readme.md) 24 | 25 | **第5讲:比较指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/05_ComparisonOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/05_ComparisonOp/readme.md) 26 | 27 | **第6讲:位级指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/06_BitwiseOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/06_BitwiseOp/readme.md) 28 | 29 | **第7讲:内存指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/07_MemoryOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/07_MemoryOp/readme.md) 30 | 31 | **第8讲:存储指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/08_StorageOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/08_StorageOp/readme.md) 32 | 33 | **第9讲:控制流指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/09_FlowOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/09_FlowOp/readme.md) 34 | 35 | **第10讲:区块信息指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/10_BlockOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/10_BlockOp/readme.md) 36 | 37 | **第11讲:堆栈指令2**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/11_StackOp2) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/11_StackOp2/readme.md) 38 | 39 | **第12讲:SHA3指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/12_SHA3) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/12_SHA3/readme.md) 40 | 41 | **第13讲:账户指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/13_AccountOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/13_AccountOp/readme.md) 42 | 43 | **第14讲:交易指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/14_TxOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/14_TxOp/readme.md) 44 | 45 | **第15讲:Log指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/15_LogOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/15_LogOp/readme.md) 46 | 47 | ## 进阶 102 48 | 49 | **第16讲:Return指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/16_ReturnOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/16_ReturnOp/readme.md) 50 | 51 | **第17讲:Revert指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/17_RevertOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/17_RevertOp/readme.md) 52 | 53 | **第18讲:Call指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/18_CallOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/18_CallOp/readme.md) 54 | 55 | **第19讲:Delegatecall指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/19_DelegatecallOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/19_DelegatecallOp/readme.md) 56 | 57 | **第20讲:Staticcall指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/20_StaticcallOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/20_StaticcallOp/readme.md) 58 | 59 | **第21讲:Create指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/21_Create) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/21_Create/readme.md) 60 | 61 | **第22讲:Create2指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/22_Create2) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/22_Create2/readme.md) 62 | 63 | **第23讲:Selfdestruct指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/23_SelfdestructOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/23_SelfdestructOp/readme.md) 64 | 65 | **第24讲:Gas指令**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/24_GasOp) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/24_GasOp/readme.md) 66 | 67 | **第25讲:优化最小代理合约 EIP-7511**:[代码](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/25_MinimalProxy) | [文章](https://github.com/WTFAcademy/WTF-Opcodes/blob/main/25_MinimalProxy/readme.md) 68 | 69 | ## WTF EVM Opcodes贡献者 70 |
71 |

72 | 贡献者是WTF学院的基石 73 |

74 | 75 | 76 | 77 |
78 | 79 | 80 | ## Reference 81 | 82 | 1. [Ethereum EVM illustrated by Takenobu T.](https://github.com/takenobu-hs/ethereum-evm-illustrated) 83 | 84 | 2. [Demystifying Ethereum Assembly by Joshua Riley](https://www.youtube.com/watch?v=btDOvn8pLkA) 85 | 86 | 3. [evm-opcodes](https://github.com/wolflo/evm-opcodes) 87 | 88 | 4. [evm-from-scratch](https://github.com/w1nt3r-eth/evm-from-scratch) 89 | 90 | 5. [evm.codes](https://evm.codes) 91 | 92 | 6. [evm-deep-dives-the-path-to-shadowy](https://noxx.substack.com/p/evm-deep-dives-the-path-to-shadowy) --------------------------------------------------------------------------------