def vm_trace(ext, msg, compustate, opcode, pushcache, tracer=log_vm_op): """ 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' """ op, in_args, out_args, fee = opcodes.opcodes[opcode] trace_data = {} trace_data["stack"] = list(map(to_string, list(compustate.prev_stack))) if compustate.prev_prev_op in ( "MLOAD", "MSTORE", "MSTORE8", "SHA3", "CALL", "CALLCODE", "CREATE", "CALLDATACOPY", "CODECOPY", "EXTCODECOPY", ): if len(compustate.prev_memory) < 4096: trace_data["memory"] = "".join( [encode_hex(ascii_chr(x)) for x in compustate.prev_memory] ) else: trace_data["sha3memory"] = encode_hex( utils.sha3(b"".join([ascii_chr(x) for x in compustate.prev_memory])) ) if compustate.prev_prev_op in ("SSTORE",) or compustate.steps == 0: trace_data["storage"] = ext.log_storage(msg.to) trace_data["gas"] = to_string(compustate.prev_gas) trace_data["gas_cost"] = to_string(compustate.prev_gas - compustate.gas) trace_data["fee"] = fee trace_data["inst"] = opcode trace_data["pc"] = to_string(compustate.prev_pc) if compustate.steps == 0: trace_data["depth"] = msg.depth trace_data["address"] = msg.to trace_data["steps"] = compustate.steps trace_data["depth"] = msg.depth if op[:4] == "PUSH": print(repr(pushcache)) trace_data["pushvalue"] = pushcache[compustate.prev_pc] tracer.trace("vm", op=op, **trace_data) compustate.steps += 1 compustate.prev_prev_op = op
def intrinsic_gas_used(self): num_zero_bytes = str_to_bytes(self.data).count(ascii_chr(0)) num_non_zero_bytes = len(self.data) - num_zero_bytes return (opcodes.GTXCOST # + (0 if self.to else opcodes.CREATE[3]) + opcodes.GTXDATAZERO * num_zero_bytes + opcodes.GTXDATANONZERO * num_non_zero_bytes)
def _send(self, sender, to, value, evmdata='', output=None, funid=None, abi=None, profiling=0): if funid is not None or abi is not None: raise Exception("Send with funid+abi is deprecated. Please use" " the abi_contract mechanism") tm, g = time.time(), self.block.gas_used sendnonce = self.block.get_nonce(u.privtoaddr(sender)) tx = t.Transaction(sendnonce, gas_price, gas_limit, to, value, evmdata) self.last_tx = tx tx.sign(sender) recorder = LogRecorder() if profiling > 1 else None (s, o) = pb.apply_transaction(self.block, tx) if not s: raise TransactionFailed() out = {"output": o} if profiling > 0: zero_bytes = tx.data.count(ascii_chr(0)) non_zero_bytes = len(tx.data) - zero_bytes intrinsic_gas_used = opcodes.GTXDATAZERO * zero_bytes + \ opcodes.GTXDATANONZERO * non_zero_bytes ntm, ng = time.time(), self.block.gas_used out["time"] = ntm - tm out["gas"] = ng - g - intrinsic_gas_used if profiling > 1: trace = recorder.pop_records() ops = [x['op'] for x in trace if x['event'] == 'vm'] opdict = {} for op in ops: opdict[op] = opdict.get(op, 0) + 1 out["ops"] = opdict return out
def vm_trace(ext, msg, compustate, opcode, pushcache):#, tracer=log_vm_op): """ 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' """ op, in_args, out_args, fee = opcodes.opcodes[opcode] trace_data = {} trace_data['stack'] = list(map(utils.to_string, list(compustate.prev_stack))) if compustate.prev_prev_op in ('MLOAD', 'MSTORE', 'MSTORE8', 'SHA3', 'CALL', 'CALLCODE', 'CREATE', 'CALLDATACOPY', 'CODECOPY', 'EXTCODECOPY'): if len(compustate.prev_memory) < 4096: trace_data['memory'] = \ ''.join([encode_hex(ascii_chr(x)) for x in compustate.prev_memory]) else: trace_data['sha3memory'] = \ encode_hex(utils.sha3(b''.join([ascii_chr(x) for x in compustate.prev_memory]))) if compustate.prev_prev_op in ('SSTORE',) or compustate.steps == 0: trace_data['storage'] = ext.log_storage(msg.to) trace_data['gas'] = utils.to_string(compustate.prev_gas) trace_data['gas_cost'] = utils.to_string(compustate.prev_gas - compustate.gas) trace_data['fee'] = fee trace_data['inst'] = opcode trace_data['pc'] = utils.to_string(compustate.prev_pc) if compustate.steps == 0: trace_data['depth'] = msg.depth trace_data['address'] = msg.to trace_data['steps'] = compustate.steps trace_data['depth'] = msg.depth if op[:4] == 'PUSH': print(repr(pushcache)[0:50]) trace_data['pushvalue'] = pushcache[compustate.prev_pc] # tracer.trace('vm', op=op, **trace_data) str = 'vm ' + repr(trace_data['steps'])+' '+repr(op)+repr(trace_data) print(str[0:50]) # print('vm', op, trace_data) compustate.steps += 1 compustate.prev_prev_op = op
def intrinsic_gas_used(self): num_zero_bytes = str_to_bytes(self.data).count(ascii_chr(0)) num_non_zero_bytes = len(self.data) - num_zero_bytes return ( opcodes.GTXCOST + (opcodes.CREATE[3] if not self.to else 0) + opcodes.GTXDATAZERO * num_zero_bytes + opcodes.GTXDATANONZERO * num_non_zero_bytes + (opcodes.GTXXSHARDCOST if self.is_cross_shard() else 0) )
def ecies_encrypt(cls, data, raw_pubkey, shared_mac_data=''): """ ECIES Encrypt, where P = recipient public key is: 1) generate r = random value 2) generate shared-secret = kdf( ecdhAgree(r, P) ) 3) generate R = rG [same op as generating a public key] 4) send 0x04 || R || AsymmetricEncrypt(shared-secret, plaintext) || tag currently used by go: ECIES_AES128_SHA256 = &ECIESParams{ Hash: sha256.New, hashAlgo: crypto.SHA256, Cipher: aes.NewCipher, BlockSize: aes.BlockSize, KeyLen: 16, } """ # 1) generate r = random value ephem = cls(None) # 2) generate shared-secret = kdf( ecdhAgree(r, P) ) key_material = ephem.raw_get_ecdh_key(pubkey_x=raw_pubkey[:32], pubkey_y=raw_pubkey[32:]) assert len(key_material) == 32 key = eciesKDF(key_material, 32) assert len(key) == 32 key_enc, key_mac = key[:16], key[16:] key_mac = hashlib.sha256(key_mac).digest() # !!! assert len(key_mac) == 32 # 3) generate R = rG [same op as generating a public key] ephem_pubkey = ephem.raw_pubkey # encrypt iv = pyelliptic.Cipher.gen_IV(cls.ecies_ciphername) assert len(iv) == 16 ctx = pyelliptic.Cipher(key_enc, iv, 1, cls.ecies_ciphername) ciphertext = ctx.ciphering(data) assert len(ciphertext) == len(data) # 4) send 0x04 || R || AsymmetricEncrypt(shared-secret, plaintext) # || tag msg = rlp_utils.ascii_chr(0x04) + ephem_pubkey + iv + ciphertext # the MAC of a message (called the tag) as per SEC 1, 3.5. tag = pyelliptic.hmac_sha256( key_mac, msg[1 + 64:] + rlp_utils.str_to_bytes(shared_mac_data)) assert len(tag) == 32 msg += tag assert len(msg) == 1 + 64 + 16 + 32 + len(data) == 113 + len(data) assert len(msg) - cls.ecies_encrypt_overhead_length == len(data) return msg
def nibbles_to_bin(nibbles): if any(x > 15 or x < 0 for x in nibbles): raise Exception("nibbles can only be [0,..15]") if len(nibbles) % 2: raise Exception("nibbles must be of even numbers") res = b'' for i in range(0, len(nibbles), 2): res += ascii_chr(16 * nibbles[i] + nibbles[i + 1]) return res
def cb(res, gas, dat, databytes): if res: b = extract_bytes(dat, 0, databytes) block.set_code(callstack[-1].to, ''.join([ascii_chr(x) for x in b])) res = utils.coerce_to_int(callstack[-1].to) else: if tx.sender != callstack[-1].sender: block.decrement_nonce(callstack[-1].sender) block.del_account(callstack[-1].to) callstack[-2].compustate.stack.append(res) callstack[-2].compustate.gas = gas
def subnib_to_binstr(nibblearr): # Used in situation, when you take nibblearr not from the beginning, thus lost 1 char with metadata if len(nibblearr) % 2: nibblearr = [HEX_SEQUENCE_ODD] + nibblearr else: nibblearr = [HEX_SEQUENCE_EVEN, 0] + nibblearr binstring = b'' for i in range(0, len(nibblearr), 2): if nibblearr[i] > 15 or nibblearr[i] < 0: raise Exception("invalid nibblearr[" + str(i) + "] =", nibblearr[i]) if nibblearr[i + 1] > 15 or nibblearr[i + 1] < 0: raise Exception("invalid nibblearr[" + str(i+1) + "] =", nibblearr[i+1]) binstring += ascii_chr(16 * nibblearr[i] + nibblearr[i + 1]) return binstring
def pack_nibbles(nibbles): if nibbles[-1:] == [NIBBLE_TERMINATOR]: flags = 2 nibbles = nibbles[:-1] else: flags = 0 oddlen = len(nibbles) % 2 flags |= oddlen if oddlen: nibbles = [flags] + nibbles else: nibbles = [flags, 0] + nibbles o = b'' for i in range(0, len(nibbles), 2): o += ascii_chr(16 * nibbles[i] + nibbles[i + 1]) return o
def subnib_to_binstr(nibblearr): # Used in situation, when you take nibblearr not from the beginning, thus lost 1 char with metadata if len(nibblearr) % 2: nibblearr = [HEX_SEQUENCE_ODD] + nibblearr else: nibblearr = [HEX_SEQUENCE_EVEN, 0] + nibblearr binstring = b'' for i in range(0, len(nibblearr), 2): if nibblearr[i] > 15 or nibblearr[i] < 0: raise Exception("invalid nibblearr[" + str(i) + "] =", nibblearr[i]) if nibblearr[i + 1] > 15 or nibblearr[i + 1] < 0: raise Exception("invalid nibblearr[" + str(i + 1) + "] =", nibblearr[i + 1]) binstring += ascii_chr(16 * nibblearr[i] + nibblearr[i + 1]) return binstring
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, [] b = [0] * 32 msg.data.extract_copy(b, 0, 0, 32) h = ''.join([ascii_chr(x) for x in b]) v = msg.data.extract32(32) r = msg.data.extract32(64) s = msg.data.extract32(96) if r >= bitcoin.N or s >= bitcoin.P or v < 27 or v > 28: return 1, msg.gas - opcodes.GECRECOVER, [0] * 32 pub = bitcoin.encode_pubkey(bitcoin.ecdsa_raw_recover(h, (v, r, s)), 'bin') o = [0] * 12 + [safe_ord(x) for x in utils.sha3(pub[1:])[-20:]] return 1, msg.gas - gas_cost, o
def create_auth_message(self, remote_pubkey, ephemeral_privkey=None, nonce=None): """ 1. initiator generates ecdhe-random and nonce and creates auth 2. initiator connects to remote and sends auth New: E(remote-pubk, S(ephemeral-privk, ecdh-shared-secret ^ nonce) || H(ephemeral-pubk) || pubk || nonce || 0x0 ) Known: E(remote-pubk, S(ephemeral-privk, token ^ nonce) || H(ephemeral-pubk) || pubk || nonce || 0x1) """ assert self.is_initiator if not self.ecc.is_valid_key(remote_pubkey): raise InvalidKeyError('invalid remote pubkey') self.remote_pubkey = remote_pubkey ecdh_shared_secret = self.ecc.get_ecdh_key(remote_pubkey) token = ecdh_shared_secret flag = 0x0 self.initiator_nonce = nonce or sha3( ienc(random.randint(0, 2**256 - 1))) assert len(self.initiator_nonce) == 32 token_xor_nonce = sxor(token, self.initiator_nonce) assert len(token_xor_nonce) == 32 ephemeral_pubkey = self.ephemeral_ecc.raw_pubkey assert len(ephemeral_pubkey) == 512 / 8 if not self.ecc.is_valid_key(ephemeral_pubkey): raise InvalidKeyError('invalid ephemeral pubkey') # S(ephemeral-privk, ecdh-shared-secret ^ nonce) S = self.ephemeral_ecc.sign(token_xor_nonce) assert len(S) == 65 # S || H(ephemeral-pubk) || pubk || nonce || 0x0 auth_message = S + sha3(ephemeral_pubkey) + self.ecc.raw_pubkey + \ self.initiator_nonce + ascii_chr(flag) assert len(auth_message) == 65 + 32 + 64 + 32 + 1 == 194 return auth_message
def OP_CREATE(): value, mstart, msz = stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, msgtop.compustate, '', mstart, msz): return drop(OUT_OF_GAS) if block.get_balance(msgtop.to) >= value: sender = decode_hex(msgtop.to) if len(msgtop.to) == 40 else msgtop.to block.increment_nonce(msgtop.to) data = [0] * ((msz >> 5) + 1) copy32(mem, data, mstart, 0, msz) create_msg = Message(msgtop.to, '', value, gaz() - 100, data, msz) msgtop.compustate.gas -= gaz() - 100 nonce = utils.encode_int(block.get_nonce(msgtop.to) - 1) create_msg.to = encode_hex(utils.sha3(rlp.encode([sender, nonce]))[12:]) special[0] = 'create' special[1] = create_msg special[2] = ''.join([ascii_chr(x) for x in extract_bytes(data, 0, msz)]) else: stk.append(0)
def compress(data): o = b'' i = 0 while i < len(data): if int_to_bytes(data[i]) == b'\xfe': o += b'\xfe\x00' elif data[i:i + 32] == NULLSHA3: o += b'\xfe\x01' i += 31 elif data[i:i + 2] == b'\x00\x00': p = 2 while p < 255 and i + p < len(data) and int_to_bytes(data[i + p]) == b'': p += 1 o += b'\xfe' + ascii_chr(p) i += p - 1 else: o += int_to_bytes(data[i]) i += 1 return o
def compress(data): o = b'' i = 0 while i < len(data): if int_to_bytes(data[i]) == b'\xfe': o += b'\xfe\x00' elif data[i:i + 32] == NULLSHA3: o += b'\xfe\x01' i += 31 elif data[i:i + 2] == b'\x00\x00': p = 2 while p < 255 and i + \ p < len(data) and int_to_bytes(data[i + p]) == b'\x00': p += 1 o += b'\xfe' + ascii_chr(p) i += p - 1 else: o += int_to_bytes(data[i]) i += 1 return o
def ecrecover_to_pub(rawhash, v, r, s): if coincurve and hasattr(coincurve, "PublicKey"): try: pk = coincurve.PublicKey.from_signature_and_message( zpad(bytearray_to_bytestr(int_to_32bytearray(r)), 32) + zpad(bytearray_to_bytestr(int_to_32bytearray(s)), 32) + ascii_chr(v - 27), rawhash, hasher=None, ) pub = pk.format(compressed=False)[1:] except BaseException: pub = b"\x00" * 64 else: result = ecdsa_raw_recover(rawhash, (v, r, s)) if result: x, y = result pub = encode_int32(x) + encode_int32(y) else: raise ValueError('Invalid VRS') assert len(pub) == 64 return pub
def ecrecover_to_pub(rawhash, v, r, s): """ Performs Solidity-like ecrecover. Arguments: - rawhash - v, r, s: Parts of the signature Returns the public key of the signer. """ try: pk = coincurve.PublicKey.from_signature_and_message( zpad(bytes(int_to_32bytearray(r)), 32) + zpad(bytes(int_to_32bytearray(s)), 32) + ascii_chr(v - 27), rawhash, hasher=None, ) pub = pk.format(compressed=False)[1:] except BaseException: pub = b"\x00" * 64 assert len(pub) == 64 return pub
def pack_nibbles(nibbles): """pack nibbles to binary :param nibbles: a nibbles sequence. may have a terminator """ if nibbles[-1:] == [NIBBLE_TERMINATOR]: flags = 2 nibbles = nibbles[:-1] else: flags = 0 oddlen = len(nibbles) % 2 flags |= oddlen # set lowest bit if odd number of nibbles if oddlen: nibbles = [flags] + nibbles else: nibbles = [flags, 0] + nibbles o = b'' for i in range(0, len(nibbles), 2): o += ascii_chr(16 * nibbles[i] + nibbles[i + 1]) return o
def _send(self, sender, to, value, evmdata='', output=None, funid=None, abi=None, profiling=0): if funid is not None or abi is not None: raise Exception("Send with funid+abi is deprecated. Please use" " the abi_contract mechanism") tm, g = time.time(), self.block.gas_used sendnonce = self.block.get_nonce(u.privtoaddr(sender)) tx = t.Transaction(sendnonce, 1, gas_limit, to, value, evmdata) self.last_tx = tx tx.sign(sender) recorder = LogRecorder() if profiling > 1 else None (s, o) = pb.apply_transaction(self.block, tx) if not s: raise Exception("Transaction failed") out = {"output": o} if profiling > 0: zero_bytes = tx.data.count(ascii_chr(0)) non_zero_bytes = len(tx.data) - zero_bytes intrinsic_gas_used = opcodes.GTXDATAZERO * zero_bytes + \ opcodes.GTXDATANONZERO * non_zero_bytes ntm, ng = time.time(), self.block.gas_used out["time"] = ntm - tm out["gas"] = ng - g - intrinsic_gas_used if profiling > 1: trace = recorder.pop_records() ops = [x['op'] for x in trace if x['event'] == 'vm'] opdict = {} for op in ops: opdict[op] = opdict.get(op, 0) + 1 out["ops"] = opdict return out
def intrinsic_gas_used(tx): num_zero_bytes = str_to_bytes(tx.data).count(ascii_chr(0)) num_non_zero_bytes = len(tx.data) - num_zero_bytes return (opcodes.GTXCOST + opcodes.GTXDATAZERO * num_zero_bytes + opcodes.GTXDATANONZERO * num_non_zero_bytes)
def extract_all(self): d = self.data[self.offset:self.offset + self.size] d += [0] * (self.size - len(d)) return b''.join([ascii_chr(x) for x in d])
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') compustate = Compustate(gas=msg.gas) stk = compustate.stack mem = compustate.memory if code in code_cache: processed_code = code_cache[code] else: processed_code = preprocess_code(code) code_cache[code] = processed_code codelen = len(processed_code) s = time.time() op = None while 1: # print 'op: ', op, time.time() - s # s = time.time() # stack size limit error if compustate.pc >= codelen: return peaceful_exit('CODE OUT OF RANGE', compustate.gas, []) op, in_args, out_args, fee, opcode, pushval = \ processed_code[compustate.pc] # out of gas error if fee > compustate.gas: 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))) 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))) # Apply operation compustate.gas -= fee compustate.pc += 1 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 = {} if log_vm_op_stack.is_active(): trace_data['stack'] = list( map(to_string, list(compustate.stack))) if log_vm_op_memory.is_active(): trace_data['memory'] = \ b''.join([encode_hex(ascii_chr(x)) for x in compustate.memory]) if log_vm_op_storage.is_active(): trace_data['storage'] = ext.log_storage(msg.to) trace_data['gas'] = to_string(compustate.gas + fee) trace_data['pc'] = to_string(compustate.pc - 1) trace_data['op'] = op if op[:4] == 'PUSH': trace_data['pushvalue'] = pushval log_vm_op.trace('vm', **trace_data) # Invalid operation if op == 'INVALID': return vm_exception('INVALID OP', opcode=opcode) # Valid operations if 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) 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 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 = b''.join(map(ascii_chr, 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(len(processed_code)) elif op == 'CODECOPY': start, s1, size = stk.pop(), stk.pop(), stk.pop() 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 s1 + i < len(processed_code): mem[start + i] = processed_code[s1 + i][4] else: mem[start + i] = 0 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 isinstance(extcode, str) 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] = ord(extcode[s2 + i]) else: mem[start + i] = 0 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) 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') data = b''.join(map(ascii_chr, mem[s0:s0 + 32])) stk.append(utils.big_endian_to_int(data)) elif op == 'MSTORE': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, 32): return vm_exception('OOG EXTENDING MEMORY') v = s1 for i in range(31, -1, -1): mem[s0 + i] = v % 256 v //= 256 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 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 ext.add_refund( refund) # adds neg gascost as a refund if below zero ext.set_storage_data(msg.to, s0, s1) elif op == 'JUMP': compustate.pc = stk.pop() opnew = processed_code[compustate.pc][0] if \ compustate.pc < len(processed_code) else 'STOP' if opnew != 'JUMPDEST': return vm_exception('BAD JUMPDEST') elif op == 'JUMPI': s0, s1 = stk.pop(), stk.pop() if s1: compustate.pc = s0 opnew = processed_code[compustate.pc][0] if \ compustate.pc < len(processed_code) else 'STOP' if opnew != 'JUMPDEST': 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 elif op[:4] == 'PUSH': pushnum = int(op[4:]) compustate.pc += pushnum stk.append(pushval) elif op[:3] == 'DUP': depth = int(op[3:]) stk.append(stk[-depth]) elif op[:4] == 'SWAP': depth = int(op[4:]) temp = stk[-depth - 1] stk[-depth - 1] = stk[-1] stk[-1] = temp 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 not mem_extend(mem, compustate, op, mstart, msz): return vm_exception('OOG EXTENDING MEMORY') data = b''.join(map(ascii_chr, mem[mstart:mstart + msz])) ext.log(msg.to, topics, data) log_log.trace('LOG', to=msg.to, topics=topics, data=list(map(ord, data))) # print('LOG', msg.to, topics, list(map(ord, data))) 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 ext.get_balance(msg.to) >= value and msg.depth < 1024: cd = CallData(mem, mstart, msz) create_msg = Message(msg.to, b'', value, compustate.gas, cd, msg.depth + 1) o, gas, addr = ext.create(create_msg) if o: stk.append(utils.coerce_to_int(addr)) compustate.gas = gas else: stk.append(0) compustate.gas = 0 else: stk.append(0) elif op == 'CALL': gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() 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.encode_int(to) to = ((b'\x00' * (32 - len(to))) + to)[12:] extra_gas = (not ext.account_exists(to)) * opcodes.GCALLNEWACCOUNT + \ (value > 0) * opcodes.GCALLVALUETRANSFER submsg_gas = gas + opcodes.GSTIPEND * (value > 0) if compustate.gas < gas + extra_gas: return vm_exception('OUT OF GAS') if ext.get_balance(msg.to) >= value and msg.depth < 1024: compustate.gas -= (gas + extra_gas) cd = CallData(mem, meminstart, meminsz) call_msg = Message(msg.to, to, value, submsg_gas, cd, msg.depth + 1, code_address=to) result, gas, data = ext.msg(call_msg) if result == 0: stk.append(0) else: stk.append(1) compustate.gas += gas for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] else: compustate.gas -= (gas + extra_gas - submsg_gas) stk.append(0) elif op == 'CALLCODE': gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, meminstart, meminsz) or \ not mem_extend(mem, compustate, op, memoutstart, memoutsz): return vm_exception('OOG EXTENDING MEMORY') extra_gas = (value > 0) * opcodes.GCALLVALUETRANSFER submsg_gas = gas + opcodes.GSTIPEND * (value > 0) if compustate.gas < gas + extra_gas: return vm_exception('OUT OF GAS') if ext.get_balance(msg.to) >= value and msg.depth < 1024: compustate.gas -= (gas + extra_gas) to = utils.encode_int(to) to = ((b'\x00' * (32 - len(to))) + to)[12:] cd = CallData(mem, meminstart, meminsz) call_msg = Message(msg.to, msg.to, value, submsg_gas, cd, msg.depth + 1, code_address=to) result, gas, data = ext.msg(call_msg) if result == 0: stk.append(0) else: stk.append(1) compustate.gas += gas for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] else: compustate.gas -= (gas + extra_gas - submsg_gas) stk.append(0) 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]) elif op == 'SUICIDE': to = utils.encode_int(stk.pop()) to = ((b'\x00' * (32 - len(to))) + to)[12:] xfer = ext.get_balance(msg.to) ext.set_balance(msg.to, 0) ext.set_balance(to, ext.get_balance(to) + xfer) ext.add_suicide(msg.to) # print('suiciding %s %s %d' % (msg.to, to, xfer)) return 1, compustate.gas, [] # this is slow! if verify_stack_after_op: for a in stk: assert is_numeric(a) assert a >= 0 and a < 2**256, (a, op, stk)
def int_to_addr(x): o = [""] * 20 for i in range(20): o[19 - i] = ascii_chr(x & 0xFF) x >>= 8 return b"".join(o)
def OP_SHA3(): s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, msgtop.compustate, '', s0, s1): return drop(OUT_OF_GAS) data = ''.join([ascii_chr(x) for x in mem[s0: s0 + s1]]) stk.append(utils.big_endian_to_int(utils.sha3(data)))
import pytest from rlp import SerializationError, utils from rlp.sedes import big_endian_int, BigEndianInt valid_data = ( (256, b'\x01\x00'), (1024, b'\x04\x00'), (65535, b'\xff\xff'), ) single_bytes = ((n, utils.ascii_chr(n)) for n in range(1, 256)) random_integers = (256, 257, 4839, 849302, 483290432, 483290483290482039482039, 48930248348219540325894323584235894327865439258743754893066) negative_ints = (-1, -100, -255, -256, -2342423) def test_neg(): for n in negative_ints: with pytest.raises(SerializationError): big_endian_int.serialize(n) def test_serialization(): for n in random_integers: serial = big_endian_int.serialize(n) deserialized = big_endian_int.deserialize(serial) assert deserialized == n if n != 0: assert serial[0] != b'\x00' # is not checked
def sxor(s1, s2): "string xor" assert len(s1) == len(s2) return b''.join( ascii_chr(safe_ord(a) ^ safe_ord(b)) for a, b in zip(s1, s2))
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') compustate = Compustate(gas=msg.gas) stk = compustate.stack mem = compustate.memory if code in code_cache: processed_code = code_cache[code] else: processed_code = preprocess_code(code) code_cache[code] = processed_code # print(processed_code.keys(), code) codelen = len(code) s = time.time() steps = 0 _prevop = None # for trace only while compustate.pc in processed_code: ops, minstack, maxstack, totgas, nextpos = processed_code[ compustate.pc] if len(compustate.stack) < minstack: return vm_exception('INSUFFICIENT STACK') if len(compustate.stack) > maxstack: return vm_exception('STACK SIZE LIMIT EXCEEDED') if totgas > compustate.gas: return vm_exception('OUT OF GAS %d %d' % (totgas, compustate.gas)) jumped = False compustate.gas -= totgas compustate.pc = nextpos # Invalid operation; can only come at the end of a chunk if ops[-1][0] == 'INVALID': return vm_exception('INVALID OP', opcode=ops[-1][1]) for op, opcode, pushval in ops: if trace_vm: """ This diverges from normal logging, as we use the logging namespace only to decide which features get logged in 'bible.vm.op' i.e. tracing can not be activated by activating a sub like 'bible.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) < 1024: 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 + totgas) 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'] = pushval log_vm_op.trace('vm', op=op, **trace_data) steps += 1 _prevop = op # Valid operations # Pushes first because they are very frequent if 0x60 <= opcode <= 0x7f: # compustate.pc += opcode - 0x5f # Move 1 byte forward for # 0x60, up to 32 bytes for 0x7f stk.append(pushval) 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_clearing_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) 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 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(len(code)) 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 < len(code): mem[mstart + i] = utils.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 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] = utils.safe_ord(extcode[s2 + i]) else: mem[start + i] = 0 elif opcode < 0x50: if op == 'BLOCKHASH': if ext.post_metropolis_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) 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() opnew = code[ compustate.pc] if compustate.pc < codelen else 0 jumped = True if opnew != JUMPDEST: return vm_exception('BAD JUMPDEST') elif op == 'JUMPI': s0, s1 = stk.pop(), stk.pop() if s1: compustate.pc = s0 opnew = code[ compustate.pc] if compustate.pc < codelen else 0 jumped = True if opnew != JUMPDEST: 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 elif op[:3] == 'DUP': # 0x7f - opcode is a negative number, -1 for 0x80 ... -16 for # 0x8f stk.append(stk[0x7f - opcode]) 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 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))) 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, addr = ext.create(create_msg) if o: stk.append(utils.coerce_to_int(addr)) else: stk.append(0) compustate.gas = compustate.gas - ingas + gas else: stk.append(0) 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: 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 hard fork-dependent factors extra_gas = (not ext.account_exists(to)) * (op == 'CALL') * (value > 0 or not ext.post_clearing_hardfork()) * opcodes.GCALLNEWACCOUNT + \ (value > 0) * opcodes.GCALLVALUETRANSFER + \ ext.post_anti_dos_hardfork() * 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) 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 op == 'DELEGATECALL': return vm_exception('OPCODE INACTIVE') elif op == 'CALLCODE': call_msg = Message(msg.to, msg.to, value, submsg_gas, cd, msg.depth + 1, code_address=to, static=msg.static) elif op == 'STATICCALL': call_msg = Message(msg.to, to, value, submsg_gas, cd, msg.depth + 1, code_address=to, static=True) 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) 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]) 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]) 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_clearing_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 1, compustate.gas, [] # assert utils.is_numeric(compustate.gas) # this is slow! # for a in stk: # assert is_numeric(a), (op, stk) # assert a >= 0 and a < 2**256, (a, op, stk) # if not jumped: # assert compustate.pc == nextpos # compustate.pc = nextpos if compustate.pc >= codelen: return peaceful_exit('CODE OUT OF RANGE', compustate.gas, []) return vm_exception('INVALID JUMP')
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') compustate = Compustate(gas=msg.gas) stk = compustate.stack mem = compustate.memory if code in code_cache: processed_code = code_cache[code] else: processed_code = preprocess_code(code) code_cache[code] = processed_code codelen = len(processed_code) s = time.time() op = None steps = 0 _prevop = None # for trace only while 1: # print('op: ', op, time.time() - s) # s = time.time() # stack size limit error if compustate.pc >= codelen: return peaceful_exit('CODE OUT OF RANGE', compustate.gas, []) op, in_args, out_args, fee, opcode, pushval = \ processed_code[compustate.pc] # out of gas error if fee > compustate.gas: 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))) 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))) # Apply operation compustate.gas -= fee compustate.pc += 1 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) < 1024: trace_data['memory'] = \ b''.join([encode_hex(ascii_chr(x)) for x in compustate.memory]) else: trace_data['sha3memory'] = \ encode_hex(utils.sha3(''.join([ascii_chr(x) for x in compustate.memory]))) if _prevop in ('SSTORE', 'SLOAD') 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['op'] = op trace_data['steps'] = steps if op[:4] == 'PUSH': trace_data['pushvalue'] = pushval log_vm_op.trace('vm', **trace_data) steps += 1 _prevop = op # Invalid operation if op == 'INVALID': return vm_exception('INVALID OP', opcode=opcode) # Valid operations if 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) 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 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 = b''.join(map(ascii_chr, 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(len(processed_code)) elif op == 'CODECOPY': start, s1, size = stk.pop(), stk.pop(), stk.pop() 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 s1 + i < len(processed_code): mem[start + i] = processed_code[s1 + i][4] else: mem[start + i] = 0 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] = utils.safe_ord(extcode[s2 + i]) else: mem[start + i] = 0 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) 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') data = b''.join(map(ascii_chr, mem[s0: s0 + 32])) stk.append(utils.big_endian_to_int(data)) elif op == 'MSTORE': s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, 32): return vm_exception('OOG EXTENDING MEMORY') v = s1 for i in range(31, -1, -1): mem[s0 + i] = v % 256 v //= 256 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 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 ext.add_refund(refund) # adds neg gascost as a refund if below zero ext.set_storage_data(msg.to, s0, s1) elif op == 'JUMP': compustate.pc = stk.pop() opnew = processed_code[compustate.pc][0] if \ compustate.pc < len(processed_code) else 'STOP' if opnew != 'JUMPDEST': return vm_exception('BAD JUMPDEST') elif op == 'JUMPI': s0, s1 = stk.pop(), stk.pop() if s1: compustate.pc = s0 opnew = processed_code[compustate.pc][0] if \ compustate.pc < len(processed_code) else 'STOP' if opnew != 'JUMPDEST': 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 elif op[:4] == 'PUSH': pushnum = int(op[4:]) compustate.pc += pushnum stk.append(pushval) elif op[:3] == 'DUP': depth = int(op[3:]) stk.append(stk[-depth]) elif op[:4] == 'SWAP': depth = int(op[4:]) temp = stk[-depth - 1] stk[-depth - 1] = stk[-1] stk[-1] = temp 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 not mem_extend(mem, compustate, op, mstart, msz): return vm_exception('OOG EXTENDING MEMORY') data = b''.join(map(ascii_chr, 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))) 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 ext.get_balance(msg.to) >= value and msg.depth < 1024: cd = CallData(mem, mstart, msz) create_msg = Message(msg.to, b'', value, compustate.gas, cd, msg.depth + 1) o, gas, addr = ext.create(create_msg) if o: stk.append(utils.coerce_to_int(addr)) compustate.gas = gas else: stk.append(0) compustate.gas = 0 else: stk.append(0) elif op == 'CALL': gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() 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.encode_int(to) to = ((b'\x00' * (32 - len(to))) + to)[12:] extra_gas = (not ext.account_exists(to)) * opcodes.GCALLNEWACCOUNT + \ (value > 0) * opcodes.GCALLVALUETRANSFER submsg_gas = gas + opcodes.GSTIPEND * (value > 0) if compustate.gas < gas + extra_gas: return vm_exception('OUT OF GAS', needed=gas+extra_gas) if ext.get_balance(msg.to) >= value and msg.depth < 1024: compustate.gas -= (gas + extra_gas) cd = CallData(mem, meminstart, meminsz) call_msg = Message(msg.to, to, value, submsg_gas, cd, msg.depth + 1, code_address=to) result, gas, data = ext.msg(call_msg) if result == 0: stk.append(0) else: stk.append(1) compustate.gas += gas for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] else: compustate.gas -= (gas + extra_gas - submsg_gas) stk.append(0) elif op == 'CALLCODE' or op == 'DELEGATECALL': if op == '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 if not mem_extend(mem, compustate, op, meminstart, meminsz) or \ not mem_extend(mem, compustate, op, memoutstart, memoutsz): return vm_exception('OOG EXTENDING MEMORY') extra_gas = (value > 0) * opcodes.GCALLVALUETRANSFER submsg_gas = gas + opcodes.GSTIPEND * (value > 0) if compustate.gas < gas + extra_gas: return vm_exception('OUT OF GAS', needed=gas+extra_gas) if ext.get_balance(msg.to) >= value and msg.depth < 1024: compustate.gas -= (gas + extra_gas) to = utils.encode_int(to) to = ((b'\x00' * (32 - len(to))) + to)[12:] cd = CallData(mem, meminstart, meminsz) if 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) elif op == 'DELEGATECALL': return vm_exception('OPCODE INACTIVE') else: call_msg = Message(msg.to, msg.to, value, submsg_gas, cd, msg.depth + 1, code_address=to) result, gas, data = ext.msg(call_msg) if result == 0: stk.append(0) else: stk.append(1) compustate.gas += gas for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] else: compustate.gas -= (gas + extra_gas - submsg_gas) stk.append(0) 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]) elif op == 'SUICIDE': to = utils.encode_int(stk.pop()) to = ((b'\x00' * (32 - len(to))) + to)[12:] xfer = ext.get_balance(msg.to) ext.set_balance(to, ext.get_balance(to) + xfer) ext.set_balance(msg.to, 0) ext.add_suicide(msg.to) # print('suiciding %s %s %d' % (msg.to, to, xfer)) return 1, compustate.gas, []
def extract_all(self): d = self.data[self.offset: self.offset + self.size] d += [0] * (self.size - len(d)) return b''.join([ascii_chr(x) for x in d])
def _send( self, sender, to, value, evmdata='', funid=None, abi=None, # pylint: disable=too-many-arguments profiling=0): # pylint: disable=too-many-locals if funid is not None or abi is not None: raise Exception( 'Send with funid+abi is deprecated. Please use the abi_contract mechanism' ) start_time = time.time() gas_used = self.block.gas_used sendnonce = self.block.get_nonce(privtoaddr(sender)) transaction = transactions.Transaction(sendnonce, gas_price, gas_limit, to, value, evmdata) self.last_tx = transaction transaction.sign(sender) recorder = None if profiling > 1: recorder = LogRecorder( disable_other_handlers=True, log_config=TRACE_LVL_MAP[3], ) try: (success, output) = processblock.apply_transaction(self.block, transaction) if not success: raise TransactionFailed() out = { 'output': output, } if profiling > 0: zero_bytes_count = transaction.data.count(ascii_chr(0)) non_zero_bytes_count = len(transaction.data) - zero_bytes_count zero_bytes_cost = opcodes.GTXDATAZERO * zero_bytes_count nonzero_bytes_cost = opcodes.GTXDATANONZERO * non_zero_bytes_count base_gas_cost = opcodes.GTXCOST intrinsic_gas_used = base_gas_cost + zero_bytes_cost + nonzero_bytes_cost out['time'] = time.time() - start_time out['gas'] = self.block.gas_used - gas_used - intrinsic_gas_used if profiling > 1: trace = recorder.pop_records() vm_operations = [ event['op'] for event in trace if event['event'] == 'vm' ] opdict = {} for operation in vm_operations: opdict[operation] = opdict.get(operation, 0) + 1 out['ops'] = opdict return out finally: # ensure LogRecorder has been disabled if recorder: recorder.pop_records()
def _send(self, sender, to, value, evmdata='', funid=None, abi=None, # pylint: disable=too-many-arguments profiling=0): # pylint: disable=too-many-locals if funid is not None or abi is not None: raise Exception( 'Send with funid+abi is deprecated. Please use the abi_contract mechanism' ) start_time = time.time() gas_used = self.block.gas_used sendnonce = self.block.get_nonce(privtoaddr(sender)) transaction = transactions.Transaction(sendnonce, gas_price, gas_limit, to, value, evmdata) self.last_tx = transaction transaction.sign(sender) recorder = None if profiling > 1: recorder = LogRecorder( disable_other_handlers=True, log_config=TRACE_LVL_MAP[3], ) try: (success, output) = processblock.apply_transaction(self.block, transaction) if not success: raise TransactionFailed() out = { 'output': output, } if profiling > 0: zero_bytes_count = transaction.data.count(ascii_chr(0)) non_zero_bytes_count = len(transaction.data) - zero_bytes_count zero_bytes_cost = opcodes.GTXDATAZERO * zero_bytes_count nonzero_bytes_cost = opcodes.GTXDATANONZERO * non_zero_bytes_count base_gas_cost = opcodes.GTXCOST intrinsic_gas_used = base_gas_cost + zero_bytes_cost + nonzero_bytes_cost out['time'] = time.time() - start_time out['gas'] = self.block.gas_used - gas_used - intrinsic_gas_used if profiling > 1: trace = recorder.pop_records() vm_operations = [ event['op'] for event in trace if event['event'] == 'vm' ] opdict = {} for operation in vm_operations: opdict[operation] = opdict.get(operation, 0) + 1 out['ops'] = opdict return out finally: # ensure LogRecorder has been disabled if recorder: recorder.pop_records()
def vm_execute(ext, msg, code): # precompute trace flag # if we trace vm, we're in slow mode anyway trace_vm = True #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 = utils.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['gas'] = utils.to_string(compustate.gas + fee) trace_data['stack'] = list(map(utils.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['inst'] = opcode trace_data['pc'] = utils.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] str = 'vm ' + repr(trace_data['steps'])+' '+repr(op)+repr(trace_data) print(str[0:200]) # print('vm', 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=utils.to_string(in_args), available=utils.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=utils.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 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 = utils.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 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] = utils.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 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 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] = utils.safe_ord(extcode[s2 + i]) else: mem[start + i] = 0 # 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': 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 = utils.bytearray_to_bytestr(mem[mstart: mstart + msz]) ext.log(msg.to, topics, 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 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): extra_gas += opcodes.GCALLNEWACCOUNT # Value transfer if value > 0: extra_gas += opcodes.GCALLVALUETRANSFER # Cost increased from 40 to 700 in Tangerine Whistle extra_gas += opcodes.CALL_SUPPLEMENTAL_GAS # 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, code_address=to, static=msg.static) elif 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 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': 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 = opcodes.SUICIDE_SUPPLEMENTAL_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: # 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, [])
def run_vm_test(params, mode, profiler=None): pre = params['pre'] exek = params['exec'] env = params['env'] if 'previousHash' not in env: env['previousHash'] = encode_hex(db_env.config['GENESIS_PREVHASH']) assert set(env.keys()) == set(['currentGasLimit', 'currentTimestamp', 'previousHash', 'currentCoinbase', 'currentDifficulty', 'currentNumber']) # setup env header = blocks.BlockHeader( prevhash=decode_hex(env['previousHash']), number=parse_int_or_hex(env['currentNumber']), coinbase=decode_hex(env['currentCoinbase']), difficulty=parse_int_or_hex(env['currentDifficulty']), gas_limit=parse_int_or_hex(env['currentGasLimit']), timestamp=parse_int_or_hex(env['currentTimestamp'])) blk = blocks.Block(header, env=db_env) # setup state for address, h in list(pre.items()): assert len(address) == 40 address = decode_hex(address) assert set(h.keys()) == set(['code', 'nonce', 'balance', 'storage']) blk.set_nonce(address, parse_int_or_hex(h['nonce'])) blk.set_balance(address, parse_int_or_hex(h['balance'])) blk.set_code(address, decode_hex(h['code'][2:])) for k, v in h['storage'].items(): blk.set_storage_data(address, utils.big_endian_to_int(decode_hex(k[2:])), utils.big_endian_to_int(decode_hex(v[2:]))) # execute transactions sender = decode_hex(exek['caller']) # a party that originates a call recvaddr = decode_hex(exek['address']) nonce = blk._get_acct_item(sender, 'nonce') gasprice = parse_int_or_hex(exek['gasPrice']) startgas = parse_int_or_hex(exek['gas']) value = parse_int_or_hex(exek['value']) data = decode_hex(exek['data'][2:]) # bypass gas check in tx initialization by temporarily increasing startgas num_zero_bytes = str_to_bytes(data).count(ascii_chr(0)) num_non_zero_bytes = len(data) - num_zero_bytes intrinsic_gas = (opcodes.GTXCOST + opcodes.GTXDATAZERO * num_zero_bytes + opcodes.GTXDATANONZERO * num_non_zero_bytes) startgas += intrinsic_gas tx = transactions.Transaction(nonce=nonce, gasprice=gasprice, startgas=startgas, to=recvaddr, value=value, data=data) tx.startgas -= intrinsic_gas tx.sender = sender # capture apply_message calls apply_message_calls = [] orig_apply_msg = pb.apply_msg ext = pb.VMExt(blk, tx) def msg_wrapper(msg): hexdata = encode_hex(msg.data.extract_all()) apply_message_calls.append(dict(gasLimit=to_string(msg.gas), value=to_string(msg.value), destination=encode_hex(msg.to), data=b'0x' + hexdata)) return 1, msg.gas, b'' def create_wrapper(msg): sender = decode_hex(msg.sender) if \ len(msg.sender) == 40 else msg.sender nonce = utils.encode_int(ext._block.get_nonce(msg.sender)) addr = utils.sha3(rlp.encode([sender, nonce]))[12:] hexdata = encode_hex(msg.data.extract_all()) apply_message_calls.append(dict(gasLimit=to_string(msg.gas), value=to_string(msg.value), destination=b'', data=b'0x' + hexdata)) return 1, msg.gas, addr ext.msg = msg_wrapper ext.create = create_wrapper def blkhash(n): if n >= ext.block_number or n < ext.block_number - 256: return b'' else: return utils.sha3(to_string(n)) ext.block_hash = blkhash msg = vm.Message(tx.sender, tx.to, tx.value, tx.startgas, vm.CallData([safe_ord(x) for x in tx.data])) code = decode_hex(exek['code'][2:]) time_pre = time.time() if profiler: profiler.enable() success, gas_remained, output = vm.vm_execute(ext, msg, code) if profiler: profiler.disable() pb.apply_msg = orig_apply_msg blk.commit_state() for s in blk.suicides: blk.del_account(s) time_post = time.time() """ generally expected that the test implementer will read env, exec and pre then check their results against gas, logs, out, post and callcreates. If an exception is expected, then latter sections are absent in the test. Since the reverting of the state is not part of the VM tests. """ params2 = copy.deepcopy(params) if success: params2['callcreates'] = apply_message_calls params2['out'] = b'0x' + encode_hex(b''.join(map(ascii_chr, output))) params2['gas'] = to_string(gas_remained) params2['logs'] = [log.to_dict() for log in blk.logs] params2['post'] = blk.to_dict(with_state=True)['state'] if mode == FILL: return params2 elif mode == VERIFY: if not success: assert 'post' not in params, 'failed, but expected to succeed' params1 = copy.deepcopy(params) shouldbe, reallyis = params1.get('post', None), params2.get('post', None) compare_post_states(shouldbe, reallyis) def normalize_value(k, p): if k in p: if k == 'gas': return parse_int_or_hex(p[k]) elif k == 'callcreates': return list(map(callcreate_standard_form, p[k])) else: return utils.to_string(k) return None for k in ['pre', 'exec', 'env', 'callcreates', 'out', 'gas', 'logs']: shouldbe = normalize_value(k, params1) reallyis = normalize_value(k, params2) if shouldbe != reallyis: raise Exception("Mismatch: " + k + ':\n shouldbe %r\n reallyis %r' % (shouldbe, reallyis)) elif mode == TIME: return time_post - time_pre
def random_string(length): return ''.join([ascii_chr(random.randint(0, 255)) for _ in range(length)])
def int_to_addr(x): o = [b''] * 20 for i in range(20): o[19 - i] = ascii_chr(x & 0xff) x >>= 8 return b''.join(o)
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') compustate = Compustate(gas=msg.gas) stk = compustate.stack mem = compustate.memory if code in code_cache: processed_code = code_cache[code] else: processed_code = preprocess_code(code) code_cache[code] = processed_code s = time.time() op = None steps = 0 _prevop = None # for trace only while 1: # print('op: ', op, time.time() - s) # s = time.time() # stack size limit error if compustate.pc not in processed_code: return vm_exception('INVALID START POINT') _data = processed_code[compustate.pc] gas, min_stack, max_stack, compustate.pc = _data[:4] ops = _data[4:] # out of gas error if gas > compustate.gas: return vm_exception('OUT OF GAS') # insufficient stack error if not (min_stack <= len(compustate.stack) <= max_stack): return vm_exception('INCOMPATIBLE STACK LENGTH', min_stack=min_stack, have=len(compustate.stack), max_stack=max_stack) # Apply operation compustate.gas -= gas for op in ops: 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 (op_MLOAD, op_MSTORE, op_MSTORE8, op_SHA3, op_CALL, op_CALLCODE, op_CREATE, op_CALLDATACOPY, op_CODECOPY, op_EXTCODECOPY): if len(compustate.memory) < 1024: trace_data['memory'] = \ b''.join([encode_hex(ascii_chr(x)) for x in compustate.memory]) else: trace_data['sha3memory'] = \ encode_hex(utils.sha3(''.join([ascii_chr(x) for x in compustate.memory]))) if _prevop in (op_SSTORE, op_SLOAD) or steps == 0: trace_data['storage'] = ext.log_storage(msg.to) # trace_data['gas'] = to_string(compustate.gas + fee) trace_data['inst'] = op trace_data['pc'] = to_string(compustate.pc - 1) if steps == 0: trace_data['depth'] = msg.depth trace_data['address'] = msg.to trace_data['op'] = op trace_data['steps'] = steps # if op[:4] == 'PUSH': # trace_data['pushvalue'] = pushval log_vm_op.trace('vm', **trace_data) steps += 1 _prevop = op # Invalid operation if op == INVALID: return vm_exception('INVALID OP', opcode=op) # Valid operations if op < 0x10: if op == op_STOP: return peaceful_exit('STOP', compustate.gas, []) elif op == op_ADD: stk.append((stk.pop() + stk.pop()) & TT256M1) elif op == op_SUB: stk.append((stk.pop() - stk.pop()) & TT256M1) elif op == op_MUL: stk.append((stk.pop() * stk.pop()) & TT256M1) elif op == op_DIV: s0, s1 = stk.pop(), stk.pop() stk.append(0 if s1 == 0 else s0 // s1) elif op == op_MOD: s0, s1 = stk.pop(), stk.pop() stk.append(0 if s1 == 0 else s0 % s1) elif op == 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 == 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 == op_ADDMOD: s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() stk.append((s0 + s1) % s2 if s2 else 0) elif op == op_MULMOD: s0, s1, s2 = stk.pop(), stk.pop(), stk.pop() stk.append((s0 * s1) % s2 if s2 else 0) elif op == 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 == 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) elif op < 0x20: if op == op_LT: stk.append(1 if stk.pop() < stk.pop() else 0) elif op == op_GT: stk.append(1 if stk.pop() > stk.pop() else 0) elif op == op_SLT: s0, s1 = utils.to_signed(stk.pop()), utils.to_signed(stk.pop()) stk.append(1 if s0 < s1 else 0) elif op == op_SGT: s0, s1 = utils.to_signed(stk.pop()), utils.to_signed(stk.pop()) stk.append(1 if s0 > s1 else 0) elif op == op_EQ: stk.append(1 if stk.pop() == stk.pop() else 0) elif op == op_ISZERO: stk.append(0 if stk.pop() else 1) elif op == op_AND: stk.append(stk.pop() & stk.pop()) elif op == op_OR: stk.append(stk.pop() | stk.pop()) elif op == op_XOR: stk.append(stk.pop() ^ stk.pop()) elif op == op_NOT: stk.append(TT256M1 - stk.pop()) elif op == op_BYTE: s0, s1 = stk.pop(), stk.pop() if s0 >= 32: stk.append(0) else: stk.append((s1 // 256 ** (31 - s0)) % 256) elif op < 0x40: if op == 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 = b''.join(map(ascii_chr, mem[s0: s0 + s1])) stk.append(utils.big_endian_to_int(utils.sha3(data))) elif op == op_ADDRESS: stk.append(utils.coerce_to_int(msg.to)) elif op == op_BALANCE: addr = utils.coerce_addr_to_hex(stk.pop() % 2**160) stk.append(ext.get_balance(addr)) elif op == op_ORIGIN: stk.append(utils.coerce_to_int(ext.tx_origin)) elif op == op_CALLER: stk.append(utils.coerce_to_int(msg.sender)) elif op == op_CALLVALUE: stk.append(msg.value) elif op == op_CALLDATALOAD: stk.append(msg.data.extract32(stk.pop())) elif op == op_CALLDATASIZE: stk.append(msg.data.size) elif op == 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 == op_CODESIZE: stk.append(len(code)) elif op == op_CODECOPY: start, s1, size = stk.pop(), stk.pop(), stk.pop() 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 s1 + i < len(code): mem[start + i] = utils.safe_ord(code[s1 + i]) else: mem[start + i] = 0 elif op == op_GASPRICE: stk.append(ext.tx_gasprice) elif op == op_EXTCODESIZE: addr = utils.coerce_addr_to_hex(stk.pop() % 2**160) stk.append(len(ext.get_code(addr) or b'')) elif op == 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] = utils.safe_ord(extcode[s2 + i]) else: mem[start + i] = 0 elif op < 0x50: if op == op_BLOCKHASH: stk.append(utils.big_endian_to_int(ext.block_hash(stk.pop()))) elif op == op_COINBASE: stk.append(utils.big_endian_to_int(ext.block_coinbase)) elif op == op_TIMESTAMP: stk.append(ext.block_timestamp) elif op == op_NUMBER: stk.append(ext.block_number) elif op == op_DIFFICULTY: stk.append(ext.block_difficulty) elif op == op_GASLIMIT: stk.append(ext.block_gas_limit) elif op < 0x60: if op == op_POP: stk.pop() elif op == op_MLOAD: s0 = stk.pop() if not mem_extend(mem, compustate, op, s0, 32): return vm_exception('OOG EXTENDING MEMORY') data = 0 for c in mem[s0: s0 + 32]: data = (data << 8) + c stk.append(data) elif op == op_MSTORE: s0, s1 = stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, s0, 32): return vm_exception('OOG EXTENDING MEMORY') v = s1 for i in range(31, -1, -1): mem[s0 + i] = v % 256 v //= 256 elif op == 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 == op_SLOAD: stk.append(ext.get_storage_data(msg.to, stk.pop())) elif op == op_SSTORE: s0, s1 = stk.pop(), stk.pop() 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 ext.add_refund(refund) # adds neg gascost as a refund if below zero ext.set_storage_data(msg.to, s0, s1) elif op == op_JUMP: compustate.pc = stk.pop() opnew = processed_code[compustate.pc][4] if \ compustate.pc in processed_code else op_STOP if opnew != op_JUMPDEST: return vm_exception('BAD JUMPDEST') elif op == op_JUMPI: s0, s1 = stk.pop(), stk.pop() if s1: compustate.pc = s0 opnew = processed_code[compustate.pc][4] if \ compustate.pc in processed_code else op_STOP if opnew != op_JUMPDEST: return vm_exception('BAD JUMPDEST') elif op == op_PC: stk.append(compustate.pc - 1) elif op == op_MSIZE: stk.append(len(mem)) elif op == op_GAS: stk.append(compustate.gas) # AFTER subtracting cost 1 elif op_PUSH1 <= (op & 255) <= op_PUSH32: # Hide push value in high-order bytes of op stk.append(op >> 8) elif op_DUP1 <= op <= op_DUP16: depth = op - op_DUP1 + 1 stk.append(stk[-depth]) elif op_SWAP1 <= op <= op_SWAP16: depth = op - op_SWAP1 + 1 temp = stk[-depth - 1] stk[-depth - 1] = stk[-1] stk[-1] = temp elif op_LOG0 <= op <= op_LOG4: """ 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 = op - op_LOG0 mstart, msz = stk.pop(), stk.pop() topics = [stk.pop() for x in range(depth)] compustate.gas -= msz * opcodes.GLOGBYTE if not mem_extend(mem, compustate, op, mstart, msz): return vm_exception('OOG EXTENDING MEMORY') data = b''.join(map(ascii_chr, 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))) elif op == 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 ext.get_balance(msg.to) >= value and msg.depth < 1024: cd = CallData(mem, mstart, msz) create_msg = Message(msg.to, b'', value, compustate.gas, cd, msg.depth + 1) o, gas, addr = ext.create(create_msg) if o: stk.append(utils.coerce_to_int(addr)) compustate.gas = gas else: stk.append(0) compustate.gas = 0 else: stk.append(0) elif op == op_CALL: gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() 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.encode_int(to) to = ((b'\x00' * (32 - len(to))) + to)[12:] extra_gas = (not ext.account_exists(to)) * opcodes.GCALLNEWACCOUNT + \ (value > 0) * opcodes.GCALLVALUETRANSFER submsg_gas = gas + opcodes.GSTIPEND * (value > 0) if compustate.gas < gas + extra_gas: return vm_exception('OUT OF GAS', needed=gas+extra_gas) if ext.get_balance(msg.to) >= value and msg.depth < 1024: compustate.gas -= (gas + extra_gas) cd = CallData(mem, meminstart, meminsz) call_msg = Message(msg.to, to, value, submsg_gas, cd, msg.depth + 1, code_address=to) result, gas, data = ext.msg(call_msg) if result == 0: stk.append(0) else: stk.append(1) compustate.gas += gas for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] else: compustate.gas -= (gas + extra_gas - submsg_gas) stk.append(0) elif op == op_CALLCODE: gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop(), stk.pop() if not mem_extend(mem, compustate, op, meminstart, meminsz) or \ not mem_extend(mem, compustate, op, memoutstart, memoutsz): return vm_exception('OOG EXTENDING MEMORY') extra_gas = (value > 0) * opcodes.GCALLVALUETRANSFER submsg_gas = gas + opcodes.GSTIPEND * (value > 0) if compustate.gas < gas + extra_gas: return vm_exception('OUT OF GAS', needed=gas+extra_gas) if ext.get_balance(msg.to) >= value and msg.depth < 1024: compustate.gas -= (gas + extra_gas) to = utils.encode_int(to) to = ((b'\x00' * (32 - len(to))) + to)[12:] cd = CallData(mem, meminstart, meminsz) call_msg = Message(msg.to, msg.to, value, submsg_gas, cd, msg.depth + 1, code_address=to) result, gas, data = ext.msg(call_msg) if result == 0: stk.append(0) else: stk.append(1) compustate.gas += gas for i in range(min(len(data), memoutsz)): mem[memoutstart + i] = data[i] else: compustate.gas -= (gas + extra_gas - submsg_gas) stk.append(0) elif op == 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]) elif op == op_SUICIDE: to = utils.encode_int(stk.pop()) to = ((b'\x00' * (32 - len(to))) + to)[12:] xfer = ext.get_balance(msg.to) ext.set_balance(to, ext.get_balance(to) + xfer) ext.set_balance(msg.to, 0) ext.add_suicide(msg.to) # print('suiciding %s %s %d' % (msg.to, to, xfer)) return 1, compustate.gas, []
def run_vm_test(params, mode, profiler=None): pre = params['pre'] exek = params['exec'] env = params['env'] if 'previousHash' not in env: env['previousHash'] = encode_hex(db_env.config['GENESIS_PREVHASH']) assert set(env.keys()) == set([ 'currentGasLimit', 'currentTimestamp', 'previousHash', 'currentCoinbase', 'currentDifficulty', 'currentNumber' ]) # setup env header = blocks.BlockHeader( prevhash=decode_hex(env['previousHash']), number=parse_int_or_hex(env['currentNumber']), coinbase=decode_hex(env['currentCoinbase']), difficulty=parse_int_or_hex(env['currentDifficulty']), gas_limit=parse_int_or_hex(env['currentGasLimit']), timestamp=parse_int_or_hex(env['currentTimestamp'])) blk = blocks.Block(header, env=db_env) # setup state for address, h in list(pre.items()): assert len(address) == 40 address = decode_hex(address) assert set(h.keys()) == set(['code', 'nonce', 'balance', 'storage']) blk.set_nonce(address, parse_int_or_hex(h['nonce'])) blk.set_balance(address, parse_int_or_hex(h['balance'])) blk.set_code(address, decode_hex(h['code'][2:])) for k, v in h['storage'].items(): blk.set_storage_data(address, utils.big_endian_to_int(decode_hex(k[2:])), utils.big_endian_to_int(decode_hex(v[2:]))) # execute transactions sender = decode_hex(exek['caller']) # a party that originates a call recvaddr = decode_hex(exek['address']) nonce = blk._get_acct_item(sender, 'nonce') gasprice = parse_int_or_hex(exek['gasPrice']) startgas = parse_int_or_hex(exek['gas']) value = parse_int_or_hex(exek['value']) data = decode_hex(exek['data'][2:]) # bypass gas check in tx initialization by temporarily increasing startgas num_zero_bytes = str_to_bytes(data).count(ascii_chr(0)) num_non_zero_bytes = len(data) - num_zero_bytes intrinsic_gas = (opcodes.GTXCOST + opcodes.GTXDATAZERO * num_zero_bytes + opcodes.GTXDATANONZERO * num_non_zero_bytes) startgas += intrinsic_gas tx = transactions.Transaction(nonce=nonce, gasprice=gasprice, startgas=startgas, to=recvaddr, value=value, data=data) tx.startgas -= intrinsic_gas tx.sender = sender # capture apply_message calls apply_message_calls = [] orig_apply_msg = pb.apply_msg ext = pb.VMExt(blk, tx) def msg_wrapper(msg): hexdata = encode_hex(msg.data.extract_all()) apply_message_calls.append( dict(gasLimit=to_string(msg.gas), value=to_string(msg.value), destination=encode_hex(msg.to), data=b'0x' + hexdata)) return 1, msg.gas, b'' def create_wrapper(msg): sender = decode_hex(msg.sender) if \ len(msg.sender) == 40 else msg.sender nonce = utils.encode_int(ext._block.get_nonce(msg.sender)) addr = utils.sha3(rlp.encode([sender, nonce]))[12:] hexdata = encode_hex(msg.data.extract_all()) apply_message_calls.append( dict(gasLimit=to_string(msg.gas), value=to_string(msg.value), destination=b'', data=b'0x' + hexdata)) return 1, msg.gas, addr ext.msg = msg_wrapper ext.create = create_wrapper def blkhash(n): if n >= ext.block_number or n < ext.block_number - 256: return b'' else: return utils.sha3(to_string(n)) ext.block_hash = blkhash msg = vm.Message(tx.sender, tx.to, tx.value, tx.startgas, vm.CallData([safe_ord(x) for x in tx.data])) code = decode_hex(exek['code'][2:]) time_pre = time.time() if profiler: profiler.enable() success, gas_remained, output = vm.vm_execute(ext, msg, code) if profiler: profiler.disable() pb.apply_msg = orig_apply_msg blk.commit_state() for s in blk.suicides: blk.del_account(s) time_post = time.time() """ generally expected that the test implementer will read env, exec and pre then check their results against gas, logs, out, post and callcreates. If an exception is expected, then latter sections are absent in the test. Since the reverting of the state is not part of the VM tests. """ params2 = copy.deepcopy(params) if success: params2['callcreates'] = apply_message_calls params2['out'] = b'0x' + encode_hex(b''.join(map(ascii_chr, output))) params2['gas'] = to_string(gas_remained) params2['logs'] = [log.to_dict() for log in blk.logs] params2['post'] = blk.to_dict(with_state=True)['state'] if mode == FILL: return params2 elif mode == VERIFY: if not success: assert 'post' not in params, 'failed, but expected to succeed' params1 = copy.deepcopy(params) shouldbe, reallyis = params1.get('post', None), params2.get('post', None) compare_post_states(shouldbe, reallyis) def normalize_value(k, p): if k in p: if k == 'gas': return parse_int_or_hex(p[k]) elif k == 'callcreates': return list(map(callcreate_standard_form, p[k])) else: return utils.to_string(k) return None for k in ['pre', 'exec', 'env', 'callcreates', 'out', 'gas', 'logs']: shouldbe = normalize_value(k, params1) reallyis = normalize_value(k, params2) if shouldbe != reallyis: raise Exception("Mismatch: " + k + ':\n shouldbe %r\n reallyis %r' % (shouldbe, reallyis)) elif mode == TIME: return time_post - time_pre