我最近在重新学以太坊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
对应的指令形式,并给出运行后的堆栈和程序计数器状态。