跳到主要内容

WTF Opcodes极简入门: 6. 位级指令

我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。

推特:@0xAA_Science

社区:Discord微信群官网 wtf.academy

所有代码和教程开源在github: github.com/WTFAcademy/WTF-Opcodes


这一讲,我们将介绍EVM中用于位级运算的8个指令,包括AND(与),OR(或),和XOR(异或)。并且,我们将在用Python写的极简版EVM中添加对他们的支持。

AND (与)

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 (或)

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 (异或)

XOR指令与ANDOR指令类似,但执行的是异或运算。操作码是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

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

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

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)

其他位级指令

  1. BYTE: BYTE指令从堆栈中弹出两个元素(ab),将第二个元素(b)看作一个字节数组,并返回该字节数组中第一个元素指定索引的字节(b[a]),并压入堆栈。如果索引大于或等于字节数组的长度,则返回0。操作码是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)
  1. SAR: SAR指令执行算术右移位操作,与SHR类似,但考虑符号位:如果我们对一个负数进行算术右移,那么在右移的过程中,最左侧(符号位)会被填充F以保持数字的负值。它从堆栈中弹出两个元素,将第二个元素以符号位填充的方式右移第一个元素位数,然后将结果推回栈顶。它的操作码是0x1D。由于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对应的指令形式,并给出运行后的堆栈状态。