Skip to content

Latest commit

 

History

History

04_ArithmeticOp

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

WTF Opcodes极简入门: 4. 算数指令

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

推特:@0xAA_Science

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

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


这一讲,我们将介绍EVM中用于基础算术运算的11个指令,包括ADD(加法),MUL(乘法),SUB(减法),和DIV(除法)。并且,我们将在用Python写的极简版EVM中添加对他们的支持。

ADD (加法)

ADD指令从堆栈中弹出两个元素,将它们相加,然后将结果推入堆栈。如果堆栈元素不足两个,那么会抛出异常。这个指令的操作码是0x01,gas消耗为3

我们可以将ADD指令的实现添加到我们的EVM模拟器中:

def add(self):
    if len(self.stack) < 2:
        raise Exception('Stack underflow')
    a = self.stack.pop()
    b = self.stack.pop()
    res = (a + b) % (2**256) # 加法结果需要模2^256,防止溢出
    self.stack.append(res)

我们在run()函数中添加对ADD指令的处理:

def run(self):
    while self.pc < len(self.code):
        op = self.next_instruction()

        if PUSH1 <= op <= PUSH32:
            size = op - PUSH1 + 1
            self.push(size)
        elif op == PUSH0:
            self.stack.append(0)
            self.pc += size
        elif op == POP:
            self.pop()
        elif op == ADD: # 处理ADD指令
            self.add()

现在,我们可以尝试运行一个包含ADD指令的字节码:0x6002600301(PUSH1 2 PUSH1 3 ADD)。这个字节码将23推入堆栈,然后将它们相加。

code = b"\x60\x02\x60\x03\x01"
evm = EVM(code)
evm.run()
print(evm.stack)
# output: [5]

MUL (乘法)

MUL指令和ADD类似,但是它将堆栈的顶部两个元素相乘。操作码是0x02,gas消耗为5

我们将MUL指令的实现添加到EVM模拟器:

def mul(self):
    if len(self.stack) < 2:
        raise Exception('Stack underflow')
    a = self.stack.pop()
    b = self.stack.pop()
    res = (a * b) % (2**256) # 乘法结果需要模2^256,防止溢出
    self.stack.append(res)

我们在run()函数中添加对MUL指令的处理:

def run(self):
    while self.pc < len(self.code):
        op = self.next_instruction()

        if PUSH1 <= op <= PUSH32:
            size = op - PUSH1 + 1
            self.push(size)
        elif op == PUSH0:
            self.stack.append(0)
            self.pc += size
        elif op == POP:
            self.pop()
        elif op == ADD:
            self.add()
        elif op == MUL: # 处理MUL指令
            self.mul()

现在,我们可以尝试运行一个包含MUL指令的字节码:0x6002600302(PUSH1 2 PUSH1 3 MUL)。这个字节码将23推入堆栈,然后将它们相乘。

code = b"\x60\x02\x60\x03\x02"
evm = EVM(code)
evm.run()
print(evm.stack)
# output: [6]

SUB (减法)

SUB指令从堆栈顶部弹出两个元素,然后计算第一个元素减去第二个元素,最后将结果推入堆栈。这个指令的操作码是0x03,gas消耗为3

我们将SUB指令的实现添加到EVM模拟器:

def sub(self):
    if len(self.stack) < 2:
        raise Exception('Stack underflow')
    a = self.stack.pop()
    b = self.stack.pop()
    res = (a - b) % (2**256) # 结果需要模2^256,防止溢出
    self.stack.append(res)

我们在run()函数中添加对SUB指令的处理:

def run(self):
    while self.pc < len(self.code):
        op = self.next_instruction()

        if PUSH1 <= op <= PUSH32:
            size = op - PUSH1 + 1
            self.push(size)
        elif op == PUSH0:
            self.stack.append(0)
            self.pc += size
        elif op == POP:
            self.pop()
        elif op == ADD:
            self.add()
        elif op == MUL:
            self.mul()
        elif op == SUB: # 处理SUB指令
            self.sub()

现在,我们可以尝试运行一个包含SUB指令的字节码:0x6002600303(PUSH1 2 PUSH1 3 SUB)。这个字节码将23推入堆栈,然后将它们相减(3-2)。

code = b"\x60\x02\x60\x03\x03"
evm = EVM(code)
evm.run()
print(evm.stack)
# output: [1]

DIV (除法)

DIV指令从堆栈顶部弹出两个元素,然后将第一个元素除以第二个元素,最后将结果推入堆栈。如果第二个元素(除数)为0,则将0推入堆栈。这个指令的操作码是0x04,gas消耗为5

我们将DIV指令的实现添加到EVM模拟器:

