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
Beispiel #2
0
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]
Beispiel #3
0
 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)
Beispiel #4
0
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
Beispiel #5
0
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."
        )
Beispiel #6
0
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)
Beispiel #7
0
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
Beispiel #8
0
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'
Beispiel #9
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)
Beispiel #11
0
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]
Beispiel #12
0
def test_compile_already_installed():
    version = solcx.get_installed_solc_versions()[0]
    assert solcx.compile_solc("latest") == version
Beispiel #13
0
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,
    }
Beispiel #14
0
 def installed_versions(self) -> List[Version]:
     return solcx.get_installed_solc_versions()
Beispiel #15
0
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
Beispiel #16
0
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
Beispiel #17
0
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,
Beispiel #18
0
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)
Beispiel #19
0
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
Beispiel #20
0
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
Beispiel #21
0
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
Beispiel #22
0
    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
Beispiel #23
0
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)
Beispiel #25
0
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)]