Support Pragmas (#1591)

* Support Pragmas

* Fix MarkUpSafe
pull/1593/head
Nikhil Parasaram 3 years ago committed by GitHub
parent 8718a4501f
commit 0a2996a83e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 52
      mythril/ethereum/util.py
  2. 22
      mythril/mythril/mythril_disassembler.py
  3. 1
      requirements.txt
  4. 56
      tests/util_tests.py

@ -10,12 +10,12 @@ import solc
from pathlib import Path from pathlib import Path
from subprocess import PIPE, Popen from subprocess import PIPE, Popen
from typing import Optional
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError
from mythril.exceptions import CompilerError from mythril.exceptions import CompilerError
from semantic_version import Version from semantic_version import Version, NpmSpec
if sys.version_info[1] >= 6:
import solcx import solcx
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -123,31 +123,39 @@ def solc_exists(version):
""" """
default_binary = "/usr/bin/solc" default_binary = "/usr/bin/solc"
if sys.version_info[1] >= 6:
if platform.system() == "Darwin": if platform.system() == "Darwin":
solcx.import_installed_solc() solcx.import_installed_solc()
solcx.install_solc("v" + version) solcx.install_solc("v" + version)
solcx.set_solc_version("v" + version) solcx.set_solc_version("v" + version)
solc_binary = solcx.install.get_executable() solc_binary = solcx.install.get_executable()
return solc_binary return solc_binary
elif Version("0.4.2") <= Version(version) <= Version("0.4.25"):
if not solc.main.is_solc_available():
solc.install_solc("v" + version)
solc_binary = solc.install.get_executable_path("v" + version)
return solc_binary
else:
solc_binaries = [
os.path.join(
os.environ.get("HOME", str(Path.home())),
".py-solc/solc-v" + version,
"bin/solc",
) # py-solc setup
]
for solc_path in solc_binaries:
if os.path.exists(solc_path):
return solc_path
elif os.path.exists(default_binary):
return default_binary
else:
all_versions = solcx.get_installable_solc_versions()
def extract_version(file: str) -> Optional[str]:
version_line = None
for line in file.split("\n"):
if "pragma solidity" not in line:
continue
version_line = line
break
if version_line is None:
return None
if version_line[-1] == ";":
version_line = version_line[:-1]
version_line = version_line.replace("pragma solidity", "").lstrip()
version_constraint = NpmSpec(version_line)
for version in all_versions:
if version in version_constraint:
return str(version)
return None return None
def extract_binary(file: str) -> str:
with open(file) as f:
version = extract_version(f.read())
if version is None:
return os.environ.get("SOLC") or "solc"
return solc_exists(version)

@ -43,7 +43,7 @@ class MythrilDisassembler:
self.contracts = [] # type: List[EVMContract] self.contracts = [] # type: List[EVMContract]
@staticmethod @staticmethod
def _init_solc_binary(version: str) -> str: def _init_solc_binary(version: str) -> Optional[str]:
""" """
Only proper versions are supported. No nightlies, commits etc (such as available in remix). Only proper versions are supported. No nightlies, commits etc (such as available in remix).
:param version: Version of the solc binary required :param version: Version of the solc binary required
@ -51,7 +51,7 @@ class MythrilDisassembler:
""" """
if not version: if not version:
return os.environ.get("SOLC") or "solc" return None
# tried converting input to semver, seemed not necessary so just slicing for now # tried converting input to semver, seemed not necessary so just slicing for now
try: try:
@ -60,7 +60,6 @@ class MythrilDisassembler:
main_version = "" # allow missing solc will download instead main_version = "" # allow missing solc will download instead
main_version_number = re.match(r"\d+.\d+.\d+", main_version) main_version_number = re.match(r"\d+.\d+.\d+", main_version)
# In case instead of just the version number, --solv v0.x.x is used
if version.startswith("v"): if version.startswith("v"):
version = version[1:] version = version[1:]
@ -70,18 +69,9 @@ class MythrilDisassembler:
else: else:
solc_binary = util.solc_exists(version) solc_binary = util.solc_exists(version)
if solc_binary is None: if solc_binary is None:
if sys.version_info[1] >= 6:
raise CriticalError( raise CriticalError(
"The version of solc that is needed cannot be installed automatically" "The version of solc that is needed cannot be installed automatically"
) )
elif sys.version_info[1] == 5:
raise CriticalError(
"Py-Solc doesn't support 0.5.*+. You can switch to python 3.6 which uses solcx."
)
else:
raise CriticalError(
"There was an error when trying to install the specified solc version"
)
else: else:
log.info("Setting the compiler to %s", solc_binary) log.info("Setting the compiler to %s", solc_binary)
@ -171,12 +161,12 @@ class MythrilDisassembler:
contract_name = None contract_name = None
file = os.path.expanduser(file) file = os.path.expanduser(file)
solc_binary = self.solc_binary or util.extract_binary(file)
try: try:
# import signatures from solidity source # import signatures from solidity source
self.sigs.import_solidity_file( self.sigs.import_solidity_file(
file, file,
solc_binary=self.solc_binary, solc_binary=solc_binary,
solc_settings_json=self.solc_settings_json, solc_settings_json=self.solc_settings_json,
) )
if contract_name is not None: if contract_name is not None:
@ -184,7 +174,7 @@ class MythrilDisassembler:
input_file=file, input_file=file,
name=contract_name, name=contract_name,
solc_settings_json=self.solc_settings_json, solc_settings_json=self.solc_settings_json,
solc_binary=self.solc_binary, solc_binary=solc_binary,
) )
self.contracts.append(contract) self.contracts.append(contract)
contracts.append(contract) contracts.append(contract)
@ -192,7 +182,7 @@ class MythrilDisassembler:
for contract in get_contracts_from_file( for contract in get_contracts_from_file(
input_file=file, input_file=file,
solc_settings_json=self.solc_settings_json, solc_settings_json=self.solc_settings_json,
solc_binary=self.solc_binary, solc_binary=solc_binary,
): ):
self.contracts.append(contract) self.contracts.append(contract)
contracts.append(contract) contracts.append(contract)

@ -15,6 +15,7 @@ eth-rlp<0.3.0,>=0.1.0
eth-typing<3.0.0,>=2.1.0 eth-typing<3.0.0,>=2.1.0
eth-utils<2 eth-utils<2
jinja2>=2.9 jinja2>=2.9
MarkupSafe<2.1.0
mock mock
persistent>=4.2.0 persistent>=4.2.0
py-flags py-flags

@ -0,0 +1,56 @@
import pytest
from mythril.ethereum.util import extract_version
test_data = (
("pragma solidity 0.5.0\n", ["0.5.0"]),
("pragma solidity ^0.4.26\n", ["0.4.26"]),
("pragma solidity ^0.6.3;\n", [f"0.6.{x}" for x in range(3, 13)]),
(
"""pragma solidity >=0.4.0 <0.6.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}""",
[f"0.4.{x}" for x in range(11, 27)] + [f"0.5.{x}" for x in range(0, 18)],
),
(
"""
pragma solidity >=0.4.0 <0.6.0
;contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}""",
[f"0.4.{x}" for x in range(11, 27)] + [f"0.5.{x}" for x in range(0, 18)],
),
(
"""
pragma solidity >=0.4.0 <0.6.0
;contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}""",
[f"0.4.{x}" for x in range(11, 27)] + [f"0.5.{x}" for x in range(0, 18)],
),
)
@pytest.mark.parametrize("input_,output", test_data)
def test_sar(input_, output):
assert extract_version(input_) in output
Loading…
Cancel
Save