Skip to content

Commit

Permalink
Merge pull request #2099 from crytic/feat/vyper-support
Browse files Browse the repository at this point in the history
Feat/vyper support
  • Loading branch information
montyly authored Oct 12, 2023
2 parents 771ad10 + 2a7e514 commit 9e003ee
Show file tree
Hide file tree
Showing 93 changed files with 4,892 additions and 119 deletions.
18 changes: 17 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,23 @@ jobs:
npm install hardhat
popd || exit
fi
- name: Install Vyper
run: |
INSTALLDIR="$RUNNER_TEMP/vyper-install"
if [[ "$RUNNER_OS" = "Windows" ]]; then
URL="https://github.com/vyperlang/vyper/releases/download/v0.3.7/vyper.0.3.7+commit.6020b8bb.windows.exe"
FILENAME="vyper.exe"
elif [[ "$RUNNER_OS" = "Linux" ]]; then
URL="https://github.com/vyperlang/vyper/releases/download/v0.3.7/vyper.0.3.7+commit.6020b8bb.linux"
FILENAME="vyper"
else
echo "Unknown OS"
exit 1
fi
mkdir -p "$INSTALLDIR"
curl "$URL" -o "$INSTALLDIR/$FILENAME" -L
chmod 755 "$INSTALLDIR/$FILENAME"
echo "$INSTALLDIR" >> "$GITHUB_PATH"
- name: Run ${{ matrix.type }} tests
env:
TEST_TYPE: ${{ matrix.type }}
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"packaging",
"prettytable>=3.3.0",
"pycryptodome>=3.4.6",
"crytic-compile>=0.3.3,<0.4.0",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile",
# "crytic-compile>=0.3.1,<0.4.0",
"crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
"web3>=6.0.0",
"eth-abi>=4.0.0",
"eth-typing>=3.0.0",
Expand Down
6 changes: 0 additions & 6 deletions slither/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -870,12 +870,6 @@ def main_impl(
logging.error(red(output_error))
logging.error("Please report an issue to https://github.com/crytic/slither/issues")

except Exception: # pylint: disable=broad-except
output_error = traceback.format_exc()
traceback.print_exc()
logging.error(f"Error in {args.filename}") # pylint: disable=logging-fstring-interpolation
logging.error(output_error)

# If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON.
if outputting_json:
if "console" in args.json_types:
Expand Down
32 changes: 32 additions & 0 deletions slither/core/compilation_unit.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import math
from enum import Enum
from typing import Optional, Dict, List, Set, Union, TYPE_CHECKING, Tuple

from crytic_compile import CompilationUnit, CryticCompile
Expand Down Expand Up @@ -29,13 +30,28 @@
from slither.core.slither_core import SlitherCore


class Language(Enum):
SOLIDITY = "solidity"
VYPER = "vyper"

@staticmethod
def from_str(label: str):
if label == "solc":
return Language.SOLIDITY
if label == "vyper":
return Language.VYPER

raise ValueError(f"Unknown language: {label}")


# pylint: disable=too-many-instance-attributes,too-many-public-methods
class SlitherCompilationUnit(Context):
def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit) -> None:
super().__init__()

self._core = core
self._crytic_compile_compilation_unit = crytic_compilation_unit
self._language = Language.from_str(crytic_compilation_unit.compiler_version.compiler)

# Top level object
self.contracts: List[Contract] = []
Expand Down Expand Up @@ -81,6 +97,17 @@ def source_units(self) -> Dict[int, str]:
# region Compiler
###################################################################################
###################################################################################
@property
def language(self) -> Language:
return self._language

@property
def is_vyper(self) -> bool:
return self._language == Language.VYPER

@property
def is_solidity(self) -> bool:
return self._language == Language.SOLIDITY

@property
def compiler_version(self) -> CompilerVersion:
Expand Down Expand Up @@ -166,6 +193,10 @@ def functions_and_modifiers(self) -> List[Function]:
return self.functions + list(self.modifiers)

def propagate_function_calls(self) -> None:
"""This info is used to compute the rvalues of Phi operations in `fix_phi` and ultimately
is responsible for the `read` property of Phi operations which is vital to
propagating taints inter-procedurally
"""
for f in self.functions_and_modifiers:
for node in f.nodes:
for ir in node.irs_ssa:
Expand Down Expand Up @@ -259,6 +290,7 @@ def get_scope(self, filename_str: str) -> FileScope:
###################################################################################

def compute_storage_layout(self) -> None:
assert self.is_solidity
for contract in self.contracts_derived:
self._storage_layouts[contract.name] = {}

