def info(self): '''Displays verbose information about the transaction, including decoded event logs.''' if self.contract_address: line = "New Contract Address: " + self.contract_address else: line = "To: {0.receiver}\nValue: {0.value}".format(self) if self.input != "0x00": line += "\nFunction: {}".format(self.fn_name) formatted = (TX_INFO.format( self, self.sender if type(self.sender) is str else self.sender.address, line, "" if self.status else " ({0[error]}{1}{0})".format( color, self.revert_msg or "reverted"), self.gas_used / self.gas_limit)) color.print_colors(formatted) if self.events: print( " Events In This Transaction\n --------------------------") for event in self.events: print(" " + color('bright yellow') + event['name'] + color()) for i in event['data']: color.print_colors(" {0[name]}: {0[value]}".format(i), value=None if i['decoded'] else "dull") print()
def _dir(self, obj=None): if obj is None: obj = self results = [(i, getattr(obj, i)) for i in builtins.dir(obj) if i[0] != "_"] print("[" + "{}, ".format(color()).join( _dir_color(i[1]) + i[0] for i in results) + color() + "]")
def _print_path(trace, idx, sep): col = "error" if trace['op'] in ("REVERT", "INVALID") else "pending" name = "{0[contractName]}.{1}{0[fn]}".format(trace, color(col)) print( (" "*sep*trace['depth']) + (" "*(trace['jumpDepth']-1)) + "{}{} {}{} ({})".format(color(col), name, color('dull'), idx, trace['address']) + color() )
def _dir_color(obj): if type(obj).__name__ == "module": return color('module') try: if issubclass(type(obj), _ContractBase): return color('contract') if issubclass(type(obj), _ContractMethod): return color('contract_method') except TypeError: pass if not callable(obj): return color('value') return color('callable')
def __repr__(self): c = {-1: 'pending', 0: 'error', 1: None} return "<Transaction object '{}{}{}'>".format(color(c[self.status]), self.txid, color)
def main(): args = docopt(__doc__) if args['<filename>']: name = args['<filename>'].replace(".py", "") if not os.path.exists("tests/{}.py".format(name)): sys.exit("{0[error]}ERROR{0}: Cannot find".format(color) + " {0[module]}tests/{1}.py{0}".format(color, name)) test_files = [name] else: test_files = [i[:-3] for i in os.listdir("tests") if i[-3:] == ".py"] test_files.remove('__init__') compiled = deepcopy(compile_contracts()) fn_map, line_map = get_coverage_map(compiled) network = Network() if args['--always-transact']: CONFIG['test']['always_transact'] = True print("Contract calls will be handled as: {0[value]}{1}{0}".format( color, "transactions" if CONFIG['test']['always_transact'] else "calls")) for filename in test_files: history, tb = run_test(filename, network) if tb: sys.exit("\n{0[error]}ERROR{0}: Cannot ".format(color) + "calculate coverage while tests are failing\n") for tx in history: if not tx.receiver: continue for i in range(len(tx.trace)): t = tx.trace[i] pc = t['pc'] name = t['contractName'] if not name: continue try: # find the function map item and record the tx fn = next(i for i in fn_map[name] if pc in i['pc']) fn['tx'].add(tx) if t['op'] != "JUMPI": # if not a JUMPI, find the line map item and record ln = next(i for i in line_map[name] if pc in i['pc']) ln['tx'].add(tx) continue # if a JUMPI, we need to have hit the jump pc AND a related opcode ln = next(i for i in line_map[name] if pc == i['jump']) if tx not in ln['tx']: continue # if the next opcode is not pc+1, the JUMPI was executed truthy key = 'false' if tx.trace[i + 1]['pc'] == pc + 1 else 'true' ln[key].add(tx) # pc didn't exist in map except StopIteration: continue for ln in [x for v in line_map.values() for x in v]: if ln['jump']: ln['jump'] = [len(ln.pop('true')), len(ln.pop('false'))] ln['count'] = len(ln.pop('tx')) del ln['pc'] for contract in fn_map: for fn in fn_map[contract].copy(): fn['count'] = len(fn.pop('tx')) del fn['pc'] line_fn = [ i for i in line_map[contract] if i['method'] == fn['method'] ] if not fn['count'] or not [i for i in line_fn if i['count']]: for ln in line_fn: line_map[contract].remove(ln) elif line_fn: fn_map[contract].remove(fn) fn_map[contract].extend(line_map[contract]) json.dump(fn_map, open("build/coverage.json", "w", encoding="utf-8"), sort_keys=True, indent=4) print("\nCoverage analysis complete!\n") for contract in fn_map: fn_list = sorted( set(i['method'] for i in fn_map[contract] if i['method'])) if not fn_list: continue if not [i for i in fn_map[contract] if i['count']]: print(" contract: {0[contract]}{1}{0} - {0[bright red]}0.0%{0}". format(color, contract)) continue print(" contract: {0[contract]}{1}{0}".format(color, contract)) for fn in fn_list: map_ = [i for i in fn_map[contract] if i['method'] == fn] count = 0 for i in map_: if not i['count']: continue if not i['jump']: count += 1 continue if i['jump'][0]: count += 1 if i['jump'][1]: count += 1 total = sum([1 if not i['jump'] else 2 for i in map_]) pct = count / total c = next(i[1] for i in COVERAGE_COLORS if pct <= i[0]) print(" {0[contract_method]}{1}{0} - {2}{3:.1%}{0}".format( color, fn, color(c), pct)) print() print( "\nDetailed results saved to {0[string]}build/coverage.json{0}".format( color))