def test_environment_var_versions(monkeypatch, tmp_path): versions = solcx.get_installed_solc_versions() monkeypatch.setenv("SOLCX_BINARY_PATH", tmp_path.as_posix()) assert solcx.get_installed_solc_versions() != versions monkeypatch.undo() assert solcx.get_installed_solc_versions() == versions
def test_compile_install_deps_fails(compile_mock, solc_binary, cwd): version = solcx.wrapper._get_solc_version(solc_binary) compile_mock.raise_on("sh") solcx.compile_solc(version) assert os.getcwd() == cwd assert solcx.get_installed_solc_versions() == [version]
def setup_solcx(solc_version): if solc_version not in solcx.get_installed_solc_versions(): try: LOGGER.debug(f"Installing solc {solc_version}") solcx.install_solc(solc_version, allow_osx=True) except Exception as e: raise click.exceptions.UsageError( f"Error installing solc version {solc_version}: {e}") solcx.set_solc_version(solc_version, silent=True)
def _get_solc_version_list() -> Tuple[List, List]: global AVAILABLE_SOLC_VERSIONS installed_versions = solcx.get_installed_solc_versions() if AVAILABLE_SOLC_VERSIONS is None: try: AVAILABLE_SOLC_VERSIONS = solcx.get_installable_solc_versions() except ConnectionError: if not installed_versions: raise ConnectionError("Solc not installed and cannot connect to GitHub") AVAILABLE_SOLC_VERSIONS = installed_versions return AVAILABLE_SOLC_VERSIONS, installed_versions
def pytest_configure(config): global VERSIONS if config.getoption("--no-install"): VERSIONS = solcx.get_installed_solc_versions() return try: VERSIONS = solcx.get_available_solc_versions() except ConnectionError: raise pytest.UsageError( "ConnectionError while attempting to get solc versions.\n" "Use the --no-install flag to only run tests against already installed versions." )
def pytest_collection(session): global VERSIONS if session.config.getoption("--solc-versions"): VERSIONS = [ Version(i) for i in session.config.getoption("--solc-versions").split(",") ] elif session.config.getoption("--no-install"): VERSIONS = solcx.get_installed_solc_versions() else: try: VERSIONS = solcx.get_installable_solc_versions() except ConnectionError: raise pytest.UsageError( "ConnectionError while attempting to get solc versions.\n" "Use the --no-install flag to only run tests against already installed versions." ) for version in VERSIONS: solcx.install_solc(version)
def compile(solc_version, evm_version, source_code_file): out = None source_code = "" with open(source_code_file, 'r') as file: source_code = file.read() try: if not str(solc_version).startswith("v"): solc_version = "v" + str(solc_version.truncate()) if not solc_version in solcx.get_installed_solc_versions(): solcx.install_solc(solc_version) solcx.set_solc_version(solc_version, True) out = solcx.compile_standard( { 'language': 'Solidity', 'sources': { source_code_file: { 'content': source_code } }, 'settings': { "optimizer": { "enabled": True, "runs": 200 }, "evmVersion": evm_version, "outputSelection": { source_code_file: { "*": [ "abi", "evm.deployedBytecode", "evm.bytecode.object", "evm.legacyAssembly", ], } } } }, allow_paths='.') except Exception as e: print("Error: Solidity compilation failed!") print(e.message) return out
def install_all_versions(): # First install last version if not install_last_version(): print("Failed to install all compiler. Trying to continue...") print("This might lead to later errors.") return False last_version = SolidityVersion(get_installed_solc_versions()[-1][1:]) next_version = MINIMAL_SOLC_VERSION while SolidityVersion(next_version) < last_version: try: install_solc(f'v{next_version}') # Increase major version new_minor = int(next_version.split(".")[2]) + 1 old_major = int(next_version.split(".")[1]) next_version = f'0.{old_major}.{new_minor}' except (requests.exceptions.ConnectionError, subprocess.CalledProcessError) as e: # Increase major version new_major = int(next_version.split(".")[1]) + 1 next_version = f'0.{new_major}.0'
with open('./data/src/' + args.contract, 'r') as file_ctrct_src: ctrct_src = file_ctrct_src.read() # VYPER if contract_lang == 'vy': log('Compiling vyper contract!') abi = 'abi' byte = 'bytecode' # compile compiled_contract = compile_code(ctrct_src, [abi, byte]) # done log('Done compiling vyper contract.') # SOLIDITY elif contract_lang == 'sol': solc_version = "v" + ctrct_src.partition(' ^')[2].partition(';\n')[0] # version if not solc_version in get_installed_solc_versions(): log("Installing solc {}".format(solc_version)) install_solc(solc_version) log("Done.") set_solc_version(solc_version) # compile log('Compiling solidity {} contract!'.format(solc_version)) abi = 'abi' byte = 'bin' compiled_contract = compile_source(ctrct_src, output_values=[abi, byte]) # done log('Done compiling solidity contract.') compiled_contract = compiled_contract['<stdin>:{}'.format(contract_name)] # write with open("./data/abi/{}.abi".format(contract_name), "w") as file_abi: json.dump(compiled_contract[abi], file_abi)
def solc_binary(): """ Yields the path to the most recent solc binary. """ version = solcx.get_installed_solc_versions()[0] yield solcx.install.get_executable(version)
def test_compile(compile_mock, solc_binary, cwd): version = solcx.wrapper._get_solc_version(solc_binary) solcx.compile_solc(version) assert os.getcwd() == cwd assert solcx.get_installed_solc_versions() == [version]
def test_compile_already_installed(): version = solcx.get_installed_solc_versions()[0] assert solcx.compile_solc("latest") == version
def generate_solidity_payload(file): """Generate a MythX analysis request from a given Solidity file. This function will open the file, try to detect the used solc version from the pragma definition, and automatically compile it. If the given solc version is not installed on the client's system, it will be automatically downloaded. From the solc output, the following data is sent to the MythX API for analysis: * :code:`abi` * :code:`ast` * :code:`bin` * :code:`bin-runtime` * :code:`srcmap` * :code:`srcmap-runtime` :param file: The path pointing towards the Solidity file :return: The payload dictionary to be sent to MythX """ with open(file) as f: source = f.read() solc_version = re.findall(PRAGMA_PATTERN, source) if not solc_version: # no pragma found, user needs to specify the version raise click.exceptions.UsageError( "No pragma found - please specify a solc version with --solc-version" ) # TODO: Pass user-defined version solc_version = "v" + solc_version[0] if solc_version not in solcx.get_installed_solc_versions(): try: solcx.install_solc(solc_version) except Exception as e: raise click.exceptions.UsageError( "Error installing solc version {}: {}".format(solc_version, e)) solcx.set_solc_version(solc_version, silent=True) try: result = solcx.compile_source( source, output_values=( "abi", "ast", "bin", "bin-runtime", "srcmap", "srcmap-runtime", ), ) except solcx.exceptions.SolcError as e: raise click.exceptions.UsageError( "Error compiling source with solc {}: {}".format(solc_version, e)) # sanitize weird solcx keys new_result = {} for key, value in result.items(): new_key = key.replace("<stdin>:", "") new_result[new_key] = value result = new_result contract_name = list(result.keys())[0] creation_bytecode = result[contract_name]["bin"] deployed_bytecode = result[contract_name]["bin-runtime"] source_map = result[contract_name]["srcmap"] deployed_source_map = result[contract_name]["srcmap-runtime"] ast = result[contract_name]["ast"] return { "contract_name": contract_name, "main_source": file, "source_list": [file], "sources": { file: { "source": source, "ast": ast } }, "bytecode": creation_bytecode, "source_map": source_map, "deployed_source_map": deployed_source_map, "deployed_bytecode": deployed_bytecode, "solc_version": solc_version, }
def installed_versions(self) -> List[Version]: return solcx.get_installed_solc_versions()
def generate_solidity_payload( file: str, version: Optional[str], contracts: List[str] = None, remappings: Tuple[str] = None, ) -> Dict: """Generate a MythX analysis request from a given Solidity file. This function will open the file, try to detect the used solc version from the pragma definition, and automatically compile it. If the given solc version is not installed on the client's system, it will be automatically downloaded. From the solc output, the following data is sent to the MythX API for analysis: * :code:`abi` * :code:`ast` * :code:`bin` * :code:`bin-runtime` * :code:`srcmap` * :code:`srcmap-runtime` :param file: The path pointing towards the Solidity file :param version: The solc version to use for compilation :param contracts: The contract name(s) to submit :param remappings: Import remappings to pass to solcx :return: The payload dictionary to be sent to MythX """ with open(file) as f: solc_version = re.findall(PRAGMA_PATTERN, f.read()) LOGGER.debug(f"solc version matches in {file}: {solc_version}") if not (solc_version or version): # no pragma found, user needs to specify the version raise click.exceptions.UsageError( "No pragma found - please specify a solc version with --solc-version" ) solc_version = f"v{version or solc_version[0]}" if solc_version not in solcx.get_installed_solc_versions(): try: LOGGER.debug(f"Installing solc {solc_version}") solcx.install_solc(solc_version) except Exception as e: raise click.exceptions.UsageError( f"Error installing solc version {solc_version}: {e}") solcx.set_solc_version(solc_version, silent=True) try: cwd = str(Path.cwd().absolute()) LOGGER.debug(f"Compiling {file} under allowed path {cwd}") result = solcx.compile_files( [file], output_values=( "abi", "ast", "bin", "bin-runtime", "srcmap", "srcmap-runtime", ), import_remappings=remappings or [ f"openzeppelin-solidity/={cwd}/node_modules/openzeppelin-solidity/", f"openzeppelin-zos/={cwd}/node_modules/openzeppelin-zos/", f"zos-lib/={cwd}/node_modules/zos-lib/", ], allow_paths=cwd, ) except solcx.exceptions.SolcError as e: raise click.exceptions.UsageError( f"Error compiling source with solc {solc_version}: {e}") # sanitize solcx keys new_result = {} for key, value in result.items(): new_key = key.split(":")[1] LOGGER.debug(f"Sanitizing solc key {key} -> {new_key}") new_result[new_key] = value result = new_result payload = {"sources": {}, "solc_version": solc_version} bytecode_max = 0 for contract_name, contract_data in result.items(): ast = contract_data["ast"] source_path = str(Path(ast.get("attributes", {}).get("absolutePath"))) creation_bytecode = contract_data["bin"] deployed_bytecode = contract_data["bin-runtime"] source_map = contract_data["srcmap"] deployed_source_map = contract_data["srcmap-runtime"] with open(source_path) as source_f: source = source_f.read() LOGGER.debug( f"Loaded contract source with {len(source)} characters") # always add source and AST, even if dependency payload["sources"][source_path] = {"source": source, "ast": ast} if (contracts and contract_name not in contracts) or ( not contracts and len(creation_bytecode) < bytecode_max): LOGGER.debug( f"Found dependency contract {contract_name} - continung") continue bytecode_max = len(creation_bytecode) LOGGER.debug(f"Updaing main payload for {contract_name}") payload.update({ "contract_name": contract_name, "main_source": source_path, "source_list": [source_path], "bytecode": patch_solc_bytecode(creation_bytecode), "source_map": zero_srcmap_indices(source_map), "deployed_source_map": zero_srcmap_indices(deployed_source_map), "deployed_bytecode": patch_solc_bytecode(deployed_bytecode), "solc_version": solc_version, }) return payload
def find_solc_versions( contract_sources: Dict[str, str], install_needed: bool = False, install_latest: bool = False, silent: bool = True, ) -> Dict: """ Analyzes contract pragmas and determines which solc version(s) to use. Args: contract_sources: a dictionary in the form of {'path': "source code"} install_needed: if True, will install when no installed version matches the contract pragma install_latest: if True, will install when a newer version is available than the installed one silent: set to False to enable verbose reporting Returns: dictionary of {'version': ['path', 'path', ..]} """ available_versions, installed_versions = _get_solc_version_list() pragma_specs: Dict = {} to_install = set() new_versions = set() for path, source in contract_sources.items(): pragma_specs[path] = sources.get_pragma_spec(source, path) version = pragma_specs[path].select(installed_versions) if not version and not (install_needed or install_latest): raise IncompatibleSolcVersion( f"No installed solc version matching '{pragma_specs[path]}' in '{path}'" ) # if no installed version of solc matches the pragma, find the latest available version latest = pragma_specs[path].select(available_versions) if not version and not latest: raise IncompatibleSolcVersion( f"No installable solc version matching '{pragma_specs[path]}' in '{path}'" ) if not version or (install_latest and latest > version): to_install.add(latest) elif latest and latest > version: new_versions.add(str(version)) # install new versions if needed if to_install: install_solc(*to_install) installed_versions = solcx.get_installed_solc_versions() elif new_versions and not silent: print( f"New compatible solc version{'s' if len(new_versions) > 1 else ''}" f" available: {', '.join(new_versions)}" ) # organize source paths by latest available solc version compiler_versions: Dict = {} for path, spec in pragma_specs.items(): version = spec.select(installed_versions) compiler_versions.setdefault(str(version), []).append(path) return compiler_versions
import json import os from solcx import (compile_files, get_installed_solc_versions, install_solc, set_solc_version_pragma) from web3 import Web3 VERSION = "v0.6.8" if VERSION not in get_installed_solc_versions(): install_solc(VERSION) set_solc_version_pragma("0.6.8") pub_address = "0xe97fe448C9E032e96ce548A98D4fD28a47eCB013" class ContractInterface(object): """A convenience interface for interacting with ethereum smart contracts This interface will handle a main contract and it's dependencies. All it requires is a path to the directory where your solidity files are stored. It will then compile, deploy, fetch a contract instance, and provide methods for transacting and calling with gas checks and event output. """ deploy_dir = os.getcwd() + "/deployment_vars" def __init__( self, web3, contract_to_deploy,
import os.path import asyncio from time import time from typing import Optional, Sequence import solcx from web3.contract import ContractConstructor, ContractFunctions from eth_abi import decode_abi from web3._utils.abi import get_abi_output_types from web3._utils.contracts import prepare_transaction from web3._utils.datatypes import PropertyCheckingFactory from eth_utils import to_checksum_address, hexstr_if_str, to_bytes from asynther.common import SubModule from asynther.account import Account, NeedPrivateKey solcx_version = solcx.get_available_solc_versions()[0] if solcx_version not in solcx.get_installed_solc_versions(): solcx.install_solc(solcx_version) class Interface(dict): """ Contract interface """ def __init__(self, source_file: str, allow_paths: Optional[Sequence[str]] = None): assert isinstance(source_file, str) and os.path.exists(source_file) assert allow_paths is None or \ (isinstance(allow_paths, (tuple, list)) and all(os.path.exists(path) for path in allow_paths)) allow_paths = ','.join(allow_paths) if allow_paths is not None else allow_paths for name, interface in solcx.compile_files([source_file], allow_paths=allow_paths).items(): if name.startswith(source_file + ':'): super().__init__(interface)
def create_app(test_cfg=None): app = FlaskAPI(__name__, instance_relative_config=True) app.config.from_mapping( SECRET_KEY=os.urandom(16), DATABASE=os.path.join(app.instance_path, 'sccontroller.sqlite'), JWT_EXPIRATION_DELTA=timedelta(hours=1) ) if test_cfg is None: app.config.from_pyfile('config.py') else: app.config.from_mapping(test_cfg) if not 'NODE' in app.config: print('Specify eth node address in instance/config.py') exit(-1) if not 'DEFAULT_ACCOUNT' in app.config: print ('Specify default eth account in instance/config.py') exit(-1) try: if not 'v0.6.2' in get_installed_solc_versions(): install_solc('v0.6.2') set_solc_version('v0.6.2') except Exception as e: print('Unable to install solc v0.6.2: ' + str(e)) exit(-1) try: os.makedirs(app.instance_path) except OSError: pass def authenticate(username, password): user = auth.get_user(username) if user and check_password_hash(user.password, password): return user def identity(payload): user_id = payload['identity'] return auth.get_user_by_id(user_id) jwt = JWT(app, authenticate, identity) @app.errorhandler(400) def custom400(error): response = Response( response=json.dumps({'error': error.description['message']}), status=400, mimetype='application/json') return response @app.errorhandler(503) def custom503(error): response = Response( response=json.dumps({'error': error.description['message']}), status=503, mimetype='application/json') return response @app.route('/templates') @jwt_required() def templates(): """Return templates""" try: templates = Template.all() return [{ 'id': it.id, 'name': it.name, 'description': it.description, 'parameters_list': [param for param in it.parameters_list if param['name'] != "refund"] } for it in templates], status.HTTP_200_OK except KeyError as e: abort(status.HTTP_400_BAD_REQUEST, {'message': str(e)}) @app.route('/templates/<int:id>') @jwt_required() def template(id): """Return template info by id Keyword arguments: id -- id of the template """ try: template = Template.by_id(id) return { 'id': template.id, 'name': template.name, 'description': template.description, 'parameters_list': [param for param in template.parameters_list if param['name'] != "refund"] }, status.HTTP_200_OK except KeyError as e: abort(status.HTTP_400_BAD_REQUEST, {'message': str(e)}) @app.route('/create-cotract', methods=['POST']) @jwt_required() def create_contract(): """Create and deploy contract from template with params Data arguments: template_id -- id of the template parameters -- params dict key=value (keys as keys from templates parameters_list) """ try: data = request.get_json(True) if not 'template_id' in data or not 'parameters' in data: abort(status.HTTP_400_BAD_REQUEST, {'message': "invalid parameters for request"}) scc = SCContoller.get_scc() contract = scc.generate(data['template_id'], data['parameters'], current_identity) if scc.w3.eth.getBalance(scc.w3.eth.defaultAccount) < scc.deploy_gas_cost(contract): abort(status.HTTP_503_SERVICE_UNAVAILABLE, {'message': 'Not enough gas in system to deploy'}) scc.deploy(contract) return {"id": contract.id}, status.HTTP_200_OK except KeyError as e: abort(status.HTTP_400_BAD_REQUEST, {'message': str(e)}) except ValueError as e: abort(status.HTTP_400_BAD_REQUEST, {'message': str(e)}) @app.route('/contracts') @jwt_required() def get_contracts(): """Return contracts info""" try: contracts = Contract.all(current_identity) return [{ 'id': contract.id, 'template_id': contract.template.id, 'address': contract.address, 'status': contract.get_status(), 'creator_guide': contract.template.creator_guide, 'users_guide': contract.template.users_guide, } for contract in contracts], status.HTTP_200_OK except KeyError as e: abort(status.HTTP_400_BAD_REQUEST, {'message': str(e)}) @app.route('/contracts', methods=['POST']) @jwt_required() def get_contracts_by_id(): """Return contracts info by ids Keyword arguments: ids -- array of ids of the contracts """ try: data = request.get_json(True) if not 'ids' in data: abort(status.HTTP_400_BAD_REQUEST, {'message': "invalid parameters for request"}) contracts = [Contract.by_id(id, current_identity) for id in data['ids']] return [{ 'id': contract.id, 'template_id': contract.template.id, 'address': contract.address, 'status': contract.get_status(), 'creator_guide': contract.template.creator_guide, 'users_guide': contract.template.users_guide, } for contract in contracts], status.HTTP_200_OK except KeyError as e: abort(status.HTTP_400_BAD_REQUEST, {'message': str(e)}) @app.route('/contracts/<int:id>') @jwt_required() def get_contract(id): """Return contract info by id Keyword arguments: id -- id of the contract """ try: contract = Contract.by_id(id, current_identity) return { 'id': contract.id, 'template_id': contract.template.id, 'address': contract.address, 'status': contract.get_status(), 'creator_guide': contract.template.creator_guide, 'users_guide': contract.template.users_guide, }, status.HTTP_200_OK except KeyError as e: abort(status.HTTP_400_BAD_REQUEST, {'message': str(e)}) @app.route('/contracts/<int:id>/destroy') @jwt_required() def destroy_contract(id): """Destroy the contract by id and return success of the destruction Keyword arguments: id -- id of the contract """ try: SCContoller.get_scc().destruct(Contract.by_id(id, current_identity)) return '', status.HTTP_204_NO_CONTENT except KeyError as e: abort(status.HTTP_400_BAD_REQUEST, {'message': str(e)}) from . import db, auth, template_controller db.init_app(app) auth.init_app(app) template_controller.init_app(app) def handle_event(event): status = event['args']['cur_status'] address = event['address'] try: contract = Contract.by_address(address) contract.set_status(status) except (KeyError, ReferenceError): pass def log_loop(event_filters, poll_interval): with app.app_context(): while True: for event_filter in event_filters: for event in event_filter.get_new_entries(): handle_event(event) time.sleep(poll_interval) with app.app_context(): try: scc = SCContoller.get_scc() event_filters = [scc.event_filter(template) for template in Template.all()] worker = Thread(target=log_loop, args=(event_filters, 5), daemon=True) worker.start() except sqlite3.DatabaseError: db.init_db() scc = SCContoller.get_scc() event_filters = [scc.event_filter(template) for template in Template.all()] worker = Thread(target=log_loop, args=(event_filters, 5), daemon=True) worker.start() except ConnectionError as e: print(str(e)) exit(-1) except ValueError as e: print(str(e)) exit(-1) return app
def find_solc_versions(contracts, install_needed=False, install_latest=False, silent=True): '''Analyzes contract pragmas and determines which solc version(s) to use. Args: contracts: a dictionary in the form of {'path': "source code"} install_needed: if True, will install when no installed version matches the contract pragma install_latest: if True, will install when a newer version is available than the installed one silent: enables verbose reporting Returns: dictionary of {'version': ['path', 'path', ..]} ''' installed_versions = [ Version(i[1:]) for i in solcx.get_installed_solc_versions() ] try: available_versions = [ Version(i[1:]) for i in solcx.get_available_solc_versions() ] except ConnectionError: if not installed_versions: raise ConnectionError( "Solc not installed and cannot connect to GitHub") available_versions = installed_versions pragma_regex = re.compile(r"pragma +solidity([^;]*);") version_regex = re.compile(r"(([<>]?=?|\^)\d+\.\d+\.\d+)+") pragma_specs = dict((i, set()) for i in contracts) to_install = set() for path, source in contracts.items(): try: pragma_string = next(pragma_regex.finditer(source))[0] except StopIteration: raise PragmaError(f"No version pragma in '{path}'") from None # convert pragma to Version objects comparator_set_range = pragma_string.replace(" ", "").split('||') for comparator_set in comparator_set_range: spec = Spec(*(i[0] for i in version_regex.findall(comparator_set))) spec = _standardize_spec(spec) pragma_specs[path].add(spec) # if no installed version of solc matches the pragma, find the latest available version if not next( (i for i in pragma_specs[path] if i.select(installed_versions)), None): try: version = _select_max(pragma_specs[path], available_versions) to_install.add(version) except ValueError: raise PragmaError( f"No installable solc version matching '{pragma_string}' in '{path}'" ) from None if not install_needed: raise IncompatibleSolcVersion( f"No installed solc version matching '{pragma_string}' in '{path}'" ) # install new versions if needed if to_install: install_solc(*to_install) installed_versions = [ Version(i[1:]) for i in solcx.get_installed_solc_versions() ] # organize source paths by latest available solc version compiler_versions = {} new_versions = set() for path, spec_list in pragma_specs.items(): version = _select_max(spec_list, installed_versions) compiler_versions.setdefault(str(version), []).append(path) latest = _select_max(spec_list, available_versions) if latest > version: new_versions.add(str(latest)) if new_versions: if install_latest: install_solc(*new_versions) return find_solc_versions(contracts) if not silent: print( f"New compatible solc version{'s' if len(new_versions) > 1 else ''}" f" available: {', '.join(new_versions)}") return compiler_versions
def launch_argument_parser(): parser = argparse.ArgumentParser() # Contract parameters group1 = parser.add_mutually_exclusive_group(required=True) group1.add_argument( "-s", "--source", type=str, help="Solidity smart contract source code file (.sol).") group1.add_argument("-a", "--abi", type=str, help="Smart contract ABI file (.json).") #group2 = parser.add_mutually_exclusive_group(required=True) parser.add_argument( "-c", "--contract", type=str, help= "Contract name to be fuzzed (if Solidity source code file provided) or blockchain contract address (if ABI file provided)." ) parser.add_argument( "-b", "--blockchain-state", type=str, help= "Initialize fuzzer with a blockchain state by providing a JSON file (if Solidity source code file provided) or a block number (if ABI file provided)." ) # Compiler parameters parser.add_argument("--solc", help="Solidity compiler version (default '" + str(solcx.get_solc_version()) + "'). Installed compiler versions: " + str(solcx.get_installed_solc_versions()) + ".", action="store", dest="solc_version", type=str) parser.add_argument( "--evm", help="Ethereum VM (default '" + str(settings.EVM_VERSION) + "'). Available VM's: 'homestead', 'byzantium' or 'petersburg'.", action="store", dest="evm_version", type=str) # Evolutionary parameters group3 = parser.add_mutually_exclusive_group(required=False) group3.add_argument("-g", "--generations", help="Number of generations (default " + str(settings.GENERATIONS) + ").", action="store", dest="generations", type=int) group3.add_argument("-t", "--timeout", help="Number of seconds for fuzzer to stop.", action="store", dest="global_timeout", type=int) parser.add_argument("-n", "--population-size", help="Size of the population.", action="store", dest="population_size", type=int) parser.add_argument("-pc", "--probability-crossover", help="Size of the population.", action="store", dest="probability_crossover", type=float) parser.add_argument("-pm", "--probability-mutation", help="Size of the population.", action="store", dest="probability_mutation", type=float) # Miscellaneous parameters parser.add_argument( "-r", "--results", type=str, help="Folder or JSON file where results should be stored.") parser.add_argument( "--seed", type=float, help="Initialize the random number generator with a given seed.") parser.add_argument( "--cfg", help="Build control-flow graph and highlight code coverage.", action="store_true") parser.add_argument("--rpc-host", help="Ethereum client RPC hostname.", action="store", dest="rpc_host", type=str) parser.add_argument("--rpc-port", help="Ethereum client RPC port.", action="store", dest="rpc_port", type=int) parser.add_argument( "--data-dependency", help= "Disable/Enable data dependency analysis: 0 - Disable, 1 - Enable (default: 1)", action="store", dest="data_dependency", type=int) parser.add_argument( "--constraint-solving", help= "Disable/Enable constraint solving: 0 - Disable, 1 - Enable (default: 1)", action="store", dest="constraint_solving", type=int) parser.add_argument( "--environmental-instrumentation", help= "Disable/Enable environmental instrumentation: 0 - Disable, 1 - Enable (default: 1)", action="store", dest="environmental_instrumentation", type=int) version = "ConFuzzius - Version 0.0.1 - " version += "\"By three methods we may learn wisdom:\n" version += "First, by reflection, which is noblest;\n" version += "Second, by imitation, which is easiest;\n" version += "And third by experience, which is the bitterest.\"\n" parser.add_argument("-v", "--version", action="version", version=version) args = parser.parse_args() if not args.contract: args.contract = "" if args.source and args.contract.startswith("0x"): parser.error( "--source requires --contract to be a name, not an address.") if args.source and args.blockchain_state and args.blockchain_state.isnumeric( ): parser.error( "--source requires --blockchain-state to be a file, not a number.") if args.abi and not args.contract.startswith("0x"): parser.error("--abi requires --contract to be an address, not a name.") if args.abi and args.blockchain_state and not args.blockchain_state.isnumeric( ): parser.error( "--abi requires --blockchain-state to be a number, not a file.") if args.evm_version: settings.EVM_VERSION = args.evm_version if not args.solc_version: args.solc_version = solcx.get_solc_version() if args.generations: settings.GENERATIONS = args.generations if args.global_timeout: settings.GLOBAL_TIMEOUT = args.global_timeout if args.population_size: settings.POPULATION_SIZE = args.population_size if args.probability_crossover: settings.PROBABILITY_CROSSOVER = args.probability_crossover if args.probability_mutation: settings.PROBABILITY_MUTATION = args.probability_mutation if args.data_dependency == None: args.data_dependency = 1 if args.constraint_solving == None: args.constraint_solving = 1 if args.environmental_instrumentation == None: args.environmental_instrumentation = 1 if args.environmental_instrumentation == 1: settings.ENVIRONMENTAL_INSTRUMENTATION = True elif args.environmental_instrumentation == 0: settings.ENVIRONMENTAL_INSTRUMENTATION = False if args.abi: settings.REMOTE_FUZZING = True if args.rpc_host: settings.RPC_HOST = args.rpc_host if args.rpc_port: settings.RPC_PORT = args.rpc_port return args
def from_explorer( cls, address: str, as_proxy_for: Optional[str] = None, owner: Optional[AccountsType] = None, silent: bool = False, ) -> "Contract": """ Create a new `Contract` object with source code queried from a block explorer. Arguments --------- address : str Address where the contract is deployed. as_proxy_for : str, optional Address of the implementation contract, if `address` is a proxy contract. The generated object will send transactions to `address`, but use the ABI and NatSpec of `as_proxy_for`. This field is only required when the block explorer API does not provide an implementation address. owner : Account, optional Contract owner. If set, transactions without a `from` field will be performed using this account. """ address = _resolve_address(address) data = _fetch_from_explorer(address, "getsourcecode", silent) is_verified = bool(data["result"][0].get("SourceCode")) if is_verified: abi = json.loads(data["result"][0]["ABI"]) name = data["result"][0]["ContractName"] else: # if the source is not available, try to fetch only the ABI try: data_abi = _fetch_from_explorer(address, "getabi", True) except ValueError as exc: _unverified_addresses.add(address) raise exc abi = json.loads(data_abi["result"].strip()) name = "UnknownContractName" warnings.warn( f"{address}: Was able to fetch the ABI but not the source code. " "Some functionality will not be available.", BrownieCompilerWarning, ) if as_proxy_for is None and data["result"][0].get("Implementation"): as_proxy_for = _resolve_address( data["result"][0]["Implementation"]) # if this is a proxy, fetch information for the implementation contract if as_proxy_for is not None: implementation_contract = Contract.from_explorer(as_proxy_for) abi = implementation_contract._build["abi"] if not is_verified: return cls.from_abi(name, address, abi, owner) try: version = Version( data["result"][0]["CompilerVersion"].lstrip("v")).truncate() except Exception: version = Version("0.0.0") if version < Version("0.4.22") or ( # special case for OSX because installing 0.4.x versions is problematic sys.platform == "darwin" and version < Version("0.5.0") and f"v{version}" not in solcx.get_installed_solc_versions()): if not silent: warnings.warn( f"{address}: target compiler '{data['result'][0]['CompilerVersion']}' is " "unsupported by Brownie. Some functionality will not be available.", BrownieCompilerWarning, ) return cls.from_abi(name, address, abi, owner) optimizer = { "enabled": bool(int(data["result"][0]["OptimizationUsed"])), "runs": int(data["result"][0]["Runs"]), } evm_version = data["result"][0].get("EVMVersion", "Default") if evm_version == "Default": evm_version = None if data["result"][0]["SourceCode"].startswith("{"): # source was verified using compiler standard JSON input_json = json.loads(data["result"][0]["SourceCode"][1:-1]) sources = { k: v["content"] for k, v in input_json["sources"].items() } evm_version = input_json["settings"].get("evmVersion", evm_version) compiler.set_solc_version(str(version)) input_json.update( compiler.generate_input_json(sources, optimizer=optimizer, evm_version=evm_version)) output_json = compiler.compile_from_input_json(input_json) build_json = compiler.generate_build_json(input_json, output_json) else: # source was submitted as a single flattened file sources = { f"{name}-flattened.sol": data["result"][0]["SourceCode"] } build_json = compiler.compile_and_format(sources, solc_version=str(version), optimizer=optimizer, evm_version=evm_version) build_json = build_json[name] if as_proxy_for is not None: build_json.update( abi=abi, natspec=implementation_contract._build.get("natspec")) if not _verify_deployed_code(address, build_json["deployedBytecode"], build_json["language"]): warnings.warn( f"{address}: Locally compiled and on-chain bytecode do not match!", BrownieCompilerWarning, ) del build_json["pcMap"] self = cls.__new__(cls) _ContractBase.__init__(self, None, build_json, sources) # type: ignore _DeployedContractBase.__init__(self, address, owner) _add_deployment(self) return self
def find_solc_versions( contracts: Dict[str, str], install_needed: bool = False, install_latest: bool = False, silent: bool = True, ) -> Dict: """Analyzes contract pragmas and determines which solc version(s) to use. Args: contracts: a dictionary in the form of {'path': "source code"} install_needed: if True, will install when no installed version matches the contract pragma install_latest: if True, will install when a newer version is available than the installed one silent: set to False to enable verbose reporting Returns: dictionary of {'version': ['path', 'path', ..]} """ installed_versions = [ Version(i[1:]) for i in solcx.get_installed_solc_versions() ] try: available_versions = [ Version(i[1:]) for i in solcx.get_available_solc_versions() ] except ConnectionError: if not installed_versions: raise ConnectionError( "Solc not installed and cannot connect to GitHub") available_versions = installed_versions pragma_regex = re.compile(r"pragma +solidity([^;]*);") pragma_specs: Dict = {} to_install = set() new_versions = set() for path, source in contracts.items(): pragma_string = next(pragma_regex.finditer(source), None) if pragma_string is None: raise PragmaError(f"No version pragma in '{path}'") pragma_specs[path] = NpmSpec(pragma_string.groups()[0]) version = pragma_specs[path].select(installed_versions) if not version and not (install_needed or install_latest): raise IncompatibleSolcVersion( f"No installed solc version matching '{pragma_string[0]}' in '{path}'" ) # if no installed version of solc matches the pragma, find the latest available version latest = pragma_specs[path].select(available_versions) if not version and not latest: raise PragmaError( f"No installable solc version matching '{pragma_string[0]}' in '{path}'" ) if not version or (install_latest and latest > version): to_install.add(latest) elif latest > version: new_versions.add(str(version)) # install new versions if needed if to_install: install_solc(*to_install) installed_versions = [ Version(i[1:]) for i in solcx.get_installed_solc_versions() ] elif new_versions and not silent: print( f"New compatible solc version{'s' if len(new_versions) > 1 else ''}" f" available: {', '.join(new_versions)}") # organize source paths by latest available solc version compiler_versions: Dict = {} for path, spec in pragma_specs.items(): version = spec.select(installed_versions) compiler_versions.setdefault(str(version), []).append(path) return compiler_versions
def generate_payloads( self, version: Optional[str], contract: str = None, remappings: Tuple[str] = None, enable_scribble: bool = False, scribble_path: str = "scribble", ): """Generate a MythX analysis request from a given Solidity file. This function will open the file, try to detect the used solc version from the pragma definition, and automatically compile it. If the given solc version is not installed on the client's system, it will be automatically downloaded. From the solc output, the following data is sent to the MythX API for analysis: * :code:`abi` * :code:`ast` * :code:`bin` * :code:`bin-runtime` * :code:`srcmap` * :code:`srcmap-runtime` :param version: The solc version to use for compilation :param contract: The contract name(s) to submit :param remappings: Import remappings to pass to solcx :param enable_scribble: Enable instrumentation with scribble :param scribble_path: Optional path to the scribble executable """ with open(self.target) as f: source = f.read() solc_version = re.findall(PRAGMA_PATTERN, source) LOGGER.debug(f"solc version matches in {self.target}: {solc_version}") if not (solc_version or version): # no pragma found, user needs to specify the version raise click.exceptions.UsageError( "No pragma found - please specify a solc version with --solc-version" ) solc_version = f"v{version or solc_version[0]}" if solc_version not in solcx.get_installed_solc_versions(): try: LOGGER.debug(f"Installing solc {solc_version}") solcx.install_solc(solc_version, allow_osx=True) except Exception as e: raise click.exceptions.UsageError( f"Error installing solc version {solc_version}: {e}") solcx.set_solc_version(solc_version, silent=True) # instrument with scribble if requested scribble_file = None if enable_scribble: process = subprocess.run( [scribble_path, self.target], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) if process.returncode != 0: click.echo( f"Scribble has encountered an error (code: {process.returncode})" ) click.echo("=====STDERR=====") click.echo(process.stderr.decode()) click.echo("=====STDOUT=====") process.stdout.decode() sys.exit(process.returncode) # don't delete temp file on close but manually unlink # after payload has been generated scribble_output_f = tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".sol") scribble_stdout = process.stdout.decode() scribble_output_f.write(scribble_stdout) scribble_file = scribble_output_f.name scribble_output_f.close() try: cwd = str(Path.cwd().absolute()) LOGGER.debug( f"Compiling {scribble_file or self.target} under allowed path {cwd}" ) result = solcx.compile_standard( input_data={ "language": "Solidity", "sources": { scribble_file or self.target: { "urls": [scribble_file or self.target] } }, "settings": { "remappings": [r.format(pwd=cwd) for r in remappings] or [ f"openzeppelin-solidity/={cwd}/node_modules/openzeppelin-solidity/", f"openzeppelin-zos/={cwd}/node_modules/openzeppelin-zos/", f"zos-lib/={cwd}/node_modules/zos-lib/", ], "outputSelection": { "*": { "*": [ "evm.bytecode.object", "evm.bytecode.sourceMap", "evm.deployedBytecode.object", "evm.deployedBytecode.sourceMap", ], "": ["ast"], } }, "optimizer": { "enabled": True, "runs": 200 }, }, }, # if scribble enabled, allow access to temporary file allow_paths=cwd if not enable_scribble else scribble_file, ) except solcx.exceptions.SolcError as e: raise click.exceptions.UsageError( f"Error compiling source with solc {solc_version}: {e}") compiled_sources = result.get("sources", {}) payload = { "sources": {}, "solc_version": solc_version, "main_source": scribble_file or self.target, "source_list": [None] * len(compiled_sources), } for file_path, file_data in compiled_sources.items(): # fill source list entry payload["source_list"][file_data.get("id")] = file_path payload_dict = payload["sources"][file_path] = {} # add AST for file if it's present ast = file_data.get("ast") if ast: payload_dict["ast"] = ast # add source from file path with open(file_path, newline="") as source_f: payload_dict["source"] = source_f.read() if contract: LOGGER.debug("Contract specified - targeted payload selection") try: # if contract specified, set its bytecode and source mapping payload["contract_name"] = contract payload["bytecode"] = patch_solc_bytecode(result["contracts"][ scribble_file or self.target][contract]["evm"]["bytecode"]["object"]) payload["source_map"] = result["contracts"][ scribble_file or self.target][contract]["evm"]["bytecode"]["sourceMap"] payload["deployed_bytecode"] = patch_solc_bytecode( result["contracts"][scribble_file or self.target][contract] ["evm"]["deployedBytecode"]["object"]) payload["deployed_source_map"] = result["contracts"][ scribble_file or self. target][contract]["evm"]["deployedBytecode"]["sourceMap"] self.payloads.append(payload) return except KeyError: LOGGER.warning( f"Could not find contract {contract} in compilation artifacts. The CLI will find the " f"largest bytecode artifact in the compilation output and submit it instead." ) # extract the largest bytecode from the compilation result and add it bytecode_max = 0 for file_path, file_element in result.get("contracts", {}).items(): for contract, contract_data in file_element.items(): contract_bytecode = contract_data["evm"]["bytecode"]["object"] contract_source_map = contract_data["evm"]["bytecode"][ "sourceMap"] contract_deployed_bytecode = contract_data["evm"][ "deployedBytecode"]["object"] contract_deployed_source_map = contract_data["evm"][ "deployedBytecode"]["sourceMap"] bytecode_length = len(contract_bytecode) if bytecode_length > bytecode_max: bytecode_max = bytecode_length payload["contract_name"] = contract payload["bytecode"] = patch_solc_bytecode( contract_bytecode) payload["source_map"] = contract_source_map payload["deployed_bytecode"] = patch_solc_bytecode( contract_deployed_bytecode) payload[ "deployed_source_map"] = contract_deployed_source_map if enable_scribble: # replace scribble tempfile name with prefixed one scribble_payload = payload["sources"].pop(scribble_file) payload["sources"]["scribble-" + str(self.target)] = scribble_payload payload["source_list"] = [ "scribble-" + str(self.target) if item == scribble_file else item for item in payload["source_list"] ] payload["main_source"] = "scribble-" + str(self.target) # delete scribble temp file os.unlink(scribble_file) self.payloads.append(payload)
def get_supported_solc_versions(): versions = [SolidityVersion(v[1:]) for v in get_installed_solc_versions()] return [v for v in versions if v >= SolidityVersion(MINIMAL_SOLC_VERSION)]