def div(self):
    if len(self.stack) < 2:
        raise Exception('Stack underflow')
    a = self.stack.pop()
    b = self.stack.pop()
    if b == 0:
        res = 0
    else:
        res =  (a // b) % (2**256)
    self.stack.append(res)

我们在run()函数中添加对DIV指令的处理:

def run(self):
    while self.pc < len(self.code):
        op = self.next_instruction()

        if PUSH1 <= op <= PUSH32:
            size = op - PUSH1 + 1
            self.push(size)
        elif op == PUSH0:
            self.stack.append(0)
            self.pc += size
        elif op == POP:
            self.pop()
        elif op == ADD:
            self.add()
        elif op == MUL:
            self.mul()
        elif op == SUB:
            self.sub()
        elif op == DIV: # 处理DIV指令
            self.div()

现在,我们可以尝试运行一个包含DIV指令的字节码:0x6003600604(PUSH1 3 PUSH1 6 DIV)。这个字节码将36推入堆栈,然后将它们相除(6//3)。

code = b"\x60\x03\x60\x06\x04"
evm = EVM(code)
evm.run()
print(evm.stack)
# output: [2]

其他算数指令

  1. SDIV: 带符号整数的除法指令。与DIV类似,这个指令会从堆栈中弹出两个元素,然后将第一个元素除以第二个元素,结果带有符号。如果第二个元素(除数)为0,结果为0。它的操作码是0x05,gas消耗为5。要注意,EVM字节码中的负数是用二进制补码(two’s complement)形式,比如-1表示为0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,它加一等于0。

    def sdiv(self):
        if len(self.stack) < 2:
            raise Exception('Stack underflow')
        a = self.stack.pop()
        b = self.stack.pop()
        res = a//b % (2**256) if b!=0 else 0
        self.stack.append(res)
  2. MOD: 取模指令。这个指令会从堆栈中弹出两个元素,然后将第一个元素除以第二个元素的余数推入堆栈。如果第二个元素(除数)为0,结果为0。它的操作码是0x06,gas消耗为5。

    def mod(self):
        if len(self.stack) < 2:
            raise Exception('Stack underflow')
        a = self.stack.pop()
        b = self.stack.pop()
        res = a % b if b != 0 else 0
        self.stack.append(res)
  3. SMOD: 带符号的取模指令。这个指令会从堆栈中弹出两个元素,然后将第一个元素除以第二个元素的余数推入堆栈,结果带符号。如果第一个元素(除数)为0,结果为0。它的操作码是0x07,gas消耗为5。

    def smod(self):
        if len(self.stack) < 2:
            raise Exception('Stack underflow')
        a = self.stack.pop()
        b = self.stack.pop()
        res = a % b if b != 0 else 0
        self.stack.append(res)
  4. ADDMOD: 模加法指令。这个指令会从堆栈中弹出三个元素,将前两个元素相加,然后对第三个元素取模,将结果推入堆栈。如果第三个元素(模数)为0,结果为0。它的操作码是0x08,gas消耗为8。

    def addmod(self):
        if len(self.stack) < 3:
            raise Exception('Stack underflow')
        a = self.stack.pop()
        b = self.stack.pop()
        n = self.stack.pop()
        res = (a + b) % n if n != 0 else 0
        self.stack.append(res)
  5. MULMOD: 模乘法指令。这个指令会从堆栈中弹出三个元素,将前两个元素相乘,然后对第三个元素取模,将结果推入堆栈。如果第三个元素(模数)为0,结果为0。它的操作码是0x09,gas消耗为5。

    def mulmod(self):
        if len(self.stack) < 3:
            raise Exception('Stack underflow')
        a = self.stack.pop()
        b = self.stack.pop()
        n = self.stack.pop()
        res = (a * b) % n if n != 0 else 0
        self.stack.append(res)
  6. EXP: 指数运算指令。这个指令会从堆栈中弹出两个元素,将第一个元素作为底数,第二个元素作为指数,进行指数运算,然后将结果推入堆栈。它的操作码是0x0A,gas消耗为10。

    def exp(self):
        if len(self.stack) < 2:
            raise Exception('Stack underflow')
        a = self.stack.pop()
        b = self.stack.pop()
        res = pow(a, b) % (2**256)
        self.stack.append(res)
  7. SIGNEXTEND: 符号位扩展指令,即在保留数字的符号(正负性)及数值的情况下,增加二进制数字位数的操作。举个例子,若计算机使用8位二进制数表示数字“0000 1010”,且此数字需要将字长符号扩充至16位,则扩充后的值为“0000 0000 0000 1010”。此时,数值与符号均保留了下来。SIGNEXTEND指令会从堆栈中弹出两个元素,对第二个元素进行符号扩展,扩展的位数由第一个元素决定,然后将结果推入堆栈。它的操作码是0x0B,gas消耗为5。

    def signextend(self):
        if len(self.stack) < 2:
            raise Exception('Stack underflow')
        b = self.stack.pop()
        x = self.stack.pop()
        if b < 32: # 如果b>=32,则不需要扩展
            sign_bit = 1 << (8 * b - 1) # b 字节的最高位(符号位)对应的掩码值,将用来检测 x 的符号位是否为1
            x = x & ((1 << (8 * b)) - 1)  # 对 x 进行掩码操作,保留 x 的前 b+1 字节的值,其余字节全部置0
            if x & sign_bit:  # 检查 x 的符号位是否为1
                x = x | ~((1 << (8 * b)) - 1)  # 将 x 的剩余部分全部置1
        self.stack.append(x)

总结

这一讲,我们介绍了EVM中的11个算数指令,并在极简版EVM中添加了对他们的支持。课后习题: 写出0x60036004600209对应的指令形式,并给出运行后的堆栈状态。