Merge branch 'dev' into dev-reworked-fail-on

pull/1462/head
Feist Josselin 1 year ago committed by GitHub
commit a96cb8f528
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .github/dependabot.yml
  2. 54
      .github/workflows/publish.yml
  3. 2
      CONTRIBUTING.md
  4. 4
      slither/core/declarations/solidity_variables.py
  5. 13
      slither/detectors/compiler_bugs/array_by_reference.py
  6. 2
      slither/detectors/statements/msg_value_in_loop.py
  7. 12
      slither/detectors/variables/similar_variables.py
  8. 17
      slither/printers/guidance/echidna.py
  9. 2
      slither/solc_parsing/expressions/expression_parsing.py
  10. 18
      slither/solc_parsing/slither_compilation_unit_solc.py
  11. 3
      slither/solc_parsing/yul/evm_functions.py
  12. 33
      slither/solc_parsing/yul/parse_yul.py
  13. 7
      slither/tools/read_storage/__main__.py
  14. 315
      slither/tools/read_storage/read_storage.py
  15. 2
      slither/tools/read_storage/utils/utils.py
  16. 4
      slither/utils/integer_conversion.py
  17. 71
      slither/visitors/expression/constants_folding.py
  18. 6
      tests/conftest.py
  19. 14
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_4_25_array_by_reference_sol__0.txt
  20. 14
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_5_16_array_by_reference_sol__0.txt
  21. 14
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_6_11_array_by_reference_sol__0.txt
  22. 14
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_7_6_array_by_reference_sol__0.txt
  23. 21
      tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol
  24. BIN
      tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol-0.4.25.zip
  25. 21
      tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol
  26. BIN
      tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol-0.5.16.zip
  27. 21
      tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol
  28. BIN
      tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol-0.6.11.zip
  29. 21
      tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol
  30. BIN
      tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol-0.7.6.zip
  31. 5
      tests/e2e/solc_parsing/test_ast_parsing.py
  32. 12
      tests/e2e/solc_parsing/test_data/assembly-functions.sol
  33. BIN
      tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.6.9-compact.zip
  34. BIN
      tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.6.9-legacy.zip
  35. BIN
      tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.7.6-compact.zip
  36. BIN
      tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.7.6-legacy.zip
  37. BIN
      tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.8.16-compact.zip
  38. BIN
      tests/e2e/solc_parsing/test_data/compile/global_variables-0.8.18.sol-0.8.18-compact.zip
  39. 12
      tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-compact.json
  40. 5
      tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-legacy.json
  41. 12
      tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-compact.json
  42. 5
      tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-legacy.json
  43. 12
      tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.8.16-compact.json
  44. 6
      tests/e2e/solc_parsing/test_data/expected/global_variables-0.8.18.sol-0.8.18-compact.json
  45. 11
      tests/e2e/solc_parsing/test_data/global_variables-0.8.18.sol
  46. 0
      tests/tools/check_erc/erc20.sol
  47. 0
      tests/tools/check_erc/test_1.txt
  48. 0
      tests/tools/check_kspec/safeAdd/safeAdd.sol
  49. 0
      tests/tools/check_kspec/safeAdd/spec.md
  50. 0
      tests/tools/check_kspec/test_1.txt
  51. 0
      tests/tools/check_upgradeability/contractV1.sol
  52. 0
      tests/tools/check_upgradeability/contractV1_struct.sol
  53. 0
      tests/tools/check_upgradeability/contractV2.sol
  54. 0
      tests/tools/check_upgradeability/contractV2_bug.sol
  55. 0
      tests/tools/check_upgradeability/contractV2_bug2.sol
  56. 0
      tests/tools/check_upgradeability/contractV2_struct.sol
  57. 0
      tests/tools/check_upgradeability/contractV2_struct_bug.sol
  58. 0
      tests/tools/check_upgradeability/contract_initialization.sol
  59. 0
      tests/tools/check_upgradeability/contract_v1_var_init.sol
  60. 0
      tests/tools/check_upgradeability/contract_v2_constant.sol
  61. 0
      tests/tools/check_upgradeability/proxy.sol
  62. 0
      tests/tools/check_upgradeability/test_1.txt
  63. 0
      tests/tools/check_upgradeability/test_10.txt
  64. 0
      tests/tools/check_upgradeability/test_11.txt
  65. 0
      tests/tools/check_upgradeability/test_12.txt
  66. 0
      tests/tools/check_upgradeability/test_13.txt
  67. 0
      tests/tools/check_upgradeability/test_2.txt
  68. 0
      tests/tools/check_upgradeability/test_3.txt
  69. 0
      tests/tools/check_upgradeability/test_4.txt
  70. 0
      tests/tools/check_upgradeability/test_5.txt
  71. 0
      tests/tools/check_upgradeability/test_6.txt
  72. 0
      tests/tools/check_upgradeability/test_7.txt
  73. 0
      tests/tools/check_upgradeability/test_8.txt
  74. 0
      tests/tools/check_upgradeability/test_9.txt
  75. 56
      tests/tools/read-storage/conftest.py
  76. 3
      tests/tools/read-storage/test_data/StorageLayout.sol
  77. 56
      tests/tools/read-storage/test_data/TEST_unstructured_storage.json
  78. 1
      tests/tools/read-storage/test_data/UnstructuredStorageLayout.abi
  79. 1
      tests/tools/read-storage/test_data/UnstructuredStorageLayout.bin
  80. 141
      tests/tools/read-storage/test_data/UnstructuredStorageLayout.sol
  81. 66
      tests/tools/read-storage/test_read_storage.py
  82. 59
      tests/unit/core/test_constant_folding.py
  83. 2
      tests/unit/core/test_data/constant_folding/constant_folding_binop.sol
  84. 2
      tests/unit/core/test_data/inheritance_resolution_error/contract_with_duplicate_names.sol
  85. 1
      tests/unit/core/test_data/inheritance_resolution_error/import.sol
  86. 18
      tests/unit/core/test_error_messages.py

@ -0,0 +1,8 @@
---
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "dev"
schedule:
interval: "weekly"

