def topics(): '''Generates event topics and saves them in brownie/topics.json''' if _topics: return _topics try: topics = json.load(open( CONFIG['folders']['brownie']+"/topics.json", encoding="utf-8" )) except (FileNotFoundError, json.decoder.JSONDecodeError): topics = {} contracts = compile_contracts() events = [x for i in contracts.values() for x in i['abi'] if x['type']=="event"] _topics.update(eth_event.get_event_abi(events)) json.dump( _topics, open(CONFIG['folders']['brownie']+"/topics.json", 'w', encoding="utf-8"), sort_keys=True, indent=4 ) return _topics
def setup(self): if self._init or config.ARGV['mode'] == "console": verbose = True self._init = False else: verbose = False if verbose: print("Using network '{0[string]}{1}{0}'".format( color, CONFIG['active_network']['name'] )) if 'test-rpc' in CONFIG['active_network']: if verbose: print("Running '{0[string]}{1}{0}'...".format( color, CONFIG['active_network']['test-rpc'] )) rpc = Rpc(self) else: rpc = None web3._connect() accounts = Accounts(web3.eth.accounts) tx.tx_history.clear() self._network_dict = { 'a': accounts, 'accounts': accounts, 'alert': alert, 'check': check, 'config': CONFIG, 'gas': gas, 'history': tx.tx_history, 'logging': logging, 'reset': self.reset, 'run': self.run, 'rpc': rpc, 'web3': web3, 'wei': wei } for name, build in compiler.compile_contracts().items(): if build['type'] == "interface": continue if name in self._network_dict: raise AttributeError("Namespace collision between Contract '{0}' and 'Network.{0}'".format(name)) self._network_dict[name] = contract.ContractContainer(build, self._network_dict) if self._module: self._module.__dict__.update(self._network_dict) # update _ImportableBrownie dict and reload all scripts sys.modules['brownie'].__dict__ = self._network_dict for module in [v for k,v in sys.modules.items() if k[:7]=='scripts']: importlib.reload(module) if not CONFIG['active_network']['persist']: return while True: persist_file = "build/networks/{}.json".format(CONFIG['active_network']['name']) exists = os.path.exists(persist_file) if not exists: print("Persistent environment for '{}' has not yet been declared.".format( CONFIG['active_network']['name'])) self._key = FernetKey(getpass( "Please set a password for the persistent environment: " )) json.dump( { 'height': web3.eth.blockNumber, 'password': self._key.encrypt('password', False) }, open(persist_file, 'w', encoding="utf-8"), sort_keys=True, indent=4 ) return try: data = json.load(open(persist_file, encoding="utf-8")) if data['height'] > web3.eth.blockNumber: print( "WARNING: This appears to be a local RPC network. Persistence is not possible." "\n Remove 'persist': true from config.json to silence this warning." ) CONFIG['active_network']['persist'] = False return if not self._key: self._key = FernetKey(getpass( "Enter the persistence password for '{}': ".format( CONFIG['active_network']['name']))) self._key.decrypt(data['password']) print("Loading persistent environment...") for priv_key in data['accounts']: self._network_dict['accounts'].add(self._key.decrypt(priv_key)) break except InvalidToken: self._key = None print("Password is incorrect, please try again or CTRL-C to disable persistence.") except KeyboardInterrupt: self._key = None print("\nPersistence has been disabled.") return
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))