Merge branch 'dev' into dev-external-totalSupply

pull/713/head
Josselin 4 years ago
commit bddf143014
  1. 17
      slither/__main__.py
  2. 26
      slither/analyses/data_dependency/data_dependency.py
  3. 28
      slither/core/declarations/contract.py
  4. 44
      slither/core/declarations/function.py
  5. 57
      slither/core/dominators/utils.py
  6. 66
      slither/core/source_mapping/source_mapping.py
  7. 4
      slither/detectors/functions/arbitrary_send.py
  8. 3
      slither/detectors/functions/external_function.py
  9. 4
      slither/detectors/operations/block_timestamp.py
  10. 3
      slither/detectors/operations/low_level_calls.py
  11. 3
      slither/detectors/statements/assembly.py
  12. 79
      slither/detectors/statements/divide_before_multiply.py
  13. 14
      slither/tools/properties/properties/ercs/erc20/properties/transfer.py
  14. 11
      tests/test_ast_parsing.py

@ -1,13 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import cProfile
import glob import glob
import inspect import inspect
import json import json
import logging import logging
import os import os
import pstats
import sys import sys
import traceback import traceback
from typing import Optional
from pkg_resources import iter_entry_points, require from pkg_resources import iter_entry_points, require
@ -489,6 +492,10 @@ def parse_args(detector_classes, printer_classes):
default=defaults_flag_in_config["ignore_return_value"], default=defaults_flag_in_config["ignore_return_value"],
) )
parser.add_argument(
"--perf", help=argparse.SUPPRESS, action="store_true", default=False,
)
# if the json is splitted in different files # if the json is splitted in different files
parser.add_argument("--splitted", help=argparse.SUPPRESS, action="store_true", default=False) parser.add_argument("--splitted", help=argparse.SUPPRESS, action="store_true", default=False)
@ -598,6 +605,11 @@ def main_impl(all_detector_classes, all_printer_classes):
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
args = parse_args(all_detector_classes, all_printer_classes) args = parse_args(all_detector_classes, all_printer_classes)
cp: Optional[cProfile.Profile] = None
if args.perf:
cp = cProfile.Profile()
cp.enable()
# Set colorization option # Set colorization option
set_colorization_enabled(not args.disable_color) set_colorization_enabled(not args.disable_color)
@ -773,6 +785,11 @@ def main_impl(all_detector_classes, all_printer_classes):
if outputting_zip: if outputting_zip:
output_to_zip(args.zip, output_error, json_results, args.zip_type) output_to_zip(args.zip, output_error, json_results, args.zip_type)
if args.perf:
cp.disable()
stats = pstats.Stats(cp).sort_stats("cumtime")
stats.print_stats()
# Exit with the appropriate status code # Exit with the appropriate status code
if output_error: if output_error:
sys.exit(-1) sys.exit(-1)