@ -0,0 +1,54 @@
name: Publish to PyPI
on:
release:
types: [published]
jobs:
build-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Build distributions
run: |
python -m pip install --upgrade pip
python -m pip install build
python -m build
- name: Upload distributions
uses: actions/upload-artifact@v3
with:
name: slither-dists
path: dist/
publish:
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write # For trusted publishing + codesigning.
contents: write # For attaching signing artifacts to the release.
needs:
- build-release
steps:
- name: fetch dists
uses: actions/download-artifact@v3
with:
name: slither-dists
path: dist/
- name: publish
uses: pypa/gh-action-pypi-publish@v1.8.6
- name: sign
uses: sigstore/gh-action-sigstore-python@v1.2.3
with:
inputs: ./dist/*.tar.gz ./dist/*.whl
release-signing-artifacts: true
bundle-only: true

@ -106,7 +106,7 @@ For each new detector, at least one regression tests must be present.
> - To run tests for a specific test case, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k user_defined_value_type` (the filename is the argument).
> - To run tests for a specific version, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k 0.8.12`.
> - To run tests for a specific compiler json format, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k legacy` (can be legacy or compact).
> - The IDs of tests can be inspected using ``pytest tests/e2e/solc_parsing/test_ast_parsing.py --collect-only`.
> - The IDs of tests can be inspected using `pytest tests/e2e/solc_parsing/test_ast_parsing.py --collect-only`.
### Synchronization with crytic-compile

@ -21,10 +21,11 @@ SOLIDITY_VARIABLES_COMPOSED = {
"block.basefee": "uint",
"block.coinbase": "address",
"block.difficulty": "uint256",
"block.prevrandao": "uint256",
"block.gaslimit": "uint256",
"block.number": "uint256",
"block.timestamp": "uint256",
"block.blockhash": "uint256", # alias for blockhash. It's a call
"block.blockhash": "bytes32", # alias for blockhash. It's a call
"block.chainid": "uint256",
"msg.data": "bytes",
"msg.gas": "uint256",
@ -60,6 +61,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"log2(bytes32,bytes32,bytes32)": [],
"log3(bytes32,bytes32,bytes32,bytes32)": [],
"blockhash(uint256)": ["bytes32"],
"prevrandao()": ["uint256"],
# the following need a special handling
# as they are recognized as a SolidityVariableComposed
# and converted to a SolidityFunction by SlithIR

@ -133,7 +133,7 @@ As a result, Bob's usage of the contract is incorrect."""
continue
# Verify one of these parameters is an array in storage.
for arg in ir.arguments:
for (param, arg) in zip(ir.function.parameters, ir.arguments):
# Verify this argument is a variable that is an array type.
if not isinstance(arg, (StateVariable, LocalVariable)):
continue
@ -141,8 +141,11 @@ As a result, Bob's usage of the contract is incorrect."""
continue
# If it is a state variable OR a local variable referencing storage, we add it to the list.
if isinstance(arg, StateVariable) or (
isinstance(arg, LocalVariable) and arg.location == "storage"
if (
isinstance(arg, StateVariable)
or (isinstance(arg, LocalVariable) and arg.location == "storage")
) and (
isinstance(param.type, ArrayType) and param.location != "storage"
):
results.append((node, arg, ir.function))
return results
@ -165,9 +168,9 @@ As a result, Bob's usage of the contract is incorrect."""
calling_node.function,
" passes array ",
affected_argument,
"by reference to ",
" by reference to ",
invoked_function,
"which only takes arrays by value\n",
" which only takes arrays by value\n",
]
res = self.generate_result(info)

@ -79,7 +79,7 @@ contract MsgValueInLoop{
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = """
Track msg.value through a local variable and decrease its amount on every iteration/usage.
Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`.
"""
def _detect(self) -> List[Output]:

@ -65,12 +65,16 @@ class SimilarVarsDetection(AbstractDetector):
contract_var = contract.variables
all_var = set(all_var + contract_var)
all_var = list(set(all_var + contract_var))
ret = []
for v1 in all_var:
for v2 in all_var:
if v1.name.lower() != v2.name.lower():
# pylint: disable=consider-using-enumerate
for i in range(len(all_var)):
v1 = all_var[i]
_v1_name_lower = v1.name.lower()
for j in range(i, len(all_var)):
v2 = all_var[j]
if _v1_name_lower != v2.name.lower():
if SimilarVarsDetection.similar(v1.name, v2.name):
if (v2, v1) not in ret:
ret.append((v1, v2))

@ -32,7 +32,7 @@ from slither.slithir.operations import (
from slither.slithir.operations.binary import Binary
from slither.slithir.variables import Constant
from slither.utils.output import Output
from slither.visitors.expression.constants_folding import ConstantFolding
from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant
def _get_name(f: Union[Function, Variable]) -> str:
@ -178,11 +178,16 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n
all_cst_used_in_binary[str(ir.type)].append(
ConstantValue(str(r.value), str(r.type))
)
if isinstance(ir.variable_left, Constant) and isinstance(ir.variable_right, Constant):
if ir.lvalue:
type_ = ir.lvalue.type
cst = ConstantFolding(ir.expression, type_).result()
all_cst_used.append(ConstantValue(str(cst.value), str(type_)))
if isinstance(ir.variable_left, Constant) or isinstance(
ir.variable_right, Constant
):
if ir.lvalue:
try:
type_ = ir.lvalue.type
cst = ConstantFolding(ir.expression, type_).result()
all_cst_used.append(ConstantValue(str(cst.value), str(type_)))
except NotConstant:
pass
if isinstance(ir, TypeConversion):
if isinstance(ir.variable, Constant):
if isinstance(ir.type, TypeAlias):

@ -433,6 +433,8 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
type_candidate = ElementaryType("uint256")
else:
type_candidate = ElementaryType("string")
elif type_candidate.startswith("rational_const "):
type_candidate = ElementaryType("uint256")
elif type_candidate.startswith("int_const "):
type_candidate = ElementaryType("uint256")
elif type_candidate.startswith("bool"):

@ -33,6 +33,10 @@ logger = logging.getLogger("SlitherSolcParsing")
logger.setLevel(logging.INFO)
class InheritanceResolutionError(SlitherException):
pass
def _handle_import_aliases(
symbol_aliases: Dict, import_directive: Import, scope: FileScope
) -> None:
@ -194,6 +198,13 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
def parse_top_level_from_loaded_json(self, data_loaded: Dict, filename: str) -> None:
if not data_loaded or data_loaded is None:
logger.error(
"crytic-compile returned an empty AST. "
"If you are trying to analyze a contract from etherscan or similar make sure it has source code available."
)
return
if "nodeType" in data_loaded:
self._is_compact_ast = True
@ -432,7 +443,12 @@ Please rename it, this name is reserved for Slither's internals"""
target = contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_name
)
assert target
if target == contract_parser.underlying_contract:
raise InheritanceResolutionError(
"Could not resolve contract inheritance. This is likely caused by an import renaming that collides with existing names (see https://github.com/crytic/slither/issues/1758)."
f"\n Try changing `contract {target}` ({target.source_mapping}) to a unique name."
)
assert target, f"Contract {contract_name} not found"
ancestors.append(target)
elif i in self._contracts_by_id:
ancestors.append(self._contracts_by_id[i])

@ -51,6 +51,7 @@ evm_opcodes = [
"TIMESTAMP",
"NUMBER",
"DIFFICULTY",
"PREVRANDAO",
"GASLIMIT",
"CHAINID",
"SELFBALANCE",
@ -168,6 +169,7 @@ builtins = [
)
] + yul_funcs
# "identifier": [input_count, output_count]
function_args = {
"byte": [2, 1],
"addmod": [3, 1],
@ -221,6 +223,7 @@ function_args = {
"timestamp": [0, 1],
"number": [0, 1],
"difficulty": [0, 1],
"prevrandao": [0, 1],
"gaslimit": [0, 1],
}

@ -181,7 +181,7 @@ class YulScope(metaclass=abc.ABCMeta):
def add_yul_local_function(self, func: "YulFunction") -> None:
self._yul_local_functions.append(func)
def get_yul_local_function_from_name(self, func_name: str) -> Optional["YulLocalVariable"]:
def get_yul_local_function_from_name(self, func_name: str) -> Optional["YulFunction"]:
return next(
(v for v in self._yul_local_functions if v.underlying.name == func_name),
None,
@ -252,6 +252,10 @@ class YulFunction(YulScope):
def function(self) -> Function:
return self._function
@property
def root(self) -> YulScope:
return self._root
def convert_body(self) -> None:
node = self.new_node(NodeType.ENTRYPOINT, self._ast["src"])
link_underlying_nodes(self._entrypoint, node)
@ -271,6 +275,9 @@ class YulFunction(YulScope):
def parse_body(self) -> None:
for node in self._nodes:
node.analyze_expressions()
for f in self._yul_local_functions:
if f != self:
f.parse_body()
def new_node(self, node_type: NodeType, src: str) -> YulNode:
if self._function:
@ -325,7 +332,10 @@ class YulBlock(YulScope):
return yul_node
def convert(self, ast: Dict) -> YulNode:
return convert_yul(self, self._entrypoint, ast, self.node_scope)
yul_node = convert_yul(self, self._entrypoint, ast, self.node_scope)
for f in self._yul_local_functions:
f.parse_body()
return yul_node
def analyze_expressions(self) -> None:
for node in self._nodes:
@ -390,7 +400,6 @@ def convert_yul_function_definition(
root.add_yul_local_function(yul_function)
yul_function.convert_body()
yul_function.parse_body()
return parent
@ -778,6 +787,7 @@ def _parse_yul_magic_suffixes(name: str, root: YulScope) -> Optional[Expression]
return None
# pylint: disable=too-many-branches
def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[Expression]:
name = ast["name"]
@ -809,6 +819,23 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[
if func:
return Identifier(func.underlying)
# check yul-block scoped function
if isinstance(root, YulFunction):
yul_block = root.root
# Iterate until we searched in all the scopes until the YulBlock scope
while not isinstance(yul_block, YulBlock):
func = yul_block.get_yul_local_function_from_name(name)
if func:
return Identifier(func.underlying)
if isinstance(yul_block, YulFunction):
yul_block = yul_block.root
func = yul_block.get_yul_local_function_from_name(name)
if func:
return Identifier(func.underlying)
magic_suffix = _parse_yul_magic_suffixes(name, root)
if magic_suffix:
return magic_suffix

@ -104,6 +104,12 @@ def parse_args() -> argparse.Namespace:
default="latest",
)
parser.add_argument(
"--unstructured",
action="store_true",
help="Include unstructured storage slots",
)
cryticparser.init(parser)
return parser.parse_args()
@ -133,6 +139,7 @@ def main() -> None:
rpc_info = RpcInfo(args.rpc_url, block)
srs = SlitherReadStorage(contracts, args.max_depth, rpc_info)
srs.unstructured = bool(args.unstructured)
# Remove target prefix e.g. rinkeby:0x0 -> 0x0.
address = target[target.find(":") + 1 :]
# Default to implementation address unless a storage address is given.

@ -15,9 +15,21 @@ from web3.middleware import geth_poa_middleware
from slither.core.declarations import Contract, Structure
from slither.core.solidity_types import ArrayType, ElementaryType, MappingType, UserDefinedType
from slither.core.solidity_types.type import Type
from slither.core.cfg.node import NodeType
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.structure_variable import StructureVariable
from slither.core.expressions import (
AssignmentOperation,
Literal,
Identifier,
BinaryOperation,
UnaryOperation,
TupleExpression,
TypeConversion,
CallExpression,
)
from slither.utils.myprettytable import MyPrettyTable
from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant
from .utils import coerce_type, get_offset_value, get_storage_data
@ -72,7 +84,7 @@ class RpcInfo:
return self._block
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-instance-attributes,too-many-public-methods
class SlitherReadStorage:
def __init__(self, contracts: List[Contract], max_depth: int, rpc_info: RpcInfo = None) -> None:
self._checksum_address: Optional[ChecksumAddress] = None
@ -81,9 +93,11 @@ class SlitherReadStorage:
self._max_depth: int = max_depth
self._slot_info: Dict[str, SlotInfo] = {}
self._target_variables: List[Tuple[Contract, StateVariable]] = []
self._constant_storage_slots: List[Tuple[Contract, StateVariable]] = []
self.rpc_info: Optional[RpcInfo] = rpc_info
self.storage_address: Optional[str] = None
self.table: Optional[MyPrettyTable] = None
self.unstructured: bool = False
@property
def contracts(self) -> List[Contract]:
@ -114,6 +128,11 @@ class SlitherReadStorage:
"""Storage variables (not constant or immutable) and their associated contract."""
return self._target_variables
@property
def constant_slots(self) -> List[Tuple[Contract, StateVariable]]:
"""Constant bytes32 variables and their associated contract."""
return self._constant_storage_slots
@property
def slot_info(self) -> Dict[str, SlotInfo]:
"""Contains the location, type, size, offset, and value of contract slots."""
@ -133,9 +152,48 @@ class SlitherReadStorage:
elif isinstance(type_, ArrayType):
elems = self._all_array_slots(var, contract, type_, info.slot)
tmp[var.name].elems = elems
if self.unstructured:
tmp.update(self.get_unstructured_layout())
self._slot_info = tmp
def get_unstructured_layout(self) -> Dict[str, SlotInfo]:
tmp: Dict[str, SlotInfo] = {}
for _, var in self.constant_slots:
var_name = var.name
try:
exp = var.expression
if isinstance(
exp,
(
BinaryOperation,
UnaryOperation,
Identifier,
TupleExpression,
TypeConversion,
CallExpression,
),
):
exp = ConstantFolding(exp, "bytes32").result()
if isinstance(exp, Literal):
slot = coerce_type("int", exp.value)
else:
continue
offset = 0
type_string, size = self.find_constant_slot_storage_type(var)
if type_string:
tmp[var.name] = SlotInfo(
name=var_name, type_string=type_string, slot=slot, size=size, offset=offset
)
self.log += (
f"\nSlot Name: {var_name}\nType: bytes32"
f"\nStorage Type: {type_string}\nSlot: {str(exp)}\n"
)
logger.info(self.log)
self.log = ""
except NotConstant:
continue
return tmp
# TODO: remove this pylint exception (montyly)
# pylint: disable=too-many-locals
def get_storage_slot(
@ -144,7 +202,8 @@ class SlitherReadStorage:
contract: Contract,
**kwargs: Any,
) -> Union[SlotInfo, None]:
"""Finds the storage slot of a variable in a given contract.
"""
Finds the storage slot of a variable in a given contract.
Args:
target_variable (`StateVariable`): The variable to retrieve the slot for.
contracts (`Contract`): The contract that contains the given state variable.
@ -230,6 +289,78 @@ class SlitherReadStorage:
if slot_info:
self._slot_info[f"{contract.name}.{var.name}"] = slot_info
def find_constant_slot_storage_type(
self, var: StateVariable
) -> Tuple[Optional[str], Optional[int]]:
"""
Given a constant bytes32 StateVariable, tries to determine which variable type is stored there, using the
heuristic that if a function reads from the slot and returns a value, it probably stores that type of value.
Also uses the StorageSlot library as a heuristic when a function has no return but uses the library's getters.
Args:
var (StateVariable): The constant bytes32 storage slot.
Returns:
type (str): The type of value stored in the slot.
size (int): The type's size in bits.
"""
assert var.is_constant and var.type == ElementaryType("bytes32")
storage_type = None
size = None
funcs = []
for c in self.contracts:
c_funcs = c.get_functions_reading_from_variable(var)
c_funcs.extend(
f
for f in c.functions
if any(str(v.expression) == str(var.expression) for v in f.variables)
)
c_funcs = list(set(c_funcs))
funcs.extend(c_funcs)
fallback = [f for f in var.contract.functions if f.is_fallback]
funcs += fallback
for func in funcs:
rets = func.return_type if func.return_type is not None else []
for ret in rets:
size, _ = ret.storage_size
if size <= 32:
return str(ret), size * 8
for node in func.all_nodes():
exp = node.expression
# Look for use of the common OpenZeppelin StorageSlot library
if f"getAddressSlot({var.name})" in str(exp):
return "address", 160
if f"getBooleanSlot({var.name})" in str(exp):
return "bool", 1
if f"getBytes32Slot({var.name})" in str(exp):
return "bytes32", 256
if f"getUint256Slot({var.name})" in str(exp):
return "uint256", 256
# Look for variable assignment in assembly loaded from a hardcoded slot
if (
isinstance(exp, AssignmentOperation)
and isinstance(exp.expression_left, Identifier)
and isinstance(exp.expression_right, CallExpression)
and "sload" in str(exp.expression_right.called)
and str(exp.expression_right.arguments[0]) == str(var.expression)
):
if func.is_fallback:
return "address", 160
storage_type = exp.expression_left.value.type.name
size, _ = exp.expression_left.value.type.storage_size
return storage_type, size * 8
# Look for variable storage in assembly stored to a hardcoded slot
if (
isinstance(exp, CallExpression)
and "sstore" in str(exp.called)
and isinstance(exp.arguments[0], Identifier)
and isinstance(exp.arguments[1], Identifier)
and str(exp.arguments[0].value.expression) == str(var.expression)
):
storage_type = exp.arguments[1].value.type.name
size, _ = exp.arguments[1].value.type.storage_size
return storage_type, size * 8
return storage_type, size
def walk_slot_info(self, func: Callable) -> None:
stack = list(self.slot_info.values())
while stack:
@ -242,7 +373,8 @@ class SlitherReadStorage:
func(slot_info)
def get_slot_values(self, slot_info: SlotInfo) -> None:
"""Fetches the slot value of `SlotInfo` object
"""
Fetches the slot value of `SlotInfo` object
:param slot_info:
"""
assert self.rpc_info is not None
@ -257,25 +389,162 @@ class SlitherReadStorage:
)
logger.info(f"\nValue: {slot_info.value}\n")
def get_all_storage_variables(self, func: Callable = None) -> None:
"""Fetches all storage variables from a list of contracts.
def get_all_storage_variables(self, func: Callable = lambda x: x) -> None:
"""
Fetches all storage variables from a list of contracts.
kwargs:
func (Callable, optional): A criteria to filter functions e.g. name.
"""
for contract in self.contracts:
self._target_variables.extend(
filter(
func,
[
(contract, var)
for var in contract.state_variables_ordered
if not var.is_constant and not var.is_immutable
],
)
for var in contract.state_variables_ordered:
if func(var):
if not var.is_constant and not var.is_immutable:
self._target_variables.append((contract, var))
elif (
self.unstructured
and var.is_constant
and var.type == ElementaryType("bytes32")
):
self._constant_storage_slots.append((contract, var))
if self.unstructured:
hardcoded_slot = self.find_hardcoded_slot_in_fallback(contract)
if hardcoded_slot is not None:
self._constant_storage_slots.append((contract, hardcoded_slot))
def find_hardcoded_slot_in_fallback(self, contract: Contract) -> Optional[StateVariable]:
"""
Searches the contract's fallback function for a sload from a literal storage slot, i.e.,
`let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7)`.
Args:
contract: a Contract object, which should have a fallback function.
Returns:
A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None.
"""
fallback = None
for func in contract.functions_entry_points:
if func.is_fallback:
fallback = func
break
if fallback is None:
return None
queue = [fallback.entry_point]
visited = []
while len(queue) > 0:
node = queue.pop(0)
visited.append(node)
queue.extend(son for son in node.sons if son not in visited)
if node.type == NodeType.ASSEMBLY and isinstance(node.inline_asm, str):
return SlitherReadStorage.find_hardcoded_slot_in_asm_str(node.inline_asm, contract)
if node.type == NodeType.EXPRESSION:
sv = self.find_hardcoded_slot_in_exp(node.expression, contract)
if sv is not None:
return sv
return None
@staticmethod
def find_hardcoded_slot_in_asm_str(
inline_asm: str, contract: Contract
) -> Optional[StateVariable]:
"""
Searches a block of assembly code (given as a string) for a sload from a literal storage slot.
Does not work if the argument passed to sload does not start with "0x", i.e., `sload(add(1,1))`
or `and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)`.
Args:
inline_asm: a string containing all the code in an assembly node (node.inline_asm for solc < 0.6.0).
Returns:
A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None.
"""
asm_split = inline_asm.split("\n")
for asm in asm_split:
if "sload(" in asm: # Only handle literals
arg = asm.split("sload(")[1].split(")")[0]
if arg.startswith("0x"):
exp = Literal(arg, ElementaryType("bytes32"))
sv = StateVariable()
sv.name = "fallback_sload_hardcoded"
sv.expression = exp
sv.is_constant = True
sv.type = exp.type
sv.set_contract(contract)
return sv
return None
def find_hardcoded_slot_in_exp(
self, exp: "Expression", contract: Contract
) -> Optional[StateVariable]:
"""
Parses an expression to see if it contains a sload from a literal storage slot,
unrolling nested expressions if necessary to determine which slot it loads from.
Args:
exp: an Expression object to search.
contract: the Contract containing exp.
Returns:
A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None.
"""
if isinstance(exp, AssignmentOperation):
exp = exp.expression_right
while isinstance(exp, BinaryOperation):
exp = next(
(e for e in exp.expressions if isinstance(e, (CallExpression, BinaryOperation))),
exp.expression_left,
)
while isinstance(exp, CallExpression) and len(exp.arguments) > 0:
called = exp.called
exp = exp.arguments[0]
if "sload" in str(called):
break
if isinstance(
exp,
(
BinaryOperation,
UnaryOperation,
Identifier,
TupleExpression,
TypeConversion,
CallExpression,
),
):
try:
exp = ConstantFolding(exp, "bytes32").result()
except NotConstant:
return None
if (
isinstance(exp, Literal)
and isinstance(exp.type, ElementaryType)
and exp.type.name in ["bytes32", "uint256"]
):
sv = StateVariable()
sv.name = "fallback_sload_hardcoded"
value = exp.value
str_value = str(value)
if str_value.isdecimal():
value = int(value)
if isinstance(value, (int, bytes)):
if isinstance(value, bytes):
str_value = "0x" + value.hex()
value = int(str_value, 16)
exp = Literal(str_value, ElementaryType("bytes32"))
state_var_slots = [
self.get_variable_info(contract, var)[0]
for contract, var in self.target_variables
]
if value in state_var_slots:
return None
sv.expression = exp
sv.is_constant = True
sv.type = ElementaryType("bytes32")
sv.set_contract(contract)
return sv
return None
def convert_slot_info_to_rows(self, slot_info: SlotInfo) -> None:
"""Convert and append slot info to table. Create table if it
"""
Convert and append slot info to table. Create table if it
does not yet exist
:param slot_info:
"""
@ -293,7 +562,8 @@ class SlitherReadStorage:
def _find_struct_var_slot(
elems: List[StructureVariable], slot_as_bytes: bytes, struct_var: str
) -> Tuple[str, str, bytes, int, int]:
"""Finds the slot of a structure variable.
"""
Finds the slot of a structure variable.
Args:
elems (List[StructureVariable]): Ordered list of structure variables.
slot_as_bytes (bytes): The slot of the struct to begin searching at.
@ -335,7 +605,8 @@ class SlitherReadStorage:
deep_key: int = None,
struct_var: str = None,
) -> Tuple[str, str, bytes, int, int]:
"""Finds the slot of array's index.
"""
Finds the slot of array's index.
Args:
target_variable (`StateVariable`): The array that contains the target variable.
slot (bytes): The starting slot of the array.
@ -438,7 +709,8 @@ class SlitherReadStorage:
deep_key: Union[int, str] = None,
struct_var: str = None,
) -> Tuple[str, str, bytes, int, int]:
"""Finds the data slot of a target variable within a mapping.
"""
Finds the data slot of a target variable within a mapping.
target_variable (`StateVariable`): The mapping that contains the target variable.
slot (bytes): The starting slot of the mapping.
key (Union[int, str]): The key the variable is stored at.
@ -509,7 +781,7 @@ class SlitherReadStorage:
)
info += info_tmp
# TODO: suppory mapping with dynamic arrays
# TODO: support mapping with dynamic arrays
# mapping(elem => elem)
elif isinstance(target_variable_type.type_to, ElementaryType):
@ -615,7 +887,8 @@ class SlitherReadStorage:
return elems
def _get_array_length(self, type_: Type, slot: int) -> int:
"""Gets the length of dynamic and fixed arrays.
"""
Gets the length of dynamic and fixed arrays.
Args:
type_ (`AbstractType`): The array type.
slot (int): Slot a dynamic array's length is stored at.

@ -37,6 +37,8 @@ def coerce_type(
(Union[int, bool, str, ChecksumAddress, hex]): The type representation of the value.
"""
if "int" in solidity_type:
if str(value).startswith("0x"):
return to_int(hexstr=value)
return to_int(value)
if "bool" in solidity_type:
return bool(to_int(value))

@ -4,7 +4,9 @@ from typing import Union
from slither.exceptions import SlitherError
def convert_string_to_fraction(val: Union[str, int]) -> Fraction:
def convert_string_to_fraction(val: Union[str, bytes, int]) -> Fraction:
if isinstance(val, bytes):
return int.from_bytes(val, byteorder="big")
if isinstance(val, int):
return Fraction(val)
if val.startswith(("0x", "0X")):

@ -1,5 +1,6 @@
from fractions import Fraction
from typing import Union
from Crypto.Hash import keccak
from slither.core import expressions
from slither.core.expressions import (
@ -11,9 +12,9 @@ from slither.core.expressions import (
UnaryOperation,
TupleExpression,
TypeConversion,
CallExpression,
)
from slither.core.variables import Variable
from slither.utils.integer_conversion import convert_string_to_fraction, convert_string_to_int
from slither.visitors.expression.expression import ExpressionVisitor
from slither.core.solidity_types.elementary_type import ElementaryType
@ -65,23 +66,31 @@ class ConstantFolding(ExpressionVisitor):
value = value & (2**256 - 1)
return Literal(value, self._type)
# pylint: disable=import-outside-toplevel
def _post_identifier(self, expression: Identifier) -> None:
if not isinstance(expression.value, Variable):
return
if not expression.value.is_constant:
from slither.core.declarations.solidity_variables import SolidityFunction
if isinstance(expression.value, Variable):
if expression.value.is_constant:
expr = expression.value.expression
# assumption that we won't have infinite loop
# Everything outside of literal
if isinstance(
expr,
(BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion),
):
cf = ConstantFolding(expr, self._type)
expr = cf.result()
assert isinstance(expr, Literal)
set_val(expression, convert_string_to_int(expr.converted_value))
else:
raise NotConstant
elif isinstance(expression.value, SolidityFunction):
set_val(expression, expression.value)
else:
raise NotConstant
expr = expression.value.expression
# assumption that we won't have infinite loop
# Everything outside of literal
if isinstance(
expr, (BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion)
):
cf = ConstantFolding(expr, self._type)
expr = cf.result()
assert isinstance(expr, Literal)
set_val(expression, convert_string_to_int(expr.converted_value))
# pylint: disable=too-many-branches
# pylint: disable=too-many-branches,too-many-statements
def _post_binary_operation(self, expression: BinaryOperation) -> None:
expression_left = expression.expression_left
expression_right = expression.expression_right
@ -95,7 +104,6 @@ class ConstantFolding(ExpressionVisitor):
(Literal, BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion),
):
raise NotConstant
left = get_val(expression_left)
right = get_val(expression_right)
@ -183,7 +191,9 @@ class ConstantFolding(ExpressionVisitor):
raise NotConstant
def _post_literal(self, expression: Literal) -> None:
if expression.converted_value in ["true", "false"]:
if str(expression.type) == "bool":
set_val(expression, expression.converted_value)
elif str(expression.type) == "string":
set_val(expression, expression.converted_value)
else:
try:
@ -195,7 +205,14 @@ class ConstantFolding(ExpressionVisitor):
raise NotConstant
def _post_call_expression(self, expression: expressions.CallExpression) -> None:
raise NotConstant
called = get_val(expression.called)
args = [get_val(arg) for arg in expression.arguments]
if called.name == "keccak256(bytes)":
digest = keccak.new(digest_bits=256)
digest.update(str(args[0]).encode("utf-8"))
set_val(expression, digest.digest())
else:
raise NotConstant
def _post_conditional_expression(self, expression: expressions.ConditionalExpression) -> None:
raise NotConstant
@ -247,10 +264,24 @@ class ConstantFolding(ExpressionVisitor):
expr = expression.expression
if not isinstance(
expr,
(Literal, BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion),
(
Literal,
BinaryOperation,
UnaryOperation,
Identifier,
TupleExpression,
TypeConversion,
CallExpression,
),
):
raise NotConstant
cf = ConstantFolding(expr, self._type)
expr = cf.result()
assert isinstance(expr, Literal)
set_val(expression, convert_string_to_fraction(expr.converted_value))
if str(expression.type).startswith("uint") and isinstance(expr.value, bytes):
value = int.from_bytes(expr.value, "big")
elif str(expression.type).startswith("byte") and isinstance(expr.value, int):
value = int.to_bytes(expr.value, 32, "big")
else:
value = convert_string_to_fraction(expr.converted_value)
set_val(expression, value)

@ -1,12 +1,12 @@
# pylint: disable=redefined-outer-name
import os
from pathlib import Path
import tempfile
import shutil
import tempfile
from pathlib import Path
from contextlib import contextmanager
import pytest
from filelock import FileLock
from solc_select import solc_select
import pytest
from slither import Slither

@ -1,12 +1,14 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value
E.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#63-66) which only takes arrays by value

@ -1,12 +1,14 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value
E.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#63-66) which only takes arrays by value

@ -1,12 +1,14 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value
E.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#63-66) which only takes arrays by value

@ -1,12 +1,14 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value
E.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#63-66) which only takes arrays by value

@ -48,4 +48,25 @@ contract D {
}
}
contract E {
uint[1] public x; // storage
uint[1] public y; // storage
function f() public {
uint[1] memory temp;
setByValue(temp, x); // can set temp, but cannot set x
setByRef(temp, y); // can set temp and y
}
function setByValue(uint[1] memory arr, uint[1] memory arr2) internal {
arr[0] = 1;
arr2[0] = 2;
}
function setByRef(uint[1] memory arr, uint[1] storage arr2) internal {
arr[0] = 2;
arr2[0] = 3;
}
}

@ -48,4 +48,25 @@ contract D {
}
}
contract E {
uint[1] public x; // storage
uint[1] public y; // storage
function f() public {
uint[1] memory temp;
setByValue(temp, x); // can set temp, but cannot set x
setByRef(temp, y); // can set temp and y
}
function setByValue(uint[1] memory arr, uint[1] memory arr2) internal {
arr[0] = 1;
arr2[0] = 2;
}
function setByRef(uint[1] memory arr, uint[1] storage arr2) internal {
arr[0] = 2;
arr2[0] = 3;
}
}

@ -48,4 +48,25 @@ contract D {
}
}
contract E {
uint[1] public x; // storage
uint[1] public y; // storage
function f() public {
uint[1] memory temp;
setByValue(temp, x); // can set temp, but cannot set x
setByRef(temp, y); // can set temp and y
}
function setByValue(uint[1] memory arr, uint[1] memory arr2) internal {
arr[0] = 1;
arr2[0] = 2;
}
function setByRef(uint[1] memory arr, uint[1] storage arr2) internal {
arr[0] = 2;
arr2[0] = 3;
}
}

@ -48,4 +48,25 @@ contract D {
}
}
contract E {
uint[1] public x; // storage
uint[1] public y; // storage
function f() public {
uint[1] memory temp;
setByValue(temp, x); // can set temp, but cannot set x
setByRef(temp, y); // can set temp and y
}
function setByValue(uint[1] memory arr, uint[1] memory arr2) internal {
arr[0] = 1;
arr2[0] = 2;
}
function setByRef(uint[1] memory arr, uint[1] storage arr2) internal {
arr[0] = 2;
arr2[0] = 3;
}
}

@ -308,6 +308,7 @@ ALL_TESTS = [
Test("units_and_global_variables-0.8.0.sol", VERSIONS_08),
Test("units_and_global_variables-0.8.4.sol", make_version(8, 4, 6)),
Test("units_and_global_variables-0.8.7.sol", make_version(8, 7, 9)),
Test("global_variables-0.8.18.sol", make_version(8, 18, 18)),
Test(
"push-all.sol",
ALL_VERSIONS,
@ -453,6 +454,10 @@ ALL_TESTS = [
Test("complex_imports/import_aliases_issue_1319/test.sol", ["0.5.12"]),
Test("yul-state-constant-access.sol", ["0.8.16"]),
Test("negate-unary-element.sol", ["0.8.16"]),
Test(
"assembly-functions.sol",
["0.6.9", "0.7.6", "0.8.16"],
),
]
# create the output folder if needed
try:

@ -0,0 +1,12 @@
contract A {
function foo() public {
assembly {
function f() { function z() { function x() { g() } x() } z() }
function w() { function a() {} function b() { a() } b() }
function g() {
f()
}
g()
}
}
}

@ -0,0 +1,12 @@
{
"A": {
"foo()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.f()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.f.z()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.f.z.x()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.w()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.w.a()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n}\n",
"foo.asm_0.w.b()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.g()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n"
}
}

@ -0,0 +1,5 @@
{
"A": {
"foo()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n}\n"
}
}

@ -0,0 +1,12 @@
{
"A": {
"foo()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.f()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.f.z()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.f.z.x()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.w()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.w.a()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n}\n",
"foo.asm_0.w.b()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.g()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n"
}
}

@ -0,0 +1,5 @@
{
"A": {
"foo()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n}\n"
}
}

@ -0,0 +1,12 @@
{
"A": {
"foo()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.f()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.f.z()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.f.z.x()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.w()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.w.a()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n}\n",
"foo.asm_0.w.b()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
"foo.asm_0.g()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n"
}
}

@ -0,0 +1,6 @@
{
"C": {
"f()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"g()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n"
}
}

@ -0,0 +1,11 @@
contract C {
function f() public view returns (uint256) {
return block.prevrandao;
}
function g() public view returns (uint256 ret) {
assembly {
ret := prevrandao()
}
}
}

@ -0,0 +1,56 @@
"""
Testing utilities for the read-storage tool
"""
import shutil
import subprocess
from time import sleep
from typing import Generator
from dataclasses import dataclass
from web3 import Web3
import pytest
@dataclass
class GanacheInstance:
def __init__(self, provider: str, eth_address: str, eth_privkey: str):
self.provider = provider
self.eth_address = eth_address
self.eth_privkey = eth_privkey
@pytest.fixture(scope="module", name="ganache")
def fixture_ganache() -> Generator[GanacheInstance, None, None]:
"""Fixture that runs ganache"""
if not shutil.which("ganache"):
raise Exception(
"ganache was not found in PATH, you can install it with `npm install -g ganache`"
)
# Address #1 when ganache is run with `--wallet.seed test`, it starts with 1000 ETH
eth_address = "0xae17D2dD99e07CA3bF2571CCAcEAA9e2Aefc2Dc6"
eth_privkey = "0xe48ba530a63326818e116be262fd39ae6dcddd89da4b1f578be8afd4e8894b8d"
eth = int(1e18 * 1e6)
port = 8545
with subprocess.Popen(
f"""ganache
--port {port}
--chain.networkId 1
--chain.chainId 1
--account {eth_privkey},{eth}
""".replace(
"\n", " "
),
shell=True,
) as p:
sleep(3)
yield GanacheInstance(f"http://127.0.0.1:{port}", eth_address, eth_privkey)
p.kill()
p.wait()
@pytest.fixture(scope="module", name="web3")
def fixture_web3(ganache: GanacheInstance):
w3 = Web3(Web3.HTTPProvider(ganache.provider, request_kwargs={"timeout": 30}))
return w3

@ -1,5 +1,6 @@
pragma solidity 0.8.10;
// overwrite abi and bin:
// solc tests/storage-layout/storage_layout-0.8.10.sol --abi --bin -o tests/storage-layout --overwrite
// solc StorageLayout.sol --abi --bin --overwrite
contract StorageLayout {
uint248 packedUint = 1;
bool packedBool = true;

@ -0,0 +1,56 @@
{
"masterCopy": {
"name": "masterCopy",
"type_string": "address",
"slot": 0,
"size": 160,
"offset": 0,
"value": "0x0000000000000000000000000000000000000000",
"elems": {}
},
"ADMIN_SLOT": {
"name": "ADMIN_SLOT",
"type_string": "address",
"slot": 7616251639890160809447714111544359812065171195189364993079081710756264753419,
"size": 160,
"offset": 0,
"value": "0xae17D2dD99e07CA3bF2571CCAcEAA9e2Aefc2Dc6",
"elems": {}
},
"IMPLEMENTATION_SLOT": {
"name": "IMPLEMENTATION_SLOT",
"type_string": "address",
"slot": 24440054405305269366569402256811496959409073762505157381672968839269610695612,
"size": 160,
"offset": 0,
"value": "0x54006763154c764da4AF42a8c3cfc25Ea29765D5",
"elems": {}
},
"ROLLBACK_SLOT": {
"name": "ROLLBACK_SLOT",
"type_string": "bool",
"slot": 33048860383849004559742813297059419343339852917517107368639918720169455489347,
"size": 1,
"offset": 0,
"value": true,
"elems": {}
},
"BEACON_SLOT": {
"name": "BEACON_SLOT",
"type_string": "address",
"slot": 74152234768234802001998023604048924213078445070507226371336425913862612794704,
"size": 160,
"offset": 0,
"value": "0x54006763154c764da4AF42a8c3cfc25Ea29765D5",
"elems": {}
},
"fallback_sload_hardcoded": {
"name": "fallback_sload_hardcoded",
"type_string": "address",
"slot": 89532207833283453166981358064394884954800891875771469636219037672473505217783,
"size": 160,
"offset": 0,
"value": "0x54006763154c764da4AF42a8c3cfc25Ea29765D5",
"elems": {}
}
}

@ -0,0 +1 @@
[{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}]

@ -0,0 +1 @@
608060405234801561001057600080fd5b5061030b806100206000396000f3fe608060405234801561001057600080fd5b506004361061002f5760003560e01c8063975057e71461009757610030565b5b600180035473ffffffffffffffffffffffffffffffffffffffff600054167fc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7543660008037600080366000845af43d806000803e816000811461009257816000f35b816000fd5b61009f6100a1565b005b60006100ab6101a8565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146100e657600080fd5b60007f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050600033905080825560007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b905060007354006763154c764da4af42a8c3cfc25ea29765d59050808255807fc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf75561018460016101d6565b6101a17354006763154c764da4af42a8c3cfc25ea29765d5610220565b5050505050565b6000807f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050805491505090565b806102037f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd914360001b61025e565b60000160006101000a81548160ff02191690831515021790555050565b600060017fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5160001c61025291906102a1565b60001b90508181555050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006102ac82610268565b91506102b783610268565b9250828210156102ca576102c9610272565b5b82820390509291505056fea2646970667358221220f079473c1b94744ac2818f521ccef06187a433d996633e61e51a86dfb60cc6ff64736f6c634300080a0033

@ -0,0 +1,141 @@
pragma solidity 0.8.10;
// overwrite abi and bin:
// solc UnstructuredStorageLayout.sol --abi --bin --overwrite
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
}
contract UnstructuredStorageLayout {
bytes32 constant ADMIN_SLOT = keccak256("org.zeppelinos.proxy.admin");
// This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
// This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
bytes32 private constant ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
bytes32 constant BEACON_SLOT = bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1);
address internal masterCopy;
function _admin() internal view returns (address admin) {
bytes32 slot = ADMIN_SLOT;
assembly {
admin := sload(slot)
}
}
function _implementation() internal view returns (address) {
address _impl;
bytes32 slot = IMPLEMENTATION_SLOT;
assembly {
_impl := sload(slot)
}
return _impl;
}
function _set_rollback(bool _rollback) internal {
StorageSlot.getBooleanSlot(ROLLBACK_SLOT).value = _rollback;
}
function _set_beacon(address _beacon) internal {
bytes32 slot = bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1);
assembly {
sstore(slot, _beacon)
}
}
function store() external {
address admin = _admin();
require(admin == address(0));
bytes32 admin_slot = ADMIN_SLOT;
address sender = msg.sender;
assembly {
sstore(admin_slot, sender)
}
bytes32 impl_slot = IMPLEMENTATION_SLOT;
address _impl = address(0x0054006763154c764da4af42a8c3cfc25ea29765d5);
assembly {
sstore(impl_slot, _impl)
sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, _impl)
}
_set_rollback(true);
_set_beacon(address(0x0054006763154c764da4af42a8c3cfc25ea29765d5));
}
// Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"
fallback() external {
assembly { // solium-disable-line
let nonsense := sload(sub(1,1))
let _masterCopy := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)
let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7)
calldatacopy(0x0, 0x0, calldatasize())
let success := delegatecall(gas(), contractLogic, 0x0, calldatasize(), 0, 0)
let retSz := returndatasize()
returndatacopy(0, 0, retSz)
switch success
case 0 {
revert(0, retSz)
}
default {
return(0, retSz)
}
}
}
}

@ -1,14 +1,9 @@
import json
import re
import shutil
import subprocess
from time import sleep
import json
from pathlib import Path
from typing import Generator
import pytest
from deepdiff import DeepDiff
from web3 import Web3
from web3.contract import Contract
from slither import Slither
@ -16,50 +11,6 @@ from slither.tools.read_storage import SlitherReadStorage, RpcInfo
TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data"
# pylint: disable=too-few-public-methods
class GanacheInstance:
def __init__(self, provider: str, eth_address: str, eth_privkey: str):
self.provider = provider
self.eth_address = eth_address
self.eth_privkey = eth_privkey
@pytest.fixture(scope="module", name="web3")
def fixture_web3(ganache: GanacheInstance):
w3 = Web3(Web3.HTTPProvider(ganache.provider, request_kwargs={"timeout": 30}))
return w3
@pytest.fixture(scope="module", name="ganache")
def fixture_ganache() -> Generator[GanacheInstance, None, None]:
"""Fixture that runs ganache"""
if not shutil.which("ganache"):
raise Exception(
"ganache was not found in PATH, you can install it with `npm install -g ganache`"
)
# Address #1 when ganache is run with `--wallet.seed test`, it starts with 1000 ETH
eth_address = "0xae17D2dD99e07CA3bF2571CCAcEAA9e2Aefc2Dc6"
eth_privkey = "0xe48ba530a63326818e116be262fd39ae6dcddd89da4b1f578be8afd4e8894b8d"
eth = int(1e18 * 1e6)
port = 8545
with subprocess.Popen(
f"""ganache
--port {port}
--chain.networkId 1
--chain.chainId 1
--account {eth_privkey},{eth}
""".replace(
"\n", " "
),
shell=True,
) as p:
sleep(3)
yield GanacheInstance(f"http://127.0.0.1:{port}", eth_address, eth_privkey)
p.kill()
p.wait()
def get_source_file(file_path) -> str:
with open(file_path, "r", encoding="utf8") as f:
@ -89,24 +40,29 @@ def deploy_contract(w3, ganache, contract_bin, contract_abi) -> Contract:
# pylint: disable=too-many-locals
@pytest.mark.parametrize(
"test_contract, storage_file",
[("StorageLayout", "storage_layout"), ("UnstructuredStorageLayout", "unstructured_storage")],
)
@pytest.mark.usefixtures("web3", "ganache")
def test_read_storage(web3, ganache, solc_binary_path) -> None:
def test_read_storage(test_contract, storage_file, web3, ganache, solc_binary_path) -> None:
solc_path = solc_binary_path(version="0.8.10")
assert web3.is_connected()
bin_path = Path(TEST_DATA_DIR, "StorageLayout.bin").as_posix()
abi_path = Path(TEST_DATA_DIR, "StorageLayout.abi").as_posix()
bin_path = Path(TEST_DATA_DIR, f"{test_contract}.bin").as_posix()
abi_path = Path(TEST_DATA_DIR, f"{test_contract}.abi").as_posix()
bytecode = get_source_file(bin_path)
abi = get_source_file(abi_path)
contract = deploy_contract(web3, ganache, bytecode, abi)
contract.functions.store().transact({"from": ganache.eth_address})
address = contract.address
sl = Slither(Path(TEST_DATA_DIR, "storage_layout-0.8.10.sol").as_posix(), solc=solc_path)
sl = Slither(Path(TEST_DATA_DIR, f"{test_contract}.sol").as_posix(), solc=solc_path)
contracts = sl.contracts
rpc_info: RpcInfo = RpcInfo(ganache.provider)
srs = SlitherReadStorage(contracts, 100, rpc_info)
srs.unstructured = True
srs.storage_address = address
srs.get_all_storage_variables()
srs.get_storage_layout()
@ -116,7 +72,7 @@ def test_read_storage(web3, ganache, solc_binary_path) -> None:
slot_infos_json = srs.to_json()
json.dump(slot_infos_json, file, indent=4)
expected_file = Path(TEST_DATA_DIR, "TEST_storage_layout.json").as_posix()
expected_file = Path(TEST_DATA_DIR, f"TEST_{storage_file}.json").as_posix()
with open(expected_file, "r", encoding="utf8") as f:
expected = json.load(f)

@ -21,39 +21,40 @@ def test_constant_folding_rational(solc_binary_path):
variable_a = contract.get_state_variable_from_name("a")
assert str(variable_a.type) == "uint256"
assert str(ConstantFolding(variable_a.expression, "uint256").result()) == "10"
assert ConstantFolding(variable_a.expression, "uint256").result().value == 10
variable_b = contract.get_state_variable_from_name("b")
assert str(variable_b.type) == "int128"
assert str(ConstantFolding(variable_b.expression, "int128").result()) == "2"
assert ConstantFolding(variable_b.expression, "int128").result().value == 2
variable_c = contract.get_state_variable_from_name("c")
assert str(variable_c.type) == "int64"
assert str(ConstantFolding(variable_c.expression, "int64").result()) == "3"
assert ConstantFolding(variable_c.expression, "int64").result().value == 3
variable_d = contract.get_state_variable_from_name("d")
assert str(variable_d.type) == "int256"
assert str(ConstantFolding(variable_d.expression, "int256").result()) == "1500"
assert ConstantFolding(variable_d.expression, "int256").result().value == 1500
variable_e = contract.get_state_variable_from_name("e")
assert str(variable_e.type) == "uint256"
assert (
str(ConstantFolding(variable_e.expression, "uint256").result())
== "57896044618658097711785492504343953926634992332820282019728792003956564819968"
ConstantFolding(variable_e.expression, "uint256").result().value
== 57896044618658097711785492504343953926634992332820282019728792003956564819968
)
variable_f = contract.get_state_variable_from_name("f")
assert str(variable_f.type) == "uint256"
assert (
str(ConstantFolding(variable_f.expression, "uint256").result())
== "115792089237316195423570985008687907853269984665640564039457584007913129639935"
ConstantFolding(variable_f.expression, "uint256").result().value
== 115792089237316195423570985008687907853269984665640564039457584007913129639935
)
variable_g = contract.get_state_variable_from_name("g")
assert str(variable_g.type) == "int64"
assert str(ConstantFolding(variable_g.expression, "int64").result()) == "-7"
assert ConstantFolding(variable_g.expression, "int64").result().value == -7
# pylint: disable=too-many-locals
def test_constant_folding_binary_expressions(solc_binary_path):
sl = Slither(
Path(CONSTANT_FOLDING_TEST_ROOT, "constant_folding_binop.sol").as_posix(),
@ -63,51 +64,65 @@ def test_constant_folding_binary_expressions(solc_binary_path):
variable_a = contract.get_state_variable_from_name("a")
assert str(variable_a.type) == "uint256"
assert str(ConstantFolding(variable_a.expression, "uint256").result()) == "0"
assert ConstantFolding(variable_a.expression, "uint256").result().value == 0
variable_b = contract.get_state_variable_from_name("b")
assert str(variable_b.type) == "uint256"
assert str(ConstantFolding(variable_b.expression, "uint256").result()) == "3"
assert ConstantFolding(variable_b.expression, "uint256").result().value == 3
variable_c = contract.get_state_variable_from_name("c")
assert str(variable_c.type) == "uint256"
assert str(ConstantFolding(variable_c.expression, "uint256").result()) == "3"
assert ConstantFolding(variable_c.expression, "uint256").result().value == 3
variable_d = contract.get_state_variable_from_name("d")
assert str(variable_d.type) == "bool"
assert str(ConstantFolding(variable_d.expression, "bool").result()) == "False"
assert ConstantFolding(variable_d.expression, "bool").result().value is False
variable_e = contract.get_state_variable_from_name("e")
assert str(variable_e.type) == "bool"
assert str(ConstantFolding(variable_e.expression, "bool").result()) == "False"
assert ConstantFolding(variable_e.expression, "bool").result().value is False
variable_f = contract.get_state_variable_from_name("f")
assert str(variable_f.type) == "bool"
assert str(ConstantFolding(variable_f.expression, "bool").result()) == "True"
assert ConstantFolding(variable_f.expression, "bool").result().value is True
variable_g = contract.get_state_variable_from_name("g")
assert str(variable_g.type) == "bool"
assert str(ConstantFolding(variable_g.expression, "bool").result()) == "False"
assert ConstantFolding(variable_g.expression, "bool").result().value is False
variable_h = contract.get_state_variable_from_name("h")
assert str(variable_h.type) == "bool"
assert str(ConstantFolding(variable_h.expression, "bool").result()) == "False"
assert ConstantFolding(variable_h.expression, "bool").result().value is False
variable_i = contract.get_state_variable_from_name("i")
assert str(variable_i.type) == "bool"
assert str(ConstantFolding(variable_i.expression, "bool").result()) == "True"
assert ConstantFolding(variable_i.expression, "bool").result().value is True
variable_j = contract.get_state_variable_from_name("j")
assert str(variable_j.type) == "bool"
assert str(ConstantFolding(variable_j.expression, "bool").result()) == "False"
assert ConstantFolding(variable_j.expression, "bool").result().value is False
variable_k = contract.get_state_variable_from_name("k")
assert str(variable_k.type) == "bool"
assert str(ConstantFolding(variable_k.expression, "bool").result()) == "True"
assert ConstantFolding(variable_k.expression, "bool").result().value is True
variable_l = contract.get_state_variable_from_name("l")
assert str(variable_l.type) == "uint256"
assert (
str(ConstantFolding(variable_l.expression, "uint256").result())
== "115792089237316195423570985008687907853269984665640564039457584007913129639935"
ConstantFolding(variable_l.expression, "uint256").result().value
== 115792089237316195423570985008687907853269984665640564039457584007913129639935
)
IMPLEMENTATION_SLOT = contract.get_state_variable_from_name("IMPLEMENTATION_SLOT")
assert str(IMPLEMENTATION_SLOT.type) == "bytes32"
assert (
int.from_bytes(
ConstantFolding(IMPLEMENTATION_SLOT.expression, "bytes32").result().value,
byteorder="big",
)
== 24440054405305269366569402256811496959409073762505157381672968839269610695612
)
variable_m = contract.get_state_variable_from_name("m")
assert str(variable_m.type) == "bytes2"
assert ConstantFolding(variable_m.expression, "bytes2").result().value == "ab"

@ -11,4 +11,6 @@ contract BinOp {
bool j = true && false;
bool k = true || false;
uint l = uint(1) - uint(2);
bytes32 IMPLEMENTATION_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
bytes2 m = "ab";
}

@ -0,0 +1,2 @@
import {ERC20 as ERC20_1} from "./import.sol";
contract ERC20 is ERC20_1 {}

@ -0,0 +1,18 @@
from pathlib import Path
import pytest
from slither import Slither
from slither.solc_parsing.slither_compilation_unit_solc import InheritanceResolutionError
TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data"
INHERITANCE_ERROR_ROOT = Path(TEST_DATA_DIR, "inheritance_resolution_error")
def test_inheritance_resolution_error(solc_binary_path) -> None:
with pytest.raises(InheritanceResolutionError):
solc_path = solc_binary_path("0.8.0")
Slither(
Path(INHERITANCE_ERROR_ROOT, "contract_with_duplicate_names.sol").as_posix(),
solc=solc_path,
)
Loading…
Cancel
Save