Math#

We are going to implement all arithmetic operations here. These are the first opcodes that actually manipulate the stack and consume some gas.

Lets see how add works. We are going to pop 2 values from the stack add them and push back the result on the stack.

We also need to increment the program counter (pc) by one. In the end we need to deduct 3 gas for executing the add operation.

Most arithmetic opcodes work like this.

def add(evm):
    a, b = evm.stack.pop(), evm.stack.pop()
    evm.stack.push(a+b)
    evm.pc += 1
    evm.gas_dec(3)
def mul(evm):
    a, b = evm.stack.pop(), evm.stack.pop()
    evm.stack.push(a*b)
    evm.pc += 1
    evm.gas_dec(5)
def sub(evm):
    a, b = evm.stack.pop(), evm.stack.pop()
    evm.stack.push(a-b)
    evm.pc += 1
    evm.gas_dec(3)

One interesting note about how the EVM handles division by 0. Most other systems would throw an exception if you try to divide by 0. Not the EVM. It just returns 0.

Division by 0 are not directly handled by the EVM and are mostly a feature of the programming language like Solidity.

def div(evm):
    a, b = evm.stack.pop(), evm.stack.pop()
    evm.stack.push(0 if b == 0 else a // b)
    evm.pc += 1
    evm.gas_dec(5)

Exactly like div but we use the absolute value for the both the denominator and numerator.

Small little helper function to determine the sign of a number.

pos_or_neg = lambda number: -1 if value < 0 else 1
def sdiv(evm):
    a, b = evm.stack.pop(), evm.stack.pop()
    sign = pos_or_neg(a*b)
    evm.stack.push(0 if b == 0 else sign * (abs(a) // abs(b)))
    evm.pc += 1
    evm.gas_dec(5)
def mod(evm):
    a, b = evm.stack.pop(), evm.stack.pop()
    evm.stack.push(0 if b == 0 else a % b)
    evm.pc += 1
    evm.gas_dec(5)
def smod(evm):
    a, b = evm.stack.pop(), evm.stack.pop()
    sign = pos_or_neg(a*b)
    evm.stack.push(0 if b == 0 else abs(a) % abs(b) * sign)
    evm.pc += 1
    evm.gas_dec(5)
def addmod(evm):
    a, b = evm.stack.pop(), evm.stack.pop()
    N = evm.stack.pop()
    evm.stack.push((a + b) % N)
    evm.pc += 1
    evm.gas_dec(8)
def mulmod(evm):
    a, b = evm.stack.pop(), evm.stack.pop()
    N = evm.stack.pop()
    evm.stack.push((a + b) * N)
    evm.pc += 1
    evm.gas_dec(8)

The gas cost for exp is dynamic. It is a function of how many bytes we need to represent the exponent in binary. This helper function calculates this.

def size_in_bytes(number):
    import math
    if number == 0: return 1
    bits_needed = math.ceil(math.log2(abs(number) + 1))
    return math.ceil(bits_needed / 8)
def exp(evm):
    a, exponent = evm.stack.pop(), evm.stack.pop()
    evm.stack.push(a ** exponent)
    evm.pc += 1
    evm.gas_dec(10 + (50 * size_in_bytes(exponent)))

More informations about this rarely used opcode signextend here.

def signextend(evm):
    b, x = evm.stack.pop(), evm.stack.pop()
    if b <= 31:
        testbit = b * 8 + 7
        sign_bit = 1 << testbit
        if x & sign_bit: result = x | (2**256 - sign_bit)
        else           : result = x & (sign_bit - 1)
    else: result = x
    
    evm.stack.push(result)
    evm.pc += 1
    evm.gas_dec(5)