@ -1,6 +1,7 @@
""" """
Compute the data depenency between all the SSA variables Compute the data depenency between all the SSA variables
""" """
from collections import defaultdict
from typing import Union, Set, Dict from typing import Union, Set, Dict
from slither.core.declarations import ( from slither.core.declarations import (
@ -297,18 +298,23 @@ def propagate_function(contract, function, context_key, context_key_non_ssa):
def transitive_close_dependencies(context, context_key, context_key_non_ssa): def transitive_close_dependencies(context, context_key, context_key_non_ssa):
# transitive closure # transitive closure
changed = True changed = True
while changed: # pylint: disable=too-many-nested-blocks keys = context.context[context_key].keys()
while changed:
changed = False changed = False
# Need to create new set() as its changed during iteration to_add = defaultdict(set)
data_depencencies = {k: set(values) for k, values in context.context[context_key].items()} [ # pylint: disable=expression-not-assigned
for key, items in data_depencencies.items(): [
for item in items: to_add[key].update(context.context[context_key][item] - {key} - items)
if item in data_depencencies: for item in items & keys
additional_items = context.context[context_key][item] ]
for additional_item in additional_items: for key, items in context.context[context_key].items()
if not additional_item in items and additional_item != key: ]
for k, v in to_add.items():
# Because we dont have any check on the update operation
# We might update an empty set with an empty set
if v:
changed = True changed = True
context.context[context_key][key].add(additional_item) context.context[context_key][k] |= v
context.context[context_key_non_ssa] = convert_to_non_ssa(context.context[context_key]) context.context[context_key_non_ssa] = convert_to_non_ssa(context.context[context_key])

@ -71,12 +71,15 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public-
self._is_upgradeable: Optional[bool] = None self._is_upgradeable: Optional[bool] = None
self._is_upgradeable_proxy: Optional[bool] = None self._is_upgradeable_proxy: Optional[bool] = None
self._is_top_level = False self.is_top_level = False # heavily used, so no @property
self._initial_state_variables: List["StateVariable"] = [] # ssa self._initial_state_variables: List["StateVariable"] = [] # ssa
self._is_incorrectly_parsed: bool = False self._is_incorrectly_parsed: bool = False
self._available_functions_as_dict: Optional[Dict[str, "Function"]] = None
self._all_functions_called: Optional[List["InternalCallType"]] = None
################################################################################### ###################################################################################
################################################################################### ###################################################################################
# region General's properties # region General's properties
@ -392,7 +395,11 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public-
return list(self._functions.values()) return list(self._functions.values())
def available_functions_as_dict(self) -> Dict[str, "Function"]: def available_functions_as_dict(self) -> Dict[str, "Function"]:
return {f.full_name: f for f in self._functions.values() if not f.is_shadowed} if self._available_functions_as_dict is None:
self._available_functions_as_dict = {
f.full_name: f for f in self._functions.values() if not f.is_shadowed
}
return self._available_functions_as_dict
def add_function(self, func: "Function"): def add_function(self, func: "Function"):
self._functions[func.canonical_name] = func self._functions[func.canonical_name] = func
@ -731,6 +738,7 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public-
list(Function): List of functions reachable from the contract list(Function): List of functions reachable from the contract
Includes super, and private/internal functions not shadowed Includes super, and private/internal functions not shadowed
""" """
if self._all_functions_called is None:
all_functions = [f for f in self.functions + self.modifiers if not f.is_shadowed] # type: ignore all_functions = [f for f in self.functions + self.modifiers if not f.is_shadowed] # type: ignore
all_callss = [f.all_internal_calls() for f in all_functions] + [list(all_functions)] all_callss = [f.all_internal_calls() for f in all_functions] + [list(all_functions)]
all_calls = [item for sublist in all_callss for item in sublist] all_calls = [item for sublist in all_callss for item in sublist]
@ -741,7 +749,8 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public-
set_all_calls = set(all_calls + list(all_constructors)) set_all_calls = set(all_calls + list(all_constructors))
return [c for c in set_all_calls if isinstance(c, Function)] self._all_functions_called = [c for c in set_all_calls if isinstance(c, Function)]
return self._all_functions_called
@property @property
def all_state_variables_written(self) -> List["StateVariable"]: def all_state_variables_written(self) -> List["StateVariable"]:
@ -1200,19 +1209,6 @@ class Contract(ChildSlither, SourceMapping): # pylint: disable=too-many-public-
for func in self.functions + self.modifiers: for func in self.functions + self.modifiers:
func.fix_phi(last_state_variables_instances, initial_state_variables_instances) func.fix_phi(last_state_variables_instances, initial_state_variables_instances)
@property
def is_top_level(self) -> bool:
"""
The "TopLevel" contract is used to hold structures and enums defined at the top level
ie. structures and enums that are represented outside of any contract
:return:
"""
return self._is_top_level
@is_top_level.setter
def is_top_level(self, t: bool):
self._is_top_level = t
# endregion # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################

@ -197,6 +197,15 @@ class Function(
self._counter_nodes = 0 self._counter_nodes = 0
# Memoize parameters:
# TODO: identify all the memoize parameters and add a way to undo the memoization
self._full_name: Optional[str] = None
self._signature: Optional[Tuple[str, List[str], List[str]]] = None
self._solidity_signature: Optional[str] = None
self._signature_str: Optional[str] = None
self._canonical_name: Optional[str] = None
self._is_protected: Optional[bool] = None
################################################################################### ###################################################################################
################################################################################### ###################################################################################
# region General properties # region General properties
@ -244,8 +253,11 @@ class Function(
str: func_name(type1,type2) str: func_name(type1,type2)
Return the function signature without the return values Return the function signature without the return values
""" """
if self._full_name is None:
name, parameters, _ = self.signature name, parameters, _ = self.signature
return ".".join(self._scope + [name]) + "(" + ",".join(parameters) + ")" full_name = ".".join(self._scope + [name]) + "(" + ",".join(parameters) + ")"
self._full_name = full_name
return self._full_name
@property @property
def canonical_name(self) -> str: def canonical_name(self) -> str:
@ -253,13 +265,15 @@ class Function(
str: contract.func_name(type1,type2) str: contract.func_name(type1,type2)
Return the function signature without the return values Return the function signature without the return values
""" """
if self._canonical_name is None:
name, parameters, _ = self.signature name, parameters, _ = self.signature
return ( self._canonical_name = (
".".join([self.contract_declarer.name] + self._scope + [name]) ".".join([self.contract_declarer.name] + self._scope + [name])
+ "(" + "("
+ ",".join(parameters) + ",".join(parameters)
+ ")" + ")"
) )
return self._canonical_name
@property @property
def contains_assembly(self) -> bool: def contains_assembly(self) -> bool:
@ -921,8 +935,12 @@ class Function(
Contract and converted into address Contract and converted into address
:return: the solidity signature :return: the solidity signature
""" """
parameters = [self._convert_type_for_solidity_signature(x.type) for x in self.parameters] if self._solidity_signature is None:
return self.name + "(" + ",".join(parameters) + ")" parameters = [
self._convert_type_for_solidity_signature(x.type) for x in self.parameters
]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
return self._solidity_signature
@property @property
def signature(self) -> Tuple[str, List[str], List[str]]: def signature(self) -> Tuple[str, List[str], List[str]]:
@ -930,11 +948,14 @@ class Function(
(str, list(str), list(str)): Function signature as (str, list(str), list(str)): Function signature as
(name, list parameters type, list return values type) (name, list parameters type, list return values type)
""" """
return ( if self._signature is None:
signature = (
self.name, self.name,
[str(x.type) for x in self.parameters], [str(x.type) for x in self.parameters],
[str(x.type) for x in self.returns], [str(x.type) for x in self.returns],
) )
self._signature = signature
return self._signature
@property @property
def signature_str(self) -> str: def signature_str(self) -> str:
@ -942,8 +963,12 @@ class Function(
str: func_name(type1,type2) returns (type3) str: func_name(type1,type2) returns (type3)
Return the function signature as a str (contains the return values) Return the function signature as a str (contains the return values)
""" """
if self._signature_str is None:
name, parameters, returnVars = self.signature name, parameters, returnVars = self.signature
return name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")" self._signature_str = (
name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")"
)
return self._signature_str
# endregion # endregion
################################################################################### ###################################################################################
@ -1407,11 +1432,16 @@ class Function(
(bool) (bool)
""" """
if self._is_protected is None:
if self.is_constructor: if self.is_constructor:
self._is_protected = True
return True return True
conditional_vars = self.all_conditional_solidity_variables_read(include_loop=False) conditional_vars = self.all_conditional_solidity_variables_read(include_loop=False)
args_vars = self.all_solidity_variables_used_as_args() args_vars = self.all_solidity_variables_used_as_args()
return SolidityVariableComposed("msg.sender") in conditional_vars + args_vars self._is_protected = (
SolidityVariableComposed("msg.sender") in conditional_vars + args_vars
)
return self._is_protected
# endregion # endregion
################################################################################### ###################################################################################

@ -15,18 +15,9 @@ def intersection_predecessor(node: "Node"):
return ret return ret
def compute_dominators(nodes: List["Node"]): def _compute_dominators(nodes: List["Node"]):
"""
Naive implementation of Cooper, Harvey, Kennedy algo
See 'A Simple,Fast Dominance Algorithm'
Compute strict domniators
"""
changed = True changed = True
for n in nodes:
n.dominators = set(nodes)
while changed: while changed:
changed = False changed = False
@ -36,20 +27,30 @@ def compute_dominators(nodes: List["Node"]):
node.dominators = new_set node.dominators = new_set
changed = True changed = True
# compute immediate dominator
def _compute_immediate_dominators(nodes: List["Node"]):
for node in nodes: for node in nodes:
idom_candidates = set(node.dominators) idom_candidates = set(node.dominators)
idom_candidates.remove(node) idom_candidates.remove(node)
for dominator in node.dominators: if len(idom_candidates) == 1:
if dominator != node: idom = idom_candidates.pop()
# pylint: disable=expression-not-assigned node.immediate_dominator = idom
[ idom.dominator_successors.add(node)
idom_candidates.remove(d) continue
for d in dominator.dominators
if d in idom_candidates and d != dominator # all_dominators contain all the dominators of all the node's dominators
] # But self inclusion is removed
# The idom is then the only node that in idom_candidate that is not in all_dominators
all_dominators = set()
for d in idom_candidates:
# optimization: if a node is already in all_dominators, then
# its dominators are already in too
if d in all_dominators:
continue
all_dominators |= d.dominators - {d}
idom_candidates = all_dominators.symmetric_difference(idom_candidates)
assert len(idom_candidates) <= 1 assert len(idom_candidates) <= 1
if idom_candidates: if idom_candidates:
idom = idom_candidates.pop() idom = idom_candidates.pop()
@ -57,6 +58,22 @@ def compute_dominators(nodes: List["Node"]):
idom.dominator_successors.add(node) idom.dominator_successors.add(node)
def compute_dominators(nodes: List["Node"]):
"""
Naive implementation of Cooper, Harvey, Kennedy algo
See 'A Simple,Fast Dominance Algorithm'
Compute strict domniators
"""
for n in nodes:
n.dominators = set(nodes)
_compute_dominators(nodes)
_compute_immediate_dominators(nodes)
def compute_dominance_frontier(nodes: List["Node"]): def compute_dominance_frontier(nodes: List["Node"]):
""" """
Naive implementation of Cooper, Harvey, Kennedy algo Naive implementation of Cooper, Harvey, Kennedy algo

@ -1,5 +1,5 @@
import re import re
from typing import Dict, Union, Optional from typing import Dict, Union, Optional, List, Tuple
from slither.core.context.context import Context from slither.core.context.context import Context
@ -9,56 +9,36 @@ class SourceMapping(Context):
super().__init__() super().__init__()
# TODO create a namedtuple for the source mapping rather than a dict # TODO create a namedtuple for the source mapping rather than a dict
self._source_mapping: Optional[Dict] = None self._source_mapping: Optional[Dict] = None
# self._start: Optional[int] = None
# self._length: Optional[int] = None
# self._filename_used: Optional[str] = None
# self._filename_relative: Optional[str] = None
# self._filename_absolute: Optional[str] = None
# self._filename_short: Optional[str] = None
# self._is_dependency: Optional[bool] = None
# self._lines: Optional[List[int]] = None
# self._starting_column: Optional[int] = None
# self._ending_column: Optional[int] = None
@property @property
def source_mapping(self) -> Optional[Dict]: def source_mapping(self) -> Optional[Dict]:
return self._source_mapping return self._source_mapping
@staticmethod @staticmethod
def _compute_line(source_code, start, length): def _compute_line(slither, filename, start: int, length: int) -> Tuple[List[int], int, int]:
""" """
Compute line(s) numbers and starting/ending columns Compute line(s) numbers and starting/ending columns
from a start/end offset. All numbers start from 1. from a start/end offset. All numbers start from 1.
Not done in an efficient way Not done in an efficient way
""" """
source_code = source_code.encode("utf-8") start_line, starting_column = slither.crytic_compile.get_line_from_offset(filename, start)
total_length = len(source_code) end_line, ending_column = slither.crytic_compile.get_line_from_offset(
source_code = source_code.splitlines(True) filename, start + length
counter = 0 )
i = 0 return list(range(start_line, end_line + 1)), starting_column, ending_column
lines = []
starting_column = None
ending_column = None
while counter < total_length:
# Determine the length of the line, and advance the line number
line_content = source_code[i]
line_length = len(line_content)
i = i + 1
# Determine our column numbers.
if starting_column is None and counter + line_length > start:
starting_column = (start - counter) + 1
if (
starting_column is not None
and ending_column is None
and counter + line_length > start + length
):
ending_column = ((start + length) - counter) + 1
# Advance the current position counter, and determine line numbers.
counter += line_length
if counter > start:
lines.append(i)
# If our advanced position for the next line is out of range, stop.
if counter > start + length:
break
return lines, starting_column, ending_column def _convert_source_mapping(self, offset: str, slither): # pylint: disable=too-many-locals
@staticmethod
def _convert_source_mapping(offset: str, slither): # pylint: disable=too-many-locals
""" """
Convert a text offset to a real offset Convert a text offset to a real offset
see https://solidity.readthedocs.io/en/develop/miscellaneous.html#source-mappings see https://solidity.readthedocs.io/en/develop/miscellaneous.html#source-mappings
@ -85,8 +65,6 @@ class SourceMapping(Context):
is_dependency = False is_dependency = False
lines = []
# If possible, convert the filename to its absolute/relative version # If possible, convert the filename to its absolute/relative version
if slither.crytic_compile: if slither.crytic_compile:
filenames = slither.crytic_compile.filename_lookup(filename_used) filenames = slither.crytic_compile.filename_lookup(filename_used)
@ -110,12 +88,8 @@ class SourceMapping(Context):
else: else:
filename = filename_used filename = filename_used
if slither.crytic_compile and filename in slither.crytic_compile.src_content: if slither.crytic_compile:
source_code = slither.crytic_compile.src_content[filename] (lines, starting_column, ending_column) = self._compute_line(slither, filename, s, l)
(lines, starting_column, ending_column) = SourceMapping._compute_line(source_code, s, l)
elif filename in slither.source_code:
source_code = slither.source_code[filename]
(lines, starting_column, ending_column) = SourceMapping._compute_line(source_code, s, l)
else: else:
(lines, starting_column, ending_column) = ([], None, None) (lines, starting_column, ending_column) = ([], None, None)

@ -117,6 +117,10 @@ Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract
info = [func, " sends eth to arbitrary user\n"] info = [func, " sends eth to arbitrary user\n"]
info += ["\tDangerous calls:\n"] info += ["\tDangerous calls:\n"]
# sort the nodes to get deterministic results
nodes.sort(key=lambda x: x.node_id)
for node in nodes: for node in nodes:
info += ["\t- ", node, "\n"] info += ["\t- ", node, "\n"]

@ -195,6 +195,9 @@ class ExternalFunction(AbstractDetector):
if f.visibility == "public" and f.contract == f.contract_declarer if f.visibility == "public" and f.contract == f.contract_declarer
] ]
if all_function_definitions: if all_function_definitions:
all_function_definitions = sorted(
all_function_definitions, key=lambda x: x.canonical_name
)
function_definition = all_function_definitions[0] function_definition = all_function_definitions[0]
all_function_definitions = all_function_definitions[1:] all_function_definitions = all_function_definitions[1:]

@ -78,6 +78,10 @@ class Timestamp(AbstractDetector):
info = [func, " uses timestamp for comparisons\n"] info = [func, " uses timestamp for comparisons\n"]
info += ["\tDangerous comparisons:\n"] info += ["\tDangerous comparisons:\n"]
# sort the nodes to get deterministic results
nodes.sort(key=lambda x: x.node_id)
for node in nodes: for node in nodes:
info += ["\t- ", node, "\n"] info += ["\t- ", node, "\n"]

@ -48,6 +48,9 @@ class LowLevelCalls(AbstractDetector):
for func, nodes in values: for func, nodes in values:
info = ["Low level call in ", func, ":\n"] info = ["Low level call in ", func, ":\n"]
# sort the nodes to get deterministic results
nodes.sort(key=lambda x: x.node_id)
for node in nodes: for node in nodes:
info += ["\t- ", node, "\n"] info += ["\t- ", node, "\n"]

@ -50,6 +50,9 @@ class Assembly(AbstractDetector):
for func, nodes in values: for func, nodes in values:
info = [func, " uses assembly\n"] info = [func, " uses assembly\n"]
# sort the nodes to get deterministic results
nodes.sort(key=lambda x: x.node_id)
for node in nodes: for node in nodes:
info += ["\t- ", node, "\n"] info += ["\t- ", node, "\n"]

@ -50,37 +50,13 @@ def is_assert(node):
return False return False
class DivideBeforeMultiply(AbstractDetector): def _explore(to_explore, f_results, divisions): # pylint: disable=too-many-branches
""" explored = set()
Divide before multiply while to_explore: # pylint: disable=too-many-nested-blocks
""" node = to_explore.pop()
ARGUMENT = "divide-before-multiply"
HELP = "Imprecise arithmetic operations order"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply"
WIKI_TITLE = "Divide before multiply"
WIKI_DESCRIPTION = """Solidity integer division might truncate. As a result, performing multiplication before division can sometimes avoid loss of precision."""
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
function f(uint n) public {
coins = (oldSupply / n) * interest;
}
}
```
If `n` is greater than `oldSupply`, `coins` will be zero. For example, with `oldSupply = 5; n = 10, interest = 2`, coins will be zero.
If `(oldSupply * interest / n)` was used, `coins` would have been `1`.
In general, it's usually a good idea to re-arrange arithmetic to perform multiplication before division, unless the limit of a smaller type makes this dangerous."""
WIKI_RECOMMENDATION = """Consider ordering multiplication before division."""
def _explore(self, node, explored, f_results, divisions): # pylint: disable=too-many-branches
if node in explored: if node in explored:
return continue
explored.add(node) explored.add(node)
equality_found = False equality_found = False
@ -121,15 +97,14 @@ In general, it's usually a good idea to re-arrange arithmetic to perform multipl
# We do not track the case where the multiplication is done in a require() or assert() # We do not track the case where the multiplication is done in a require() or assert()
# Which also contains a ==, to prevent FP due to the form # Which also contains a ==, to prevent FP due to the form
# assert(a == b * c + a % b) # assert(a == b * c + a % b)
if is_assert(node) and equality_found: if not (is_assert(node) and equality_found):
pass
else:
f_results.append(node_results) f_results.append(node_results)
for son in node.sons: for son in node.sons:
self._explore(son, explored, f_results, divisions) to_explore.add(son)
def detect_divide_before_multiply(self, contract): def detect_divide_before_multiply(contract):
""" """
Detects and returns all nodes with multiplications of division results. Detects and returns all nodes with multiplications of division results.
:param contract: Contract to detect assignment within. :param contract: Contract to detect assignment within.
@ -155,7 +130,7 @@ In general, it's usually a good idea to re-arrange arithmetic to perform multipl
# track all the division results (and the assignment of the division results) # track all the division results (and the assignment of the division results)
divisions = defaultdict(list) divisions = defaultdict(list)
self._explore(function.entry_point, set(), f_results, divisions) _explore({function.entry_point}, f_results, divisions)
for f_result in f_results: for f_result in f_results:
results.append((function, f_result)) results.append((function, f_result))
@ -163,13 +138,42 @@ In general, it's usually a good idea to re-arrange arithmetic to perform multipl
# Return the resulting set of nodes with divisions before multiplications # Return the resulting set of nodes with divisions before multiplications
return results return results
class DivideBeforeMultiply(AbstractDetector):
"""
Divide before multiply
"""
ARGUMENT = "divide-before-multiply"
HELP = "Imprecise arithmetic operations order"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply"
WIKI_TITLE = "Divide before multiply"
WIKI_DESCRIPTION = """Solidity integer division might truncate. As a result, performing multiplication before division can sometimes avoid loss of precision."""
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
function f(uint n) public {
coins = (oldSupply / n) * interest;
}
}
```
If `n` is greater than `oldSupply`, `coins` will be zero. For example, with `oldSupply = 5; n = 10, interest = 2`, coins will be zero.
If `(oldSupply * interest / n)` was used, `coins` would have been `1`.
In general, it's usually a good idea to re-arrange arithmetic to perform multiplication before division, unless the limit of a smaller type makes this dangerous."""
WIKI_RECOMMENDATION = """Consider ordering multiplication before division."""
def _detect(self): def _detect(self):
""" """
Detect divisions before multiplications Detect divisions before multiplications
""" """
results = [] results = []
for contract in self.contracts: for contract in self.contracts:
divisions_before_multiplications = self.detect_divide_before_multiply(contract) divisions_before_multiplications = detect_divide_before_multiply(contract)
if divisions_before_multiplications: if divisions_before_multiplications:
for (func, nodes) in divisions_before_multiplications: for (func, nodes) in divisions_before_multiplications:
@ -178,6 +182,9 @@ In general, it's usually a good idea to re-arrange arithmetic to perform multipl
" performs a multiplication on the result of a division:\n", " performs a multiplication on the result of a division:\n",
] ]
# sort the nodes to get deterministic results
nodes.sort(key=lambda x: x.node_id)
for node in nodes: for node in nodes:
info += ["\t-", node, "\n"] info += ["\t-", node, "\n"]

@ -57,7 +57,7 @@ ERC20_Transferable = [
), ),
Property( Property(
name="crytic_revert_transfer_to_zero_ERC20PropertiesTransferable()", name="crytic_revert_transfer_to_zero_ERC20PropertiesTransferable()",
description="No one should be able to send tokens to the address 0x0 (transfer).", description="Using transfer to send tokens to the address 0x0 will revert.",
content=""" content="""
\t\tif (this.balanceOf(msg.sender) == 0){ \t\tif (this.balanceOf(msg.sender) == 0){
\t\t\trevert(); \t\t\trevert();
@ -71,7 +71,7 @@ ERC20_Transferable = [
), ),
Property( Property(
name="crytic_revert_transferFrom_to_zero_ERC20PropertiesTransferable()", name="crytic_revert_transferFrom_to_zero_ERC20PropertiesTransferable()",
description="No one should be able to send tokens to the address 0x0 (transferFrom).", description="Using transferFrom to send tokens to the address 0x0 will revert.",
content=""" content="""
\t\tuint balance = this.balanceOf(msg.sender); \t\tuint balance = this.balanceOf(msg.sender);
\t\tif (balance == 0){ \t\tif (balance == 0){
@ -87,7 +87,7 @@ ERC20_Transferable = [
), ),
Property( Property(
name="crytic_self_transferFrom_ERC20PropertiesTransferable()", name="crytic_self_transferFrom_ERC20PropertiesTransferable()",
description="Self transferFrom works.", description="Self transfering tokens using transferFrom works as expected.",
content=""" content="""
\t\tuint balance = this.balanceOf(msg.sender); \t\tuint balance = this.balanceOf(msg.sender);
\t\tbool approve_return = approve(msg.sender, balance); \t\tbool approve_return = approve(msg.sender, balance);
@ -101,7 +101,7 @@ ERC20_Transferable = [
), ),
Property( Property(
name="crytic_self_transferFrom_to_other_ERC20PropertiesTransferable()", name="crytic_self_transferFrom_to_other_ERC20PropertiesTransferable()",
description="transferFrom works.", description="Transfering tokens to other address using transferFrom works as expected.",
content=""" content="""
\t\tuint balance = this.balanceOf(msg.sender); \t\tuint balance = this.balanceOf(msg.sender);
\t\tbool approve_return = approve(msg.sender, balance); \t\tbool approve_return = approve(msg.sender, balance);
@ -119,7 +119,7 @@ ERC20_Transferable = [
), ),
Property( Property(
name="crytic_self_transfer_ERC20PropertiesTransferable()", name="crytic_self_transfer_ERC20PropertiesTransferable()",
description="Self transfer works.", description="Self transfering tokens using transfer works as expected.",
content=""" content="""
\t\tuint balance = this.balanceOf(msg.sender); \t\tuint balance = this.balanceOf(msg.sender);
\t\tbool transfer_return = transfer(msg.sender, balance); \t\tbool transfer_return = transfer(msg.sender, balance);
@ -132,7 +132,7 @@ ERC20_Transferable = [
), ),
Property( Property(
name="crytic_transfer_to_other_ERC20PropertiesTransferable()", name="crytic_transfer_to_other_ERC20PropertiesTransferable()",
description="transfer works.", description="Transfering tokens to other address using transfer works as expected.",
content=""" content="""
\t\tuint balance = this.balanceOf(msg.sender); \t\tuint balance = this.balanceOf(msg.sender);
\t\taddress other = crytic_user; \t\taddress other = crytic_user;
@ -152,7 +152,7 @@ ERC20_Transferable = [
), ),
Property( Property(
name="crytic_revert_transfer_to_user_ERC20PropertiesTransferable()", name="crytic_revert_transfer_to_user_ERC20PropertiesTransferable()",
description="Cannot transfer more than the balance.", description="Transfering more tokens than the balance will revert.",
content=""" content="""
\t\tuint balance = this.balanceOf(msg.sender); \t\tuint balance = this.balanceOf(msg.sender);
\t\tif (balance == (2 ** 256 - 1)) \t\tif (balance == (2 ** 256 - 1))

@ -84,6 +84,8 @@ XFAIL = [
"function_0.7.3_compact", "function_0.7.3_compact",
"function_0.7.4_legacy", "function_0.7.4_legacy",
"function_0.7.4_compact", "function_0.7.4_compact",
"function_0.7.5_legacy",
"function_0.7.5_compact",
"import_0.4.0_legacy", "import_0.4.0_legacy",
"import_0.4.1_legacy", "import_0.4.1_legacy",
"import_0.4.2_legacy", "import_0.4.2_legacy",
@ -198,6 +200,8 @@ XFAIL = [
"import_0.7.3_compact", "import_0.7.3_compact",
"import_0.7.4_legacy", "import_0.7.4_legacy",
"import_0.7.4_compact", "import_0.7.4_compact",
"import_0.7.5_legacy",
"import_0.7.5_compact",
"indexrangeaccess_0.6.1_legacy", "indexrangeaccess_0.6.1_legacy",
"indexrangeaccess_0.6.2_legacy", "indexrangeaccess_0.6.2_legacy",
"indexrangeaccess_0.6.3_legacy", "indexrangeaccess_0.6.3_legacy",
@ -215,6 +219,7 @@ XFAIL = [
"indexrangeaccess_0.7.2_legacy", "indexrangeaccess_0.7.2_legacy",
"indexrangeaccess_0.7.3_legacy", "indexrangeaccess_0.7.3_legacy",
"indexrangeaccess_0.7.4_legacy", "indexrangeaccess_0.7.4_legacy",
"indexrangeaccess_0.7.5_legacy",
"literal_0.7.0_legacy", "literal_0.7.0_legacy",
"literal_0.7.0_compact", "literal_0.7.0_compact",
"literal_0.7.1_legacy", "literal_0.7.1_legacy",
@ -225,6 +230,8 @@ XFAIL = [
"literal_0.7.3_compact", "literal_0.7.3_compact",
"literal_0.7.4_legacy", "literal_0.7.4_legacy",
"literal_0.7.4_compact", "literal_0.7.4_compact",
"literal_0.7.5_legacy",
"literal_0.7.5_compact",
"memberaccess_0.6.8_legacy", "memberaccess_0.6.8_legacy",
"memberaccess_0.6.9_legacy", "memberaccess_0.6.9_legacy",
"memberaccess_0.6.10_legacy", "memberaccess_0.6.10_legacy",
@ -251,6 +258,7 @@ XFAIL = [
"struct_0.7.2_legacy", "struct_0.7.2_legacy",
"struct_0.7.3_legacy", "struct_0.7.3_legacy",
"struct_0.7.4_legacy", "struct_0.7.4_legacy",
"struct_0.7.5_legacy",
"trycatch_0.6.0_legacy", "trycatch_0.6.0_legacy",
"trycatch_0.6.1_legacy", "trycatch_0.6.1_legacy",
"trycatch_0.6.2_legacy", "trycatch_0.6.2_legacy",
@ -269,6 +277,7 @@ XFAIL = [
"trycatch_0.7.2_legacy", "trycatch_0.7.2_legacy",
"trycatch_0.7.3_legacy", "trycatch_0.7.3_legacy",
"trycatch_0.7.4_legacy", "trycatch_0.7.4_legacy",
"trycatch_0.7.5_legacy",
"variable_0.6.5_legacy", "variable_0.6.5_legacy",
"variable_0.6.5_compact", "variable_0.6.5_compact",
"variable_0.6.6_legacy", "variable_0.6.6_legacy",
@ -367,6 +376,7 @@ XFAIL = [
"variabledeclaration_0.7.2_legacy", "variabledeclaration_0.7.2_legacy",
"variabledeclaration_0.7.3_legacy", "variabledeclaration_0.7.3_legacy",
"variabledeclaration_0.7.4_legacy", "variabledeclaration_0.7.4_legacy",
"variabledeclaration_0.7.5_legacy",
"yul_0.6.0_compact", "yul_0.6.0_compact",
"yul_0.6.1_compact", "yul_0.6.1_compact",
"yul_0.6.2_compact", "yul_0.6.2_compact",
@ -385,6 +395,7 @@ XFAIL = [
"yul_0.7.2_compact", "yul_0.7.2_compact",
"yul_0.7.3_compact", "yul_0.7.3_compact",
"yul_0.7.4_compact", "yul_0.7.4_compact",
"yul_0.7.5_compact",
] ]

Loading…
Cancel
Save