def proc_ecrecover(ext, msg): # print('ecrecover proc', msg.gas) OP_GAS = opcodes.GECRECOVER gas_cost = OP_GAS if msg.gas < gas_cost: return 0, 0, [] message_hash_bytes = [0] * 32 msg.data.extract_copy(message_hash_bytes, 0, 0, 32) message_hash = b''.join(map(ascii_chr, message_hash_bytes)) # TODO: This conversion isn't really necessary. # TODO: Invesitage if the check below is really needed. v = msg.data.extract32(32) r = msg.data.extract32(64) s = msg.data.extract32(96) if r >= secp256k1n or s >= secp256k1n or v < 27 or v > 28: return 1, msg.gas - opcodes.GECRECOVER, [] try: pub = utils.ecrecover_to_pub(message_hash, v, r, s) except Exception as e: return 1, msg.gas - gas_cost, [] o = [0] * 12 + [safe_ord(x) for x in utils.sha3(pub)[-20:]] return 1, msg.gas - gas_cost, o
def consume_length_prefix(rlp, start): """Read a length prefix from an RLP string. :param rlp: the rlp string to read from :param start: the position at which to start reading :returns: a tuple ``(type, length, end)``, where ``type`` is either ``str`` or ``list`` depending on the type of the following payload, ``length`` is the length of the payload in bytes, and ``end`` is the position of the first payload byte in the rlp string """ b0 = safe_ord(rlp[start]) if b0 < 128: # single byte return (str, 1, start) elif b0 < 128 + 56: # short string return (str, b0 - 128, start + 1) elif b0 < 192: # long string ll = b0 - 128 - 56 + 1 l = big_endian_to_int(rlp[start + 1 : start + 1 + ll]) return (str, l, start + 1 + ll) elif b0 < 192 + 56: # short list return (list, b0 - 192, start + 1) else: # long list ll = b0 - 192 - 56 + 1 l = big_endian_to_int(rlp[start + 1 : start + 1 + ll]) return (list, l, start + 1 + ll)
def proc_sha256(ext, msg): OP_GAS = (opcodes.GSHA256BASE + (utils.ceil32(msg.data.size) // 32) * opcodes.GSHA256WORD) gas_cost = OP_GAS if msg.gas < gas_cost: return 0, 0, [] d = msg.data.extract_all() o = [safe_ord(x) for x in hashlib.sha256(d).digest()] return 1, msg.gas - gas_cost, o
def optimized_decode_single(x, pos): z = safe_ord(x[pos]) if z < 128: return x[pos : pos + 1], 1 elif z < 184: return x[pos + 1 : pos + z - 127], z - 127 else: ll = big_endian_to_int(x[pos + 1 : pos + z - 182]) return x[pos + z - 182 : pos + z - 182 + ll], z - 182 + ll
def proc_ripemd160(ext, msg): OP_GAS = (opcodes.GRIPEMD160BASE + (utils.ceil32(msg.data.size) // 32) * opcodes.GRIPEMD160WORD) gas_cost = OP_GAS if msg.gas < gas_cost: return 0, 0, [] d = msg.data.extract_all() o = [0] * 12 + [safe_ord(x) for x in hashlib.new("ripemd160", d).digest()] return 1, msg.gas - gas_cost, o
def preprocess_code(code): o = 0 i = 0 pushcache = {} code = code + b"\x00" * 32 while i < len(code) - 32: codebyte = safe_ord(code[i]) if codebyte == 0x5B: o |= 1 << i if 0x60 <= codebyte <= 0x7F: pushcache[i] = utils.big_endian_to_int(code[i + 1 : i + codebyte - 0x5E]) i += codebyte - 0x5E else: i += 1 return o, pushcache
def proc_ecmul(ext, msg): if not ext.post_metropolis_hardfork(): return 1, msg.gas, [] import py_ecc.optimized_bn128 as bn128 FQ = bn128.FQ print('ecmul proc', msg.gas) if msg.gas < opcodes.GECMUL: return 0, 0, [] x = msg.data.extract32(0) y = msg.data.extract32(32) m = msg.data.extract32(64) p = validate_point(x, y) if p is False: return 0, 0, [] o = bn128.normalize(bn128.multiply(p, m)) return (1, msg.gas - opcodes.GECMUL, [safe_ord(c) for c in (encode_int32(o[0].n) + encode_int32(o[1].n))])
def proc_ecmul(ext, msg): import py_ecc.optimized_bn128 as bn128 FQ = bn128.FQ if msg.gas < opcodes.GECMUL: return 0, 0, [] x = msg.data.extract32(0) y = msg.data.extract32(32) m = msg.data.extract32(64) p = validate_point(x, y) if p is False: return 0, 0, [] o = bn128.normalize(bn128.multiply(p, m)) return ( 1, msg.gas - opcodes.GECMUL, [safe_ord(c) for c in (encode_int32(o[0].n) + encode_int32(o[1].n))], )
def apply_xshard_desposit(state, deposit, gas_used_start): state.logs = [] state.suicides = [] state.refunds = 0 state.full_shard_key = deposit.to_address.full_shard_key state.delta_token_balance( deposit.from_address.recipient, deposit.transfer_token_id, deposit.value ) message_data = vm.CallData( [safe_ord(x) for x in deposit.message_data], 0, len(deposit.message_data) ) message = vm.Message( deposit.from_address.recipient, deposit.to_address.recipient, deposit.value, deposit.gas_remained, message_data, code_address=deposit.to_address.recipient, from_full_shard_key=deposit.from_address.full_shard_key, to_full_shard_key=deposit.to_address.full_shard_key, tx_hash=deposit.tx_hash, transfer_token_id=deposit.transfer_token_id, gas_token_id=deposit.gas_token_id, ) # MESSAGE ext = VMExt( state, sender=deposit.from_address.recipient, gas_price=deposit.gas_price ) return apply_transaction_message( state, message, ext, should_create_contract=deposit.create_contract, gas_used_start=gas_used_start, is_cross_shard=True, contract_address=deposit.to_address.recipient if deposit.create_contract else b"", )
def proc_ecadd(ext, msg): import py_ecc.optimized_bn128 as bn128 FQ = bn128.FQ if msg.gas < opcodes.GECADD: return 0, 0, [] x1 = msg.data.extract32(0) y1 = msg.data.extract32(32) x2 = msg.data.extract32(64) y2 = msg.data.extract32(96) p1 = validate_point(x1, y1) p2 = validate_point(x2, y2) if p1 is False or p2 is False: return 0, 0, [] o = bn128.normalize(bn128.add(p1, p2)) return ( 1, msg.gas - opcodes.GECADD, [safe_ord(x) for x in (encode_int32(o[0].n) + encode_int32(o[1].n))], )
def proc_ecadd(ext, msg): if not ext.post_metropolis_hardfork(): return 1, msg.gas, [] import py_ecc.optimized_bn128 as bn128 FQ = bn128.FQ print('ecadd proc:', msg.gas) if msg.gas < opcodes.GECADD: return 0, 0, [] x1 = msg.data.extract32(0) y1 = msg.data.extract32(32) x2 = msg.data.extract32(64) y2 = msg.data.extract32(96) p1 = validate_point(x1, y1) p2 = validate_point(x2, y2) if p1 is False or p2 is False: return 0, 0, [] o = bn128.normalize(bn128.add(p1, p2)) return 1, msg.gas - \ opcodes.GECADD, [safe_ord(x) for x in ( encode_int32(o[0].n) + encode_int32(o[1].n))]
def proc_modexp(ext, msg): if not ext.post_metropolis_hardfork(): return 1, msg.gas, [] print('modexp proc', msg.gas) baselen = msg.data.extract32(0) explen = msg.data.extract32(32) modlen = msg.data.extract32(64) first_exp_bytes = msg.data.extract32( 96 + baselen) >> (8 * max(32 - explen, 0)) bitlength = -1 while first_exp_bytes: bitlength += 1 first_exp_bytes >>= 1 adjusted_explen = max(bitlength, 0) + 8 * max(explen - 32, 0) gas_cost = (mult_complexity(max(modlen, baselen)) * max(adjusted_explen, 1)) // opcodes.GMODEXPQUADDIVISOR print(baselen, explen, modlen, 'expected gas cost', gas_cost) if msg.gas < gas_cost: return 0, 0, [] if baselen == 0: return 1, msg.gas - gas_cost, [0] * modlen if modlen == 0: return 1, msg.gas - gas_cost, [] base = bytearray(baselen) msg.data.extract_copy(base, 0, 96, baselen) exp = bytearray(explen) msg.data.extract_copy(exp, 0, 96 + baselen, explen) mod = bytearray(modlen) msg.data.extract_copy(mod, 0, 96 + baselen + explen, modlen) if utils.big_endian_to_int(mod) == 0: return 1, msg.gas - gas_cost, [0] * modlen o = pow( utils.big_endian_to_int(base), utils.big_endian_to_int(exp), utils.big_endian_to_int(mod)) return 1, msg.gas - \ gas_cost, [ safe_ord(x) for x in utils.zpad( utils.int_to_big_endian(o), modlen)]
def vm_execute(ext, msg, code): # precompute trace flag # if we trace vm, we're in slow mode anyway trace_vm = log_vm_op.is_active("trace") # Initialize stack, memory, program counter, etc compustate = Compustate(gas=msg.gas) stk = compustate.stack mem = compustate.memory # Compute jumpdest_mask, pushcache = preprocess_code(code) codelen = len(code) # For tracing purposes op = None _prevop = None steps = 0 while compustate.pc < codelen: opcode = safe_ord(code[compustate.pc]) # Invalid operation if opcode not in opcodes.opcodes: return vm_exception("INVALID OP", opcode=opcode) op, in_args, out_args, fee = opcodes.opcodes[opcode] # Apply operation if trace_vm: compustate.reset_prev() compustate.gas -= fee compustate.pc += 1 # Tracing if trace_vm: """ This diverges from normal logging, as we use the logging namespace only to decide which features get logged in 'eth.vm.op' i.e. tracing can not be activated by activating a sub like 'eth.vm.op.stack' """ trace_data = {} trace_data["stack"] = list(map(to_string, list(compustate.stack))) if _prevop in ( "MLOAD", "MSTORE", "MSTORE8", "SHA3", "CALL", "CALLCODE", "CREATE", "CALLDATACOPY", "CODECOPY", "EXTCODECOPY", ): if len(compustate.memory) < 4096: trace_data["memory"] = "".join( [encode_hex(ascii_chr(x)) for x in compustate.memory] ) else: trace_data["sha3memory"] = encode_hex( utils.sha3(b"".join([ascii_chr(x) for x in compustate.memory])) ) if _prevop in ("SSTORE",) or steps == 0: trace_data["storage"] = ext.log_storage(msg.to) trace_data["gas"] = to_string(compustate.gas + fee) trace_data["inst"] = opcode trace_data["pc"] = to_string(compustate.pc - 1) if steps == 0: trace_data["depth"] = msg.depth trace_data["address"] = msg.to trace_data["steps"] = steps trace_data["depth"] = msg.depth if op[:4] == "PUSH": trace_data["pushvalue"] = pushcache[compustate.pc - 1] log_vm_op.trace("vm", op=op, **trace_data) steps += 1 _prevop = op # out of gas error if compustate.gas < 0: return vm_exception("OUT OF GAS") # empty stack error if in_args > len(compustate.stack): return vm_exception( "INSUFFICIENT STACK", op=op, needed=to_string(in_args), available=to_string(len(compustate.stack)), ) # overfull stack error if len(compustate.stack) - in_args + out_args > 1024: return vm_exception( "STACK SIZE LIMIT EXCEEDED", op=op, pre_height=to_string(len(compustate.stack)), ) # Valid operations # Pushes first because they are very frequent if 0x60 <= opcode <= 0x7F: stk.append(pushcache[compustate.pc - 1]) # Move 1 byte forward for 0x60, up to 32 bytes for 0x7f compustate.pc += opcode - 0x5F # Arithmetic elif opcode < 0x10: if op == "STOP": return peaceful_exit("STOP", compustate.gas, []) elif op == "ADD": stk.append((stk.pop() + stk.pop()) & TT256M1) elif op == "SUB": stk.append((stk.pop() - stk.pop()) & TT256M1) elif op == "MUL": stk.append((stk.pop() * stk.pop()) & TT256M1) elif op == "DIV": s0, s1 = stk.pop(), stk.pop() stk.append(0 if s1 == 0 else s0 // s1) elif op == "MOD": s0, s1 = stk.pop(), stk.pop() stk.append(0 if s1 == 0 else s0 % s1) elif op == "SDIV": s0, s1 = utils.to_signed(stk.pop()), utils.to_signed(stk.pop()) stk.append( 0 if s1 == 0 else (abs(s0) // abs(s1) * (-1 if s0 * s1 < 0 else 1)) & TT256M1 ) elif op == "SMOD": s0, s1 = utils.to_signed(stk.pop()), utils.to_signed(stk.pop()) stk.append( 0 if s1 == 0 else (abs(s0) % abs(s1) * (-1 if s0 < 0 else 1)) & TT256M1 ) elif op == "ADDMOD": s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() stk.append((s0 + s1) % s2 if s2 else 0) elif op == "MULMOD": s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() stk.append((s0 * s1) % s2 if s2 else 0) elif op == "EXP": base, exponent = stk.pop(), stk.pop() # fee for exponent is dependent on its bytes # calc n bytes to represent exponent nbytes = len(utils.encode_int(exponent)) expfee = nbytes * opcodes.GEXPONENTBYTE if compustate.gas < expfee: compustate.gas = 0 return vm_exception("OOG EXPONENT") compustate.gas -= expfee stk.append(pow(base, exponent, TT256)) elif op == "SIGNEXTEND": s0, s1 = stk.pop(), stk.pop() if s0 <= 31: testbit = s0 * 8 + 7 if s1 & (1 << testbit): stk.append(s1 | (TT256 - (1 << testbit))) else: stk.append(s1 & ((1 << testbit) - 1)) else: stk.append(s1) # Comparisons elif opcode < 0x20: if op == "LT": stk.append(1 if stk.pop() < stk.pop() else 0) elif op == "GT": stk.append(1 if stk.pop() > stk.pop() else 0) elif op == "SLT": s0, s1 = utils.to_signed(stk.pop()), utils.to_signed(stk.pop()) stk.append(1 if s0 < s1 else 0) elif op == "SGT": s0, s1 = utils.to_signed(stk.pop()), utils.to_signed(stk.pop()) stk.append(1 if s0 > s1 else 0) elif op == "EQ": stk.append(1 if stk.pop() == stk.pop() else 0) elif op == "ISZERO": stk.append(0 if stk.pop() else 1) elif op == "AND": stk.append(stk.pop() & stk.pop()) elif op == "OR": stk.append(stk.pop() | stk.pop()) elif op == "XOR": stk.append(stk.pop() ^ stk.pop()) elif op == "NOT": stk.append(TT256M1 - stk.pop()) elif op == "BYTE": s0, s1 = stk.pop(), stk.pop() if s0 >= 32: stk.append(0) else: stk.append((s1 // 256 ** (31 - s0)) % 256) elif op == "SHL": s0, s1 = stk.pop(), stk.pop() stk.append(0 if s0 >= 256 else (s1 << s0) & TT256M1) elif op == "SHR": s0, s1 = stk.pop(), stk.pop() stk.append(0 if s0 >= 256 else s1 >> s0) elif op == "SAR": s0, s1 = stk.pop(), utils.to_signed(stk.pop()) if s0 >= 256: ret = 0 if s1 >= 0 else TT256M1 else: ret = (s1 >> s0) & TT256M1 stk.append(ret) # SHA3 and environment info elif opcode < 0x40: if op == "SHA3": s0, s1 = stk.pop(), stk.pop() compustate.gas -= opcodes.GSHA3WORD * (utils.ceil32(s1) // 32) if compustate.gas < 0: return vm_exception("OOG PAYING FOR SHA3") if not mem_extend(mem, compustate, op, s0, s1): return vm_exception("OOG EXTENDING MEMORY") data = bytearray_to_bytestr(mem[s0 : s0 + s1]) stk.append(utils.big_endian_to_int(utils.sha3(data))) elif op == "ADDRESS": stk.append(utils.coerce_to_int(msg.to)) elif op == "BALANCE": addr = utils.coerce_addr_to_hex(stk.pop() % 2 ** 160) stk.append(ext.get_balance(addr)) elif op == "ORIGIN": stk.append(utils.coerce_to_int(ext.tx_origin)) elif op == "CALLER": stk.append(utils.coerce_to_int(msg.sender)) elif op == "CALLVALUE": stk.append(msg.value) elif op == "CALLDATALOAD": stk.append(msg.data.extract32(stk.pop())) elif op == "CALLDATASIZE": stk.append(msg.data.size) elif op == "CALLDATACOPY": mstart, dstart, size = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, mstart, size): return vm_exception("OOG EXTENDING MEMORY") if not data_copy(compustate, size): return vm_exception("OOG COPY DATA") msg.data.extract_copy(mem, mstart, dstart, size) elif op == "CODESIZE": stk.append(codelen) elif op == "CODECOPY": mstart, dstart, size = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, mstart, size): return vm_exception("OOG EXTENDING MEMORY") if not data_copy(compustate, size): return vm_exception("OOG COPY DATA") for i in range(size): if dstart + i < codelen: mem[mstart + i] = safe_ord(code[dstart + i]) else: mem[mstart + i] = 0 elif op == "RETURNDATACOPY": mstart, dstart, size = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, mstart, size): return vm_exception("OOG EXTENDING MEMORY") if not data_copy(compustate, size): return vm_exception("OOG COPY DATA") if dstart + size > len(compustate.last_returned): return vm_exception("RETURNDATACOPY out of range") mem[mstart : mstart + size] = compustate.last_returned[ dstart : dstart + size ] elif op == "RETURNDATASIZE": stk.append(len(compustate.last_returned)) elif op == "GASPRICE": stk.append(ext.tx_gasprice) elif op == "EXTCODESIZE": addr = utils.coerce_addr_to_hex(stk.pop() % 2 ** 160) stk.append(len(ext.get_code(addr) or b"")) elif op == "EXTCODECOPY": addr = utils.coerce_addr_to_hex(stk.pop() % 2 ** 160) start, s2, size = stk.pop(), stk.pop(), stk.pop() extcode = ext.get_code(addr) or b"" assert utils.is_string(extcode) if not mem_extend(mem, compustate, op, start, size): return vm_exception("OOG EXTENDING MEMORY") if not data_copy(compustate, size): return vm_exception("OOG COPY DATA") for i in range(size): if s2 + i < len(extcode): mem[start + i] = safe_ord(extcode[s2 + i]) else: mem[start + i] = 0 elif op == "EXTCODEHASH": addr = utils.coerce_addr_to_hex(stk.pop() % 2 ** 160) if not ext.account_exists(addr): stk.append(0) else: extcode = ext.get_code(addr) or b"" assert utils.is_string(extcode) stk.append(utils.big_endian_to_int(utils.sha3(extcode))) # Block info elif opcode < 0x50: if op == "BLOCKHASH": stk.append(utils.big_endian_to_int(ext.block_hash(stk.pop()))) elif op == "COINBASE": stk.append(utils.big_endian_to_int(ext.block_coinbase)) elif op == "TIMESTAMP": stk.append(ext.block_timestamp) elif op == "NUMBER": stk.append(ext.block_number) elif op == "DIFFICULTY": stk.append(ext.block_difficulty) elif op == "GASLIMIT": stk.append(ext.block_gas_limit) # VM state manipulations elif opcode < 0x60: if op == "POP": stk.pop() elif op == "MLOAD": s0 = stk.pop() if not mem_extend(mem, compustate, op, s0, 32): return vm_exception("OOG EXTENDING MEMORY") stk.append(utils.bytes_to_int(mem[s0 : s0 + 32])) elif op == "MSTORE": s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, 32): return vm_exception("OOG EXTENDING MEMORY") mem[s0 : s0 + 32] = utils.encode_int32(s1) elif op == "MSTORE8": s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, 1): return vm_exception("OOG EXTENDING MEMORY") mem[s0] = s1 % 256 elif op == "SLOAD": stk.append(ext.get_storage_data(msg.to, stk.pop())) elif op == "SSTORE": s0, s1 = stk.pop(), stk.pop() if msg.static: return vm_exception("Cannot SSTORE inside a static context") if ext.get_storage_data(msg.to, s0): gascost = opcodes.GSTORAGEMOD if s1 else opcodes.GSTORAGEKILL refund = 0 if s1 else opcodes.GSTORAGEREFUND else: gascost = opcodes.GSTORAGEADD if s1 else opcodes.GSTORAGEMOD refund = 0 if compustate.gas < gascost: return vm_exception("OUT OF GAS") compustate.gas -= gascost # adds neg gascost as a refund if below zero ext.add_refund(refund) ext.set_storage_data(msg.to, s0, s1) elif op == "JUMP": compustate.pc = stk.pop() if compustate.pc >= codelen or not ( (1 << compustate.pc) & jumpdest_mask ): return vm_exception("BAD JUMPDEST") elif op == "JUMPI": s0, s1 = stk.pop(), stk.pop() if s1: compustate.pc = s0 if compustate.pc >= codelen or not ( (1 << compustate.pc) & jumpdest_mask ): return vm_exception("BAD JUMPDEST") elif op == "PC": stk.append(compustate.pc - 1) elif op == "MSIZE": stk.append(len(mem)) elif op == "GAS": stk.append(compustate.gas) # AFTER subtracting cost 1 # DUPn (eg. DUP1: a b c -> a b c c, DUP3: a b c -> a b c a) elif op[:3] == "DUP": # 0x7f - opcode is a negative number, -1 for 0x80 ... -16 for 0x8f stk.append(stk[0x7F - opcode]) # SWAPn (eg. SWAP1: a b c d -> a b d c, SWAP3: a b c d -> d b c a) elif op[:4] == "SWAP": # 0x8e - opcode is a negative number, -2 for 0x90 ... -17 for 0x9f temp = stk[0x8E - opcode] stk[0x8E - opcode] = stk[-1] stk[-1] = temp # Logs (aka "events") elif op[:3] == "LOG": """ 0xa0 ... 0xa4, 32/64/96/128/160 + len(data) gas a. Opcodes LOG0...LOG4 are added, takes 2-6 stack arguments MEMSTART MEMSZ (TOPIC1) (TOPIC2) (TOPIC3) (TOPIC4) b. Logs are kept track of during tx execution exactly the same way as suicides (except as an ordered list, not a set). Each log is in the form [address, [topic1, ... ], data] where: * address is what the ADDRESS opcode would output * data is mem[MEMSTART: MEMSTART + MEMSZ] * topics are as provided by the opcode c. The ordered list of logs in the transaction are expressed as [log0, log1, ..., logN]. """ depth = int(op[3:]) mstart, msz = stk.pop(), stk.pop() topics = [stk.pop() for x in range(depth)] compustate.gas -= msz * opcodes.GLOGBYTE if msg.static: return vm_exception("Cannot LOG inside a static context") if not mem_extend(mem, compustate, op, mstart, msz): return vm_exception("OOG EXTENDING MEMORY") data = bytearray_to_bytestr(mem[mstart : mstart + msz]) ext.log(msg.to, topics, data) log_log.trace( "LOG", to=msg.to, topics=topics, data=list(map(utils.safe_ord, data)) ) # Create a new contract elif op in ("CREATE", "CREATE2"): salt = None if op == "CREATE": value, mstart, msz = stk.pop(), stk.pop(), stk.pop() else: # CREATE2 value, mstart, msz, salt = stk.pop(), stk.pop(), stk.pop(), stk.pop() salt = salt.to_bytes(32, byteorder="big") compustate.gas -= opcodes.GSHA3WORD * ceil(msz / 32) if not mem_extend(mem, compustate, op, mstart, msz): return vm_exception("OOG EXTENDING MEMORY") if msg.static: return vm_exception("Cannot CREATE inside a static context") if ext.get_balance(msg.to) >= value and msg.depth < MAX_DEPTH: cd = CallData(mem, mstart, msz) ingas = compustate.gas ingas = all_but_1n(ingas, opcodes.CALL_CHILD_LIMIT_DENOM) create_msg = Message( msg.to, b"", value, ingas, cd, msg.depth + 1, # Used for calculating contract address to_full_shard_key=msg.to_full_shard_key, transfer_token_id=msg.transfer_token_id, gas_token_id=msg.gas_token_id, ) o, gas, data = ext.create(create_msg, salt) if o: stk.append(utils.coerce_to_int(data)) compustate.last_returned = bytearray(b"") else: stk.append(0) compustate.last_returned = bytearray(data) compustate.gas = compustate.gas - ingas + gas else: stk.append(0) compustate.last_returned = bytearray(b"") # Calls elif op in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"): # Pull arguments from the stack if op in ("CALL", "CALLCODE"): gas, to, value, meminstart, meminsz, memoutstart, memoutsz = ( stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), ) else: gas, to, meminstart, meminsz, memoutstart, memoutsz = ( stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), ) value = 0 # Static context prohibition if msg.static and value > 0 and op == "CALL": return vm_exception( "Cannot make a non-zero-value call inside a static context" ) # Expand memory if not mem_extend( mem, compustate, op, meminstart, meminsz ) or not mem_extend(mem, compustate, op, memoutstart, memoutsz): return vm_exception("OOG EXTENDING MEMORY") to = utils.int_to_addr(to) # Extra gas costs based on various factors extra_gas = 0 # Creating a new account if op == "CALL" and not ext.account_exists(to) and (value > 0): extra_gas += opcodes.GCALLNEWACCOUNT # Value transfer if value > 0: extra_gas += opcodes.GCALLVALUETRANSFER # Compute child gas limit if compustate.gas < extra_gas: return vm_exception("OUT OF GAS", needed=extra_gas) gas = min( gas, all_but_1n(compustate.gas - extra_gas, opcodes.CALL_CHILD_LIMIT_DENOM), ) submsg_gas = gas + opcodes.GSTIPEND * (value > 0) # Verify that there is sufficient balance and depth if ext.get_balance(msg.to) < value or msg.depth >= MAX_DEPTH: compustate.gas -= gas + extra_gas - submsg_gas stk.append(0) compustate.last_returned = bytearray(b"") else: # Subtract gas from parent compustate.gas -= gas + extra_gas assert compustate.gas >= 0 cd = CallData(mem, meminstart, meminsz) # Generate the message if op == "CALL": call_msg = Message( msg.to, to, value, submsg_gas, cd, msg.depth + 1, # Used for calculating contract address to_full_shard_key=msg.to_full_shard_key, code_address=to, static=msg.static, transfer_token_id=msg.transfer_token_id, gas_token_id=msg.gas_token_id, ) elif op == "DELEGATECALL": call_msg = Message( msg.sender, msg.to, msg.value, submsg_gas, cd, msg.depth + 1, # Used for calculating contract address to_full_shard_key=msg.to_full_shard_key, code_address=to, transfers_value=False, static=msg.static, transfer_token_id=msg.transfer_token_id, gas_token_id=msg.gas_token_id, ) elif op == "STATICCALL": call_msg = Message( msg.to, to, value, submsg_gas, cd, msg.depth + 1, # Used for calculating contract address to_full_shard_key=msg.to_full_shard_key, code_address=to, static=True, transfer_token_id=msg.transfer_token_id, gas_token_id=msg.gas_token_id, ) elif op == "CALLCODE": call_msg = Message( msg.to, msg.to, value, submsg_gas, cd, msg.depth + 1, # Used for calculating contract address to_full_shard_key=msg.to_full_shard_key, code_address=to, static=msg.static, transfer_token_id=msg.transfer_token_id, gas_token_id=msg.gas_token_id, ) else: raise Exception("Lolwut") # Get result if call_msg.to == PROC_CURRENT_MNT_ID: msg.token_id_queried = True result, gas, data = ext.msg(call_msg) if result == 0: stk.append(0) else: stk.append(1) # Set output memory for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] compustate.gas += gas compustate.last_returned = bytearray(data) # Return opcode elif op == "RETURN": s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, s1): return vm_exception("OOG EXTENDING MEMORY") return peaceful_exit("RETURN", compustate.gas, mem[s0 : s0 + s1]) # Revert opcode (Metropolis) elif op == "REVERT": s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, s1): return vm_exception("OOG EXTENDING MEMORY") return revert(compustate.gas, mem[s0 : s0 + s1]) # SUICIDE opcode (also called SELFDESTRUCT) elif op == "SUICIDE": if msg.static: return vm_exception("Cannot SUICIDE inside a static context") to = utils.encode_int(stk.pop()) to = ((b"\x00" * (32 - len(to))) + to)[12:] xfer = ext.get_balance(msg.to) extra_gas = ( (not ext.account_exists(to)) * (xfer > 0) * opcodes.GCALLNEWACCOUNT ) if not eat_gas(compustate, extra_gas): return vm_exception("OUT OF GAS") ext.set_balance(to, ext.get_balance(to) + xfer) ext.set_balance(msg.to, 0) ext.add_suicide(msg.to) log_msg.debug( "SUICIDING", addr=utils.checksum_encode(msg.to), to=utils.checksum_encode(to), xferring=xfer, ) return peaceful_exit("SUICIDED", compustate.gas, []) if trace_vm: compustate.reset_prev() return peaceful_exit("CODE OUT OF RANGE", compustate.gas, [])
def apply_transaction(state, tx: transactions.Transaction, tx_wrapper_hash): """tx_wrapper_hash is the hash for quarkchain.core.Transaction TODO: remove quarkchain.core.Transaction wrapper and use evm.Transaction directly """ state.logs = [] state.suicides = [] state.refunds = 0 validate_transaction(state, tx) state.full_shard_key = tx.to_full_shard_key intrinsic_gas = tx.intrinsic_gas_used log_tx.debug("TX NEW", txdict=tx.to_dict()) # start transacting ################# state.increment_nonce(tx.sender) # part of fees should go to root chain miners local_fee_rate = (1 - state.qkc_config.reward_tax_rate if state.qkc_config else Fraction(1)) # buy startgas gasprice, refund_rate = tx.gasprice, 100 # convert gas if using non-genesis native token if gasprice != 0 and tx.gas_token_id != state.genesis_token: refund_rate, converted_genesis_token_gas_price = pay_native_token_as_gas( state, tx.gas_token_id, tx.startgas, tx.gasprice) # guaranteed by validation check(converted_genesis_token_gas_price > 0) gasprice = converted_genesis_token_gas_price contract_addr = SystemContract.GENERAL_NATIVE_TOKEN.addr() # guaranteed by validation check( state.deduct_value( contract_addr, state.genesis_token, tx.startgas * converted_genesis_token_gas_price, )) state.delta_token_balance(contract_addr, tx.gas_token_id, tx.startgas * tx.gasprice) check( state.deduct_value(tx.sender, tx.gas_token_id, tx.startgas * tx.gasprice)) message_data = vm.CallData([safe_ord(x) for x in tx.data], 0, len(tx.data)) message = vm.Message( tx.sender, tx.to, tx.value, tx.startgas - intrinsic_gas, message_data, code_address=tx.to, from_full_shard_key=tx.from_full_shard_key if not tx.is_testing else None, to_full_shard_key=tx.to_full_shard_key if not tx.is_testing else None, tx_hash=tx_wrapper_hash, transfer_token_id=tx.transfer_token_id, # always genesis token for gas token gas_token_id=state.genesis_token, ) # MESSAGE ext = VMExt(state, tx.sender, gasprice) contract_address = b"" if not tx.is_cross_shard: return apply_transaction_message(state, message, ext, tx.to == b"", intrinsic_gas, refund_rate=refund_rate) # handle xshard local_gas_used = intrinsic_gas remote_gas_reserved = 0 if transfer_failure_by_posw_balance_check(ext, message): success = 0 # Currently, burn all gas local_gas_used = tx.startgas elif tx.to == b"": check(state.deduct_value(tx.sender, tx.transfer_token_id, tx.value)) remote_gas_reserved = tx.startgas - intrinsic_gas ext.add_cross_shard_transaction_deposit( quarkchain.core.CrossShardTransactionDeposit( tx_hash=tx_wrapper_hash, from_address=quarkchain.core.Address(tx.sender, tx.from_full_shard_key), to_address=quarkchain.core.Address( mk_contract_address(tx.sender, state.get_nonce(tx.sender), tx.from_full_shard_key), tx.to_full_shard_key, ), value=tx.value, # convert to genesis token and use converted gas price gas_token_id=state.genesis_token, gas_price=gasprice, transfer_token_id=tx.transfer_token_id, message_data=tx.data, create_contract=True, gas_remained=remote_gas_reserved, refund_rate=refund_rate, )) success = 1 else: check(state.deduct_value(tx.sender, tx.transfer_token_id, tx.value)) if (state.qkc_config.ENABLE_EVM_TIMESTAMP is None or state.timestamp >= state.qkc_config.ENABLE_EVM_TIMESTAMP): remote_gas_reserved = tx.startgas - intrinsic_gas ext.add_cross_shard_transaction_deposit( quarkchain.core.CrossShardTransactionDeposit( tx_hash=tx_wrapper_hash, from_address=quarkchain.core.Address(tx.sender, tx.from_full_shard_key), to_address=quarkchain.core.Address(tx.to, tx.to_full_shard_key), value=tx.value, # convert to genesis token and use converted gas price gas_token_id=state.genesis_token, gas_price=gasprice, transfer_token_id=tx.transfer_token_id, message_data=tx.data, create_contract=False, gas_remained=remote_gas_reserved, refund_rate=refund_rate, )) success = 1 gas_remained = tx.startgas - local_gas_used - remote_gas_reserved _refund(state, message, ext.tx_gasprice * gas_remained, refund_rate) # if x-shard, reserve part of the gas for the target shard miner for fee fee = (ext.tx_gasprice * (local_gas_used - (opcodes.GTXXSHARDCOST if success else 0)) * local_fee_rate.numerator // local_fee_rate.denominator) state.delta_token_balance(state.block_coinbase, state.genesis_token, fee) add_dict(state.block_fee_tokens, {state.genesis_token: fee}) output = [] state.gas_used += local_gas_used if (state.qkc_config.ENABLE_EVM_TIMESTAMP is None or state.timestamp >= state.qkc_config.ENABLE_EVM_TIMESTAMP): state.gas_used -= opcodes.GTXXSHARDCOST if success else 0 # Construct a receipt r = mk_receipt(state, success, state.logs, contract_address, state.full_shard_key) state.logs = [] state.add_receipt(r) return success, output
def apply_transaction(state, tx: transactions.Transaction, tx_wrapper_hash): """tx_wrapper_hash is the hash for quarkchain.core.Transaction TODO: remove quarkchain.core.Transaction wrapper and use evm.Transaction directly """ state.logs = [] state.suicides = [] state.refunds = 0 validate_transaction(state, tx) state.full_shard_key = tx.to_full_shard_key intrinsic_gas = tx.intrinsic_gas_used log_tx.debug("TX NEW", txdict=tx.to_dict()) # start transacting ################# state.increment_nonce(tx.sender) # part of fees should go to root chain miners local_fee_rate = (1 - state.qkc_config.reward_tax_rate if state.qkc_config else Fraction(1)) # buy startgas assert (state.get_balance(tx.sender, token_id=tx.gas_token_id) >= tx.startgas * tx.gasprice) state.delta_token_balance(tx.sender, tx.gas_token_id, -tx.startgas * tx.gasprice) message_data = vm.CallData([safe_ord(x) for x in tx.data], 0, len(tx.data)) message = vm.Message( tx.sender, tx.to, tx.value, tx.startgas - intrinsic_gas, message_data, code_address=tx.to, from_full_shard_key=tx.from_full_shard_key if not tx.is_testing else None, to_full_shard_key=tx.to_full_shard_key if not tx.is_testing else None, tx_hash=tx_wrapper_hash, transfer_token_id=tx.transfer_token_id, gas_token_id=tx.gas_token_id, ) # MESSAGE ext = VMExt(state, tx.sender, tx.gasprice) contract_address = b"" if tx.is_cross_shard: local_gas_used = intrinsic_gas remote_gas_reserved = 0 if transfer_failure_by_posw_balance_check(ext, message): success = 0 # Currently, burn all gas local_gas_used = tx.startgas elif tx.to == b"": state.delta_token_balance(tx.sender, tx.transfer_token_id, -tx.value) remote_gas_reserved = tx.startgas - intrinsic_gas ext.add_cross_shard_transaction_deposit( quarkchain.core.CrossShardTransactionDeposit( tx_hash=tx_wrapper_hash, from_address=quarkchain.core.Address( tx.sender, tx.from_full_shard_key), to_address=quarkchain.core.Address( mk_contract_address( tx.sender, state.get_nonce(tx.sender), tx.from_full_shard_key, ), tx.to_full_shard_key, ), value=tx.value, gas_price=tx.gasprice, gas_token_id=tx.gas_token_id, transfer_token_id=tx.transfer_token_id, message_data=tx.data, create_contract=True, gas_remained=remote_gas_reserved, )) success = 1 else: state.delta_token_balance(tx.sender, tx.transfer_token_id, -tx.value) if (state.qkc_config.ENABLE_EVM_TIMESTAMP is None or state.timestamp >= state.qkc_config.ENABLE_EVM_TIMESTAMP): remote_gas_reserved = tx.startgas - intrinsic_gas ext.add_cross_shard_transaction_deposit( quarkchain.core.CrossShardTransactionDeposit( tx_hash=tx_wrapper_hash, from_address=quarkchain.core.Address( tx.sender, tx.from_full_shard_key), to_address=quarkchain.core.Address(tx.to, tx.to_full_shard_key), value=tx.value, gas_price=tx.gasprice, gas_token_id=tx.gas_token_id, transfer_token_id=tx.transfer_token_id, message_data=tx.data, create_contract=False, gas_remained=remote_gas_reserved, )) success = 1 gas_remained = tx.startgas - local_gas_used - remote_gas_reserved # Refund state.delta_token_balance(message.sender, message.gas_token_id, ext.tx_gasprice * gas_remained) # if x-shard, reserve part of the gas for the target shard miner for fee fee = (tx.gasprice * (local_gas_used - (opcodes.GTXXSHARDCOST if success else 0)) * local_fee_rate.numerator // local_fee_rate.denominator) state.delta_token_balance(state.block_coinbase, tx.gas_token_id, fee) add_dict(state.block_fee_tokens, {message.gas_token_id: fee}) output = [] state.gas_used += local_gas_used if (state.qkc_config.ENABLE_EVM_TIMESTAMP is None or state.timestamp >= state.qkc_config.ENABLE_EVM_TIMESTAMP): state.gas_used -= opcodes.GTXXSHARDCOST if success else 0 # Construct a receipt r = mk_receipt(state, success, state.logs, contract_address, state.full_shard_key) state.logs = [] state.add_receipt(r) return success, output return apply_transaction_message(state, message, ext, tx.to == b"", intrinsic_gas)
def apply_transaction(state, tx: transactions.Transaction, tx_wrapper_hash): """tx_wrapper_hash is the hash for quarkchain.core.Transaction TODO: remove quarkchain.core.Transaction wrapper and use evm.Transaction directly """ state.logs = [] state.suicides = [] state.refunds = 0 validate_transaction(state, tx) state.full_shard_key = tx.to_full_shard_key intrinsic_gas = tx.intrinsic_gas_used log_tx.debug("TX NEW", txdict=tx.to_dict()) # start transacting ################# if tx.sender != null_address: state.increment_nonce(tx.sender) # part of fees should go to root chain miners local_fee_rate = (1 - state.qkc_config.reward_tax_rate if state.qkc_config else Fraction(1)) # buy startgas assert state.get_balance(tx.sender) >= tx.startgas * tx.gasprice state.delta_balance(tx.sender, -tx.startgas * tx.gasprice) message_data = vm.CallData([safe_ord(x) for x in tx.data], 0, len(tx.data)) message = vm.Message( tx.sender, tx.to, tx.value, tx.startgas - intrinsic_gas, message_data, code_address=tx.to, is_cross_shard=tx.is_cross_shard, from_full_shard_key=tx.from_full_shard_key, to_full_shard_key=tx.to_full_shard_key, tx_hash=tx_wrapper_hash, ) # MESSAGE ext = VMExt(state, tx) contract_address = b"" if tx.to != b"": result, gas_remained, data = apply_msg(ext, message) else: # CREATE result, gas_remained, data = create_contract(ext, message) contract_address = (data if data else b"" ) # data could be [] when vm failed execution assert gas_remained >= 0 log_tx.debug("TX APPLIED", result=result, gas_remained=gas_remained, data=data) gas_used = tx.startgas - gas_remained # pay CORRECT tx fee (after tax) to coinbase so that each step of state is accurate # Transaction failed if not result: log_tx.debug( "TX FAILED", reason="out of gas", startgas=tx.startgas, gas_remained=gas_remained, ) state.delta_balance(tx.sender, tx.gasprice * gas_remained) fee = (tx.gasprice * gas_used * local_fee_rate.numerator // local_fee_rate.denominator) state.delta_balance(state.block_coinbase, fee) state.block_fee += tx.gasprice * gas_used output = b"" success = 0 # Transaction success else: log_tx.debug("TX SUCCESS", data=data) state.refunds += len(set(state.suicides)) * opcodes.GSUICIDEREFUND if state.refunds > 0: log_tx.debug("Refunding", gas_refunded=min(state.refunds, gas_used // 2)) gas_remained += min(state.refunds, gas_used // 2) gas_used -= min(state.refunds, gas_used // 2) state.refunds = 0 # sell remaining gas state.delta_balance(tx.sender, tx.gasprice * gas_remained) # if x-shard, reserve part of the gas for the target shard miner fee = (tx.gasprice * (gas_used - (opcodes.GTXXSHARDCOST if tx.is_cross_shard else 0)) * local_fee_rate.numerator // local_fee_rate.denominator) state.delta_balance(state.block_coinbase, fee) state.block_fee += fee if tx.to: output = bytearray_to_bytestr(data) else: output = data success = 1 # TODO: check if the destination address is correct, and consume xshard gas of the state # the xshard gas and fee is consumed by destination shard block state.gas_used += gas_used # Clear suicides suicides = state.suicides state.suicides = [] for s in suicides: state.set_balance(s, 0) state.del_account(s) # Pre-Metropolis: commit state after every tx if not state.is_METROPOLIS() and not SKIP_MEDSTATES: state.commit() # Construct a receipt r = mk_receipt(state, success, state.logs, contract_address, state.full_shard_key) state.logs = [] state.add_receipt(r) state.set_param("bloom", state.bloom | r.bloom) state.set_param("txindex", state.txindex + 1) return success, output
def vm_execute(ext, msg, code): # precompute trace flag # if we trace vm, we're in slow mode anyway trace_vm = log_vm_op.is_active('trace') # Initialize stack, memory, program counter, etc compustate = Compustate(gas=msg.gas) stk = compustate.stack mem = compustate.memory # Compute jumpdest_mask, pushcache = preprocess_code(code) codelen = len(code) # For tracing purposes op = None _prevop = None steps = 0 while compustate.pc < codelen: opcode = safe_ord(code[compustate.pc]) # Invalid operation if opcode not in opcodes.opcodes: return vm_exception('INVALID OP', opcode=opcode) if opcode in opcodes.opcodesMetropolis and not ext.post_metropolis_hardfork( ): return vm_exception('INVALID OP (not yet enabled)', opcode=opcode) op, in_args, out_args, fee = opcodes.opcodes[opcode] # Apply operation if trace_vm: compustate.reset_prev() compustate.gas -= fee compustate.pc += 1 # Tracing if trace_vm: """ This diverges from normal logging, as we use the logging namespace only to decide which features get logged in 'eth.vm.op' i.e. tracing can not be activated by activating a sub like 'eth.vm.op.stack' """ trace_data = {} trace_data['stack'] = list(map(to_string, list(compustate.stack))) if _prevop in ('MLOAD', 'MSTORE', 'MSTORE8', 'SHA3', 'CALL', 'CALLCODE', 'CREATE', 'CALLDATACOPY', 'CODECOPY', 'EXTCODECOPY'): if len(compustate.memory) < 4096: trace_data['memory'] = \ ''.join([encode_hex(ascii_chr(x)) for x in compustate.memory]) else: trace_data['sha3memory'] = \ encode_hex(utils.sha3(b''.join([ascii_chr(x) for x in compustate.memory]))) if _prevop in ('SSTORE', ) or steps == 0: trace_data['storage'] = ext.log_storage(msg.to) trace_data['gas'] = to_string(compustate.gas + fee) trace_data['inst'] = opcode trace_data['pc'] = to_string(compustate.pc - 1) if steps == 0: trace_data['depth'] = msg.depth trace_data['address'] = msg.to trace_data['steps'] = steps trace_data['depth'] = msg.depth if op[:4] == 'PUSH': trace_data['pushvalue'] = pushcache[compustate.pc - 1] log_vm_op.trace('vm', op=op, **trace_data) steps += 1 _prevop = op # out of gas error if compustate.gas < 0: return vm_exception('OUT OF GAS') # empty stack error if in_args > len(compustate.stack): return vm_exception('INSUFFICIENT STACK', op=op, needed=to_string(in_args), available=to_string(len(compustate.stack))) # overfull stack error if len(compustate.stack) - in_args + out_args > 1024: return vm_exception('STACK SIZE LIMIT EXCEEDED', op=op, pre_height=to_string(len(compustate.stack))) # Valid operations # Pushes first because they are very frequent if 0x60 <= opcode <= 0x7f: stk.append(pushcache[compustate.pc - 1]) # Move 1 byte forward for 0x60, up to 32 bytes for 0x7f compustate.pc += opcode - 0x5f # Arithmetic elif opcode < 0x10: if op == 'STOP': return peaceful_exit('STOP', compustate.gas, []) elif op == 'ADD': stk.append((stk.pop() + stk.pop()) & TT256M1) elif op == 'SUB': stk.append((stk.pop() - stk.pop()) & TT256M1) elif op == 'MUL': stk.append((stk.pop() * stk.pop()) & TT256M1) elif op == 'DIV': s0, s1 = stk.pop(), stk.pop() stk.append(0 if s1 == 0 else s0 // s1) elif op == 'MOD': s0, s1 = stk.pop(), stk.pop() stk.append(0 if s1 == 0 else s0 % s1) elif op == 'SDIV': s0, s1 = utils.to_signed(stk.pop()), utils.to_signed(stk.pop()) stk.append(0 if s1 == 0 else (abs(s0) // abs(s1) * (-1 if s0 * s1 < 0 else 1)) & TT256M1) elif op == 'SMOD': s0, s1 = utils.to_signed(stk.pop()), utils.to_signed(stk.pop()) stk.append(0 if s1 == 0 else (abs(s0) % abs(s1) * (-1 if s0 < 0 else 1)) & TT256M1) elif op == 'ADDMOD': s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() stk.append((s0 + s1) % s2 if s2 else 0) elif op == 'MULMOD': s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() stk.append((s0 * s1) % s2 if s2 else 0) elif op == 'EXP': base, exponent = stk.pop(), stk.pop() # fee for exponent is dependent on its bytes # calc n bytes to represent exponent nbytes = len(utils.encode_int(exponent)) expfee = nbytes * opcodes.GEXPONENTBYTE if ext.post_spurious_dragon_hardfork(): expfee += opcodes.EXP_SUPPLEMENTAL_GAS * nbytes if compustate.gas < expfee: compustate.gas = 0 return vm_exception('OOG EXPONENT') compustate.gas -= expfee stk.append(pow(base, exponent, TT256)) elif op == 'SIGNEXTEND': s0, s1 = stk.pop(), stk.pop() if s0 <= 31: testbit = s0 * 8 + 7 if s1 & (1 << testbit): stk.append(s1 | (TT256 - (1 << testbit))) else: stk.append(s1 & ((1 << testbit) - 1)) else: stk.append(s1) # Comparisons elif opcode < 0x20: if op == 'LT': stk.append(1 if stk.pop() < stk.pop() else 0) elif op == 'GT': stk.append(1 if stk.pop() > stk.pop() else 0) elif op == 'SLT': s0, s1 = utils.to_signed(stk.pop()), utils.to_signed(stk.pop()) stk.append(1 if s0 < s1 else 0) elif op == 'SGT': s0, s1 = utils.to_signed(stk.pop()), utils.to_signed(stk.pop()) stk.append(1 if s0 > s1 else 0) elif op == 'EQ': stk.append(1 if stk.pop() == stk.pop() else 0) elif op == 'ISZERO': stk.append(0 if stk.pop() else 1) elif op == 'AND': stk.append(stk.pop() & stk.pop()) elif op == 'OR': stk.append(stk.pop() | stk.pop()) elif op == 'XOR': stk.append(stk.pop() ^ stk.pop()) elif op == 'NOT': stk.append(TT256M1 - stk.pop()) elif op == 'BYTE': s0, s1 = stk.pop(), stk.pop() if s0 >= 32: stk.append(0) else: stk.append((s1 // 256**(31 - s0)) % 256) # SHA3 and environment info elif opcode < 0x40: if op == 'SHA3': s0, s1 = stk.pop(), stk.pop() compustate.gas -= opcodes.GSHA3WORD * (utils.ceil32(s1) // 32) if compustate.gas < 0: return vm_exception('OOG PAYING FOR SHA3') if not mem_extend(mem, compustate, op, s0, s1): return vm_exception('OOG EXTENDING MEMORY') data = bytearray_to_bytestr(mem[s0:s0 + s1]) stk.append(utils.big_endian_to_int(utils.sha3(data))) elif op == 'ADDRESS': stk.append(utils.coerce_to_int(msg.to)) elif op == 'BALANCE': if ext.post_anti_dos_hardfork(): if not eat_gas(compustate, opcodes.BALANCE_SUPPLEMENTAL_GAS): return vm_exception("OUT OF GAS") addr = utils.coerce_addr_to_hex(stk.pop() % 2**160) stk.append(ext.get_balance(addr)) elif op == 'ORIGIN': stk.append(utils.coerce_to_int(ext.tx_origin)) elif op == 'CALLER': stk.append(utils.coerce_to_int(msg.sender)) elif op == 'CALLVALUE': stk.append(msg.value) elif op == 'CALLDATALOAD': stk.append(msg.data.extract32(stk.pop())) elif op == 'CALLDATASIZE': stk.append(msg.data.size) elif op == 'CALLDATACOPY': mstart, dstart, size = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, mstart, size): return vm_exception('OOG EXTENDING MEMORY') if not data_copy(compustate, size): return vm_exception('OOG COPY DATA') msg.data.extract_copy(mem, mstart, dstart, size) elif op == 'CODESIZE': stk.append(codelen) elif op == 'CODECOPY': mstart, dstart, size = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, mstart, size): return vm_exception('OOG EXTENDING MEMORY') if not data_copy(compustate, size): return vm_exception('OOG COPY DATA') for i in range(size): if dstart + i < codelen: mem[mstart + i] = safe_ord(code[dstart + i]) else: mem[mstart + i] = 0 elif op == 'RETURNDATACOPY': mstart, dstart, size = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, mstart, size): return vm_exception('OOG EXTENDING MEMORY') if not data_copy(compustate, size): return vm_exception('OOG COPY DATA') if dstart + size > len(compustate.last_returned): return vm_exception('RETURNDATACOPY out of range') mem[mstart:mstart + size] = compustate.last_returned[dstart:dstart + size] elif op == 'RETURNDATASIZE': stk.append(len(compustate.last_returned)) elif op == 'GASPRICE': stk.append(ext.tx_gasprice) elif op == 'EXTCODESIZE': if ext.post_anti_dos_hardfork(): if not eat_gas(compustate, opcodes.EXTCODELOAD_SUPPLEMENTAL_GAS): return vm_exception("OUT OF GAS") addr = utils.coerce_addr_to_hex(stk.pop() % 2**160) stk.append(len(ext.get_code(addr) or b'')) elif op == 'EXTCODECOPY': if ext.post_anti_dos_hardfork(): if not eat_gas(compustate, opcodes.EXTCODELOAD_SUPPLEMENTAL_GAS): return vm_exception("OUT OF GAS") addr = utils.coerce_addr_to_hex(stk.pop() % 2**160) start, s2, size = stk.pop(), stk.pop(), stk.pop() extcode = ext.get_code(addr) or b'' assert utils.is_string(extcode) if not mem_extend(mem, compustate, op, start, size): return vm_exception('OOG EXTENDING MEMORY') if not data_copy(compustate, size): return vm_exception('OOG COPY DATA') for i in range(size): if s2 + i < len(extcode): mem[start + i] = safe_ord(extcode[s2 + i]) else: mem[start + i] = 0 # Block info elif opcode < 0x50: if op == 'BLOCKHASH': if ext.post_constantinople_hardfork() and False: bh_addr = ext.blockhash_store stk.append(ext.get_storage_data(bh_addr, stk.pop())) else: stk.append( utils.big_endian_to_int(ext.block_hash(stk.pop()))) elif op == 'COINBASE': stk.append(utils.big_endian_to_int(ext.block_coinbase)) elif op == 'TIMESTAMP': stk.append(ext.block_timestamp) elif op == 'NUMBER': stk.append(ext.block_number) elif op == 'DIFFICULTY': stk.append(ext.block_difficulty) elif op == 'GASLIMIT': stk.append(ext.block_gas_limit) # VM state manipulations elif opcode < 0x60: if op == 'POP': stk.pop() elif op == 'MLOAD': s0 = stk.pop() if not mem_extend(mem, compustate, op, s0, 32): return vm_exception('OOG EXTENDING MEMORY') stk.append(utils.bytes_to_int(mem[s0:s0 + 32])) elif op == 'MSTORE': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, 32): return vm_exception('OOG EXTENDING MEMORY') mem[s0:s0 + 32] = utils.encode_int32(s1) elif op == 'MSTORE8': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, 1): return vm_exception('OOG EXTENDING MEMORY') mem[s0] = s1 % 256 elif op == 'SLOAD': if ext.post_anti_dos_hardfork(): if not eat_gas(compustate, opcodes.SLOAD_SUPPLEMENTAL_GAS): return vm_exception("OUT OF GAS") stk.append(ext.get_storage_data(msg.to, stk.pop())) elif op == 'SSTORE': s0, s1 = stk.pop(), stk.pop() if msg.static: return vm_exception( 'Cannot SSTORE inside a static context') if ext.get_storage_data(msg.to, s0): gascost = opcodes.GSTORAGEMOD if s1 else opcodes.GSTORAGEKILL refund = 0 if s1 else opcodes.GSTORAGEREFUND else: gascost = opcodes.GSTORAGEADD if s1 else opcodes.GSTORAGEMOD refund = 0 if compustate.gas < gascost: return vm_exception('OUT OF GAS') compustate.gas -= gascost # adds neg gascost as a refund if below zero ext.add_refund(refund) ext.set_storage_data(msg.to, s0, s1) elif op == 'JUMP': compustate.pc = stk.pop() if compustate.pc >= codelen or not ( (1 << compustate.pc) & jumpdest_mask): return vm_exception('BAD JUMPDEST') elif op == 'JUMPI': s0, s1 = stk.pop(), stk.pop() if s1: compustate.pc = s0 if compustate.pc >= codelen or not ( (1 << compustate.pc) & jumpdest_mask): return vm_exception('BAD JUMPDEST') elif op == 'PC': stk.append(compustate.pc - 1) elif op == 'MSIZE': stk.append(len(mem)) elif op == 'GAS': stk.append(compustate.gas) # AFTER subtracting cost 1 # DUPn (eg. DUP1: a b c -> a b c c, DUP3: a b c -> a b c a) elif op[:3] == 'DUP': # 0x7f - opcode is a negative number, -1 for 0x80 ... -16 for 0x8f stk.append(stk[0x7f - opcode]) # SWAPn (eg. SWAP1: a b c d -> a b d c, SWAP3: a b c d -> d b c a) elif op[:4] == 'SWAP': # 0x8e - opcode is a negative number, -2 for 0x90 ... -17 for 0x9f temp = stk[0x8e - opcode] stk[0x8e - opcode] = stk[-1] stk[-1] = temp # Logs (aka "events") elif op[:3] == 'LOG': """ 0xa0 ... 0xa4, 32/64/96/128/160 + len(data) gas a. Opcodes LOG0...LOG4 are added, takes 2-6 stack arguments MEMSTART MEMSZ (TOPIC1) (TOPIC2) (TOPIC3) (TOPIC4) b. Logs are kept track of during tx execution exactly the same way as suicides (except as an ordered list, not a set). Each log is in the form [address, [topic1, ... ], data] where: * address is what the ADDRESS opcode would output * data is mem[MEMSTART: MEMSTART + MEMSZ] * topics are as provided by the opcode c. The ordered list of logs in the transaction are expressed as [log0, log1, ..., logN]. """ depth = int(op[3:]) mstart, msz = stk.pop(), stk.pop() topics = [stk.pop() for x in range(depth)] compustate.gas -= msz * opcodes.GLOGBYTE if msg.static: return vm_exception('Cannot LOG inside a static context') if not mem_extend(mem, compustate, op, mstart, msz): return vm_exception('OOG EXTENDING MEMORY') data = bytearray_to_bytestr(mem[mstart:mstart + msz]) ext.log(msg.to, topics, data) log_log.trace('LOG', to=msg.to, topics=topics, data=list(map(utils.safe_ord, data))) # print('LOG', msg.to, topics, list(map(ord, data))) # Create a new contract elif op == 'CREATE': value, mstart, msz = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, mstart, msz): return vm_exception('OOG EXTENDING MEMORY') if msg.static: return vm_exception('Cannot CREATE inside a static context') if ext.get_balance(msg.to) >= value and msg.depth < MAX_DEPTH: cd = CallData(mem, mstart, msz) ingas = compustate.gas if ext.post_anti_dos_hardfork(): ingas = all_but_1n(ingas, opcodes.CALL_CHILD_LIMIT_DENOM) create_msg = Message(msg.to, b'', value, ingas, cd, msg.depth + 1) o, gas, data = ext.create(create_msg) if o: stk.append(utils.coerce_to_int(data)) compustate.last_returned = bytearray(b'') else: stk.append(0) compustate.last_returned = bytearray(data) compustate.gas = compustate.gas - ingas + gas else: stk.append(0) compustate.last_returned = bytearray(b'') # Calls elif op in ('CALL', 'CALLCODE', 'DELEGATECALL', 'STATICCALL'): # Pull arguments from the stack if op in ('CALL', 'CALLCODE'): gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() else: gas, to, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() value = 0 # Static context prohibition if msg.static and value > 0 and op == 'CALL': return vm_exception( 'Cannot make a non-zero-value call inside a static context' ) # Expand memory if not mem_extend(mem, compustate, op, meminstart, meminsz) or \ not mem_extend(mem, compustate, op, memoutstart, memoutsz): return vm_exception('OOG EXTENDING MEMORY') to = utils.int_to_addr(to) # Extra gas costs based on various factors extra_gas = 0 # Creating a new account if op == 'CALL' and not ext.account_exists(to) and ( value > 0 or not ext.post_spurious_dragon_hardfork()): extra_gas += opcodes.GCALLNEWACCOUNT # Value transfer if value > 0: extra_gas += opcodes.GCALLVALUETRANSFER # Cost increased from 40 to 700 in Tangerine Whistle if ext.post_anti_dos_hardfork(): extra_gas += opcodes.CALL_SUPPLEMENTAL_GAS # Compute child gas limit if ext.post_anti_dos_hardfork(): if compustate.gas < extra_gas: return vm_exception('OUT OF GAS', needed=extra_gas) gas = min( gas, all_but_1n(compustate.gas - extra_gas, opcodes.CALL_CHILD_LIMIT_DENOM)) else: if compustate.gas < gas + extra_gas: return vm_exception('OUT OF GAS', needed=gas + extra_gas) submsg_gas = gas + opcodes.GSTIPEND * (value > 0) # Verify that there is sufficient balance and depth if ext.get_balance(msg.to) < value or msg.depth >= MAX_DEPTH: compustate.gas -= (gas + extra_gas - submsg_gas) stk.append(0) compustate.last_returned = bytearray(b'') else: # Subtract gas from parent compustate.gas -= (gas + extra_gas) assert compustate.gas >= 0 cd = CallData(mem, meminstart, meminsz) # Generate the message if op == 'CALL': call_msg = Message(msg.to, to, value, submsg_gas, cd, msg.depth + 1, code_address=to, static=msg.static) elif ext.post_homestead_hardfork() and op == 'DELEGATECALL': call_msg = Message(msg.sender, msg.to, msg.value, submsg_gas, cd, msg.depth + 1, code_address=to, transfers_value=False, static=msg.static) elif ext.post_metropolis_hardfork() and op == 'STATICCALL': call_msg = Message(msg.to, to, value, submsg_gas, cd, msg.depth + 1, code_address=to, static=True) elif op in ('DELEGATECALL', 'STATICCALL'): return vm_exception('OPCODE %s INACTIVE' % op) elif op == 'CALLCODE': call_msg = Message(msg.to, msg.to, value, submsg_gas, cd, msg.depth + 1, code_address=to, static=msg.static) else: raise Exception("Lolwut") # Get result result, gas, data = ext.msg(call_msg) if result == 0: stk.append(0) else: stk.append(1) # Set output memory for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] compustate.gas += gas compustate.last_returned = bytearray(data) # Return opcode elif op == 'RETURN': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, s1): return vm_exception('OOG EXTENDING MEMORY') return peaceful_exit('RETURN', compustate.gas, mem[s0:s0 + s1]) # Revert opcode (Metropolis) elif op == 'REVERT': if not ext.post_metropolis_hardfork(): return vm_exception('Opcode not yet enabled') s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, s1): return vm_exception('OOG EXTENDING MEMORY') return revert(compustate.gas, mem[s0:s0 + s1]) # SUICIDE opcode (also called SELFDESTRUCT) elif op == 'SUICIDE': if msg.static: return vm_exception('Cannot SUICIDE inside a static context') to = utils.encode_int(stk.pop()) to = ((b'\x00' * (32 - len(to))) + to)[12:] xfer = ext.get_balance(msg.to) if ext.post_anti_dos_hardfork(): extra_gas = opcodes.SUICIDE_SUPPLEMENTAL_GAS + \ (not ext.account_exists(to)) * (xfer > 0 or not ext.post_spurious_dragon_hardfork()) * \ opcodes.GCALLNEWACCOUNT if not eat_gas(compustate, extra_gas): return vm_exception("OUT OF GAS") ext.set_balance(to, ext.get_balance(to) + xfer) ext.set_balance(msg.to, 0) ext.add_suicide(msg.to) log_msg.debug('SUICIDING', addr=utils.checksum_encode(msg.to), to=utils.checksum_encode(to), xferring=xfer) return peaceful_exit('SUICIDED', compustate.gas, []) if trace_vm: vm_trace(ext, msg, compustate, opcode, pushcache) if trace_vm: compustate.reset_prev() vm_trace(ext, msg, compustate, 0, None) return peaceful_exit('CODE OUT OF RANGE', compustate.gas, [])