Expand Down
2 changes: 1 addition & 1 deletion slither/core/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def name(self, name: str) -> None:
@property
def id(self) -> int:
"""Unique id."""
assert self._id
assert self._id is not None
return self._id

@id.setter
Expand Down
31 changes: 19 additions & 12 deletions slither/core/declarations/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
self._parameters: List["LocalVariable"] = []
self._parameters_ssa: List["LocalIRVariable"] = []
self._parameters_src: SourceMapping = SourceMapping()
# This is used for vyper calls with default arguments
self._default_args_as_expressions: List["Expression"] = []
self._returns: List["LocalVariable"] = []
self._returns_ssa: List["LocalIRVariable"] = []
self._returns_src: SourceMapping = SourceMapping()
Expand Down Expand Up @@ -217,8 +219,9 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:

self.compilation_unit: "SlitherCompilationUnit" = compilation_unit

# Assume we are analyzing Solidity by default
self.function_language: FunctionLanguage = FunctionLanguage.Solidity
self.function_language: FunctionLanguage = (
FunctionLanguage.Solidity if compilation_unit.is_solidity else FunctionLanguage.Vyper
)

self._id: Optional[str] = None

Expand All @@ -238,7 +241,7 @@ def name(self) -> str:
"""
if self._name == "" and self._function_type == FunctionType.CONSTRUCTOR:
return "constructor"
if self._function_type == FunctionType.FALLBACK:
if self._name == "" and self._function_type == FunctionType.FALLBACK:
return "fallback"
if self._function_type == FunctionType.RECEIVE:
return "receive"
Expand Down Expand Up @@ -985,14 +988,15 @@ def signature(self) -> Tuple[str, List[str], List[str]]:
(str, list(str), list(str)): Function signature as
(name, list parameters type, list return values type)
"""
if self._signature is None:
signature = (
self.name,
[str(x.type) for x in self.parameters],
[str(x.type) for x in self.returns],
)
self._signature = signature
return self._signature
# FIXME memoizing this function is not working properly for vyper
# if self._signature is None:
return (
self.name,
[str(x.type) for x in self.parameters],
[str(x.type) for x in self.returns],
)
# self._signature = signature
# return self._signature

