我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。
所有代码和教程开源在github: github.com/WTFAcademy/WTF-Opcodes
在这一讲,我们将介绍EVM中用于控制流的5个指令,包括STOP,JUMP,JUMPI,JUMPDEST,和PC。我们将在用Python写的极简版EVM中添加对这些操作的支持。
EVM中的控制流
我们在第三讲介绍了程序计数器(Program Counter,PC),而EVM的控制流是由跳转指令(JUMP,JUMPI,JUMPDEST)控制PC指向新的指令位置而实现的,这允许合约进行条件执行和循环执行。
STOP(停止)
STOP是EVM的停止指令,它的作用是停止当前上下文的执行,并成功退出。它的操作码是0x00,gas消耗为0。
将STOP操作作码设为0x00有一个好处:当一个调用被执行到一个没有代码的地址(EOA),并且EVM尝试读取代码数据时,系统会返回一个默认值0,这个默认值对应的就是STOP指令,程序就会停止执行。
下面,让我们在run()函数中添加对STOP指令的处理:
现在,我们可以尝试运行一个包含STOP指令的字节码:
JUMPDEST(跳转目标)
JUMPDEST指令标记一个有效的跳转目标位置,不然无法使用JUMP和JUMPI进行跳转。它的操作码是0x5b,gas消耗为1。
但是0x5b有时会作为PUSH的参数(详情可看黄皮书中的9.4.3. Jump Destination Validity),所以需要在运行代码前,筛选字节码中有效的JUMPDEST指令,使用ValidJumpDest 来存储有效的JUMPDEST指令所在位置。
JUMP(跳转)
JUMP指令用于无条件跳转到一个新的程序计数器位置。它从堆栈中弹出一个元素,将这个元素设定为新的程序计数器(pc)的值。操作码是0x56,gas消耗为8。
我们在run()函数中添加对JUMP和JUMPDEST指令的处理:
现在,我们可以尝试运行一个包含JUMP和JUMPDEST指令的字节码:0x600456005B(PUSH1 4 JUMP STOP JUMPDEST)。这段字节码将4推入堆栈,然后进行JUMP,跳转到pc = 4的位置,该位置正好是JUMPDEST指令,跳转成功,程序没有被STOP指令中断。
JUMPI(条件跳转)
JUMPI指令用于条件跳转,它从堆栈中弹出两个元素,如果第二个元素(条件,condition)不为0,那么将第一个元素(目标,destination)设定为新的pc的值。操作码是0x57,gas消耗为10。
我们在run()函数中添加对JUMPI指令的处理:
现在,我们可以尝试运行一个包含JUMPI和JUMPDEST指令的字节码:0x6001600657005B(PUSH1 01 PUSH1 6 JUMPI STOP JUMPDEST)。这个字节码将1和6推入堆栈,然后进行JUMPI,由于条件不为0,执行跳转到pc = 6的位置,该位置正好是JUMPDEST指令,跳转成功,程序没有被STOP指令中断。
PC(程序计数器)
PC指令将当前的程序计数器(pc)的值压入堆栈。操作码为0x58,gas消耗为2。
总结
这一讲,我们介绍了EVM中的控制流程指令,并在极简版EVM中添加了对它们的支持。这些操作为合约提供了控制流程的能力,为编写更复杂的合约逻辑提供了可能。
课后习题: 写出0x5F600557005B对应的指令形式,并给出运行后的堆栈和程序计数器状态。