From 11b7061f583d19ec143ff8a06fe6c6787f58f393 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Thu, 1 Feb 2024 03:54:56 +0400 Subject: [PATCH 1/3] chore: remove pythx --- brownie/_cli/__main__.py | 2 - brownie/_cli/analyze.py | 363 -------------------------------------- requirements.in | 3 +- requirements.txt | 133 +++++++------- tests/cli/test_analyze.py | 237 ------------------------- 5 files changed, 60 insertions(+), 678 deletions(-) delete mode 100644 brownie/_cli/analyze.py delete mode 100644 tests/cli/test_analyze.py diff --git a/brownie/_cli/__main__.py b/brownie/_cli/__main__.py index 74e8b116e..8e2fabc88 100644 --- a/brownie/_cli/__main__.py +++ b/brownie/_cli/__main__.py @@ -23,7 +23,6 @@ accounts Manage local accounts networks Manage network settings gui Load the GUI to view opcodes and test coverage - analyze Find security vulnerabilities using the MythX API Options: --help -h Display this message @@ -34,7 +33,6 @@ def main(): - print(f"Brownie v{__version__} - Python development framework for Ethereum\n") if "--version" in sys.argv: diff --git a/brownie/_cli/analyze.py b/brownie/_cli/analyze.py deleted file mode 100644 index f266a5b4c..000000000 --- a/brownie/_cli/analyze.py +++ /dev/null @@ -1,363 +0,0 @@ -#!/usr/bin/python3 - -import importlib -import json -import re -import time -from os import environ -from typing import Dict - -from mythx_models.request import AnalysisSubmissionRequest -from mythx_models.response import AnalysisSubmissionResponse, DetectedIssuesResponse -from pythx import Client, ValidationError -from pythx.middleware import ClientToolNameMiddleware, GroupDataMiddleware - -from brownie import project -from brownie._config import ( - CONFIG, - __version__, - _load_project_structure_config, - _update_argv_from_docopt, -) -from brownie.exceptions import ProjectNotFound -from brownie.utils import color, notify -from brownie.utils.docopt import docopt - -__doc__ = """Usage: brownie analyze [options] [--async | --interval=] - -Options: - --gui Launch the Brownie GUI after analysis - --mode= The analysis mode (quick, standard, deep) [default: quick] - --interval= Result polling interval in seconds [default: 3] - --async Do not poll for results, print job IDs and exit - --api-key= The JWT access token from the MythX dashboard - --help -h Display this message - -Submits your project to the MythX API for smart contract security analysis. - -In order to perform an analysis you must register for a MythX account and -generate a JWT access token. This access token may be passed through an -environment variable "MYTHX_API_KEY", or given via a command line option. - -Visit https://mythx.io/ to learn more about MythX and sign up for an account. -""" - - -ANALYSIS_MODES = ("quick", "standard", "deep") -SEVERITY_COLOURS = {"LOW": "yellow", "MEDIUM": "orange", "HIGH": "red"} -DASHBOARD_BASE_URL = "https://dashboard.mythx.io/#/console/analyses/" - - -class SubmissionPipeline: - """A helper class to submit MythX analysis requests and retrieve reports.""" - - BYTECODE_ADDRESS_PATCH = re.compile(r"__\w{38}") - DEPLOYED_ADDRESS_PATCH = re.compile(r"__\$\w{34}\$__") - - def __init__(self, build, client: Client = None): - self.requests: Dict[str, AnalysisSubmissionRequest] = {} - self.responses: Dict[str, AnalysisSubmissionResponse] = {} - self.reports: Dict[str, DetectedIssuesResponse] = {} - self.build = build - self.client = client or self.get_mythx_client() - self.highlight_report: Dict[str, dict] = {"highlights": {"MythX": {}}} - self.stdout_report: Dict[str, dict] = {} - - @staticmethod - def get_mythx_client() -> Client: - """Generate a MythX client instance. - - This method will look for an API key passed as a parameter, and if none - is found, look for a key in the environment variable :code:`MYTHX_API_KEY`. - If a key is detected, a PythX client instance is returned, otherwise a - :code:`ValidationError` is raised. - - :raises: ValidationError if no valid API key is provided - :return: A PythX client instance - """ - - if CONFIG.argv["api-key"]: - auth_args = {"api_key": CONFIG.argv["api-key"]} - elif environ.get("MYTHX_API_KEY"): - auth_args = {"api_key": environ.get("MYTHX_API_KEY")} - else: - raise ValidationError( - "You must provide a MythX API key via environment variable or the command line" - ) - - return Client( - **auth_args, middlewares=[ClientToolNameMiddleware(name=f"brownie-{__version__}")] - ) - - def prepare_requests(self) -> None: - """Transform an artifact into a MythX payload. - - This will enumerate all the contracts and libraries across the Brownie - artifacts. For each contract, the dependencies are recursively listed and - attached to the contract's MythX payload to paint the most precise picture - possible. - - :return: None - """ - - contracts = {n: d for n, d in self.build.items() if d["type"] == "contract"} - libraries = {n: d for n, d in self.build.items() if d["type"] == "library"} - - requests = {} - for contract, artifact in contracts.items(): - requests[contract] = self.construct_request_from_artifact(artifact) - - # update requests with library dependencies - for library, artifact in libraries.items(): - library_dependents = set(self.build.get_dependents(library)) - contract_dependencies = set(contracts.keys()).intersection(library_dependents) - for contract in contract_dependencies: - requests[contract].sources.update( - { - artifact.get("sourcePath"): { - "source": artifact.get("source"), - "ast": artifact.get("ast"), - } - } - ) - - self.requests = requests - - @classmethod - def construct_request_from_artifact(cls, artifact) -> AnalysisSubmissionRequest: - """Construct a raw submission request from an artifact JSON file. - - This will transform a Brownie contract JSON artifact into a MythX payload - object. Additionally, bytecode-level placeholders will be patched to provide - valid bytecode to the MythX analysis API. - - :param artifact: The Brownie JSON artifact - :return: :code:`AnalysisSubmissionRequest` for the artifact - """ - - bytecode = artifact.get("bytecode", "") - deployed_bytecode = artifact.get("deployedBytecode", "") - source_map = artifact.get("sourceMap", "") - deployed_source_map = artifact.get("deployedSourceMap", "") - - bytecode = re.sub(cls.BYTECODE_ADDRESS_PATCH, "0" * 40, bytecode) - deployed_bytecode = re.sub(cls.DEPLOYED_ADDRESS_PATCH, "0" * 40, deployed_bytecode) - - source_list = sorted(artifact.get("allSourcePaths", {}).values()) - return AnalysisSubmissionRequest( - contract_name=artifact.get("contractName"), - bytecode=bytecode or None, - deployed_bytecode=deployed_bytecode or None, - source_map=source_map or None, - deployed_source_map=deployed_source_map or None, - sources={ - artifact.get("sourcePath"): { - "source": artifact.get("source"), - "ast": artifact.get("ast"), - } - }, - source_list=source_list or None, - main_source=artifact.get("sourcePath"), - solc_version=artifact.get("compiler", {}).get("version"), - analysis_mode=CONFIG.argv["mode"] or ANALYSIS_MODES[0], - ) - - def send_requests(self) -> None: - """Send the prepared requests to MythX. - - This will send the requests in the class' requests dict to MythX for - analysis. The API's response is parsed and added to the internal - :code:`responses` dict. - - :return: None - """ - - group_resp = self.client.create_group() - self.client.handler.middlewares.append( - GroupDataMiddleware(group_id=group_resp.group.identifier) - ) - for contract_name, request in self.requests.items(): - response = self.client.analyze(payload=request) - self.responses[contract_name] = response - print( - f"Submitted analysis {color('bright blue')}{response.uuid}{color} for " - f"contract {color('bright magenta')}{request.contract_name}{color})" - ) - print(f"You can also check the results at {DASHBOARD_BASE_URL}{response.uuid}\n") - self.client.seal_group(group_id=group_resp.group.identifier) - self.client.handler.middlewares.pop(-1) - - def wait_for_jobs(self) -> None: - """Poll the MythX API and returns once all requests have been processed. - - This will wait for all analysis requests in the internal :code:`responses` - dict to finish. If a user passes the :code:`--interval` option, the plugin - will poll the MythX API in the user-specified interval (in seconds) to see - whether the analysis request is done processing. - - If the internal responses dict is empty, a :code:`ValidationError` is raised. - - :raise: :code:`ValidationError` - :return: None - """ - - if not self.responses: - raise ValidationError("No requests given") - for contract_name, response in self.responses.items(): - while not self.client.analysis_ready(response.uuid): - time.sleep(int(CONFIG.argv["interval"])) - self.reports[contract_name] = self.client.report(response.uuid) - - def generate_highlighting_report(self) -> None: - """Generate a Brownie highlighting report from a MythX issue report. - - This will convert a MythX issue report into a Brownie report that allows - issues to be highlighted in the native Brownie GUI. It iterates over the - internal :code:`reports` dictionary and fills the :code:`highlight_report` - instance variable. - - :return: None - """ - - source_to_name = {d["sourcePath"]: d["contractName"] for _, d in self.build.items()} - for idx, (contract_name, issue_report) in enumerate(self.reports.items()): - print( - "Generating report for {}{}{} ({}/{})".format( - color("bright blue"), contract_name, color, idx, len(self.reports) - ) - ) - for report in issue_report.issue_reports: - for issue in report: - # convert issue locations to report locations - # severities are highlighted according to SEVERITY_COLOURS - for loc in issue.locations: - comp = loc.source_map.components[0] - source_list = loc.source_list or report.source_list - - if source_list and 0 <= comp.file_id < len(source_list): - filename = source_list[comp.file_id] - if filename not in source_to_name: - continue - contract_name = source_to_name[filename] - severity = issue.severity.name - self.highlight_report["highlights"]["MythX"].setdefault( - contract_name, {filename: []} - ) - self.highlight_report["highlights"]["MythX"][contract_name][ - filename - ].append( - [ - comp.offset, - comp.offset + comp.length, - SEVERITY_COLOURS[severity], - "{}: {}\n{}".format( - issue.swc_id, - issue.description_short, - issue.description_long, - ), - ] - ) - - def generate_stdout_report(self) -> None: - """Generated a stdout report overview from a MythX issue report. - - This will convert a MythX issue report into a Brownie report that is - printed on stdout when analysis results have been received. It iterates - over the internal :code:`reports` dictionary and fills the - :code:`highlight_report` instance variable. - - :return: None - """ - - for contract_name, issue_report in self.reports.items(): - for issue in issue_report: - severity = issue.severity.name - self.stdout_report.setdefault(contract_name, {}).setdefault(severity, 0) - self.stdout_report[contract_name][severity] += 1 - - -def print_console_report(stdout_report) -> None: - """Highlight and print a given stdout report to the console. - - This adds color formatting to the given stdout report and prints - a summary of the vulnerabilities MythX has detected. - - :return: None - """ - - total_issues = sum(x for i in stdout_report.values() for x in i.values()) - if not total_issues: - notify("SUCCESS", "No issues found!") - return - - # display console report - total_high_severity = sum(i.get("HIGH", 0) for i in stdout_report.values()) - if total_high_severity: - notify( - "WARNING", f"Found {total_issues} issues including {total_high_severity} high severity!" - ) - else: - print(f"Found {total_issues} issues:") - for name in sorted(stdout_report): - print(f"\n contract: {color('bright magenta')}{name}{color}") - for key in [i for i in ("HIGH", "MEDIUM", "LOW") if i in stdout_report[name]]: - c = color("bright red" if key == "HIGH" else "bright yellow") - print(f" {key.title()}: {c}{stdout_report[name][key]}{color}") - - -def main(): - """The main entry point of the MythX plugin for Brownie.""" - - args = docopt(__doc__) - _update_argv_from_docopt(args) - - if CONFIG.argv["mode"] not in ANALYSIS_MODES: - raise ValidationError( - "Invalid analysis mode: Must be one of [{}]".format(", ".join(ANALYSIS_MODES)) - ) - - project_path = project.check_for_project(".") - if project_path is None: - raise ProjectNotFound - - build = project.load()._build - submission = SubmissionPipeline(build) - - print("Preparing project data for submission to MythX...") - submission.prepare_requests() - - print("Sending analysis requests to MythX...") - submission.send_requests() - - # exit if user wants an async analysis run - if CONFIG.argv["async"]: - print( - "\nAll contracts were submitted successfully. Check the dashboard at " - "https://dashboard.mythx.io/ for the progress and results of your analyses" - ) - return - - print("\nWaiting for results...") - - submission.wait_for_jobs() - submission.generate_stdout_report() - submission.generate_highlighting_report() - - # erase previous report - report_path = project_path.joinpath(_load_project_structure_config(project_path)["reports"]) - - report_path = report_path.joinpath("security.json") - if report_path.exists(): - report_path.unlink() - - print_console_report(submission.stdout_report) - - # Write report to Brownie directory - with report_path.open("w+") as fp: - json.dump(submission.highlight_report, fp, indent=2, sort_keys=True) - - # Launch GUI if user requested it - if CONFIG.argv["gui"]: - print("Launching the Brownie GUI") - gui = importlib.import_module("brownie._gui").Gui - gui().mainloop() diff --git a/requirements.in b/requirements.in index 3256d529f..41613a91e 100644 --- a/requirements.in +++ b/requirements.in @@ -19,13 +19,12 @@ pygments<3 pytest-xdist<2 pytest<7 python-dotenv>=0.16.0,<0.17.0 -pythx<=1.6.1 pyyaml>=5.3.0,<6 requests>=2.25.0,<3 rlp<3 semantic-version<3 tqdm<5 -vvm>=0.1.0,<1 +vvm==0.1.0 # 0.2.0 switches from semantic-version to packaging.version and things break vyper>=0.2.11,<1 # web3 versions 5.29.0 thru 5.31.2 have a bug when using the async provider in subthreads. This is relevant for some ecosystem libs downstream from brownie. web3>=5.22.0,!=5.29.*,!=5.30.*,!=5.31.1,!=5.31.2,<6 diff --git a/requirements.txt b/requirements.txt index 6723ac959..9fed8f8ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,38 +1,37 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile +# pip-compile requirements.in # -aiohttp==3.8.3 +aiohttp==3.9.3 # via web3 -aiosignal==1.2.0 +aiosignal==1.3.1 # via aiohttp asttokens==2.0.5 # via vyper -async-timeout==4.0.2 +async-timeout==4.0.3 # via aiohttp -attrs==22.1.0 +attrs==23.2.0 # via # aiohttp # hypothesis # jsonschema # pytest + # referencing base58==2.1.1 # via multiaddr -bitarray>=2.6.0,<3 +bitarray==2.9.2 # via eth-account -black==22.10.0 +black==24.1.1 # via -r requirements.in -certifi==2022.9.24 +certifi==2023.11.17 # via requests -charset-normalizer==2.1.1 - # via - # aiohttp - # requests -click==8.1.3 +charset-normalizer==3.3.2 + # via requests +click==8.1.7 # via black -cytoolz==0.12.0 +cytoolz==0.12.3 # via # eth-keyfile # eth-utils @@ -53,7 +52,7 @@ eth-account==0.5.9 # web3 eth-event==1.2.3 # via -r requirements.in -eth-hash[pycryptodome]==0.3.3 +eth-hash[pycryptodome]==0.6.0 # via # -r requirements.in # eth-event @@ -77,7 +76,7 @@ eth-typing==2.3.0 # eth-keys # eth-utils # web3 -eth-utils==1.10.0 +eth-utils==1.9.5 # via # -r requirements.in # eip712 @@ -89,9 +88,9 @@ eth-utils==1.10.0 # eth-rlp # rlp # web3 -execnet==1.9.0 +execnet==2.0.2 # via pytest-xdist -frozenlist==1.3.1 +frozenlist==1.4.1 # via # aiohttp # aiosignal @@ -105,53 +104,49 @@ hexbytes==0.2.3 # web3 hypothesis==6.27.3 # via -r requirements.in -idna==3.4 +idna==3.6 # via # requests # yarl -inflection==0.5.0 - # via - # mythx-models - # pythx -iniconfig==1.1.1 +iniconfig==2.0.0 # via pytest ipfshttpclient==0.8.0a2 # via web3 -jsonschema==3.2.0 - # via - # mythx-models - # web3 -lazy-object-proxy==1.7.1 +jsonschema==4.21.1 + # via web3 +jsonschema-specifications==2023.12.1 + # via jsonschema +lazy-object-proxy==1.10.0 # via -r requirements.in -lru-dict==1.1.8 +lru-dict==1.3.0 # via web3 multiaddr==0.0.9 # via ipfshttpclient -multidict==6.0.2 +multidict==6.0.4 # via # aiohttp # yarl -mypy-extensions==0.4.3 +mypy-extensions==1.0.0 # via black -mythx-models==1.9.1 - # via pythx -netaddr==0.8.0 +netaddr==0.10.1 # via multiaddr -packaging==21.3 - # via pytest +packaging==23.2 + # via + # black + # pytest parsimonious==0.8.1 # via eth-abi -pathspec==0.10.1 +pathspec==0.12.1 # via black -platformdirs==2.5.2 +platformdirs==4.2.0 # via black -pluggy==1.0.0 +pluggy==1.4.0 # via pytest -prompt-toolkit==3.0.31 +prompt-toolkit==3.0.43 # via -r requirements.in protobuf==3.19.5 # via web3 -psutil==5.9.2 +psutil==5.9.8 # via -r requirements.in py==1.11.0 # via @@ -162,49 +157,40 @@ py-solc-ast==1.2.10 # via -r requirements.in py-solc-x==1.1.1 # via -r requirements.in -pycryptodome==3.15.0 +pycryptodome==3.20.0 # via # eip712 # eth-hash # eth-keyfile # vyper -pygments==2.13.0 +pygments==2.17.2 # via # -r requirements.in # pygments-lexer-solidity pygments-lexer-solidity==0.7.0 # via -r requirements.in -pyjwt==1.7.1 - # via pythx -pyparsing==3.0.9 - # via packaging -pyrsistent==0.18.1 - # via jsonschema pytest==6.2.5 # via # -r requirements.in # pytest-forked # pytest-xdist -pytest-forked==1.4.0 +pytest-forked==1.6.0 # via pytest-xdist pytest-xdist==1.34.0 # via -r requirements.in -python-dateutil==2.8.1 - # via - # mythx-models - # pythx python-dotenv==0.16.0 # via -r requirements.in -pythx==1.6.1 - # via -r requirements.in pyyaml==5.4.1 # via -r requirements.in -requests==2.28.1 +referencing==0.33.0 + # via + # jsonschema + # jsonschema-specifications +requests==2.31.0 # via # -r requirements.in # ipfshttpclient # py-solc-x - # pythx # vvm # web3 rlp==2.0.1 @@ -212,6 +198,10 @@ rlp==2.0.1 # -r requirements.in # eth-account # eth-rlp +rpds-py==0.17.1 + # via + # jsonschema + # referencing semantic-version==2.10.0 # via # -r requirements.in @@ -221,24 +211,22 @@ semantic-version==2.10.0 six==1.16.0 # via # asttokens - # jsonschema # multiaddr # parsimonious # pytest-xdist - # python-dateutil sortedcontainers==2.4.0 # via hypothesis toml==0.10.2 # via pytest tomli==2.0.1 # via black -toolz==0.12.0 +toolz==0.12.1 # via cytoolz -tqdm==4.64.1 +tqdm==4.66.1 # via -r requirements.in -typing-extensions==4.4.0 +typing-extensions==4.9.0 # via black -urllib3==1.26.12 +urllib3==2.2.0 # via requests varint==1.0.2 # via multiaddr @@ -246,18 +234,15 @@ vvm==0.1.0 # via -r requirements.in vyper==0.3.7 # via -r requirements.in -wcwidth==0.2.5 +wcwidth==0.2.13 # via prompt-toolkit -web3==5.31.3 +web3==5.31.4 # via -r requirements.in websockets==9.1 # via web3 -wheel==0.37.1 +wheel==0.42.0 # via vyper -wrapt==1.14.1 +wrapt==1.16.0 # via -r requirements.in -yarl==1.8.2 +yarl==1.9.4 # via aiohttp - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/tests/cli/test_analyze.py b/tests/cli/test_analyze.py deleted file mode 100644 index 40d0e28d1..000000000 --- a/tests/cli/test_analyze.py +++ /dev/null @@ -1,237 +0,0 @@ -import json -from copy import deepcopy -from pathlib import Path -from unittest.mock import MagicMock, patch - -import pytest -from mythx_models.response import DetectedIssuesResponse -from pythx import ValidationError - -from brownie._cli.analyze import SubmissionPipeline, print_console_report - -with open(str(Path(__file__).parent / "test-artifact.json"), "r") as artifact_f: - TEST_ARTIFACT = json.load(artifact_f) - -with open(str(Path(__file__).parent / "test-report.json"), "r") as artifact_f: - TEST_REPORT = json.load(artifact_f) - - -def empty_field(field): - artifact = deepcopy(TEST_ARTIFACT) - artifact[field] = "" - return artifact - - -def test_source_dict_from_artifact(): - assert SubmissionPipeline.construct_request_from_artifact(TEST_ARTIFACT).sources == { - TEST_ARTIFACT["sourcePath"]: { - "source": TEST_ARTIFACT["source"], - "ast": TEST_ARTIFACT["ast"], - } - } - - -def test_request_bytecode_patch(): - artifact = deepcopy(TEST_ARTIFACT) - artifact["bytecode"] = "0x00000000__MyLibrary_____________________________00" - - assert ( - SubmissionPipeline.construct_request_from_artifact(artifact).bytecode - == "0x00000000000000000000000000000000000000000000000000" - ) - - -def test_request_deployed_bytecode_patch(): - artifact = deepcopy(TEST_ARTIFACT) - artifact[ - "deployedBytecode" - ] = "0x00000000__$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$__00000000000000000000" - - assert ( - SubmissionPipeline.construct_request_from_artifact(artifact).deployed_bytecode - == "0x00000000000000000000000000000000000000000000000000000000000000000000" - ) - - -@pytest.mark.parametrize( - "artifact_file,key,value", - ( - (TEST_ARTIFACT, "contract_name", "SafeMath"), - (empty_field("contractName"), "contract_name", ""), - (TEST_ARTIFACT, "bytecode", TEST_ARTIFACT["bytecode"]), - (empty_field("bytecode"), "bytecode", None), - (TEST_ARTIFACT, "deployed_bytecode", TEST_ARTIFACT["deployedBytecode"]), - (empty_field("deployedBytecode"), "deployed_bytecode", None), - (TEST_ARTIFACT, "source_map", TEST_ARTIFACT["sourceMap"]), - (empty_field("sourceMap"), "source_map", None), - (TEST_ARTIFACT, "deployed_source_map", TEST_ARTIFACT["deployedSourceMap"]), - (empty_field("deployedSourceMap"), "deployed_source_map", None), - (TEST_ARTIFACT, "source_list", sorted(TEST_ARTIFACT["allSourcePaths"].values())), - (TEST_ARTIFACT, "main_source", TEST_ARTIFACT["sourcePath"]), - (empty_field("sourcePath"), "main_source", ""), - (TEST_ARTIFACT, "solc_version", "0.5.11+commit.22be8592.Linux.g++"), - (TEST_ARTIFACT, "analysis_mode", "quick"), - ), -) -def test_request_from_artifact(artifact_file, key, value): - request_dict = SubmissionPipeline.construct_request_from_artifact(artifact_file) - assert getattr(request_dict, key) == value - - -def assert_client_access_token(): - client = SubmissionPipeline.get_mythx_client() - assert client is not None - assert client.api_key == "foo" - assert client.username is None - assert client.password is None - - -def test_mythx_client_from_access_token_env(monkeypatch): - monkeypatch.setenv("MYTHX_API_KEY", "foo") - assert_client_access_token() - monkeypatch.delenv("MYTHX_API_KEY") - - -def test_mythx_client_from_access_token_arg(argv): - argv["api-key"] = "foo" - assert_client_access_token() - - -def test_without_api_key(): - with pytest.raises(ValidationError): - SubmissionPipeline.get_mythx_client() - - -def test_highlighting_report(monkeypatch): - monkeypatch.setenv("MYTHX_API_KEY", "foo") - submission = SubmissionPipeline( - {"test": {"sourcePath": "contracts/SafeMath.sol", "contractName": "SafeMath"}} - ) - submission.reports = {"contracts/SafeMath.sol": DetectedIssuesResponse.from_dict(TEST_REPORT)} - submission.generate_highlighting_report() - assert submission.highlight_report == { - "highlights": { - "MythX": { - "SafeMath": { - "contracts/SafeMath.sol": [ - [ - 0, - 23, - "yellow", - ( - "SWC-103: A floating pragma is set.\nIt is recommended to make " - "a conscious choice on what version of Solidity is used for " - 'compilation. Currently multiple versions "^0.5.0" are allowed.' - ), - ] - ] - } - } - } - } - monkeypatch.delenv("MYTHX_API_KEY") - - -def test_stdout_report(monkeypatch): - monkeypatch.setenv("MYTHX_API_KEY", "foo") - submission = SubmissionPipeline( - {"test": {"sourcePath": "contracts/SafeMath.sol", "contractName": "SafeMath"}} - ) - submission.reports = {"contracts/SafeMath.sol": DetectedIssuesResponse.from_dict(TEST_REPORT)} - submission.generate_stdout_report() - assert submission.stdout_report == {"contracts/SafeMath.sol": {"LOW": 3}} - monkeypatch.delenv("MYTHX_API_KEY") - - -def test_wait_without_responses(monkeypatch): - monkeypatch.setenv("MYTHX_API_KEY", "foo") - submission = SubmissionPipeline( - {"test": {"sourcePath": "contracts/SafeMath.sol", "contractName": "SafeMath"}} - ) - with pytest.raises(ValidationError): - submission.wait_for_jobs() - monkeypatch.delenv("MYTHX_API_KEY") - - -def test_wait_with_responses(monkeypatch): - monkeypatch.setenv("MYTHX_API_KEY", "foo") - submission = SubmissionPipeline( - {"test": {"sourcePath": "contracts/SafeMath.sol", "contractName": "SafeMath"}} - ) - response_mock = MagicMock() - response_mock.uuid = "test-uuid" - ready_mock = MagicMock() - ready_mock.return_value = True - report_mock = MagicMock() - report_mock.return_value = "test-report" - submission.responses = {"test": response_mock} - submission.client.analysis_ready = ready_mock - submission.client.report = report_mock - - submission.wait_for_jobs() - - assert submission.reports == {"test": "test-report"} - - monkeypatch.delenv("MYTHX_API_KEY") - - -def test_send_requests(monkeypatch): - monkeypatch.setenv("MYTHX_API_KEY", "foo") - submission = SubmissionPipeline( - {"test": {"sourcePath": "contracts/SafeMath.sol", "contractName": "SafeMath"}} - ) - submission.requests = {"test": MagicMock()} - analyze_mock = MagicMock() - group_mock = MagicMock() - group_mock.group.identifier = "test-gid" - response_mock = MagicMock() - response_mock.uuid = "test-uuid" - analyze_mock.return_value = response_mock - submission.client.analyze = analyze_mock - submission.client.create_group = group_mock - submission.client.seal_group = group_mock - - submission.send_requests() - - assert submission.responses == {"test": response_mock} - monkeypatch.delenv("MYTHX_API_KEY") - - -def test_prepare_requests(monkeypatch): - monkeypatch.setenv("MYTHX_API_KEY", "foo") - build_mock = MagicMock() - build_mock.items = { - "SafeMath": { - "sourcePath": "contracts/SafeMath.sol", - "contractName": "SafeMath", - "type": "library", - }, - "Token": {"sourcePath": "contracts/Token.sol", "contractName": "Token", "type": "contract"}, - }.items - build_mock.get_dependents.return_value = ["Token"] - submission = SubmissionPipeline(build_mock) - submission.prepare_requests() - assert list(submission.requests.keys()) == ["Token"] - token_request = submission.requests["Token"] - assert token_request.contract_name == "Token" - assert "contracts/Token.sol" in token_request.sources.keys() - assert "contracts/SafeMath.sol" in token_request.sources.keys() - monkeypatch.delenv("MYTHX_API_KEY") - - -def test_console_notify_high(): - with patch("brownie._cli.analyze.notify") as notify_patch: - print_console_report({"Token": {"HIGH": 2}}) - assert notify_patch.called - - -def test_console_notify_low(): - with patch("brownie._cli.analyze.notify") as notify_patch: - print_console_report({"Token": {"LOW": 2}}) - assert not notify_patch.called - - -def test_console_notify_none(): - with patch("brownie._cli.analyze.notify") as notify_patch: - print_console_report({"Token": {}}) - assert notify_patch.called From 7320a5b28590df38bdd06924702a921e2e6bb631 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Thu, 1 Feb 2024 16:28:40 +0400 Subject: [PATCH 2/3] chore: drop 3.7 support, test against 3.10 --- ...e-ganache-3.7.yaml => core-ganache-3.10.yaml} | 14 +++++++------- tox.ini | 16 +++++++--------- 2 files changed, 14 insertions(+), 16 deletions(-) rename .github/workflows/{core-ganache-3.7.yaml => core-ganache-3.10.yaml} (79%) diff --git a/.github/workflows/core-ganache-3.7.yaml b/.github/workflows/core-ganache-3.10.yaml similarity index 79% rename from .github/workflows/core-ganache-3.7.yaml rename to .github/workflows/core-ganache-3.10.yaml index 18b210ed5..bdf1fa657 100644 --- a/.github/workflows/core-ganache-3.7.yaml +++ b/.github/workflows/core-ganache-3.10.yaml @@ -1,4 +1,4 @@ -name: Core Ganache (py37) +name: Core Ganache (py10) on: ["push", "pull_request"] env: @@ -7,11 +7,11 @@ env: WEB3_INFURA_PROJECT_ID: ddddf0c53f254d36aa76ce4e3a6a390e jobs: - py37core: + py310core: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Cache Solidity Installations uses: actions/cache@v2 @@ -27,16 +27,16 @@ jobs: - name: Install Ganache run: npm install -g ganache@7.0.2 - - name: Setup Python 3.7 - uses: actions/setup-python@v2 + - name: Setup Python 3.10 + uses: actions/setup-python@v5 with: - python-version: 3.7 + python-version: "3.10" - name: Install Tox run: pip install tox - name: Run Tox - run: tox -e py37 + run: tox -e py310 - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 diff --git a/tox.ini b/tox.ini index c1dbb6d74..52f940fa8 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ envlist = lint docs-{local,external} - py{37,38,39} + py{38,39,310} {pm,evm,plugin}test evm-{byzantium,petersburg,istanbul,latest} @@ -12,16 +12,16 @@ passenv = GITHUB_TOKEN WEB3_INFURA_PROJECT_ID deps = - py{37,38,39},{pm,plugin}test,evm-{byzantium,petersburg,istanbul,latest}: coverage==5.2.1 - py{37,38,39},{pm,plugin}test,evm-{byzantium,petersburg,istanbul,latest}: pytest==6.0.1 - py{37,38,39},{pm,plugin}test,evm-{byzantium,petersburg,istanbul,latest}: pytest-cov==2.10.1 - py{37,38,39},{pm,plugin}test,evm-{byzantium,petersburg,istanbul,latest}: pytest-mock==3.3.1 - py{37,38,39},{pm,plugin}test,evm-{byzantium,petersburg,istanbul,latest}: pytest-xdist==1.34.0 + py{38,39,310},{pm,plugin}test,evm-{byzantium,petersburg,istanbul,latest}: coverage==5.2.1 + py{38,39,310},{pm,plugin}test,evm-{byzantium,petersburg,istanbul,latest}: pytest==6.0.1 + py{38,39,310},{pm,plugin}test,evm-{byzantium,petersburg,istanbul,latest}: pytest-cov==2.10.1 + py{38,39,310},{pm,plugin}test,evm-{byzantium,petersburg,istanbul,latest}: pytest-mock==3.3.1 + py{38,39,310},{pm,plugin}test,evm-{byzantium,petersburg,istanbul,latest}: pytest-xdist==1.34.0 docs-{local,external}: sphinx docs-{local,external}: sphinx_rtd_theme docs-{local,external}: pygments_lexer_solidity commands = - py{37,38,39}: python -m pytest tests/ {posargs} + py{38,39,310}: python -m pytest tests/ {posargs} evm-byzantium: python -m pytest tests/ --evm 0.4.22,0.4.26,0.5.0,0.5.17,0.6.3,0.6.9 byzantium 0,10000 evm-petersburg: python -m pytest tests/ --evm 0.5.5,0.5.17,0.6.3,0.6.9 petersburg 0,10000 evm-istanbul: python -m pytest tests/ --evm 0.5.13,0.5.17,0.6.3,0.6.9 istanbul 0,10000 @@ -43,5 +43,3 @@ commands = black --check {toxinidir}/brownie {toxinidir}/tests flake8 {toxinidir}/brownie {toxinidir}/tests isort --check-only --diff {toxinidir}/brownie {toxinidir}/tests --skip brownie/__init__.py - mypy --disallow-untyped-defs {toxinidir}/brownie/convert {toxinidir}/brownie/network {toxinidir}/brownie/project - mypy --allow-untyped-defs {toxinidir}/brownie From 6fd0597676aeb50dd30bb30742e73d7b0ec7c058 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Thu, 1 Feb 2024 16:40:11 +0400 Subject: [PATCH 3/3] chore: bump pyyaml --- requirements-windows.txt | 151 ++++++++++++++++++--------------------- requirements.in | 4 +- requirements.txt | 9 +-- 3 files changed, 75 insertions(+), 89 deletions(-) diff --git a/requirements-windows.txt b/requirements-windows.txt index 08c0ca4c3..198b85c0c 100644 --- a/requirements-windows.txt +++ b/requirements-windows.txt @@ -1,14 +1,14 @@ # -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: # # pip-compile requirements-windows.in # -aiohttp==3.8.1 +aiohttp==3.9.3 # via # -r requirements.txt # web3 -aiosignal==1.2.0 +aiosignal==1.3.1 # via # -r requirements.txt # aiohttp @@ -16,43 +16,43 @@ asttokens==2.0.5 # via # -r requirements.txt # vyper -async-timeout==4.0.2 +async-timeout==4.0.3 # via # -r requirements.txt # aiohttp -attrs==22.1.0 +attrs==23.2.0 # via # -r requirements.txt # aiohttp # hypothesis # jsonschema # pytest + # referencing base58==2.1.1 # via # -r requirements.txt # multiaddr -bitarray>=2.6.0,<3 +bitarray==2.9.2 # via # -r requirements.txt # eth-account -black==22.6.0 +black==24.1.1 # via -r requirements.txt -certifi==2022.6.15 +certifi==2023.11.17 # via # -r requirements.txt # requests -charset-normalizer==2.1.0 +charset-normalizer==3.3.2 # via # -r requirements.txt - # aiohttp # requests -click==8.1.3 +click==8.1.7 # via # -r requirements.txt # black -colorama==0.4.5 +colorama==0.4.6 # via -r requirements-windows.in -cytoolz==0.12.0 +cytoolz==0.12.3 # via # -r requirements.txt # eth-keyfile @@ -80,6 +80,7 @@ eth-hash[pycryptodome]==0.3.3 # via # -r requirements.txt # eth-event + # eth-hash # eth-utils # web3 eth-keyfile==0.5.1 @@ -116,16 +117,16 @@ eth-utils==1.10.0 # eth-rlp # rlp # web3 -execnet==1.9.0 +execnet==2.0.2 # via # -r requirements.txt # pytest-xdist -frozenlist==1.3.1 +frozenlist==1.4.1 # via # -r requirements.txt # aiohttp # aiosignal -hexbytes==0.2.2 +hexbytes==0.2.3 # via # -r requirements.txt # eip712 @@ -135,17 +136,12 @@ hexbytes==0.2.2 # web3 hypothesis==6.27.3 # via -r requirements.txt -idna==3.3 +idna==3.6 # via # -r requirements.txt # requests # yarl -inflection==0.5.0 - # via - # -r requirements.txt - # mythx-models - # pythx -iniconfig==1.1.1 +iniconfig==2.0.0 # via # -r requirements.txt # pytest @@ -153,14 +149,17 @@ ipfshttpclient==0.8.0a2 # via # -r requirements.txt # web3 -jsonschema==3.2.0 +jsonschema==4.21.1 # via # -r requirements.txt - # mythx-models # web3 -lazy-object-proxy==1.7.1 +jsonschema-specifications==2023.12.1 + # via + # -r requirements.txt + # jsonschema +lazy-object-proxy==1.10.0 # via -r requirements.txt -lru-dict==1.1.8 +lru-dict==1.3.0 # via # -r requirements.txt # web3 @@ -168,113 +167,95 @@ multiaddr==0.0.9 # via # -r requirements.txt # ipfshttpclient -multidict==6.0.2 +multidict==6.0.4 # via # -r requirements.txt # aiohttp # yarl -mypy-extensions==0.4.3 +mypy-extensions==1.0.0 # via # -r requirements.txt # black -mythx-models==1.9.1 - # via - # -r requirements.txt - # pythx -netaddr==0.8.0 +netaddr==0.10.1 # via # -r requirements.txt # multiaddr -packaging==21.3 +packaging==23.2 # via # -r requirements.txt + # black # pytest parsimonious==0.8.1 # via # -r requirements.txt # eth-abi -pathspec==0.9.0 +pathspec==0.12.1 # via # -r requirements.txt # black -platformdirs==2.5.2 +platformdirs==4.2.0 # via # -r requirements.txt # black -pluggy==1.0.0 +pluggy==1.4.0 # via # -r requirements.txt # pytest -prompt-toolkit==3.0.30 +prompt-toolkit==3.0.43 # via -r requirements.txt -protobuf==3.20.1 +protobuf==3.19.5 # via # -r requirements.txt # web3 -psutil==5.9.1 +psutil==5.9.8 # via -r requirements.txt py==1.11.0 # via # -r requirements.txt # pytest # pytest-forked -py-solc-ast==1.2.9 +py-solc-ast==1.2.10 # via -r requirements.txt py-solc-x==1.1.1 # via -r requirements.txt -pycryptodome==3.15.0 +pycryptodome==3.20.0 # via # -r requirements.txt # eip712 # eth-hash # eth-keyfile # vyper -pygments==2.12.0 +pygments==2.17.2 # via # -r requirements.txt # pygments-lexer-solidity pygments-lexer-solidity==0.7.0 # via -r requirements.txt -pyjwt==1.7.1 - # via - # -r requirements.txt - # pythx -pyparsing==3.0.9 - # via - # -r requirements.txt - # packaging -pyrsistent==0.18.1 - # via - # -r requirements.txt - # jsonschema pytest==6.2.5 # via # -r requirements.txt # pytest-forked # pytest-xdist -pytest-forked==1.4.0 +pytest-forked==1.6.0 # via # -r requirements.txt # pytest-xdist pytest-xdist==1.34.0 # via -r requirements.txt -python-dateutil==2.8.1 - # via - # -r requirements.txt - # mythx-models - # pythx python-dotenv==0.16.0 # via -r requirements.txt -pythx==1.6.1 +pyyaml==6.0.1 # via -r requirements.txt -pyyaml==5.4.1 - # via -r requirements.txt -requests==2.28.1 +referencing==0.33.0 + # via + # -r requirements.txt + # jsonschema + # jsonschema-specifications +requests==2.31.0 # via # -r requirements.txt # ipfshttpclient # py-solc-x - # pythx # vvm # web3 rlp==2.0.1 @@ -282,7 +263,12 @@ rlp==2.0.1 # -r requirements.txt # eth-account # eth-rlp -semantic-version==2.8.5 +rpds-py==0.17.1 + # via + # -r requirements.txt + # jsonschema + # referencing +semantic-version==2.10.0 # via # -r requirements.txt # py-solc-x @@ -292,11 +278,9 @@ six==1.16.0 # via # -r requirements.txt # asttokens - # jsonschema # multiaddr # parsimonious # pytest-xdist - # python-dateutil sortedcontainers==2.4.0 # via # -r requirements.txt @@ -309,13 +293,17 @@ tomli==2.0.1 # via # -r requirements.txt # black -toolz==0.12.0 +toolz==0.12.1 # via # -r requirements.txt # cytoolz -tqdm==4.64.0 +tqdm==4.66.1 # via -r requirements.txt -urllib3==1.26.11 +typing-extensions==4.9.0 + # via + # -r requirements.txt + # black +urllib3==2.2.0 # via # -r requirements.txt # requests @@ -325,28 +313,25 @@ varint==1.0.2 # multiaddr vvm==0.1.0 # via -r requirements.txt -vyper==0.3.6 +vyper==0.3.7 # via -r requirements.txt -wcwidth==0.2.5 +wcwidth==0.2.13 # via # -r requirements.txt # prompt-toolkit -web3==5.30.0 +web3==5.31.4 # via -r requirements.txt websockets==9.1 # via # -r requirements.txt # web3 -wheel==0.37.1 +wheel==0.42.0 # via # -r requirements.txt # vyper -wrapt==1.14.1 +wrapt==1.16.0 # via -r requirements.txt -yarl==1.8.2 +yarl==1.9.4 # via # -r requirements.txt # aiohttp - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements.in b/requirements.in index 41613a91e..f55e61a70 100644 --- a/requirements.in +++ b/requirements.in @@ -19,13 +19,13 @@ pygments<3 pytest-xdist<2 pytest<7 python-dotenv>=0.16.0,<0.17.0 -pyyaml>=5.3.0,<6 +pyyaml>=6,<7 requests>=2.25.0,<3 rlp<3 semantic-version<3 tqdm<5 vvm==0.1.0 # 0.2.0 switches from semantic-version to packaging.version and things break -vyper>=0.2.11,<1 +vyper==0.3.7 # >=0.3.8 requires python 3.10 # web3 versions 5.29.0 thru 5.31.2 have a bug when using the async provider in subthreads. This is relevant for some ecosystem libs downstream from brownie. web3>=5.22.0,!=5.29.*,!=5.30.*,!=5.31.1,!=5.31.2,<6 wrapt>=1.12.1,<2 diff --git a/requirements.txt b/requirements.txt index 9fed8f8ce..7a5357a42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile requirements.in @@ -52,10 +52,11 @@ eth-account==0.5.9 # web3 eth-event==1.2.3 # via -r requirements.in -eth-hash[pycryptodome]==0.6.0 +eth-hash[pycryptodome]==0.3.3 # via # -r requirements.in # eth-event + # eth-hash # eth-utils # web3 eth-keyfile==0.5.1 @@ -76,7 +77,7 @@ eth-typing==2.3.0 # eth-keys # eth-utils # web3 -eth-utils==1.9.5 +eth-utils==1.10.0 # via # -r requirements.in # eip712 @@ -180,7 +181,7 @@ pytest-xdist==1.34.0 # via -r requirements.in python-dotenv==0.16.0 # via -r requirements.in -pyyaml==5.4.1 +pyyaml==6.0.1 # via -r requirements.in referencing==0.33.0 # via