@property
def signature_str(self) -> str:
Expand Down Expand Up @@ -1497,7 +1501,9 @@ def is_reentrant(self) -> bool:
Determine if the function can be re-entered
"""
# TODO: compare with hash of known nonReentrant modifier instead of the name
if "nonReentrant" in [m.name for m in self.modifiers]:
if "nonReentrant" in [m.name for m in self.modifiers] or "nonreentrant(lock)" in [
m.name for m in self.modifiers
]:
return False

if self.visibility in ["public", "external"]:
Expand Down Expand Up @@ -1756,6 +1762,7 @@ def fix_phi(
node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)]

def generate_slithir_and_analyze(self) -> None:

for node in self.nodes:
node.slithir_generation()

Expand Down
33 changes: 33 additions & 0 deletions slither/core/declarations/solidity_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
SOLIDITY_VARIABLES = {
"now": "uint256",
"this": "address",
"self": "address",
"abi": "address", # to simplify the conversion, assume that abi return an address
"msg": "",
"tx": "",
"block": "",
"super": "",
"chain": "",
"ZERO_ADDRESS": "address",
}

SOLIDITY_VARIABLES_COMPOSED = {
Expand All @@ -34,6 +37,10 @@
"msg.value": "uint256",
"tx.gasprice": "uint256",
"tx.origin": "address",
# Vyper
"chain.id": "uint256",
"block.prevhash": "bytes32",
"self.balance": "uint256",
}

SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
Expand Down Expand Up @@ -81,6 +88,32 @@
"balance(address)": ["uint256"],
"code(address)": ["bytes"],
"codehash(address)": ["bytes32"],
# Vyper
"create_from_blueprint()": [],
"create_minimal_proxy_to()": [],
"empty()": [],
"convert()": [],
"len()": ["uint256"],
"method_id()": [],
"unsafe_sub()": [],
"unsafe_add()": [],
"unsafe_div()": [],
"unsafe_mul()": [],
"pow_mod256()": [],
"max_value()": [],
"min_value()": [],
"concat()": [],
"ecrecover()": [],
"isqrt()": [],
"range()": [],
"min()": [],
"max()": [],
"shift()": [],
"abs()": [],
"raw_call()": ["bool", "bytes32"],
"_abi_encode()": [],
"slice()": [],
"uint2str()": ["string"],
}


Expand Down
1 change: 1 addition & 0 deletions slither/core/expressions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .new_elementary_type import NewElementaryType
from .super_call_expression import SuperCallExpression
from .super_identifier import SuperIdentifier
from .self_identifier import SelfIdentifier
from .tuple_expression import TupleExpression
from .type_conversion import TypeConversion
from .unary_operation import UnaryOperation, UnaryOperationType
2 changes: 1 addition & 1 deletion slither/core/expressions/binary_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class BinaryOperationType(Enum):
# pylint: disable=too-many-branches
@staticmethod
def get_type(
operation_type: "BinaryOperation",
operation_type: "str",
) -> "BinaryOperationType":
if operation_type == "**":
return BinaryOperationType.POWER
Expand Down
1 change: 0 additions & 1 deletion slither/core/expressions/identifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ def __init__(
],
) -> None:
super().__init__()

# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract, SolidityVariable, SolidityFunction
from slither.solc_parsing.yul.evm_functions import YulBuiltin
Expand Down
6 changes: 6 additions & 0 deletions slither/core/expressions/self_identifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from slither.core.expressions.identifier import Identifier


class SelfIdentifier(Identifier):
def __str__(self):
return "self." + str(self._value)
24 changes: 20 additions & 4 deletions slither/detectors/abstract_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from logging import Logger
from typing import Optional, List, TYPE_CHECKING, Dict, Union, Callable

from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.compilation_unit import SlitherCompilationUnit, Language
from slither.core.declarations import Contract
from slither.formatters.exceptions import FormatImpossible
from slither.formatters.utils.patches import apply_patch, create_diff
Expand Down Expand Up @@ -80,6 +80,9 @@ class AbstractDetector(metaclass=abc.ABCMeta):
# list of vulnerable solc versions as strings (e.g. ["0.4.25", "0.5.0"])
# If the detector is meant to run on all versions, use None
VULNERABLE_SOLC_VERSIONS: Optional[List[str]] = None
# If the detector is meant to run on all languages, use None
# Otherwise, use `solidity` or `vyper`
LANGUAGE: Optional[str] = None

def __init__(
self, compilation_unit: SlitherCompilationUnit, slither: "Slither", logger: Logger
Expand Down Expand Up @@ -133,6 +136,14 @@ def __init__(
f"VULNERABLE_SOLC_VERSIONS should not be an empty list {self.__class__.__name__}"
)

if self.LANGUAGE is not None and self.LANGUAGE not in [
Language.SOLIDITY.value,
Language.VYPER.value,
]:
raise IncorrectDetectorInitialization(
f"LANGUAGE should not be either 'solidity' or 'vyper' {self.__class__.__name__}"
)

if re.match("^[a-zA-Z0-9_-]*$", self.ARGUMENT) is None:
raise IncorrectDetectorInitialization(
f"ARGUMENT has illegal character {self.__class__.__name__}"
Expand Down Expand Up @@ -164,9 +175,14 @@ def _log(self, info: str) -> None:
if self.logger:
self.logger.info(self.color(info))

def _uses_vulnerable_solc_version(self) -> bool:
def _is_applicable_detector(self) -> bool:
if self.VULNERABLE_SOLC_VERSIONS:
return self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS
return (
self.compilation_unit.is_solidity
and self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS
)
if self.LANGUAGE:
return self.compilation_unit.language.value == self.LANGUAGE
return True

@abc.abstractmethod
Expand All @@ -179,7 +195,7 @@ def detect(self) -> List[Dict]:
results: List[Dict] = []

# check solc version
if not self._uses_vulnerable_solc_version():
if not self._is_applicable_detector():
return results

# only keep valid result, and remove duplicate
Expand Down
2 changes: 1 addition & 1 deletion slither/detectors/attributes/incorrect_solc.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class IncorrectSolc(AbstractDetector):
HELP = "Incorrect Solidity version"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH

LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity"

WIKI_TITLE = "Incorrect versions of Solidity"
Expand Down
2 changes: 1 addition & 1 deletion slither/detectors/naming_convention/naming_convention.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class NamingConvention(AbstractDetector):
HELP = "Conformity to Solidity naming conventions"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH

LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions"

WIKI_TITLE = "Conformance to Solidity naming conventions"
Expand Down
2 changes: 1 addition & 1 deletion slither/detectors/statements/deprecated_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class DeprecatedStandards(AbstractDetector):
HELP = "Deprecated Solidity Standards"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH

LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards"

WIKI_TITLE = "Deprecated standards"
Expand Down
Loading

0 comments on commit 9e003ee

Please sign in to comment.