我最近在重新学以太坊 opcodes,也写一个“WTF EVM Opcodes 极简入门”,供小白们使用。
所有代码和教程开源在 github: github.com/WTFAcademy/WTF-Opcodes
这一讲,我们将介绍 EVM 中用于位级运算的 8 个指令,包括AND
(与),OR
(或),和XOR
(异或)。并且,我们将在用 Python 写的极简版 EVM 中添加对他们的支持。
AND
指令从堆栈中弹出两个元素,对它们进行位与运算,并将结果推入堆栈。操作码是0x16
,gas 消耗为3
。
我们将AND
指令的实现添加到我们的 EVM 模拟器中:
def and_op(self):
if len(self.stack) < 2:
raise Exception('Stack underflow')
a = self.stack.pop()
b = self.stack.pop()
self.stack.append(a & b)
我们在run()
函数中添加对AND
指令的处理:
def run(self):
while self.pc < len(self.code):
op = self.next_instruction()
# ... 其他指令的处理 ...
elif op == AND: # 处理AND指令
self.and_op()
现在,我们可以尝试运行一个包含AND
指令的字节码:0x6002600316
(PUSH1 2 PUSH1 3 AND)。这个字节码将2
(0000 0010)和3
(0000 0011)推入堆栈,然后进行位级与运算,结果应该为2
(0000 0010)。
code = b"\x60\x02\x60\x03\x16"
evm = EVM(code)
evm.run()
print(evm.stack)
# output: [2]
OR
指令与AND
指令类似,但执行的是位或运算。操作码是0x17
,gas 消耗为3
。
我们将OR
指令的实现添加到 EVM 模拟器:
def or_op(self):
if len(self.stack) < 2:
raise Exception('Stack underflow')
a = self.stack.pop()
b = self.stack.pop()
self.stack.append(a | b)
我们在run()
函数中添加对OR
指令的处理:
def run(self):
while self.pc < len(self.code):
op = self.next_instruction()
# ... 其他指令的处理 ...
elif op == OR: # 处理OR指令
self.or_op()
现在,我们可以尝试运行一个包含OR
指令的字节码:0x6002600317
(PUSH1 2 PUSH1 3 OR)。这个字节码将2
(0000 0010)和3
(0000 0011)推入堆栈,然后进行位级与运算,结果应该为3
(0000 0011)。
code = b"\x60\x02\x60\x03\x17"
evm = EVM(code)
evm.run()
print(evm.stack)
# output: [3]
XOR
指令与AND
和OR
指令类似,但执行的是异或运算。操作码是0x18
,gas 消耗为3
。
我们将XOR
指令的实现添加到 EVM 模拟器:
def xor_op(self):
if len(self.stack) < 2:
raise Exception('Stack underflow')
a = self.stack.pop()
b = self.stack.pop()
self.stack.append(a ^ b)
我们在run()
函数中添加对XOR
指令的处理:
def run(self):
while self.pc < len(self.code):
op = self.next_instruction()
# ... 其他指令的处理 ...
elif op == XOR: # 处理XOR指令
self.xor_op()
现在,我们可以尝试运行一个包含XOR
指令的字节码:0x6002600318
(PUSH1 2 PUSH1 3 XOR)。这个字节码将2
(0000 0010)和3
(0000 0011)推入堆栈,然后进行位级与运算,结果应该为1
(0000 0001)。
code = b"\x60\x02\x60\x03\x18"
evm = EVM(code)
evm.run()
print(evm.stack)
# output: [1]
NOT
指令执行按位非操作,取栈顶元素的补码,然后将结果推回栈顶。它的操作码是0x19
,gas 消耗为3
。
我们将NOT
指令的实现添加到 EVM 模拟器:
def not_op(self):
if len(self.stack) < 1:
raise Exception('Stack underflow')
a = self.stack.pop()
self.stack.append(~a % (2**256)) # 按位非操作的结果需要模2^256,防止溢出
在run()
函数中添加对NOT
指令的处理:
elif op == NOT: # 处理NOT指令
self.not_op()
现在,我们可以尝试运行一个包含NOT
指令的字节码:0x600219
(PUSH1 2 NOT)。这个字节码将2
(0000 0010)推入堆栈,然后进行位级非运算,结果应该为很大的数
(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd)。
# NOT
code = b"\x60\x02\x19"
evm = EVM(code)
evm.run()
print(evm.stack)
# output: [很大的数] (fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd)
SHL
指令执行左移位操作,从堆栈中弹出两个元素,将第二个元素左移第一个元素位数,然后将结果推回栈顶。它的操作码是0x1B
,gas 消耗为3
。
我们将SHL
指令的实现添加到 EVM 模拟器:
def shl(self):
if len(self.stack) < 2:
raise Exception('Stack underflow')
a = self.stack.pop()
b = self.stack.pop()
self.stack.append((b << a) % (2**256)) # 左移位操作的结果需要模2^256
在run()
函数中添加对SHL
指令的处理:
elif op == SHL: # 处理SHL指令
self.shl()
现在,我们可以尝试运行一个包含XOR
指令的字节码:0x600260031B
(PUSH1 2 PUSH1 3 SHL)。这个字节码将2
(0000 0010)和3
(0000 0011)推入堆栈,然后将2
左移3
位,结果应该为16
(0001 0000)。
code = b"\x60\x02\x60\x03\x1B"
evm = EVM(code)
evm.run()
print(evm.stack)
# output: [16] (0x000000010 << 3 => 0x00010000)
SHR
指令执行右移位操作,从堆栈中弹出两个元素,将第二个元素右移第一个元素位数,然后将结果推回栈顶。它的操作码是0x1C
,gas 消耗为3
。
我们将SHR
指令的实现添加到 EVM 模拟器:
def shr(self):
if len(self.stack) < 2:
raise Exception('Stack underflow')
a = self.stack.pop()
b = self.stack.pop()
self.stack.append(b >> a) # 右移位操作
在run()
函数中添加对SHR
指令的处理:
elif op == SHR: # 处理SHR指令
self.shr()
现在,我们可以尝试运行一个包含XOR
指令的字节码:0x601060031C
(PUSH1 16 PUSH1 3 SHL)。这个字节码将16
(0001 0000)和3
(0000 0011)推入堆栈,然后将16
右移3
位,结果应该为2
(0000 0010)。
code = b"\x60\x10\x60\x03\x1C"
evm = EVM(code)
evm.run()
print(evm.stack)
# output: [2] (0x00010000 >> 3 => 0x000000010)
-
BYTE:
BYTE
指令从堆栈中弹出两个元素(a
和b
),将第二个元素(b
)看作一个32字节
的数组,不够位数补 0,并返回该字节数组中从高位开始的第a
个索引的字节,即(b[31-a]
),并压入堆栈。如果索引a
大于或等于 32,返回0
,否则返回b[31-a]
。 操作码是0x1a
,gas 消耗为3
。def byte_op(self): if len(self.stack) < 2: raise Exception('Stack underflow') position = self.stack.pop() value = self.stack.pop() if position >= 32: res = 0 else: res = (value // pow(256, 31 - position)) & 0xFF self.stack.append(res)
-
SAR:
SAR
指令执行算术右移位操作,与SHR
类似,但考虑符号位:如果我们对一个负数进行算术右移,那么在右移的过程中,最左侧(符号位)会被填充F
以保持数字的负值。它从堆栈中弹出两个元素,将第二个元素以符号位填充的方式右移第一个元素位数,然后将结果推回栈顶。它的操作码是0x1D
,gas 消耗为3
。由于 Python 的>>
操作符已经是算术右移,我们可以直接复用shr
函数的代码。def sar(self): if len(self.stack) < 2: raise Exception('Stack underflow') a = self.stack.pop() b = self.stack.pop() self.stack.append(b >> a) # 右移位操作
这一讲,我们介绍了 EVM 中的 8 个位级指令,并在极简版 EVM 中添加了对他们的支持。课后习题: 写出0x6002600160011B1B
对应的指令形式,并给出运行后的堆栈状态。