├── __init__.py ├── test ├── __init__.py └── test_arithmetic.py ├── Makefile ├── static └── logo.png ├── README.md ├── ops ├── dup.py ├── swap.py ├── push.py ├── bit.py ├── logic.py ├── comp.py ├── jump.py ├── memory_ops.py ├── storage_ops.py ├── misc.py ├── log.py ├── arithmetic.py └── env.py ├── ethereum ├── account.py ├── precompiles.py ├── number.py └── opcodes.py ├── utils.py ├── computer ├── storage.py ├── stack.py ├── memory.py └── cpu.py ├── run.py ├── .gitignore └── notebook └── evm.ipynb /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | python -m unittest 3 | -------------------------------------------------------------------------------- /static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shafu0x/evm-from-scratch-python/HEAD/static/logo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # evm-from-scratch 2 | 3 | ![alt text](/static/logo.png) 4 | 5 | An EVM from scratch implementation in Python for educational purposes. -------------------------------------------------------------------------------- /ops/dup.py: -------------------------------------------------------------------------------- 1 | def _dup(cpu, n): 2 | value = cpu.stack[n] 3 | cpu.stack.push(value) 4 | 5 | cpu.pc += 1 6 | cpu.gas_dec(3) 7 | 8 | -------------------------------------------------------------------------------- /ops/swap.py: -------------------------------------------------------------------------------- 1 | def _swap(cpu, n): 2 | value1, value2 = cpu.stack.get(0), cpu.stack.get(n+1) 3 | cpu.stack.set(0, value2) 4 | cpu.stack.set(n+1, value1) 5 | 6 | cpu.pc += 1 7 | cpu.gas_dec(3) 8 | -------------------------------------------------------------------------------- /ops/push.py: -------------------------------------------------------------------------------- 1 | def _push(cpu, n): 2 | cpu.pc += 1 3 | cpu.gas_dec(3) 4 | 5 | value = [] 6 | for _ in range(n): 7 | value.append(cpu.peek()) 8 | cpu.pc += 1 9 | cpu.stack.push(value) 10 | -------------------------------------------------------------------------------- /ethereum/account.py: -------------------------------------------------------------------------------- 1 | from eth_hash.auto import keccak 2 | 3 | class Account: 4 | def __init__(self, code, balance): 5 | self.code = code 6 | self.balance = balance 7 | self.codesize = len(code) 8 | self.hash = keccak(code) 9 | -------------------------------------------------------------------------------- /ops/bit.py: -------------------------------------------------------------------------------- 1 | def shl(cpu): 2 | shift, value = cpu.stack.pop().value, cpu.stack.pop().value 3 | cpu.stack.push(value << shift) 4 | cpu.pc += 1 5 | cpu.gas_dec(3) 6 | 7 | def shr(cpu): 8 | shift, value = cpu.stack.pop().value, cpu.stack.pop().value 9 | cpu.stack.push(value >> shift) 10 | cpu.pc += 1 11 | cpu.gas_dec(3) 12 | -------------------------------------------------------------------------------- /ethereum/precompiles.py: -------------------------------------------------------------------------------- 1 | # TODO: implementation 2 | def ecRecover(_hash, v, r, s): pass 3 | def sha2_256(data): pass 4 | def ripemd_160(data): pass 5 | def identity(data): pass 6 | def modexp(Bsize, Esize, Msize, B, E, M): pass 7 | def ecAdd(x1, y1, x2, y2): pass 8 | def ecMul(x1, y1, s): pass 9 | def ecPairing(x1, y1, x2, y2): pass #TODO: arguments 10 | def blake2f(rounds, h, m, t, f): pass 11 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | def twos_comp(val, bits=256): 2 | if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255 3 | val = val - (1 << bits) # compute negative value 4 | return val 5 | 6 | def to32(value): 7 | # if only one byte is passed 8 | if type(value) == int: 9 | value = [value] 10 | 11 | if len(value) < 32: 12 | while len(value) < 32: 13 | value.insert(0, 0x00) 14 | 15 | return value 16 | -------------------------------------------------------------------------------- /ops/logic.py: -------------------------------------------------------------------------------- 1 | def _and(cpu): 2 | a, b = cpu.stack.pop(), cpu.stack.pop() 3 | cpu.stack.push(a & b) 4 | cpu.pc += 1 5 | cpu.gas_dec(3) 6 | 7 | def _or(cpu): 8 | a, b = cpu.stack.pop(), cpu.stack.pop() 9 | cpu.stack.push(a | b) 10 | cpu.pc += 1 11 | cpu.gas_dec(3) 12 | 13 | def _xor(cpu): 14 | a, b = cpu.stack.pop(), cpu.stack.pop() 15 | cpu.stack.push(a ^ b) 16 | cpu.pc += 1 17 | cpu.gas_dec(3) 18 | 19 | def _not(cpu): 20 | a = cpu.stack.pop() 21 | cpu.stack.push(~a) 22 | cpu.pc += 1 23 | cpu.gas_dec(3) 24 | -------------------------------------------------------------------------------- /computer/storage.py: -------------------------------------------------------------------------------- 1 | class Storage: 2 | def __init__(self): 3 | self.storage = {} 4 | self.cache = [] 5 | 6 | def store(self, key, value): 7 | old_value = self.load(key) 8 | self.storage[key] = value 9 | 10 | warm = True if key in self.cache else False 11 | return warm, old_value 12 | 13 | def load(self, key): 14 | warm = True if key in self.cache else False 15 | 16 | # every key-value pair exists 17 | if key not in self.storage: return 0x00 18 | 19 | 20 | return warm, self.storage[key] 21 | -------------------------------------------------------------------------------- /ethereum/number.py: -------------------------------------------------------------------------------- 1 | MIN_UINT = 0 2 | MAX_UINT = 2**256 3 | 4 | MIN_INT = -2*255 5 | MAX_INT = 2**255-1 6 | 7 | class Number: 8 | def __init__(self, value): 9 | self.bytes = value # list of bytes 10 | self.value = self.parse(value) 11 | 12 | def parse(self, value): 13 | if type(value) == list: 14 | return int.from_bytes(bytearray(value), "big", signed=False) 15 | return value 16 | 17 | def to_bytes(self): 18 | return [hex(a) for a in self.value.to_bytes(32, byteorder="big")] 19 | 20 | def __len__(self): return len(self.bytes) 21 | def __str__(self): return f"{self.value}" 22 | -------------------------------------------------------------------------------- /ops/comp.py: -------------------------------------------------------------------------------- 1 | def lt(cpu): 2 | a, b = cpu.stack.pop(), cpu.stack.pop() 3 | cpu.stack.push(1 if a < b else 0) 4 | cpu.pc += 1 5 | cpu.gas_dec(3) 6 | 7 | def gt(cpu): 8 | a, b = cpu.stack.pop(), cpu.stack.pop() 9 | cpu.stack.push(1 if a > b else 0) 10 | cpu.pc += 1 11 | cpu.gas_dec(3) 12 | 13 | # TODO: signed ops 14 | 15 | def eq(cpu): 16 | a, b = cpu.stack.pop(), cpu.stack.pop() 17 | cpu.stack.push(1 if a == b else 0) 18 | cpu.pc += 1 19 | cpu.gas_dec(3) 20 | 21 | def iszero(cpu): 22 | a = cpu.stack.pop() 23 | cpu.stack.push(1 if a == 0 else 0) 24 | cpu.pc += 1 25 | cpu.gas_dec(3) 26 | -------------------------------------------------------------------------------- /ops/jump.py: -------------------------------------------------------------------------------- 1 | from ethereum.opcodes import * 2 | 3 | def jump(cpu): 4 | counter = cpu.stack.pop().value 5 | 6 | # make sure that we jump to an JUMPDEST opcode 7 | if not cpu.program[counter] == JUMPDEST: 8 | raise Exception("Can only jump to JUMPDEST") 9 | 10 | cpu.pc = counter 11 | cpu.gas_dec(8) 12 | 13 | def jumpi(cpu): 14 | counter, b = cpu.stack.pop().value, cpu.stack.pop().value 15 | 16 | if b != 0: cpu.pc = counter 17 | else : cpu.pc += 1 18 | 19 | cpu.gas_dec(10) 20 | 21 | def pc(cpu): 22 | cpu.stack.push(cpu.pc) 23 | cpu.pc += 1 24 | cpu.gas_dec(2) 25 | 26 | def jumpdest(cpu): 27 | cpu.pc += 1 28 | cpu.gas_dec(1) 29 | -------------------------------------------------------------------------------- /ops/memory_ops.py: -------------------------------------------------------------------------------- 1 | from utils import * 2 | 3 | def byte(cpu): 4 | i, x = cpu.stack.pop().value, to32(cpu.stack.pop().bytes) 5 | try: cpu.stack.push(x[i]) 6 | except: cpu.stack.push(0x00) 7 | cpu.pc += 1 8 | cpu.gas_dec(3) 9 | 10 | def mstore8(cpu): 11 | offset, value = cpu.stack.pop().value, cpu.stack.pop().bytes 12 | cpu.memory.store(offset, value) 13 | cpu.pc += 1 14 | 15 | def mstore(cpu): 16 | offset, value = cpu.stack.pop().value, cpu.stack.pop().bytes 17 | cpu.memory.store(offset, value) 18 | cpu.pc += 1 19 | 20 | def mload(cpu): 21 | offset = cpu.stack.pop().value 22 | value = cpu.memory.load(offset) 23 | cpu.stack.push(value) 24 | cpu.pc += 1 25 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from computer.cpu import CPU 2 | 3 | GAS = 21000 4 | BALANCE = 500 # in wei 5 | CALLDATA = [0xFF, 0xFF, 0xFF, 0xFF] 6 | PREV_RETURNDATA = [0xAA, 0xAA] 7 | 8 | PUSH = [0x62, 0xFF, 0xFF, 0xFF, 0x60, 0xFF] 9 | # MSTORE = [0x52, 0x60, 0xFF, 0x60, 0x1C, 0x53] 10 | MSTORE = [0x52] 11 | MLOAD = [0x60, 0x00, 0x51] 12 | ADD = [0x01] 13 | END = [0x00] 14 | 15 | BYTE = [0x68, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x60, 0x1F, 0x1A] 16 | 17 | CALLDATACOPY = [0x60, 0x02, 0x60, 0x00, 0x60, 0x00, 0x37] 18 | 19 | # PROGRAM = PUSH + MSTORE + MLOAD*2 + ADD 20 | # PROGRAM = PUSH + MSTORE 21 | # PROGRAM = PUSH + ADD 22 | PROGRAM = CALLDATACOPY 23 | 24 | cpu = CPU(PROGRAM, BALANCE, GAS, CALLDATA, PREV_RETURNDATA) 25 | cpu.run() 26 | -------------------------------------------------------------------------------- /computer/stack.py: -------------------------------------------------------------------------------- 1 | from ethereum.number import Number 2 | 3 | class StackTooLargeException(Exception): 4 | pass 5 | class StackEmptyException(Exception): 6 | pass 7 | 8 | class Stack: 9 | def __init__(self): 10 | self.stack = [] 11 | 12 | def pop(self): 13 | if len(self.stack) == 0: raise StackEmptyException() 14 | 15 | return self.stack.pop() 16 | 17 | def push(self, value): 18 | if len(self.stack) > 1024: raise StackTooLargeException() 19 | 20 | self.stack.append(Number(value)) 21 | 22 | def __str__(self): return " ".join([str(x) for x in self.stack]) 23 | 24 | def get(self, index) : return self.stack[index] 25 | def set(self, index, value): self.stack[index] = value 26 | -------------------------------------------------------------------------------- /ops/storage_ops.py: -------------------------------------------------------------------------------- 1 | def sload(cpu): 2 | key = cpu.stack.pop().value 3 | warm, value = cpu.storage.load(key) 4 | cpu.stack.push(value) 5 | 6 | cpu.gas_dec(100 if warm else 2100) 7 | cpu.pc += 1 8 | 9 | def sstore(cpu): 10 | key, value = cpu.stack.pop().value, cpu.stack.pop().value 11 | warm, old_value = cpu.storage.store(key, value) 12 | 13 | base_dynamic_gas = 0 14 | 15 | # TODO: test 16 | if value != old_value: 17 | if old_value == 0: 18 | base_dynamic_gas = 20000 19 | else: 20 | base_dynamic_gas = 2900 21 | 22 | access_cost = 100 if warm else 2100 23 | cpu.gas_dec(base_dynamic_gas + access_cost) 24 | 25 | cpu.pc += 1 26 | 27 | # TODO: do refunds 28 | 29 | -------------------------------------------------------------------------------- /ops/misc.py: -------------------------------------------------------------------------------- 1 | from ethereum.number import * 2 | from eth_hash.auto import keccak 3 | 4 | def sha3(cpu): 5 | offset, size = cpu.stack.pop().value, cpu.stack.pop().value 6 | value = cpu.memory.access(offset, size) 7 | cpu.stack.push(keccak(value)) 8 | 9 | cpu.pc += 1 10 | 11 | # calculate gas 12 | minimum_word_size = (size + 31) / 32 13 | dynamic_gas = 6 * minimum_word_size # TODO: + memory_expansion_cost 14 | cpu.gas_dec(30 + dynamic_gas) 15 | 16 | def _return(cpu): 17 | offset, size = cpu.stack.pop().value, cpu.stack.pop().value 18 | cpu.returndata = cpu.memory.access(offset, size) 19 | 20 | cpu.stop_flag = True 21 | cpu.pc += 1 22 | cpu.gas_dec(0) 23 | 24 | def revert(cpu): 25 | offset, size = cpu.stack.pop().value, cpu.stack.pop().value 26 | cpu.returndata = cpu.memory.access(offset, size) 27 | 28 | cpu.stop_flag = True 29 | cpu.revert_flag = True 30 | cpu.pc += 1 31 | cpu.gas_dec(0) 32 | 33 | def selfdestruct(cpu): 34 | address = cpu.stack.pop().value 35 | 36 | # TODO: send address all the balance 37 | # something with cpu.balance 38 | 39 | cpu.stop_flag = True 40 | cpu.pc += 1 41 | cpu.gas_dec(5000) 42 | 43 | -------------------------------------------------------------------------------- /test/test_arithmetic.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | sys.path.insert(1, os.path.join(sys.path[0], '..')) 3 | import unittest 4 | from computer.cpu import * 5 | from ethereum.number import * 6 | 7 | PUSH = [0x60, 0x03, 0x60, 0x02] 8 | 9 | class TestArithmetic(unittest.TestCase): 10 | 11 | def test_add(self): 12 | PROGRAM = PUSH + [0x01] 13 | cpu = CPU(PROGRAM, 0, 21000, [], []) 14 | cpu.run() 15 | 16 | self.assertEqual(cpu.stack.get(0).value, 0x05) 17 | 18 | def test_mul(self): 19 | PROGRAM = PUSH + [0x02] 20 | cpu = CPU(PROGRAM, 0, 21000, [], []) 21 | cpu.run() 22 | 23 | self.assertEqual(cpu.stack.get(0).value, 0x06) 24 | 25 | def test_sub(self): 26 | PROGRAM = PUSH + [0x03] 27 | cpu = CPU(PROGRAM, 0, 21000, [], []) 28 | cpu.run() 29 | 30 | self.assertEqual(cpu.stack.get(0).value, MAX_UINT-1) 31 | 32 | def test_div(self): 33 | PROGRAM = PUSH + [0x04] 34 | cpu = CPU(PROGRAM, 0, 21000, [], []) 35 | cpu.run() 36 | 37 | self.assertEqual(cpu.stack.get(0).value, 0) 38 | 39 | def test_mod(self): 40 | PROGRAM = PUSH + [0x06] 41 | cpu = CPU(PROGRAM, 0, 21000, [], []) 42 | cpu.run() 43 | 44 | self.assertEqual(cpu.stack.get(0).value, 2) 45 | 46 | def test_add_mod(self): 47 | PROGRAM = PUSH + [0x60, 0x05, 0x08] 48 | cpu = CPU(PROGRAM, 0, 21000, [], []) 49 | cpu.run() 50 | 51 | self.assertEqual(cpu.stack.get(0).value, 1) 52 | 53 | if __name__ == '__main__': 54 | unittest.main() 55 | -------------------------------------------------------------------------------- /computer/memory.py: -------------------------------------------------------------------------------- 1 | class Memory: 2 | def __init__(self): 3 | self.memory = [] 4 | 5 | def access(self, offset, size): 6 | # TODO: there is some gas for accessing higher offsets 7 | return self.memory[offset:offset+size] 8 | 9 | def store(self, offset, value): 10 | memory_expansion_cost = 0 11 | 12 | # increase memory if not enough available 13 | if len(self.memory) <= offset: 14 | 15 | expansion_size = 0 16 | 17 | # init memory if memory is empty 18 | if len(self.memory) == 0: 19 | expansion_size = expansion_size + 32 20 | self.memory = [0x00 for _ in range(32)] 21 | 22 | # extend memory if needed 23 | if len(self.memory) - offset < 0: 24 | expansion_size += offset - len(self.memory) 25 | self.memory.append(0x00 * expansion_size) 26 | 27 | # calc memory expansion gas cost 28 | memory_expansion_cost = self.calc_memory_expansion_gas(expansion_size) 29 | 30 | self.memory[offset:len(value)] = value 31 | return memory_expansion_cost 32 | 33 | def load(self, offset): 34 | value = self.memory[offset:offset+32] 35 | if len(value) < 32: raise Exception("mload: Not 32 Bytes") 36 | 37 | return value 38 | 39 | def calc_memory_expansion_gas(self, memory_byte_size): 40 | memory_size_word = (memory_byte_size + 31) / 32 41 | memory_cost = (memory_size_word ** 2) / 512 + (3 * memory_size_word) 42 | return round(memory_cost) 43 | 44 | class ROM: 45 | pass 46 | -------------------------------------------------------------------------------- /ops/log.py: -------------------------------------------------------------------------------- 1 | class Log: 2 | def __init__(self, data, topic1=None, topic2=None, topic3=None, topic4=None): 3 | self.data = data 4 | self.topic1 = topic1 5 | self.topic2 = topic2 6 | self.topic3 = topic3 7 | self.topic4 = topic4 8 | 9 | def __str__(self): return f"Log: {self.data}" 10 | 11 | def calc_gas(topic_count, size, memory_expansion_cost=0): 12 | # 375 := static_gas 13 | return 375 * topic_count + 8 * size + memory_expansion_cost 14 | 15 | def log0(cpu): 16 | offset, size = cpu.stack.pop().value, cpu.stack.pop().value 17 | 18 | data = cpu.memory.access(offset, size) 19 | log = Log(data) 20 | cpu.append_log(log) 21 | 22 | cpu.pc += 1 23 | cpu.gas_dec(calc_gas(0, size)) # TODO: memeory expansion cost 24 | 25 | def log1(cpu): 26 | offset, size = cpu.stack.pop().value, cpu.stack.pop().value 27 | topic = cpu.stack.pop().value 28 | 29 | data = cpu.memory.access(offset, size) 30 | log = Log(data, topic) 31 | cpu.append_log(log) 32 | 33 | cpu.pc += 1 34 | cpu.gas_dec(calc_gas(1, size)) # TODO: memeory expansion cost 35 | 36 | def log2(cpu): 37 | offset, size = cpu.stack.pop().value, cpu.stack.pop().value 38 | topic1, topic2 = cpu.stack.pop().value, cpu.stack.pop().value 39 | 40 | data = cpu.memory.access(offset, size) 41 | log = Log(data, topic1, topic2) 42 | cpu.append_log(log) 43 | 44 | cpu.pc += 1 45 | cpu.gas_dec(calc_gas(2, size)) # TODO: memeory expansion cost 46 | 47 | def log3(cpu): 48 | offset, size = cpu.stack.pop().value, cpu.stack.pop().value 49 | topic1 = cpu.stack.pop().value 50 | topic2 = cpu.stack.pop().value 51 | topic3 = cpu.stack.pop().value 52 | 53 | data = cpu.memory.access(offset, size) 54 | log = Log(data, topic1, topic2, topic3) 55 | cpu.append_log(log) 56 | 57 | cpu.pc += 1 58 | cpu.gas_dec(calc_gas(3, size)) # TODO: memeory expansion cost 59 | 60 | def log4(cpu): 61 | offset, size = cpu.stack.pop().value, cpu.stack.pop().value 62 | topic1 = cpu.stack.pop().value 63 | topic2 = cpu.stack.pop().value 64 | topic3 = cpu.stack.pop().value 65 | topic4 = cpu.stack.pop().value 66 | 67 | data = cpu.memory.access(offset, size) 68 | log = Log(data, topic1, topic2, topic3, topic4) 69 | cpu.append_log(log) 70 | 71 | cpu.pc += 1 72 | cpu.gas_dec(calc_gas(4, size)) # TODO: memeory expansion cost 73 | -------------------------------------------------------------------------------- /ops/arithmetic.py: -------------------------------------------------------------------------------- 1 | from utils import * 2 | from ethereum.number import * 3 | 4 | # protect from under-overflow 5 | # always stay at 32 bytes! 6 | def protect(value): 7 | if value < 0 : return value + MAX_UINT # handle underflow 8 | elif value > MAX_UINT: return 0 + (value - MAX_UINT) # handle overflow 9 | else : return value % MAX_UINT # just to be sure 10 | 11 | def add(cpu): 12 | a, b = cpu.stack.pop().value, cpu.stack.pop().value 13 | cpu.stack.push(protect(a+b)) 14 | cpu.pc += 1 15 | cpu.gas_dec(3) 16 | 17 | def mul(cpu): 18 | a, b = cpu.stack.pop().value, cpu.stack.pop().value 19 | cpu.stack.push(protect(a*b)) 20 | cpu.pc += 1 21 | cpu.gas_dec(5) 22 | 23 | def sub(cpu): 24 | a, b = cpu.stack.pop().value, cpu.stack.pop().value 25 | cpu.stack.push(protect(a-b)) 26 | cpu.pc += 1 27 | cpu.gas_dec(3) 28 | 29 | def div(cpu): 30 | a, b = cpu.stack.pop().value, cpu.stack.pop().value 31 | cpu.stack.push(0 if b == 0 else a // b) 32 | cpu.pc += 1 33 | cpu.gas_dec(5) 34 | 35 | # TODO: overflow protection 36 | def sdiv(cpu): 37 | a, b = cpu.stack.pop().value, cpu.stack.pop().value 38 | cpu.stack.push(0 if b == 0 else a // b) 39 | cpu.pc += 1 40 | cpu.gas -= 5 41 | cpu.gas_dec(5) 42 | 43 | def mod(cpu): 44 | a, b = cpu.stack.pop().value, cpu.stack.pop().value 45 | cpu.stack.push(0 if b == 0 else a % b) 46 | cpu.pc += 1 47 | cpu.gas_dec(5) 48 | 49 | def smod(cpu): 50 | a, b = cpu.stack.pop().value, cpu.stack.pop().value 51 | cpu.stack.push(0 if b == 0 else a % b) 52 | cpu.pc += 1 53 | cpu.gas_dec(5) 54 | 55 | def addmod(cpu): 56 | a, b = cpu.stack.pop().value, cpu.stack.pop().value 57 | N = cpu.stack.pop().value 58 | print("addmod:", a,b,N) 59 | cpu.stack.push(protect(a + b) % N) 60 | cpu.pc += 1 61 | cpu.gas_dec(8) 62 | 63 | def mulmod(cpu): 64 | a, b, N = cpu.stack.pop().value, cpu.stack.pop().value, cpu.stack.pop().value 65 | cpu.stack.push(protect(a + b) * N) 66 | cpu.pc += 1 67 | cpu.gas_dec(8) 68 | 69 | def exp(cpu): 70 | a, exponent = cpu.stack.pop().value, cpu.stack.pop() 71 | cpu.stack.push(protect(a ** exponent.value)) 72 | cpu.pc += 1 73 | cpu.gas_dec(10 + (50 * len(exponent.bytes))) 74 | 75 | # TODO 76 | def signextend(cpu): 77 | b, x = cpu.stack.pop().value, cpu.stack.pop().value 78 | 79 | 80 | -------------------------------------------------------------------------------- /ethereum/opcodes.py: -------------------------------------------------------------------------------- 1 | STOP = 0x0 2 | 3 | # MATH 4 | ADD = 0x1 5 | MUL = 0x2 6 | SUB = 0x3 7 | DIV = 0x4 8 | SDIV = 0x5 9 | MOD = 0x6 10 | SMOD = 0x7 11 | ADDMOD = 0x8 12 | MULMOD = 0x9 13 | EXP = 0xA 14 | SIGNEXTEND = 0xB 15 | 16 | # COMP 17 | LT = 0x10 18 | GT = 0x11 19 | SLT = 0x12 20 | SGT = 0x13 21 | EQ = 0x14 22 | ISZERO = 0x15 23 | 24 | # LOGIC 25 | AND = 0x16 26 | OR = 0x17 27 | XOR = 0x18 28 | NOT = 0x19 29 | 30 | # BIT 31 | BYTE = 0x1A 32 | SHL = 0x1B 33 | SHR = 0x1C 34 | SAR = 0x1D 35 | 36 | # MISC 37 | SHA3 = 0x20 38 | 39 | # ENV 40 | ADDRESS = 0x30 41 | BALANCE = 0x31 42 | ORIGIN = 0x32 43 | CALLER = 0x33 44 | CALLVALUE = 0x34 45 | CALLDATALOAD = 0x35 46 | CALLDATASIZE = 0x36 47 | CALLDATACOPY = 0x37 48 | CODESIZE = 0x38 49 | CODECOPY = 0x39 50 | GASPRICE = 0x3A 51 | EXTCODESIZE = 0x3B 52 | EXTCODECOPY = 0x3C 53 | RETURNDATASIZE = 0x3D 54 | RETURNDATACOPY = 0x3E 55 | EXTCODEHASH = 0x3F 56 | BLOCKHASH = 0x40 57 | COINBASE = 0x41 58 | TIMESTAMP = 0x42 59 | NUMBER = 0x43 60 | DIFFICULTY = 0x44 61 | GASLIMIT = 0x45 62 | CHAINID = 0x46 63 | SELFBALANCE = 0x47 64 | BASEFEE = 0x48 65 | 66 | POP = 0x50 67 | 68 | # MEMORY 69 | MLOAD = 0x51 70 | MSTORE = 0x52 71 | MSTORE8 = 0x53 72 | 73 | # MEMORY 74 | SLOAD = 0x54 75 | SSTORE = 0x55 76 | 77 | # JUMP 78 | JUMP = 0x56 79 | JUMPI = 0x57 80 | PC = 0x58 81 | JUMPDEST = 0x5B 82 | 83 | # PUSH 84 | PUSH1 = 0x60 85 | PUSH2 = 0x61 86 | PUSH3 = 0x62 87 | PUSH4 = 0x63 88 | PUSH5 = 0x64 89 | PUSH6 = 0x65 90 | PUSH7 = 0x66 91 | PUSH8 = 0x67 92 | PUSH9 = 0x68 93 | PUSH10 = 0x69 94 | PUSH11 = 0x6A 95 | PUSH12 = 0x6B 96 | PUSH13 = 0x6C 97 | PUSH14 = 0x6D 98 | PUSH15 = 0x6E 99 | PUSH16 = 0x6F 100 | PUSH17 = 0x70 101 | PUSH18 = 0x71 102 | PUSH19 = 0x72 103 | PUSH20 = 0x73 104 | PUSH21 = 0x74 105 | PUSH22 = 0x75 106 | PUSH23 = 0x76 107 | PUSH24 = 0x77 108 | PUSH25 = 0x78 109 | PUSH26 = 0x79 110 | PUSH27 = 0x7A 111 | PUSH28 = 0x7B 112 | PUSH29 = 0x7C 113 | PUSH30 = 0x7D 114 | PUSH31 = 0x7E 115 | PUSH32 = 0x7F 116 | 117 | # DUP 118 | DUP1 = 0x80 119 | DUP2 = 0x81 120 | DUP3 = 0x82 121 | DUP4 = 0x83 122 | DUP5 = 0x84 123 | DUP6 = 0x85 124 | DUP7 = 0x86 125 | DUP8 = 0x87 126 | DUP9 = 0x88 127 | DUP10 = 0x89 128 | DUP11 = 0x8A 129 | DUP12 = 0x8B 130 | DUP13 = 0x8C 131 | DUP14 = 0x8D 132 | DUP15 = 0x8E 133 | DUP16 = 0x8F 134 | 135 | # SWAP 136 | SWAP1 = 0x90 137 | SWAP2 = 0x91 138 | SWAP3 = 0x92 139 | SWAP4 = 0x93 140 | SWAP5 = 0x94 141 | SWAP6 = 0x95 142 | SWAP7 = 0x96 143 | SWAP8 = 0x97 144 | SWAP9 = 0x98 145 | SWAP10 = 0x99 146 | SWAP11 = 0x9A 147 | SWAP12 = 0x9B 148 | SWAP13 = 0x9C 149 | SWAP14 = 0x9D 150 | SWAP15 = 0x9E 151 | SWAP16 = 0x9F 152 | 153 | # LOG 154 | LOG0 = 0xA0 155 | LOG1 = 0xA1 156 | LOG2 = 0xA2 157 | LOG3 = 0xA3 158 | LOG4 = 0xA4 159 | 160 | # CONTRACT 161 | CREATE = 0xF0 162 | CALL = 0xF1 163 | CALLCODE = 0xF2 # legacy NOT supported by us, fixed by DELEGATECALL 164 | RETURN = 0xF3 165 | DELEGATECALL = 0xF4 166 | CREATE2 = 0xF5 167 | STATICCALL = 0xFA 168 | REVERT = 0xFD 169 | INVALID = 0xFE 170 | SELFDESTRUCT = 0xFF 171 | 172 | 173 | # you could have a list of all opcodes but 174 | # I was to lazy to do that 175 | def str_2_opcode(opcode_name): 176 | return globals()[opcode_name] 177 | def opcode_2_str(opcode): 178 | for k,v in globals().items(): 179 | if v == opcode: return k 180 | 181 | def is_valid(opcode): 182 | if opcode == 0xFE or opcode == 0xF2: return False 183 | return opcode_2_str(opcode) in globals() 184 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | -------------------------------------------------------------------------------- /ops/env.py: -------------------------------------------------------------------------------- 1 | def address(cpu): 2 | cpu.stack.push("0xD912bCb457Ea4FB70EEAD8cfbbb97a32319e4dC7") 3 | cpu.pc += 1 4 | cpu.gas_dec(3) 5 | 6 | def balance(cpu): 7 | address = cpu.stack.pop() 8 | warm, account = cpu.access_account(address) 9 | cpu.stack.push(account.balance) 10 | 11 | cpu.pc += 1 12 | cpu.gas_dec(100 if warm else 2600) 13 | 14 | def origin(cpu): 15 | cpu.stack.push("0xD912bCb457Ea4FB70EEAD8cfbbb97a32319e4dC7") 16 | cpu.pc += 1 17 | cpu.gas_dec(2) 18 | 19 | def caller(cpu): 20 | cpu.stack.push("0xD912bCb457Ea4FB70EEAD8cfbbb97a32319e4dC7") 21 | cpu.pc += 1 22 | cpu.gas_dec(2) 23 | 24 | def callvalue(cpu): 25 | cpu.stack.push(0x00) 26 | cpu.pc += 1 27 | cpu.gas_dec(2) 28 | 29 | def calldataload(cpu): 30 | i = cpu.stack.pop() 31 | 32 | delta = 0 33 | if i+32 > len(cpu.calldata): 34 | delta = i+32 - len(cpu.calldata) 35 | 36 | # always has to be 32 bytes 37 | # if its not we append 0x00 bytes until it is 38 | calldata = cpu.calldata[i:i+32-delta] 39 | calldata += 0x00*delta 40 | 41 | cpu.stack.push(calldata) 42 | cpu.pc += 1 43 | cpu.gas_dec(3) 44 | 45 | def calldatasize(cpu): 46 | cpu.stack.push(0x00) 47 | cpu.pc += 1 48 | cpu.gas_dec(2) 49 | 50 | def calldatacopy(cpu): 51 | destOffset = cpu.stack.pop().value 52 | offset = cpu.stack.pop().value 53 | size = cpu.stack.pop().value 54 | 55 | calldata = cpu.calldata[offset:offset+size] 56 | memory_expansion_cost = cpu.memory.store(destOffset, calldata) 57 | 58 | static_gas = 3 59 | minimum_word_size = (size + 31) // 32 60 | dynamic_gas = 3 * minimum_word_size + memory_expansion_cost 61 | 62 | cpu.gas_dec(static_gas + dynamic_gas) 63 | cpu.pc += 1 64 | 65 | def codesize(cpu): 66 | cpu.stack.push(0x00) 67 | cpu.pc += 1 68 | cpu.gas_dec(2) 69 | 70 | def codecopy(cpu): 71 | destOffset = cpu.stack.pop().value 72 | offset = cpu.stack.pop().value 73 | size = cpu.stack.pop().value 74 | 75 | code = cpu.program[offset:offset+size] 76 | memory_expansion_cost = cpu.memory.store(destOffset, code) 77 | 78 | static_gas = 3 79 | minimum_word_size = (size + 31) / 32 80 | dynamic_gas = 3 * minimum_word_size + memory_expansion_cost 81 | 82 | cpu.gas_dec(static_gas + dynamic_gas) 83 | cpu.pc += 1 84 | 85 | def gasprice(cpu): 86 | cpu.stack.push(0x00) 87 | cpu.pc += 1 88 | cpu.gas_dec(2) 89 | 90 | def extcodesize(cpu): 91 | address = cpu.stack.pop() 92 | 93 | warm, account = cpu.access_account(address) 94 | cpu.stack.push(account.codesize) 95 | 96 | cpu.gas_dec(100 if warm else 2600) 97 | cpu.pc += 1 98 | 99 | def extcodecopy(cpu): 100 | address = cpu.stack.pop() 101 | destOffset = cpu.stack.pop() 102 | offset = cpu.stack.pop() 103 | size = cpu.stack.pop() 104 | 105 | warm, account = cpu.access_account(address) 106 | extcode = account.code[offset:offset+size] 107 | memory_expansion_cost = cpu.memory.store(destOffset, extcode) 108 | 109 | # refactor this in seperate method 110 | minimum_word_size = (size + 31) / 32 111 | dynamic_gas = 3 * minimum_word_size + memory_expansion_cost 112 | address_access_cost = 100 if warm else 2600 113 | 114 | cpu.gas_dec(dynamic_gas + address_access_cost) 115 | cpu.pc += 1 116 | 117 | def returndatasize(cpu): 118 | cpu.stack.push(len(cpu.prev_returndata)) 119 | cpu.pc += 1 120 | cpu.gas_dec(2) 121 | 122 | def returndatacopy(cpu): 123 | destOffset = cpu.stack.pop() 124 | offset = cpu.stack.pop() 125 | size = cpu.stack.pop() 126 | 127 | returndata = cpu.program[offset:offset+size] 128 | memory_expansion_cost = cpu.memory.store(destOffset, returndata) 129 | 130 | minimum_word_size = (size + 31) / 32 131 | dynamic_gas = 3 * minimum_word_size + memory_expansion_cost 132 | 133 | cpu.gas_dec(3 + dynamic_gas) 134 | cpu.pc += 1 135 | 136 | 137 | def extcodehash(cpu): 138 | address = cpu.stack.pop() 139 | 140 | warm, account = cpu.access_account(address) 141 | cpu.stack.push(account.hash) 142 | 143 | cpu.gas_dec(100 if warm else 2600) 144 | cpu.pc += 1 145 | 146 | def blockhash(cpu): 147 | blockNumber = cpu.stack.pop().value 148 | if blockNumber > 256: raise Exception("blockhash: Only last 256 blocks can be accessed") 149 | cpu.stack.push(0x1cbcfa1ffb1ca1ca8397d4f490194db5fc0543089b9dee43f76cf3f962a185e8) 150 | 151 | cpu.pc += 1 152 | cpu.gas_dec(20) 153 | 154 | def coinbase(cpu): 155 | # address of miner 156 | cpu.stack.push(0x1cbcfa1ffb1ca1ca8397d4f490194db5fc0543089b9dee43f76cf3f962a185e8) 157 | 158 | cpu.pc += 1 159 | cpu.gas_dec(2) 160 | -------------------------------------------------------------------------------- /computer/cpu.py: -------------------------------------------------------------------------------- 1 | from computer.stack import Stack 2 | from ethereum.opcodes import * 3 | from ops.arithmetic import * 4 | from ops.push import _push 5 | from ops.comp import * 6 | from ops.logic import * 7 | from ops.bit import * 8 | from ops.env import * 9 | from computer.memory import * 10 | from computer.storage import * 11 | from ops.memory_ops import * 12 | from ops.misc import * 13 | from ethereum.account import * 14 | from ops.jump import * 15 | from ops.dup import * 16 | from ops.swap import * 17 | from ops.swap import * 18 | from ops.log import * 19 | 20 | 21 | # TODO: rename to execution engine or something 22 | class CPU: 23 | def __init__(self, 24 | program, 25 | balance, 26 | gas, 27 | calldata=[], 28 | prev_returndata=[]): 29 | self.pc = 0 30 | self.balance = balance 31 | self.stack = Stack() 32 | self.memory = Memory() 33 | self.storage = Storage() 34 | self.program = self.load(program) 35 | 36 | self.stop_flag = False 37 | self.revert_flag = False 38 | 39 | # inputs to program 40 | self.gas = gas 41 | self.calldata = calldata 42 | 43 | # return of prev call 44 | self.prev_returndata = prev_returndata 45 | 46 | # cache 47 | self.address_cache = [] 48 | 49 | # output 50 | self.returndata = [] 51 | self.logs = [] 52 | 53 | def load(self, program): 54 | self.reset() 55 | return program 56 | 57 | def append_log(log): self.logs.append(log) 58 | 59 | def reset(self): 60 | self.pc, self.stack = 0, Stack() 61 | self.memory, self.storage = Memory(), Storage() 62 | 63 | def peek(self): return self.program[self.pc] 64 | 65 | def gas_dec(self, amount): 66 | if self.gas - amount < 0: 67 | raise Exception(f"{self.gas} gas left and {amount} required") 68 | self.gas -= amount 69 | 70 | def gas_inc(self, amount): self.gas += amount 71 | 72 | def access_account(self, address): 73 | warm = False # check if address is warm or cold 74 | 75 | if address in self.address_cache: warm = True 76 | else : self.address_cache.append(address) 77 | 78 | return warm, Account([0xFF], 0xAA) # TODO: return proper Account 79 | 80 | # check if we want to run the next opcode 81 | def exec_next_opcode(self): 82 | if self.pc > len(self.program)-1 : return False 83 | if self.stop_flag : return False 84 | if not is_valid(self.program[self.pc]): return False 85 | 86 | return True 87 | 88 | def run(self): 89 | # run until we run out of opcodes 90 | while self.exec_next_opcode(): 91 | op = self.program[self.pc] 92 | print("op: ", hex(op)) 93 | print("pc: " , self.pc) 94 | 95 | # ARITHMETIC 96 | if op == ADD: add(self) 97 | if op == MUL: mul(self) 98 | if op == SUB: sub(self) 99 | if op == DIV: div(self) 100 | if op == SDIV: sdiv(self) 101 | if op == MOD: mod(self) 102 | if op == SMOD: smod(self) 103 | if op == ADDMOD: addmod(self) 104 | if op == MULMOD: mulmod(self) 105 | if op == EXP: exp(self) 106 | if op == SIGNEXTEND: signextend(self) 107 | 108 | # COMP 109 | if op == LT: lt(self) 110 | if op == GT: gt(self) 111 | if op == EQ: eq(self) 112 | if op == ISZERO: iszero(self) 113 | 114 | # LOGIC 115 | if op == AND: _and(self) 116 | if op == OR: _or(self) 117 | if op == XOR: _xor(self) 118 | if op == NOT: _not(self) 119 | 120 | # BIT 121 | if op == SHL: shl(self) 122 | if op == SHR: shr(self) 123 | 124 | # MISC 125 | if op == SHA3: sha3(self) 126 | 127 | # ENV 128 | if op == ADDRESS: address(self) 129 | if op == BALANCE: balance(self) 130 | if op == ORIGIN: origin(self) 131 | if op == CALLER: caller(self) 132 | if op == CALLVALUE: callvalue(self) 133 | if op == CALLDATALOAD: calldataload(self) 134 | if op == CALLDATASIZE: calldatasize(self) 135 | if op == CALLDATACOPY: calldatacopy(self) 136 | if op == CODESIZE: codesize(self) 137 | if op == CODECOPY: codecopy(self) 138 | if op == GASPRICE: gasprice(self) 139 | 140 | # MEMORY 141 | if op == BYTE: byte(self) 142 | if op == MLOAD: mload(self) 143 | if op == MSTORE8: mstore8(self) 144 | if op == MSTORE: mstore(self) 145 | 146 | # MEMORY 147 | if op == SLOAD: sload(self) 148 | if op == SSTORE: sstore(self) 149 | 150 | # JUMP 151 | if op == JUMP: jump(self) 152 | if op == JUMPI: jumpi(self) 153 | if op == PC: pc(self) 154 | if op == JUMPDEST: jumpest(self) 155 | 156 | # PUSH 157 | if op == PUSH1: _push(self, 1) 158 | if op == PUSH2: _push(self, 2) 159 | if op == PUSH3: _push(self, 3) 160 | if op == PUSH4: _push(self, 4) 161 | if op == PUSH5: _push(self, 5) 162 | if op == PUSH6: _push(self, 6) 163 | if op == PUSH7: _push(self, 7) 164 | if op == PUSH8: _push(self, 8) 165 | if op == PUSH9: _push(self, 9) 166 | if op == PUSH10: _push(self, 10) 167 | if op == PUSH11: _push(self, 11) 168 | if op == PUSH12: _push(self, 12) 169 | if op == PUSH13: _push(self, 13) 170 | if op == PUSH14: _push(self, 14) 171 | if op == PUSH15: _push(self, 15) 172 | if op == PUSH16: _push(self, 16) 173 | if op == PUSH17: _push(self, 17) 174 | if op == PUSH18: _push(self, 18) 175 | if op == PUSH19: _push(self, 19) 176 | if op == PUSH20: _push(self, 20) 177 | if op == PUSH21: _push(self, 21) 178 | if op == PUSH22: _push(self, 22) 179 | if op == PUSH23: _push(self, 23) 180 | if op == PUSH24: _push(self, 24) 181 | if op == PUSH25: _push(self, 25) 182 | if op == PUSH26: _push(self, 26) 183 | if op == PUSH27: _push(self, 27) 184 | if op == PUSH28: _push(self, 28) 185 | if op == PUSH29: _push(self, 29) 186 | if op == PUSH30: _push(self, 30) 187 | if op == PUSH31: _push(self, 31) 188 | if op == PUSH32: _push(self, 32) 189 | 190 | # DUP 191 | if op == DUP1: _dup(self, 1) 192 | if op == DUP2: _dup(self, 2) 193 | if op == DUP3: _dup(self, 3) 194 | if op == DUP4: _dup(self, 4) 195 | if op == DUP5: _dup(self, 5) 196 | if op == DUP6: _dup(self, 6) 197 | if op == DUP7: _dup(self, 7) 198 | if op == DUP8: _dup(self, 8) 199 | if op == DUP9: _dup(self, 9) 200 | if op == DUP10: _dup(self, 10) 201 | if op == DUP11: _dup(self, 11) 202 | if op == DUP12: _dup(self, 12) 203 | if op == DUP13: _dup(self, 13) 204 | if op == DUP14: _dup(self, 14) 205 | if op == DUP15: _dup(self, 15) 206 | if op == DUP16: _dup(self, 16) 207 | 208 | # SWAP 209 | if op == SWAP1: _swap(self, 1) 210 | if op == SWAP2: _swap(self, 2) 211 | if op == SWAP3: _swap(self, 3) 212 | if op == SWAP4: _swap(self, 4) 213 | if op == SWAP5: _swap(self, 5) 214 | if op == SWAP6: _swap(self, 6) 215 | if op == SWAP7: _swap(self, 7) 216 | if op == SWAP8: _swap(self, 8) 217 | if op == SWAP9: _swap(self, 9) 218 | if op == SWAP10: _swap(self, 10) 219 | if op == SWAP11: _swap(self, 11) 220 | if op == SWAP12: _swap(self, 12) 221 | if op == SWAP13: _swap(self, 13) 222 | if op == SWAP14: _swap(self, 14) 223 | if op == SWAP15: _swap(self, 15) 224 | if op == SWAP16: _swap(self, 16) 225 | 226 | # LOG 227 | if op == LOG0: log0(self) 228 | if op == LOG1: log1(self) 229 | if op == LOG2: log2(self) 230 | if op == LOG3: log3(self) 231 | if op == LOG4: log4(self) 232 | 233 | if op == RETURN: _return(self) 234 | if op == REVERT: revert(self) 235 | if op == SELFDESTRUCT: selfdestruct(self) 236 | 237 | print("gas: ", self.gas) 238 | print("stack: ", self.stack) 239 | print("memory: ", self.memory.memory) 240 | 241 | if self.revert_flag: pass # TODO: revert state 242 | # TODO: return things 243 | -------------------------------------------------------------------------------- /notebook/evm.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 49, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "STOP = 0x0\n", 10 | "\n", 11 | "# MATH\n", 12 | "ADD = 0x1\n", 13 | "MUL = 0x2\n", 14 | "SUB = 0x3\n", 15 | "DIV = 0x4\n", 16 | "SDIV = 0x5\n", 17 | "MOD = 0x6\n", 18 | "SMOD = 0x7\n", 19 | "ADDMOD = 0x8\n", 20 | "EXP = 0x9\n", 21 | "\n", 22 | "# COMP\n", 23 | "LT = 0x10\n", 24 | "GT = 0x11\n", 25 | "SLT = 0x12\n", 26 | "SGT = 0x13\n", 27 | "EQ = 0x14\n", 28 | "ISZERO = 0x15\n", 29 | "\n", 30 | "# LOGIC\n", 31 | "AND = 0x16\n", 32 | "OR = 0x17\n", 33 | "XOR = 0x18\n", 34 | "NOT = 0x19\n", 35 | "\n", 36 | "# LOGIC\n", 37 | "BYTE = 0x1A\n", 38 | "SHL = 0x1B\n", 39 | "SHR = 0x1C\n", 40 | "SAR = 0x1D\n", 41 | "\n", 42 | "# ENV\n", 43 | "ADDRESS = 0x30\n", 44 | "BALANCE = 0x31\n", 45 | "ORIGIN = 0x32\n", 46 | "CALLER = 0x33\n", 47 | "\n", 48 | "# PUSH\n", 49 | "PUSH1 = 0x60\n", 50 | "PUSH2 = 0x61\n", 51 | "PUSH3 = 0x62\n", 52 | "PUSH4 = 0x63\n", 53 | "PUSH5 = 0x64\n", 54 | "PUSH6 = 0x65\n", 55 | "PUSH7 = 0x66\n", 56 | "PUSH8 = 0x67\n", 57 | "PUSH9 = 0x68\n", 58 | "PUSH10 = 0x69\n", 59 | "PUSH11 = 0x6A\n", 60 | "PUSH12 = 0x6B\n", 61 | "PUSH13 = 0x6C\n", 62 | "PUSH14 = 0x6D\n", 63 | "PUSH15 = 0x6E\n", 64 | "PUSH16 = 0x6F\n", 65 | "PUSH17 = 0x70\n", 66 | "PUSH18 = 0x71\n", 67 | "PUSH19 = 0x72\n", 68 | "PUSH20 = 0x73\n", 69 | "PUSH21 = 0x74\n", 70 | "PUSH22 = 0x75\n", 71 | "PUSH23 = 0x76\n", 72 | "PUSH24 = 0x77\n", 73 | "PUSH25 = 0x78\n", 74 | "PUSH26 = 0x79\n", 75 | "PUSH27 = 0x7A\n", 76 | "PUSH28 = 0x7B\n", 77 | "PUSH29 = 0x7C\n", 78 | "PUSH30 = 0x7D\n", 79 | "PUSH31 = 0x7E\n", 80 | "PUSH32 = 0x7F" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 17, 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [ 89 | "PROGRAM = [0x60, 0x01, 0x60, 0x02, 0x5, 0x60, 0x05, 0x2, 0x0]" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 35, 95 | "metadata": {}, 96 | "outputs": [], 97 | "source": [ 98 | "def twos_comp(val, bits=256):\n", 99 | " if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255\n", 100 | " val = val - (1 << bits) # compute negative value\n", 101 | " return val " 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 59, 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [ 110 | "class CPU:\n", 111 | " def __init__(self):\n", 112 | " self.pc = 0\n", 113 | " self.stack = []\n", 114 | " self.program = []\n", 115 | " \n", 116 | " def load(self, program):\n", 117 | " self.reset()\n", 118 | " self.program = program\n", 119 | " \n", 120 | " def reset(self):\n", 121 | " self.pc, self.stack = 0, []\n", 122 | " \n", 123 | " def peek(self, i=1):\n", 124 | " return self.program[self.pc + i]\n", 125 | " \n", 126 | " def pop(self): return self.stack.pop() \n", 127 | " def push(self, data): self.stack.append(data)\n", 128 | " \n", 129 | " def run(self):\n", 130 | " op = self.program[self.pc]\n", 131 | "\n", 132 | " while op != STOP:\n", 133 | " # TODO: check for overflow\n", 134 | " if op == ADD: self.add()\n", 135 | " if op == MUL: self.mul()\n", 136 | " # TODO: check for underflow\n", 137 | " if op == SUB: self.sub()\n", 138 | " if op == DIV: self.div()\n", 139 | " if op == SDIV: self.sdiv()\n", 140 | " if op == MOD: self.mod()\n", 141 | " if op == SMOD: self.smod()\n", 142 | " if op == ADDMOD: self.addmod()\n", 143 | " if op == EXP: self.exp()\n", 144 | " \n", 145 | " # COMP\n", 146 | " if op == LT: self.lt()\n", 147 | " if op == GT: self.gt()\n", 148 | " if op == EQ: self.eq()\n", 149 | " if op == ISZERO: self.iszero()\n", 150 | " \n", 151 | " # LOGIC\n", 152 | " if op == AND: self._and()\n", 153 | " if op == OR: self._or()\n", 154 | " if op == XOR: self._xor()\n", 155 | " if op == NOT: self._not()\n", 156 | " \n", 157 | " # BIT OPS\n", 158 | " if op == SHL: self.shl()\n", 159 | " if op == SHR: self.shr()\n", 160 | " \n", 161 | " # ENV\n", 162 | " if op == ADDRESS: self.address()\n", 163 | " \n", 164 | " # PUSH\n", 165 | " if op == PUSH1: self.push(1)\n", 166 | " if op == PUSH2: self.push(2)\n", 167 | " if op == PUSH3: self.push(3)\n", 168 | " if op == PUSH4: self.push(4)\n", 169 | " if op == PUSH5: self.push(5)\n", 170 | " if op == PUSH6: self.push(6)\n", 171 | " if op == PUSH7: self.push(7)\n", 172 | " if op == PUSH8: self.push(8)\n", 173 | " if op == PUSH9: self.push(9)\n", 174 | " if op == PUSH10: self.push(10)\n", 175 | " if op == PUSH11: self.push(11)\n", 176 | " if op == PUSH12: self.push(12)\n", 177 | " if op == PUSH13: self.push(13)\n", 178 | " if op == PUSH14: self.push(14)\n", 179 | " if op == PUSH15: self.push(15)\n", 180 | " if op == PUSH16: self.push(16)\n", 181 | " if op == PUSH17: self.push(17)\n", 182 | " if op == PUSH18: self.push(18)\n", 183 | " if op == PUSH19: self.push(19)\n", 184 | " if op == PUSH20: self.push(20)\n", 185 | " if op == PUSH21: self.push(21)\n", 186 | " if op == PUSH22: self.push(22)\n", 187 | " if op == PUSH23: self.push(23)\n", 188 | " if op == PUSH24: self.push(24)\n", 189 | " if op == PUSH25: self.push(25)\n", 190 | " if op == PUSH26: self.push(26)\n", 191 | " if op == PUSH27: self.push(27)\n", 192 | " if op == PUSH28: self.push(28)\n", 193 | " if op == PUSH29: self.push(29)\n", 194 | " if op == PUSH30: self.push(30)\n", 195 | " if op == PUSH31: self.push(31)\n", 196 | " if op == PUSH32: self.push(32)\n", 197 | " \n", 198 | " op = self.program[self.pc]\n", 199 | " \n", 200 | " def add(self):\n", 201 | " a = self.stack.pop()\n", 202 | " b = self.stack.pop()\n", 203 | " self.stack.append(a + b)\n", 204 | " self.pc += 1\n", 205 | " def mul(self):\n", 206 | " a = self.stack.pop()\n", 207 | " b = self.stack.pop()\n", 208 | " self.stack.append(a * b)\n", 209 | " self.pc += 1\n", 210 | " def sub(self):\n", 211 | " a = self.stack.pop()\n", 212 | " b = self.stack.pop()\n", 213 | " self.stack.append(a - b)\n", 214 | " self.pc += 1\n", 215 | " def div(self):\n", 216 | " a = self.stack.pop()\n", 217 | " b = self.stack.pop()\n", 218 | " self.stack.append(0 if b == 0 else a // b) \n", 219 | " self.pc += 1\n", 220 | " def sdiv(self):\n", 221 | " a = twos_comp(self.stack.pop())\n", 222 | " b = twos_comp(self.stack.pop())\n", 223 | " self.stack.append(0 if b == 0 else a // b)\n", 224 | " self.pc += 1\n", 225 | " def mod(self):\n", 226 | " a = self.stack.pop()\n", 227 | " b = self.stack.pop()\n", 228 | " self.stack.append(0 if b == 0 else a % b)\n", 229 | " self.pc += 1\n", 230 | " def smod(self):\n", 231 | " a = twos_comp(self.stack.pop())\n", 232 | " b = twos_comp(self.stack.pop())\n", 233 | " self.stack.append(0 if b == 0 else a % b)\n", 234 | " self.pc += 1\n", 235 | " def addmod(self):\n", 236 | " a = self.stack.pop()\n", 237 | " b = self.stack.pop()\n", 238 | " N = self.stack.pop()\n", 239 | " self.stack.append((a + b) % N)\n", 240 | " self.pc += 1\n", 241 | " def exp(self):\n", 242 | " a = self.stack.pop()\n", 243 | " exponent = self.stack.pop()\n", 244 | " self.stack.append(a ** exponent)\n", 245 | " self.pc += 1\n", 246 | " \n", 247 | " def lt(self):\n", 248 | " a, b = self.pop(), self.pop()\n", 249 | " self.push(1 if a < b else 0)\n", 250 | " def gt(self):\n", 251 | " a, b = self.pop(), self.pop()\n", 252 | " self.push(1 if a > b else 0)\n", 253 | " def eq(self):\n", 254 | " a, b = self.pop(), self.pop()\n", 255 | " self.push(1 if a == b else 0)\n", 256 | " def iszero(self):\n", 257 | " a = self.pop()\n", 258 | " self.push(1 if a == 0 else 0)\n", 259 | " \n", 260 | " def _and(self):\n", 261 | " a, b = self.pop(), self.pop()\n", 262 | " self.push(a & b)\n", 263 | " def _or(self): \n", 264 | " a, b = self.pop(), self.pop()\n", 265 | " self.push(a | b)\n", 266 | " def _xor(self): \n", 267 | " a, b = self.pop(), self.pop()\n", 268 | " self.push(a ^ b)\n", 269 | " def _not(self): \n", 270 | " a = self.pop()\n", 271 | " self.push(~a)\n", 272 | " \n", 273 | " def shl(self): \n", 274 | " shift, value = self.pop(), self.pop()\n", 275 | " self.push(value << shift)\n", 276 | " def shr(self): \n", 277 | " shift, value = self.pop(), self.pop()\n", 278 | " self.push(value >> shift)\n", 279 | " \n", 280 | " def address(self): \n", 281 | " self.push(0xD912bCb457Ea4FB70EEAD8cfbbb97a32319e4dC7)\n", 282 | " \n", 283 | " def push(self, n):\n", 284 | " for i in n: self.stack.append(self.peek(i))\n", 285 | " self.pc += n" 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": 20, 291 | "metadata": {}, 292 | "outputs": [], 293 | "source": [ 294 | "cpu = CPU()\n", 295 | "cpu.load(PROGRAM)\n", 296 | "cpu.run()" 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": 25, 302 | "metadata": {}, 303 | "outputs": [ 304 | { 305 | "data": { 306 | "text/plain": [ 307 | "[10]" 308 | ] 309 | }, 310 | "execution_count": 25, 311 | "metadata": {}, 312 | "output_type": "execute_result" 313 | } 314 | ], 315 | "source": [ 316 | "cpu.stack" 317 | ] 318 | } 319 | ], 320 | "metadata": { 321 | "kernelspec": { 322 | "display_name": "Python 3", 323 | "language": "python", 324 | "name": "python3" 325 | }, 326 | "language_info": { 327 | "codemirror_mode": { 328 | "name": "ipython", 329 | "version": 3 330 | }, 331 | "file_extension": ".py", 332 | "mimetype": "text/x-python", 333 | "name": "python", 334 | "nbconvert_exporter": "python", 335 | "pygments_lexer": "ipython3", 336 | "version": "3.7.3" 337 | } 338 | }, 339 | "nbformat": 4, 340 | "nbformat_minor": 5 341 | } 342 | --------------------------------------------------------------------------------