Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hardhat integration #252

Merged
merged 5 commits into from
May 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,6 @@ ENV/
# mypy
.mypy_cache/
.idea/
.vscode/
.vscode/

.mythx.yml
9 changes: 0 additions & 9 deletions .mythx.yml

This file was deleted.

3 changes: 2 additions & 1 deletion mythx_cli/fuzz/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
class FaaSError(Exception):
"""Base class for FaaS module exceptions"""

def __init__(self, message):
def __init__(self, message, detail=None):
self.message = message
self.detail = detail

pass

Expand Down
12 changes: 4 additions & 8 deletions mythx_cli/fuzz/faas.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import json
import logging
import random
import string

import click
import requests

from mythx_cli.analyze.scribble import ScribbleMixin
Expand Down Expand Up @@ -47,10 +45,13 @@ def start_faas_campaign(self, payload):
response_data = response.json()
if response.status_code != requests.codes.ok:
raise BadStatusCode(
f"Got http status code {response.status_code} for request {req_url}"
f"Got http status code {response.status_code} for request {req_url}",
detail=response_data["detail"],
)
return response_data["id"]
except Exception as e:
if isinstance(e, BadStatusCode):
raise e
raise RequestError(f"Error starting FaaS campaign.")

def create_faas_campaign(self, campaign_data, seed_state):
Expand All @@ -72,11 +73,6 @@ def create_faas_campaign(self, campaign_data, seed_state):
:return: Campaign ID
"""
try:
if self.project_type != "brownie":
raise click.exceptions.UsageError(
"Currently only Brownie projects are supported"
)

try:
api_payload_params = {
"discovery-probability-threshold": seed_state[
Expand Down
8 changes: 1 addition & 7 deletions mythx_cli/fuzz/ide/brownie.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import json
import logging
import os
from pathlib import Path
from typing import Dict, List

from mythx_cli.fuzz.exceptions import (
BrownieError,
BuildArtifactsError,
PayloadError,
SourceError,
)
from mythx_cli.fuzz.exceptions import BuildArtifactsError
from mythx_cli.fuzz.ide.generic import IDEArtifacts, JobBuilder

from ...util import sol_files_by_directory
Expand Down
111 changes: 111 additions & 0 deletions mythx_cli/fuzz/ide/hardhat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import json
from os.path import commonpath, relpath
from pathlib import Path
from typing import List

from mythx_cli.fuzz.exceptions import BuildArtifactsError
from mythx_cli.fuzz.ide.generic import IDEArtifacts, JobBuilder

from ...util import sol_files_by_directory


class HardhatArtifacts(IDEArtifacts):
def __init__(self, build_dir=None, targets=None):
self._include = []
if targets:
include = []
for target in targets:
include.extend(sol_files_by_directory(target))
self._include = include

self._build_dir = Path(build_dir).absolute() or Path("./artifacts").absolute()
self._contracts, self._sources = self.fetch_data()

@property
def contracts(self):
return self._contracts

@property
def sources(self):
return self._sources

def fetch_data(self):
result_contracts = {}
result_sources = {}

for file_path in self._include:
cp = commonpath([self._build_dir, file_path])
relative_file_path = relpath(file_path, cp)

if relative_file_path in result_contracts:
continue

file_name = Path(file_path).stem
file_artifact_path: Path = self._build_dir.joinpath(
relative_file_path
).joinpath(f"{file_name}.json")
file_debug_path: Path = self._build_dir.joinpath(
relative_file_path
).joinpath(f"{file_name}.dbg.json")
if not file_artifact_path.exists() or not file_debug_path.exists():
raise BuildArtifactsError("Could not find target artifacts")

with file_artifact_path.open("r") as file:
file_artifact = json.load(file)
with file_debug_path.open("r") as file:
file_debug_artifact = json.load(file)
build_info_name = Path(file_debug_artifact["buildInfo"]).name
with self._build_dir.joinpath(f"build-info/{build_info_name}").open(
"r"
) as file:
build_info = json.load(file)

result_contracts[relative_file_path] = []

contracts = build_info["output"]["contracts"][relative_file_path]

for contract, data in contracts.items():
if data["evm"]["bytecode"]["object"] == "":
continue
result_contracts[relative_file_path] += [
{
"sourcePaths": {
i: k
for i, k in enumerate(
build_info["output"]["contracts"].keys()
)
},
"deployedSourceMap": data["evm"]["deployedBytecode"][
"sourceMap"
],
"deployedBytecode": data["evm"]["deployedBytecode"]["object"],
"sourceMap": data["evm"]["bytecode"]["sourceMap"],
"bytecode": data["evm"]["bytecode"]["object"],
"contractName": file_artifact["contractName"],
"mainSourceFile": file_artifact["sourceName"],
}
]

for source_file_dep, data in build_info["output"]["sources"].items():
if source_file_dep in result_sources.keys():
continue

result_sources[source_file_dep] = {
"fileIndex": data["id"],
"source": build_info["input"]["sources"][source_file_dep][
"content"
],
"ast": data["ast"],
}

return result_contracts, result_sources


class HardhatJob:
def __init__(self, target: List[str], build_dir: Path):
artifacts = HardhatArtifacts(build_dir, targets=target)
self._jb = JobBuilder(artifacts)
self.payload = None

def generate_payload(self):
self.payload = self._jb.payload()
29 changes: 15 additions & 14 deletions mythx_cli/fuzz/rpc.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import json
import logging
import os
from pathlib import Path
from typing import Optional

import click
import requests
Expand Down Expand Up @@ -78,8 +76,20 @@ def get_all_blocks(self):
blocks.append(self.get_block(block_number=i))
return blocks

def get_seed_state(self, address: str, other_addresses: [str]):
def get_seed_state(
self, address: str, other_addresses: [str], corpus_target: Optional[str] = None
):
seed_state = {
"time-limit-secs": time_limit_seconds,
"discovery-probability-threshold": 0.0,
"assertion-checking-mode": 1,
"emit-mythx-report": True,
"num-cores": self.number_of_cores,
}
"""Get a seed state for the target contract to be used by Harvey"""
if corpus_target:
return dict({**seed_state, "analysis-setup": {"target": corpus_target}})

try:
blocks = self.get_all_blocks()
processed_transactions = []
Expand All @@ -96,16 +106,7 @@ def get_seed_state(self, address: str, other_addresses: [str]):
"other-addresses-under-test": other_addresses,
}
)
return dict(
{
"time-limit-secs": time_limit_seconds,
"analysis-setup": setup,
"discovery-probability-threshold": 0.0,
"assertion-checking-mode": 1,
"emit-mythx-report": True,
"num-cores": self.number_of_cores,
}
)
return dict({**seed_state, "analysis-setup": setup})
except Exception as e:
LOGGER.warning(f"Could not generate seed state for address: {address}")
raise click.exceptions.UsageError(
Expand Down