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 subprocess import PIPE, Popen
from typing import Optional
from json.decoder import JSONDecodeError
from mythril.exceptions import CompilerError
from semantic_version import Version
from semantic_version import Version, NpmSpec
if sys.version_info[1] >= 6:
import solcx
log = logging.getLogger(__name__)
@ -123,31 +123,39 @@ def solc_exists(version):
"""
default_binary = "/usr/bin/solc"
if sys.version_info[1] >= 6:
if platform.system() == "Darwin":
solcx.import_installed_solc()
solcx.install_solc("v" + version)
solcx.set_solc_version("v" + version)
solc_binary = solcx.install.get_executable()
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
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]
@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).
:param version: Version of the solc binary required
@ -51,7 +51,7 @@ class MythrilDisassembler:
"""
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
try:
@ -60,7 +60,6 @@ class MythrilDisassembler:
main_version = "" # allow missing solc will download instead
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"):
version = version[1:]
@ -70,18 +69,9 @@ class MythrilDisassembler:
else:
solc_binary = util.solc_exists(version)
if solc_binary is None:
if sys.version_info[1] >= 6:
raise CriticalError(
"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:
log.info("Setting the compiler to %s", solc_binary)
@ -171,12 +161,12 @@ class MythrilDisassembler:
contract_name = None
file = os.path.expanduser(file)
solc_binary = self.solc_binary or util.extract_binary(file)
try:
# import signatures from solidity source
self.sigs.import_solidity_file(
file,
solc_binary=self.solc_binary,
solc_binary=solc_binary,
solc_settings_json=self.solc_settings_json,
)
if contract_name is not None:
@ -184,7 +174,7 @@ class MythrilDisassembler:
input_file=file,
name=contract_name,
solc_settings_json=self.solc_settings_json,
solc_binary=self.solc_binary,
solc_binary=solc_binary,
)
self.contracts.append(contract)
contracts.append(contract)
@ -192,7 +182,7 @@ class MythrilDisassembler:
for contract in get_contracts_from_file(
input_file=file,
solc_settings_json=self.solc_settings_json,
solc_binary=self.solc_binary,
solc_binary=solc_binary,
):
self.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-utils<2
jinja2>=2.9
MarkupSafe<2.1.0
mock
persistent>=4.2.0
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