.
位级指令

我最近在重新学以太坊 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指令与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

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指令从堆栈中弹出两个元素(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)
  2. 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对应的指令形式,并给出运行后的堆栈状态。

PreviousNext

EVM Opcodes 101

1. Hello Opcodes
2. Opcodes分类
3. 堆栈指令
4. 算数指令
5. 比较指令
6. 位级指令
7. 内存指令
8. 存储指令
9. 控制流指令
10. 区块信息指令
11. 堆栈指令2
12. SHA3指令
13. 账户指令
14. 交易指令
15. Log指令