From 636d055c53ccf2cb5e2f29250c6e13f0b4aef4fd Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 13 Mar 2023 12:29:54 -0500 Subject: [PATCH 01/70] Handle conversion from hex string to int in `coerce_type(solidity_type, value)` --- slither/tools/read_storage/utils/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/tools/read_storage/utils/utils.py b/slither/tools/read_storage/utils/utils.py index 3e51e21813..437cb5ec87 100644 --- a/slither/tools/read_storage/utils/utils.py +++ b/slither/tools/read_storage/utils/utils.py @@ -36,6 +36,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)) From 7311ca02c22e7a1c4fff8e899ac97637e8945bb5 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 13 Mar 2023 12:31:49 -0500 Subject: [PATCH 02/70] Get constant storage slots, i.e., all constant bytes32 variables --- slither/tools/read_storage/read_storage.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index bb662c4d5e..8107ea2ada 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -59,6 +59,7 @@ def __init__(self, contracts: List[Contract], max_depth: int) -> None: 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._web3: Optional[Web3] = None self.block: Union[str, int] = "latest" self.rpc: Optional[str] = None @@ -100,6 +101,11 @@ def target_variables(self) -> List[Tuple[Contract, StateVariable]]: """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.""" @@ -259,6 +265,22 @@ def get_all_storage_variables(self, func: Callable = None) -> None: ) ) + def get_constant_storage_slots(self, func: Callable = None) -> None: + """ + Retrieves all constant bytes32 variables from a list of contracts. + """ + for contract in self.contracts: + self._constant_storage_slots.extend( + filter( + func, + [ + (contract, var) + for var in contract.state_variables_ordered + if var.is_constant and str(var.type) == "bytes32" + ] + ) + ) + def convert_slot_info_to_rows(self, slot_info: SlotInfo) -> None: """Convert and append slot info to table. Create table if it does not yet exist From f53d725c2faf1642e471cc9c712278e51c924090 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 13 Mar 2023 12:34:39 -0500 Subject: [PATCH 03/70] Determine value type stored in constant slot --- slither/tools/read_storage/read_storage.py | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 8107ea2ada..862c889944 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -222,6 +222,45 @@ def get_target_variables(self, **kwargs) -> None: 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. + """ + if not (var.is_constant and str(var.type) == "bytes32"): + return None + storage_type = None + size = None + funcs = [f for f in var.contract.functions if var in f.state_variables_read] + if len(funcs) == 0: + for c in self.contracts: + funcs.extend([f for f in c.functions if var in f.state_variables_read]) + for func in funcs: + if func.return_type is not None: + ret = func.return_type[0] + size, _ = ret.storage_size + return str(ret), size * 8 + for node in func.all_nodes(): + exp = str(node.expression) + if f"getAddressSlot({var.name})" in exp: + return "address", 160 + if f"getBooleanSlot({var.name})" in exp: + return "bool", 1 + if f"getBytes32Slot({var.name})" in exp: + return "bytes32", 256 + if f"getUint256Slot({var.name})" in exp: + return "uint256", 256 + return storage_type, size + def walk_slot_info(self, func: Callable) -> None: stack = list(self.slot_info.values()) while stack: From 200f2cc61ffa4639e2c38df870f278993dce0bfc Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 13 Mar 2023 12:35:28 -0500 Subject: [PATCH 04/70] Include constant slots in `get_storage_layout()` --- slither/tools/read_storage/read_storage.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 862c889944..ad3b66cc38 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -125,7 +125,24 @@ def get_storage_layout(self) -> None: elif isinstance(type_, ArrayType): elems = self._all_array_slots(var, contract, type_, info.slot) tmp[var.name].elems = elems - + for contract, var in self.constant_slots: + var_name = var.name + try: + slot = coerce_type("int", str(var.expression)) + 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(var.expression)}\n" + ) + logger.info(self.log) + self.log = "" + except TypeError: + continue self._slot_info = tmp # TODO: remove this pylint exception (montyly) From ea0146c2d9ecea3327685926fad8b759099cce55 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 13 Mar 2023 12:35:38 -0500 Subject: [PATCH 05/70] Black format --- slither/tools/read_storage/read_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index ad3b66cc38..67c6dad2ce 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -333,7 +333,7 @@ def get_constant_storage_slots(self, func: Callable = None) -> None: (contract, var) for var in contract.state_variables_ordered if var.is_constant and str(var.type) == "bytes32" - ] + ], ) ) From dabe3180b8b057c1f406b795ae1addd131c3394e Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 13 Mar 2023 12:37:34 -0500 Subject: [PATCH 06/70] Call `get_constant_storage_slots` in `main` before calling `get_storage_layout` --- slither/tools/read_storage/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/tools/read_storage/__main__.py b/slither/tools/read_storage/__main__.py index 1a8901321d..5374ee828b 100644 --- a/slither/tools/read_storage/__main__.py +++ b/slither/tools/read_storage/__main__.py @@ -147,9 +147,11 @@ def main() -> None: # Use a lambda func to only return variables that have same name as target. # x is a tuple (`Contract`, `StateVariable`). srs.get_all_storage_variables(lambda x: bool(x[1].name == args.variable_name)) + srs.get_constant_storage_slots(lambda x: bool(x[1].name == args.variable_name)) srs.get_target_variables(**vars(args)) else: srs.get_all_storage_variables() + srs.get_constant_storage_slots() srs.get_storage_layout() # To retrieve slot values an rpc url is required. From 4db313b79a03175ef1957a52ae3e4ece9d1d8ea2 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 09:20:08 -0500 Subject: [PATCH 07/70] Handle keccak256 in bytes32 constant declaration --- slither/tools/read_storage/read_storage.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 67c6dad2ce..0278e78e8d 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -128,7 +128,15 @@ def get_storage_layout(self) -> None: for contract, var in self.constant_slots: var_name = var.name try: - slot = coerce_type("int", str(var.expression)) + exp = var.expression + if str(exp).startswith("0x"): + slot = coerce_type("int", str(exp)) + elif str(exp).startswith("keccak256(bytes)"): + slot_str = str(exp).split("(")[2].replace(")", "") + slot_hash = keccak(text=slot_str) + slot = coerce_type("int", slot_hash) + else: + continue offset = 0 type_string, size = self.find_constant_slot_storage_type(var) if type_string: @@ -137,7 +145,7 @@ def get_storage_layout(self) -> None: ) self.log += ( f"\nSlot Name: {var_name}\nType: bytes32" - f"\nStorage Type: {type_string}\nSlot: {str(var.expression)}\n" + f"\nStorage Type: {type_string}\nSlot: {str(exp)}\n" ) logger.info(self.log) self.log = "" From 1fa25ccb1c72f635e5f4abdcdcb09cbfa81c4cc1 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 10:40:09 -0500 Subject: [PATCH 08/70] Add test for unstructured storage support using slither-read-storage --- tests/storage-layout/StorageSlot.abi | 1 + tests/storage-layout/StorageSlot.bin | 1 + .../TEST_unstructured_storage.json | 29 ++++ .../UnstructuredStorageLayout.abi | 1 + .../UnstructuredStorageLayout.bin | 1 + .../unstructured_storage-0.8.10.sol | 108 +++++++++++++ tests/test_unstructured_storage.py | 142 ++++++++++++++++++ unstructured_storage.json | 29 ++++ 8 files changed, 312 insertions(+) create mode 100644 tests/storage-layout/StorageSlot.abi create mode 100644 tests/storage-layout/StorageSlot.bin create mode 100644 tests/storage-layout/TEST_unstructured_storage.json create mode 100644 tests/storage-layout/UnstructuredStorageLayout.abi create mode 100644 tests/storage-layout/UnstructuredStorageLayout.bin create mode 100644 tests/storage-layout/unstructured_storage-0.8.10.sol create mode 100644 tests/test_unstructured_storage.py create mode 100644 unstructured_storage.json diff --git a/tests/storage-layout/StorageSlot.abi b/tests/storage-layout/StorageSlot.abi new file mode 100644 index 0000000000..0637a088a0 --- /dev/null +++ b/tests/storage-layout/StorageSlot.abi @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/storage-layout/StorageSlot.bin b/tests/storage-layout/StorageSlot.bin new file mode 100644 index 0000000000..9cda2b5577 --- /dev/null +++ b/tests/storage-layout/StorageSlot.bin @@ -0,0 +1 @@ +60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220b19f0baad7f0ddaf31a5de68681e53b99a795cfe56bdd586e475316fbb4d6ecf64736f6c634300080a0033 \ No newline at end of file diff --git a/tests/storage-layout/TEST_unstructured_storage.json b/tests/storage-layout/TEST_unstructured_storage.json new file mode 100644 index 0000000000..1830154606 --- /dev/null +++ b/tests/storage-layout/TEST_unstructured_storage.json @@ -0,0 +1,29 @@ +{ + "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": {} + } +} \ No newline at end of file diff --git a/tests/storage-layout/UnstructuredStorageLayout.abi b/tests/storage-layout/UnstructuredStorageLayout.abi new file mode 100644 index 0000000000..a60ca06615 --- /dev/null +++ b/tests/storage-layout/UnstructuredStorageLayout.abi @@ -0,0 +1 @@ +[{"inputs":[],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/tests/storage-layout/UnstructuredStorageLayout.bin b/tests/storage-layout/UnstructuredStorageLayout.bin new file mode 100644 index 0000000000..92dcb6c8f7 --- /dev/null +++ b/tests/storage-layout/UnstructuredStorageLayout.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506101b9806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063975057e714610030575b600080fd5b61003861003a565b005b6000610044610101565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461007f57600080fd5b60007f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050600033905080825560007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b905060007354006763154c764da4af42a8c3cfc25ea29765d590508082556100fa600161012f565b5050505050565b6000807f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050805491505090565b8061015c7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd914360001b610179565b60000160006101000a81548160ff02191690831515021790555050565b600081905091905056fea2646970667358221220ec4f5646e03d5da06f717a9c05c4ed0652abf53527dd518d1a1b7d5a92d1c0c164736f6c634300080a0033 \ No newline at end of file diff --git a/tests/storage-layout/unstructured_storage-0.8.10.sol b/tests/storage-layout/unstructured_storage-0.8.10.sol new file mode 100644 index 0000000000..40cd8afab1 --- /dev/null +++ b/tests/storage-layout/unstructured_storage-0.8.10.sol @@ -0,0 +1,108 @@ +// overwrite abi and bin: +// solc tests/storage-layout/unstructured_storage-0.8.10.sol --abi --bin -o tests/storage-layout --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; + + 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 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) + } + + _set_rollback(true); + } +} diff --git a/tests/test_unstructured_storage.py b/tests/test_unstructured_storage.py new file mode 100644 index 0000000000..37cd6c0ad0 --- /dev/null +++ b/tests/test_unstructured_storage.py @@ -0,0 +1,142 @@ +import re +import os +import sys +import json +import shutil +import subprocess +from time import sleep +from typing import Generator + +import pytest +from deepdiff import DeepDiff +from slither import Slither +from slither.tools.read_storage import SlitherReadStorage + +try: + from web3 import Web3 + from web3.contract import Contract +except ImportError: + print("ERROR: in order to use slither-read-storage, you need to install web3") + print("$ pip3 install web3 --user\n") + sys.exit(-1) + +SLITHER_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +STORAGE_TEST_ROOT = os.path.join(SLITHER_ROOT, "tests", "storage-layout") + +# 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: + source = f.read() + + return source + + +def deploy_contract(w3, ganache, contract_bin, contract_abi) -> Contract: + """Deploy contract to the local ganache network""" + signed_txn = w3.eth.account.sign_transaction( + dict( + nonce=w3.eth.get_transaction_count(ganache.eth_address), + maxFeePerGas=20000000000, + maxPriorityFeePerGas=1, + gas=15000000, + to=b"", + data="0x" + contract_bin, + chainId=1, + ), + ganache.eth_privkey, + ) + tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction) + address = w3.eth.get_transaction_receipt(tx_hash)["contractAddress"] + contract = w3.eth.contract(address, abi=contract_abi) + return contract + + +# pylint: disable=too-many-locals +@pytest.mark.usefixtures("web3", "ganache") +def test_read_storage(web3, ganache) -> None: + assert web3.isConnected() + bin_path = os.path.join(STORAGE_TEST_ROOT, "UnstructuredStorageLayout.bin") + abi_path = os.path.join(STORAGE_TEST_ROOT, "UnstructuredStorageLayout.abi") + 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(os.path.join(STORAGE_TEST_ROOT, "unstructured_storage-0.8.10.sol")) + contracts = sl.contracts + + srs = SlitherReadStorage(contracts, 100) + srs.rpc = ganache.provider + srs.storage_address = address + srs.get_all_storage_variables() + srs.get_constant_storage_slots() + srs.get_storage_layout() + srs.walk_slot_info(srs.get_slot_values) + with open("unstructured_storage.json", "w", encoding="utf-8") as file: + slot_infos_json = srs.to_json() + json.dump(slot_infos_json, file, indent=4) + + expected_file = os.path.join(STORAGE_TEST_ROOT, "TEST_unstructured_storage.json") + actual_file = os.path.join(SLITHER_ROOT, "unstructured_storage.json") + + with open(expected_file, "r", encoding="utf8") as f: + expected = json.load(f) + with open(actual_file, "r", encoding="utf8") as f: + actual = json.load(f) + + diff = DeepDiff(expected, actual, ignore_order=True, verbose_level=2, view="tree") + if diff: + for change in diff.get("values_changed", []): + path_list = re.findall(r"\['(.*?)'\]", change.path()) + path = "_".join(path_list) + with open(f"{path}_expected.txt", "w", encoding="utf8") as f: + f.write(str(change.t1)) + with open(f"{path}_actual.txt", "w", encoding="utf8") as f: + f.write(str(change.t2)) + + assert not diff diff --git a/unstructured_storage.json b/unstructured_storage.json new file mode 100644 index 0000000000..1830154606 --- /dev/null +++ b/unstructured_storage.json @@ -0,0 +1,29 @@ +{ + "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": {} + } +} \ No newline at end of file From 0dd63218fecebea86a4bbbfb141b6e0ca53dc7cc Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 11:37:55 -0500 Subject: [PATCH 09/70] Merge `get_constant_storage_slots` into `get_all_storage_variables` --- slither/tools/read_storage/__main__.py | 2 -- slither/tools/read_storage/read_storage.py | 6 ------ tests/test_unstructured_storage.py | 1 - 3 files changed, 9 deletions(-) diff --git a/slither/tools/read_storage/__main__.py b/slither/tools/read_storage/__main__.py index 5374ee828b..1a8901321d 100644 --- a/slither/tools/read_storage/__main__.py +++ b/slither/tools/read_storage/__main__.py @@ -147,11 +147,9 @@ def main() -> None: # Use a lambda func to only return variables that have same name as target. # x is a tuple (`Contract`, `StateVariable`). srs.get_all_storage_variables(lambda x: bool(x[1].name == args.variable_name)) - srs.get_constant_storage_slots(lambda x: bool(x[1].name == args.variable_name)) srs.get_target_variables(**vars(args)) else: srs.get_all_storage_variables() - srs.get_constant_storage_slots() srs.get_storage_layout() # To retrieve slot values an rpc url is required. diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 0278e78e8d..dc49f60ef3 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -328,12 +328,6 @@ def get_all_storage_variables(self, func: Callable = None) -> None: ], ) ) - - def get_constant_storage_slots(self, func: Callable = None) -> None: - """ - Retrieves all constant bytes32 variables from a list of contracts. - """ - for contract in self.contracts: self._constant_storage_slots.extend( filter( func, diff --git a/tests/test_unstructured_storage.py b/tests/test_unstructured_storage.py index 37cd6c0ad0..4d3e728de2 100644 --- a/tests/test_unstructured_storage.py +++ b/tests/test_unstructured_storage.py @@ -114,7 +114,6 @@ def test_read_storage(web3, ganache) -> None: srs.rpc = ganache.provider srs.storage_address = address srs.get_all_storage_variables() - srs.get_constant_storage_slots() srs.get_storage_layout() srs.walk_slot_info(srs.get_slot_values) with open("unstructured_storage.json", "w", encoding="utf-8") as file: From 8dfe072b1fa3afed1c7135b172b6af4ac86b1848 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 11:38:23 -0500 Subject: [PATCH 10/70] Handle storage slot hardcoded in fallback --- slither/tools/read_storage/read_storage.py | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index dc49f60ef3..ed9bb5413c 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -20,9 +20,11 @@ import dataclasses from slither.utils.myprettytable import MyPrettyTable +from slither.core.cfg.node import NodeType from slither.core.solidity_types.type import Type from slither.core.solidity_types import ArrayType, ElementaryType, UserDefinedType, MappingType from slither.core.declarations import Contract, Structure +from slither.core.expressions import AssignmentOperation, CallExpression, Literal from slither.core.variables.state_variable import StateVariable from slither.core.variables.structure_variable import StructureVariable @@ -338,6 +340,50 @@ def get_all_storage_variables(self, func: Callable = None) -> None: ], ) ) + hardcoded_slot = self.find_hardcoded_slot_in_fallback(contract) + if hardcoded_slot is not None: + self._constant_storage_slots.append((contract, hardcoded_slot)) + + @staticmethod + def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariable]: + fallback = None + for func in contract.functions_entry_points: + if func.is_fallback: + fallback = func + break + if fallback is None: + return None + for node in fallback.all_nodes(): + if node.type == NodeType.ASSEMBLY and isinstance(node.inline_asm, str): + asm_split = node.inline_asm.split("\n") + for asm in asm_split: + if "sload(" in asm: + arg = asm.split("sload(")[1].split(")")[0] + exp = Literal(arg, ElementaryType("bytes32")) + sv = StateVariable() + sv.name = "fallback_sload_hardcoded" + sv.expression = exp + sv.is_constant = True + sv.type = exp.type + return sv + elif node.type == NodeType.EXPRESSION: + exp = node.expression + if isinstance(exp, AssignmentOperation): + exp = exp.expression_right + if isinstance(exp, CallExpression) and "sload" in str(exp.called): + exp = exp.arguments[0] + if ( + isinstance(exp, Literal) + and isinstance(exp.type, ElementaryType) + and exp.type.name == "bytes32" + ): + sv = StateVariable() + sv.name = "fallback_sload_hardcoded" + sv.expression = exp + sv.is_constant = True + sv.type = exp.type + 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 From 7c8f14e95aba2bd65f5cbb02b37020c3230728d4 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 12:24:01 -0500 Subject: [PATCH 11/70] Handle storage slot hardcoded in fallback --- slither/tools/read_storage/read_storage.py | 30 +++++++++++++------ tests/storage-layout/StorageSlot.bin | 2 +- .../TEST_unstructured_storage.json | 9 ++++++ .../UnstructuredStorageLayout.abi | 2 +- .../UnstructuredStorageLayout.bin | 2 +- .../unstructured_storage-0.8.10.sol | 19 ++++++++++++ unstructured_storage.json | 29 ------------------ 7 files changed, 52 insertions(+), 41 deletions(-) delete mode 100644 unstructured_storage.json diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index ed9bb5413c..b2661baad5 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -24,7 +24,7 @@ from slither.core.solidity_types.type import Type from slither.core.solidity_types import ArrayType, ElementaryType, UserDefinedType, MappingType from slither.core.declarations import Contract, Structure -from slither.core.expressions import AssignmentOperation, CallExpression, Literal +from slither.core.expressions import AssignmentOperation, CallExpression, Literal, Identifier from slither.core.variables.state_variable import StateVariable from slither.core.variables.structure_variable import StructureVariable @@ -270,22 +270,32 @@ def find_constant_slot_storage_type( funcs = [f for f in var.contract.functions if var in f.state_variables_read] if len(funcs) == 0: for c in self.contracts: - funcs.extend([f for f in c.functions if var in f.state_variables_read]) + funcs.extend([f for f in c.functions if var in f.state_variables_read or f.is_fallback]) for func in funcs: if func.return_type is not None: ret = func.return_type[0] size, _ = ret.storage_size return str(ret), size * 8 for node in func.all_nodes(): - exp = str(node.expression) - if f"getAddressSlot({var.name})" in exp: + exp = node.expression + if f"getAddressSlot({var.name})" in str(exp): return "address", 160 - if f"getBooleanSlot({var.name})" in exp: + if f"getBooleanSlot({var.name})" in str(exp): return "bool", 1 - if f"getBytes32Slot({var.name})" in exp: + if f"getBytes32Slot({var.name})" in str(exp): return "bytes32", 256 - if f"getUint256Slot({var.name})" in exp: + if f"getUint256Slot({var.name})" in str(exp): return "uint256", 256 + if isinstance(exp, AssignmentOperation): + left = exp.expression_left + right = exp.expression_right + if ( + isinstance(left, Identifier) and + isinstance(right, CallExpression) and + "sload" in str(right.called) and + right.arguments[0] == var.expression + ): + return "address", 160 return storage_type, size def walk_slot_info(self, func: Callable) -> None: @@ -365,6 +375,7 @@ def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariabl sv.expression = exp sv.is_constant = True sv.type = exp.type + sv.set_contract(contract) return sv elif node.type == NodeType.EXPRESSION: exp = node.expression @@ -375,13 +386,14 @@ def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariabl if ( isinstance(exp, Literal) and isinstance(exp.type, ElementaryType) - and exp.type.name == "bytes32" + and exp.type.name in ["bytes32", "uint256"] ): sv = StateVariable() sv.name = "fallback_sload_hardcoded" sv.expression = exp sv.is_constant = True - sv.type = exp.type + sv.type = ElementaryType("bytes32") + sv.set_contract(contract) return sv return None diff --git a/tests/storage-layout/StorageSlot.bin b/tests/storage-layout/StorageSlot.bin index 9cda2b5577..0b4f28c359 100644 --- a/tests/storage-layout/StorageSlot.bin +++ b/tests/storage-layout/StorageSlot.bin @@ -1 +1 @@ -60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220b19f0baad7f0ddaf31a5de68681e53b99a795cfe56bdd586e475316fbb4d6ecf64736f6c634300080a0033 \ No newline at end of file +60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220751c52d8c02daf668d8eb45369730e244a2652349bdd625cf9e59e646d66b80e64736f6c63430008000033 \ No newline at end of file diff --git a/tests/storage-layout/TEST_unstructured_storage.json b/tests/storage-layout/TEST_unstructured_storage.json index 1830154606..206bf01acd 100644 --- a/tests/storage-layout/TEST_unstructured_storage.json +++ b/tests/storage-layout/TEST_unstructured_storage.json @@ -25,5 +25,14 @@ "offset": 0, "value": true, "elems": {} + }, + "fallback_sload_hardcoded": { + "name": "fallback_sload_hardcoded", + "type_string": "address", + "slot": 89532207833283453166981358064394884954800891875771469636219037672473505217783, + "size": 160, + "offset": 0, + "value": "0x54006763154c764da4AF42a8c3cfc25Ea29765D5", + "elems": {} } } \ No newline at end of file diff --git a/tests/storage-layout/UnstructuredStorageLayout.abi b/tests/storage-layout/UnstructuredStorageLayout.abi index a60ca06615..9b579f254e 100644 --- a/tests/storage-layout/UnstructuredStorageLayout.abi +++ b/tests/storage-layout/UnstructuredStorageLayout.abi @@ -1 +1 @@ -[{"inputs":[],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/tests/storage-layout/UnstructuredStorageLayout.bin b/tests/storage-layout/UnstructuredStorageLayout.bin index 92dcb6c8f7..afa19ca361 100644 --- a/tests/storage-layout/UnstructuredStorageLayout.bin +++ b/tests/storage-layout/UnstructuredStorageLayout.bin @@ -1 +1 @@ -608060405234801561001057600080fd5b506101b9806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063975057e714610030575b600080fd5b61003861003a565b005b6000610044610101565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461007f57600080fd5b60007f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050600033905080825560007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b905060007354006763154c764da4af42a8c3cfc25ea29765d590508082556100fa600161012f565b5050505050565b6000807f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050805491505090565b8061015c7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd914360001b610179565b60000160006101000a81548160ff02191690831515021790555050565b600081905091905056fea2646970667358221220ec4f5646e03d5da06f717a9c05c4ed0652abf53527dd518d1a1b7d5a92d1c0c164736f6c634300080a0033 \ No newline at end of file +608060405234801561001057600080fd5b50610225806100206000396000f3fe608060405234801561001057600080fd5b506004361061002f5760003560e01c8063975057e71461007957610030565b5b7fc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7543660008037600080366000845af43d806000803e816000811461007457816000f35b816000fd5b610081610083565b005b600061008d61016d565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146100c857600080fd5b60007f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050600033905080825560007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b905060007354006763154c764da4af42a8c3cfc25ea29765d59050808255807fc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf755610166600161019b565b5050505050565b6000807f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050805491505090565b806101c87f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd914360001b6101e5565b60000160006101000a81548160ff02191690831515021790555050565b600081905091905056fea26469706673582212200d5986a006a2bea622290309f63af92a4188b6e288be9b88b052c534c185931f64736f6c63430008000033 \ No newline at end of file diff --git a/tests/storage-layout/unstructured_storage-0.8.10.sol b/tests/storage-layout/unstructured_storage-0.8.10.sol index 40cd8afab1..c1556c90c5 100644 --- a/tests/storage-layout/unstructured_storage-0.8.10.sol +++ b/tests/storage-layout/unstructured_storage-0.8.10.sol @@ -101,8 +101,27 @@ contract UnstructuredStorageLayout { address _impl = address(0x0054006763154c764da4af42a8c3cfc25ea29765d5); assembly { sstore(impl_slot, _impl) + sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, _impl) } _set_rollback(true); } + + // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" + fallback() external { + assembly { // solium-disable-line + 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) + } + } + } } diff --git a/unstructured_storage.json b/unstructured_storage.json deleted file mode 100644 index 1830154606..0000000000 --- a/unstructured_storage.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "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": {} - } -} \ No newline at end of file From 90df442a02177e8dfe727d268b5562dbabd973fa Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 12:46:08 -0500 Subject: [PATCH 12/70] Compact `get_all_storage_variables` --- slither/tools/read_storage/read_storage.py | 26 +++++----------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index b2661baad5..02d77383ae 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -330,26 +330,12 @@ def get_all_storage_variables(self, func: Callable = None) -> None: 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 - ], - ) - ) - self._constant_storage_slots.extend( - filter( - func, - [ - (contract, var) - for var in contract.state_variables_ordered - if var.is_constant and str(var.type) == "bytes32" - ], - ) - ) + for var in contract.state_variables_ordered: + if func is None or (func is not None and func(var)): + if not var.is_constant and not var.is_immutable: + self._target_variables.append((contract, var)) + elif var.is_constant and var.type == ElementaryType("bytes32"): + self._constant_storage_slots.append((contract, var)) hardcoded_slot = self.find_hardcoded_slot_in_fallback(contract) if hardcoded_slot is not None: self._constant_storage_slots.append((contract, hardcoded_slot)) From ea72130c44ec2c0181724d0a675c4cec55b53c32 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 12:48:16 -0500 Subject: [PATCH 13/70] Black --- slither/tools/read_storage/read_storage.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 02d77383ae..3d9ac56aaa 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -270,7 +270,9 @@ def find_constant_slot_storage_type( funcs = [f for f in var.contract.functions if var in f.state_variables_read] if len(funcs) == 0: for c in self.contracts: - funcs.extend([f for f in c.functions if var in f.state_variables_read or f.is_fallback]) + funcs.extend( + [f for f in c.functions if var in f.state_variables_read or f.is_fallback] + ) for func in funcs: if func.return_type is not None: ret = func.return_type[0] @@ -290,10 +292,10 @@ def find_constant_slot_storage_type( left = exp.expression_left right = exp.expression_right if ( - isinstance(left, Identifier) and - isinstance(right, CallExpression) and - "sload" in str(right.called) and - right.arguments[0] == var.expression + isinstance(left, Identifier) + and isinstance(right, CallExpression) + and "sload" in str(right.called) + and right.arguments[0] == var.expression ): return "address", 160 return storage_type, size From c8d7420bb4924dbd408cbbe4ec79992728e63957 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 12:58:09 -0500 Subject: [PATCH 14/70] Use Literal.value instead of string casting --- tests/test_unstructured_storage.py | 91 +++--------------------------- 1 file changed, 7 insertions(+), 84 deletions(-) diff --git a/tests/test_unstructured_storage.py b/tests/test_unstructured_storage.py index 4d3e728de2..68c2782c9d 100644 --- a/tests/test_unstructured_storage.py +++ b/tests/test_unstructured_storage.py @@ -1,99 +1,22 @@ import re import os -import sys import json -import shutil -import subprocess -from time import sleep -from typing import Generator import pytest from deepdiff import DeepDiff from slither import Slither from slither.tools.read_storage import SlitherReadStorage - -try: - from web3 import Web3 - from web3.contract import Contract -except ImportError: - print("ERROR: in order to use slither-read-storage, you need to install web3") - print("$ pip3 install web3 --user\n") - sys.exit(-1) +from tests.test_read_storage import ( + GanacheInstance, + fixture_ganache, + fixture_web3, + get_source_file, + deploy_contract, +) SLITHER_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STORAGE_TEST_ROOT = os.path.join(SLITHER_ROOT, "tests", "storage-layout") -# 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: - source = f.read() - - return source - - -def deploy_contract(w3, ganache, contract_bin, contract_abi) -> Contract: - """Deploy contract to the local ganache network""" - signed_txn = w3.eth.account.sign_transaction( - dict( - nonce=w3.eth.get_transaction_count(ganache.eth_address), - maxFeePerGas=20000000000, - maxPriorityFeePerGas=1, - gas=15000000, - to=b"", - data="0x" + contract_bin, - chainId=1, - ), - ganache.eth_privkey, - ) - tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction) - address = w3.eth.get_transaction_receipt(tx_hash)["contractAddress"] - contract = w3.eth.contract(address, abi=contract_abi) - return contract - # pylint: disable=too-many-locals @pytest.mark.usefixtures("web3", "ganache") From 6b0f49ac8bf154e3c3ee2305137d6e4d25424789 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 13:57:59 -0500 Subject: [PATCH 15/70] Use `Contract.get_functions_reading_from_variable`, check function return type sizes, and add comments in `find_constant_slot_storage_type` --- slither/tools/read_storage/read_storage.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 3d9ac56aaa..ffdff81b7a 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -267,19 +267,20 @@ def find_constant_slot_storage_type( return None storage_type = None size = None - funcs = [f for f in var.contract.functions if var in f.state_variables_read] + funcs = var.contract.get_functions_reading_from_variable(var) if len(funcs) == 0: for c in self.contracts: - funcs.extend( - [f for f in c.functions if var in f.state_variables_read or f.is_fallback] - ) + funcs.extend(c.get_functions_reading_from_variable(var)) for func in funcs: if func.return_type is not None: - ret = func.return_type[0] - size, _ = ret.storage_size - return str(ret), size * 8 + rets = func.return_type + 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): @@ -288,6 +289,7 @@ def find_constant_slot_storage_type( 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): left = exp.expression_left right = exp.expression_right From 285a6baafd6a801bfd56e61060a26e5cee68f00e Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 13:58:34 -0500 Subject: [PATCH 16/70] Get Literal from expression instead of string casting --- slither/tools/read_storage/read_storage.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index ffdff81b7a..2e69c4e978 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -131,12 +131,15 @@ def get_storage_layout(self) -> None: var_name = var.name try: exp = var.expression - if str(exp).startswith("0x"): - slot = coerce_type("int", str(exp)) - elif str(exp).startswith("keccak256(bytes)"): - slot_str = str(exp).split("(")[2].replace(")", "") - slot_hash = keccak(text=slot_str) - slot = coerce_type("int", slot_hash) + if isinstance(exp, CallExpression) and str(exp.called) == "keccak256(bytes)": + exp = exp.arguments[0] + if isinstance(exp, Literal): + if exp.value.startswith("0x"): + slot = coerce_type("int", exp.value) + else: + slot_str = exp.value + slot_hash = keccak(text=slot_str) + slot = coerce_type("int", slot_hash) else: continue offset = 0 From 3e3dc68e02490be7071a6ef1ddac80b9e425ea58 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 14:16:32 -0500 Subject: [PATCH 17/70] Always check fallback function --- slither/tools/read_storage/read_storage.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 2e69c4e978..e9b08156fc 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -274,6 +274,8 @@ def find_constant_slot_storage_type( if len(funcs) == 0: for c in self.contracts: funcs.extend(c.get_functions_reading_from_variable(var)) + fallback = [f for f in var.contract.functions if f.is_fallback] + funcs += fallback for func in funcs: if func.return_type is not None: rets = func.return_type From c8a89ae31e76d783e8000189178f9b78e1fe1d4e Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 15:00:14 -0500 Subject: [PATCH 18/70] Modularize pytest fixtures used by both `test_read_storage` and `test_unstructured_storage` by moving them into individual files in tests/fixtures/ and importing them into conftest.py --- tests/conftest.py | 4 +++ tests/fixtures/__init__.py | 0 tests/fixtures/fixture_ganache.py | 38 +++++++++++++++++++++++ tests/fixtures/fixture_web3.py | 17 +++++++++++ tests/fixtures/ganache_instance.py | 6 ++++ tests/test_read_storage.py | 49 ------------------------------ tests/test_unstructured_storage.py | 8 +---- 7 files changed, 66 insertions(+), 56 deletions(-) create mode 100644 tests/conftest.py create mode 100644 tests/fixtures/__init__.py create mode 100644 tests/fixtures/fixture_ganache.py create mode 100644 tests/fixtures/fixture_web3.py create mode 100644 tests/fixtures/ganache_instance.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000..c9d043bfad --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,4 @@ +# pylint: disable=unused-import +import pytest +from tests.fixtures.fixture_ganache import fixture_ganache +from tests.fixtures.fixture_web3 import fixture_web3 diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/fixture_ganache.py b/tests/fixtures/fixture_ganache.py new file mode 100644 index 0000000000..d46a8e203e --- /dev/null +++ b/tests/fixtures/fixture_ganache.py @@ -0,0 +1,38 @@ +import shutil +import subprocess +from time import sleep +from typing import Generator + +import pytest +from tests.fixtures.ganache_instance import GanacheInstance + + +@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() diff --git a/tests/fixtures/fixture_web3.py b/tests/fixtures/fixture_web3.py new file mode 100644 index 0000000000..0a7eafd754 --- /dev/null +++ b/tests/fixtures/fixture_web3.py @@ -0,0 +1,17 @@ +import sys + +import pytest +from tests.fixtures.ganache_instance import GanacheInstance + +try: + from web3 import Web3 +except ImportError: + print("ERROR: in order to use slither-read-storage, you need to install web3") + print("$ pip3 install web3 --user\n") + sys.exit(-1) + + +@pytest.fixture(scope="module", name="web3") +def fixture_web3(ganache: GanacheInstance): + w3 = Web3(Web3.HTTPProvider(ganache.provider, request_kwargs={"timeout": 30})) + return w3 diff --git a/tests/fixtures/ganache_instance.py b/tests/fixtures/ganache_instance.py new file mode 100644 index 0000000000..b0c180c278 --- /dev/null +++ b/tests/fixtures/ganache_instance.py @@ -0,0 +1,6 @@ +# 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 diff --git a/tests/test_read_storage.py b/tests/test_read_storage.py index 67a89dae87..05d2c78dab 100644 --- a/tests/test_read_storage.py +++ b/tests/test_read_storage.py @@ -2,10 +2,6 @@ import os import sys import json -import shutil -import subprocess -from time import sleep -from typing import Generator import pytest from deepdiff import DeepDiff @@ -13,7 +9,6 @@ from slither.tools.read_storage import SlitherReadStorage try: - from web3 import Web3 from web3.contract import Contract except ImportError: print("ERROR: in order to use slither-read-storage, you need to install web3") @@ -23,50 +18,6 @@ SLITHER_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STORAGE_TEST_ROOT = os.path.join(SLITHER_ROOT, "tests", "storage-layout") -# 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: diff --git a/tests/test_unstructured_storage.py b/tests/test_unstructured_storage.py index 68c2782c9d..33c6faed2d 100644 --- a/tests/test_unstructured_storage.py +++ b/tests/test_unstructured_storage.py @@ -6,13 +6,7 @@ from deepdiff import DeepDiff from slither import Slither from slither.tools.read_storage import SlitherReadStorage -from tests.test_read_storage import ( - GanacheInstance, - fixture_ganache, - fixture_web3, - get_source_file, - deploy_contract, -) +from tests.test_read_storage import get_source_file, deploy_contract SLITHER_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STORAGE_TEST_ROOT = os.path.join(SLITHER_ROOT, "tests", "storage-layout") From f26ba134334f3b2875484afe6336968a139386ac Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 15 Mar 2023 16:54:17 -0500 Subject: [PATCH 19/70] pylint --- slither/tools/read_storage/read_storage.py | 25 ++++++++++------------ 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index e9b08156fc..75512d7175 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -270,10 +270,9 @@ def find_constant_slot_storage_type( return None storage_type = None size = None - funcs = var.contract.get_functions_reading_from_variable(var) - if len(funcs) == 0: - for c in self.contracts: - funcs.extend(c.get_functions_reading_from_variable(var)) + funcs = [] + for c in self.contracts: + funcs.extend(c.get_functions_reading_from_variable(var)) fallback = [f for f in var.contract.functions if f.is_fallback] funcs += fallback for func in funcs: @@ -295,16 +294,14 @@ def find_constant_slot_storage_type( 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): - left = exp.expression_left - right = exp.expression_right - if ( - isinstance(left, Identifier) - and isinstance(right, CallExpression) - and "sload" in str(right.called) - and right.arguments[0] == var.expression - ): - return "address", 160 + 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 exp.expression_right.arguments[0] == var.expression + ): + return "address", 160 return storage_type, size def walk_slot_info(self, func: Callable) -> None: From 4d807419ba0abae8cfe620b07fa04f996e66c76f Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 16 Mar 2023 11:33:30 -0500 Subject: [PATCH 20/70] Change `isConnected` to `is_connected` --- tests/test_unstructured_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_unstructured_storage.py b/tests/test_unstructured_storage.py index 33c6faed2d..c4eaa8309d 100644 --- a/tests/test_unstructured_storage.py +++ b/tests/test_unstructured_storage.py @@ -15,7 +15,7 @@ # pylint: disable=too-many-locals @pytest.mark.usefixtures("web3", "ganache") def test_read_storage(web3, ganache) -> None: - assert web3.isConnected() + assert web3.is_connected() bin_path = os.path.join(STORAGE_TEST_ROOT, "UnstructuredStorageLayout.bin") abi_path = os.path.join(STORAGE_TEST_ROOT, "UnstructuredStorageLayout.abi") bytecode = get_source_file(bin_path) From 54341887844b7bec29e46c56ec9a6821372ea17e Mon Sep 17 00:00:00 2001 From: William E Bodell III Date: Thu, 16 Mar 2023 12:21:57 -0500 Subject: [PATCH 21/70] pylint: remove unused import Now that the fixtures are stand-alone modules, Web3 is no longer used here other than web3.Contract --- tests/test_read_storage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_read_storage.py b/tests/test_read_storage.py index 60b36cdc3c..b483e30d59 100644 --- a/tests/test_read_storage.py +++ b/tests/test_read_storage.py @@ -4,7 +4,6 @@ import pytest from deepdiff import DeepDiff -from web3 import Web3 from web3.contract import Contract from slither import Slither From 9f44726b01bf0aac16455a9d6f9a05ae6ddca577 Mon Sep 17 00:00:00 2001 From: William E Bodell III Date: Wed, 22 Mar 2023 16:30:51 -0500 Subject: [PATCH 22/70] Apply suggestions from code review Co-authored-by: alpharush <0xalpharush@protonmail.com> --- slither/tools/read_storage/read_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 58a3ab6c04..c8a9d3cfc7 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -263,7 +263,7 @@ def find_constant_slot_storage_type( storage_type = None size = None funcs = [] - for c in self.contracts: + for c in self.contracts_derived: funcs.extend(c.get_functions_reading_from_variable(var)) fallback = [f for f in var.contract.functions if f.is_fallback] funcs += fallback From cac3427e147b950702b979a0218989d9518f3160 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 22 Mar 2023 16:36:31 -0500 Subject: [PATCH 23/70] Move fixtures into conftest.py instead of separate files --- tests/conftest.py | 53 ++++++++++++++++++++++++++++-- tests/fixtures/__init__.py | 0 tests/fixtures/fixture_ganache.py | 38 --------------------- tests/fixtures/fixture_web3.py | 17 ---------- tests/fixtures/ganache_instance.py | 6 ---- 5 files changed, 51 insertions(+), 63 deletions(-) delete mode 100644 tests/fixtures/__init__.py delete mode 100644 tests/fixtures/fixture_ganache.py delete mode 100644 tests/fixtures/fixture_web3.py delete mode 100644 tests/fixtures/ganache_instance.py diff --git a/tests/conftest.py b/tests/conftest.py index c9d043bfad..d93dec242b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,53 @@ # pylint: disable=unused-import +import sys +import shutil +import subprocess +from time import sleep +from web3 import Web3 +from typing import Generator + import pytest -from tests.fixtures.fixture_ganache import fixture_ganache -from tests.fixtures.fixture_web3 import fixture_web3 + + +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 diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/fixtures/fixture_ganache.py b/tests/fixtures/fixture_ganache.py deleted file mode 100644 index d46a8e203e..0000000000 --- a/tests/fixtures/fixture_ganache.py +++ /dev/null @@ -1,38 +0,0 @@ -import shutil -import subprocess -from time import sleep -from typing import Generator - -import pytest -from tests.fixtures.ganache_instance import GanacheInstance - - -@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() diff --git a/tests/fixtures/fixture_web3.py b/tests/fixtures/fixture_web3.py deleted file mode 100644 index 0a7eafd754..0000000000 --- a/tests/fixtures/fixture_web3.py +++ /dev/null @@ -1,17 +0,0 @@ -import sys - -import pytest -from tests.fixtures.ganache_instance import GanacheInstance - -try: - from web3 import Web3 -except ImportError: - print("ERROR: in order to use slither-read-storage, you need to install web3") - print("$ pip3 install web3 --user\n") - sys.exit(-1) - - -@pytest.fixture(scope="module", name="web3") -def fixture_web3(ganache: GanacheInstance): - w3 = Web3(Web3.HTTPProvider(ganache.provider, request_kwargs={"timeout": 30})) - return w3 diff --git a/tests/fixtures/ganache_instance.py b/tests/fixtures/ganache_instance.py deleted file mode 100644 index b0c180c278..0000000000 --- a/tests/fixtures/ganache_instance.py +++ /dev/null @@ -1,6 +0,0 @@ -# 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 From 42698f2495b51655acdffc222c3a110c92f43c2a Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 22 Mar 2023 16:55:15 -0500 Subject: [PATCH 24/70] Revert using `contracts_derived` --- slither/tools/read_storage/read_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index c8a9d3cfc7..58a3ab6c04 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -263,7 +263,7 @@ def find_constant_slot_storage_type( storage_type = None size = None funcs = [] - for c in self.contracts_derived: + for c in self.contracts: funcs.extend(c.get_functions_reading_from_variable(var)) fallback = [f for f in var.contract.functions if f.is_fallback] funcs += fallback From 67aa471bf8625e8fb9acce793b96435ecd32e407 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 22 Mar 2023 16:55:37 -0500 Subject: [PATCH 25/70] pylint disable too-few-public-methods --- tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/conftest.py b/tests/conftest.py index d93dec242b..fa2e4f14eb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,6 +9,7 @@ import pytest +# pylint: disable=too-few-public-methods class GanacheInstance: def __init__(self, provider: str, eth_address: str, eth_privkey: str): self.provider = provider From f09aa2d2709c5a0cfbd2940e79211028c381ae8c Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 22 Mar 2023 16:58:49 -0500 Subject: [PATCH 26/70] pylint --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index fa2e4f14eb..0a6f0e3c83 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,9 +3,9 @@ import shutil import subprocess from time import sleep -from web3 import Web3 from typing import Generator +from web3 import Web3 import pytest From 40bf399a46667a0bdca58b7131653d1248969bf6 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 22 Mar 2023 18:15:26 -0500 Subject: [PATCH 27/70] Only handle sload from Literal in `find_hardcoded_slot_in_fallback` --- slither/tools/read_storage/read_storage.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 58a3ab6c04..0b52e2a527 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -351,7 +351,7 @@ def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariabl if node.type == NodeType.ASSEMBLY and isinstance(node.inline_asm, str): asm_split = node.inline_asm.split("\n") for asm in asm_split: - if "sload(" in asm: + if "sload(0x" in asm: # Only handle literals arg = asm.split("sload(")[1].split(")")[0] exp = Literal(arg, ElementaryType("bytes32")) sv = StateVariable() @@ -365,8 +365,11 @@ def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariabl exp = node.expression if isinstance(exp, AssignmentOperation): exp = exp.expression_right - if isinstance(exp, CallExpression) and "sload" in str(exp.called): + while isinstance(exp, CallExpression): + called = exp.called exp = exp.arguments[0] + if "sload" in str(called): + break if ( isinstance(exp, Literal) and isinstance(exp.type, ElementaryType) From 9ae6c2d83d7baefe33847caaf1eef1ca107daad1 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 22 Mar 2023 18:23:34 -0500 Subject: [PATCH 28/70] Black --- slither/tools/read_storage/read_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 0b52e2a527..cc7a009b2f 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -351,7 +351,7 @@ def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariabl if node.type == NodeType.ASSEMBLY and isinstance(node.inline_asm, str): asm_split = node.inline_asm.split("\n") for asm in asm_split: - if "sload(0x" in asm: # Only handle literals + if "sload(0x" in asm: # Only handle literals arg = asm.split("sload(")[1].split(")")[0] exp = Literal(arg, ElementaryType("bytes32")) sv = StateVariable() From b173082aadc1df882cac6f62d2117edc4904af65 Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 23 Mar 2023 13:02:58 -0500 Subject: [PATCH 29/70] Change default value of `func` to lambda expression in `get_all_storage_variables(func: Callable)` instead of None --- slither/tools/read_storage/read_storage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index cc7a009b2f..aae6a0998d 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -322,14 +322,14 @@ def get_slot_values(self, slot_info: SlotInfo) -> None: ) logger.info(f"\nValue: {slot_info.value}\n") - def get_all_storage_variables(self, func: Callable = None) -> None: + 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: for var in contract.state_variables_ordered: - if func is None or (func is not None and func(var)): + if func(var): if not var.is_constant and not var.is_immutable: self._target_variables.append((contract, var)) elif var.is_constant and var.type == ElementaryType("bytes32"): From d5054c53c78dd21f595f9fe76ce333e9a42e5bae Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 23 Mar 2023 13:06:59 -0500 Subject: [PATCH 30/70] Minor fix --- slither/tools/read_storage/read_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index aae6a0998d..c7ab242c82 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -258,7 +258,7 @@ def find_constant_slot_storage_type( type (str): The type of value stored in the slot. size (int): The type's size in bits. """ - if not (var.is_constant and str(var.type) == "bytes32"): + if not (var.is_constant and var.type == ElementaryType("bytes32")): return None storage_type = None size = None From 4c36d1b3773b6b1a8e827f313eac16243ecd99f5 Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 23 Mar 2023 13:43:01 -0500 Subject: [PATCH 31/70] Fixes in `find_hardcoded_slot_in_fallback` --- slither/tools/read_storage/read_storage.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index c7ab242c82..b3ab5c23bf 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -347,7 +347,10 @@ def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariabl break if fallback is None: return None - for node in fallback.all_nodes(): + queue = [fallback.entry_point] + while len(queue) > 0: + node = queue.pop(0) + queue.extend(node.sons) if node.type == NodeType.ASSEMBLY and isinstance(node.inline_asm, str): asm_split = node.inline_asm.split("\n") for asm in asm_split: @@ -365,7 +368,7 @@ def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariabl exp = node.expression if isinstance(exp, AssignmentOperation): exp = exp.expression_right - while isinstance(exp, CallExpression): + while isinstance(exp, CallExpression) and len(exp.arguments) > 0: called = exp.called exp = exp.arguments[0] if "sload" in str(called): @@ -374,6 +377,7 @@ def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariabl isinstance(exp, Literal) and isinstance(exp.type, ElementaryType) and exp.type.name in ["bytes32", "uint256"] + and exp.value.startswith("0x") ): sv = StateVariable() sv.name = "fallback_sload_hardcoded" From ef5d05b5d6d391ab9924a3f57eeff0d3b8713eab Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 23 Mar 2023 13:54:30 -0500 Subject: [PATCH 32/70] fold constant of hash expressions --- slither/printers/guidance/echidna.py | 9 ++-- .../expressions/expression_parsing.py | 2 + slither/utils/integer_conversion.py | 4 +- .../visitors/expression/constants_folding.py | 51 ++++++++++++------- tests/constant_folding_binop.sol | 1 + tests/test_constant_folding.py | 51 +++++++++++-------- 6 files changed, 73 insertions(+), 45 deletions(-) diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index acbf5b0158..68a7523dbb 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -178,11 +178,10 @@ 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 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, TypeConversion): if isinstance(ir.variable, Constant): if isinstance(ir.type, TypeAlias): diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index d0dc4c7e02..1126d430ae 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -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"): diff --git a/slither/utils/integer_conversion.py b/slither/utils/integer_conversion.py index e4dff333ca..99064f5644 100644 --- a/slither/utils/integer_conversion.py +++ b/slither/utils/integer_conversion.py @@ -4,7 +4,9 @@ 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")): diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index 12eb6be9d1..ad5d5c751c 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -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 @@ 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 @@ -63,23 +64,30 @@ def result(self) -> "Literal": # emulate 256-bit wrapping if str(self._type).startswith("uint"): value = value & (2**256 - 1) + if str(self._type).startswith("byte"): + print(value) + print(type(value)) + value = int.to_bytes(int(value), 32, "big") return Literal(value, self._type) 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)) + 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 def _post_binary_operation(self, expression: BinaryOperation) -> None: @@ -183,7 +191,9 @@ def _post_unary_operation(self, expression: UnaryOperation) -> None: 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 @@ def _post_assignement_operation(self, expression: expressions.AssignmentOperatio 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, int(digest.hexdigest(), 16)) + else: + raise NotConstant def _post_conditional_expression(self, expression: expressions.ConditionalExpression) -> None: raise NotConstant @@ -247,7 +264,7 @@ def _post_type_conversion(self, expression: expressions.TypeConversion) -> None: 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) diff --git a/tests/constant_folding_binop.sol b/tests/constant_folding_binop.sol index 923418ce71..dc4bb735b2 100644 --- a/tests/constant_folding_binop.sol +++ b/tests/constant_folding_binop.sol @@ -11,4 +11,5 @@ 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); } \ No newline at end of file diff --git a/tests/test_constant_folding.py b/tests/test_constant_folding.py index 21517ddc4c..85c37a8fea 100644 --- a/tests/test_constant_folding.py +++ b/tests/test_constant_folding.py @@ -12,37 +12,37 @@ def test_constant_folding_rational(): 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 def test_constant_folding_binary_expressions(): @@ -51,51 +51,58 @@ def test_constant_folding_binary_expressions(): 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 == 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 == 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 == 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 == 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 == 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 == 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 == 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 == 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 + ) \ No newline at end of file From faf54a951d9eab0cc1742d7a433afcdd60877532 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 23 Mar 2023 14:48:51 -0500 Subject: [PATCH 33/70] handle exception --- slither/printers/guidance/echidna.py | 16 +++++++++----- .../visitors/expression/constants_folding.py | 21 +++++++++++++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index 68a7523dbb..25e0968cdb 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -32,7 +32,7 @@ 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,10 +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 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): diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index ad5d5c751c..320b9737d2 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -12,7 +12,7 @@ UnaryOperation, TupleExpression, TypeConversion, - CallExpression + CallExpression, ) from slither.core.variables import Variable from slither.utils.integer_conversion import convert_string_to_fraction, convert_string_to_int @@ -72,18 +72,22 @@ def result(self) -> "Literal": def _post_identifier(self, expression: Identifier) -> None: 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) + 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: @@ -103,7 +107,8 @@ def _post_binary_operation(self, expression: BinaryOperation) -> None: (Literal, BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion), ): raise NotConstant - + print(expression_left) + print(expression_right) left = get_val(expression_left) right = get_val(expression_right) @@ -264,7 +269,15 @@ def _post_type_conversion(self, expression: expressions.TypeConversion) -> None: expr = expression.expression if not isinstance( expr, - (Literal, BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion, CallExpression), + ( + Literal, + BinaryOperation, + UnaryOperation, + Identifier, + TupleExpression, + TypeConversion, + CallExpression, + ), ): raise NotConstant cf = ConstantFolding(expr, self._type) From 854f878e11718a1d6edf0c87b218764de6c2b20e Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 23 Mar 2023 16:27:01 -0500 Subject: [PATCH 34/70] Add a few unhappy paths to unstructured_storage-0.8.10.sol --- tests/storage-layout/unstructured_storage-0.8.10.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/storage-layout/unstructured_storage-0.8.10.sol b/tests/storage-layout/unstructured_storage-0.8.10.sol index c1556c90c5..eb0624da5f 100644 --- a/tests/storage-layout/unstructured_storage-0.8.10.sol +++ b/tests/storage-layout/unstructured_storage-0.8.10.sol @@ -66,6 +66,8 @@ contract UnstructuredStorageLayout { 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); + function _admin() internal view returns (address admin) { bytes32 slot = ADMIN_SLOT; @@ -87,6 +89,13 @@ contract UnstructuredStorageLayout { 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)); @@ -105,11 +114,13 @@ contract UnstructuredStorageLayout { } _set_rollback(true); + _set_beacon(address(this)); } // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" fallback() external { assembly { // solium-disable-line + let nonsense := sload(add(1,1)) let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) calldatacopy(0x0, 0x0, calldatasize()) let success := delegatecall(gas(), contractLogic, 0x0, calldatasize(), 0, 0) From 5220e5048a86e4ace0a33ca373fae8cc46513f21 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 10:25:21 -0500 Subject: [PATCH 35/70] Clean up and comment `find_hardcoded_slot_in_fallback` --- slither/tools/read_storage/read_storage.py | 55 +++++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index b3ab5c23bf..5903e8fc5b 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -340,6 +340,16 @@ def get_all_storage_variables(self, func: Callable = lambda x: x) -> None: @staticmethod def find_hardcoded_slot_in_fallback(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: @@ -348,22 +358,13 @@ def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariabl if fallback is None: return None queue = [fallback.entry_point] + visited = [] while len(queue) > 0: node = queue.pop(0) - queue.extend(node.sons) + 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): - asm_split = node.inline_asm.split("\n") - for asm in asm_split: - if "sload(0x" in asm: # Only handle literals - arg = asm.split("sload(")[1].split(")")[0] - 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 SlitherReadStorage.find_hardcoded_slot_in_asm_str(node.inline_asm) elif node.type == NodeType.EXPRESSION: exp = node.expression if isinstance(exp, AssignmentOperation): @@ -388,6 +389,34 @@ def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariabl return sv return None + @staticmethod + def find_hardcoded_slot_in_asm_str(inline_asm: str) -> 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 convert_slot_info_to_rows(self, slot_info: SlotInfo) -> None: """Convert and append slot info to table. Create table if it does not yet exist From f8d94e8c02dd32f974f6ab69d193782d27fdae1a Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 12:03:57 -0500 Subject: [PATCH 36/70] Make exp parsing more robust, use ConstantFolding in `find_hardcoded_slot_in_fallback` --- slither/tools/read_storage/read_storage.py | 96 ++++++++++++++++------ 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 5903e8fc5b..cab9cdc965 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -15,8 +15,18 @@ 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, CallExpression, Literal, Identifier +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 from .utils import coerce_type, get_offset_value, get_storage_data @@ -44,7 +54,7 @@ class SlitherReadStorageException(Exception): pass -# 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) -> None: self._checksum_address: Optional[ChecksumAddress] = None @@ -338,8 +348,7 @@ def get_all_storage_variables(self, func: Callable = lambda x: x) -> None: if hardcoded_slot is not None: self._constant_storage_slots.append((contract, hardcoded_slot)) - @staticmethod - def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariable]: + 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)`. @@ -364,33 +373,17 @@ def find_hardcoded_slot_in_fallback(contract: Contract) -> Optional[StateVariabl 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) - elif node.type == NodeType.EXPRESSION: - exp = node.expression - if isinstance(exp, AssignmentOperation): - exp = exp.expression_right - 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, Literal) - and isinstance(exp.type, ElementaryType) - and exp.type.name in ["bytes32", "uint256"] - and exp.value.startswith("0x") - ): - sv = StateVariable() - sv.name = "fallback_sload_hardcoded" - sv.expression = exp - sv.is_constant = True - sv.type = ElementaryType("bytes32") - sv.set_contract(contract) + 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) -> Optional[StateVariable]: + 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))` @@ -417,6 +410,55 @@ def find_hardcoded_slot_in_asm_str(inline_asm: str) -> Optional[StateVariable]: return sv return None + def find_hardcoded_slot_in_exp( + self, exp: "Expression", contract: Contract + ) -> Optional[StateVariable]: + 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 not isinstance(exp, Literal) and isinstance( + exp, + (BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion), + ): + exp = ConstantFolding(exp, "bytes32").result() + if ( + isinstance(exp, Literal) + and isinstance(exp.type, ElementaryType) + and exp.type.name in ["bytes32", "uint256"] + # and (str(exp.value).startswith("0x") or isinstance(exp.value, bytes)) + ): + 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)): + str_value = str(value).replace("b'", "0x").replace("\\x", "").replace("'", "") + exp = Literal(str_value, ElementaryType("bytes32")) + int_value = int(str_value, 16) + state_var_slots = [ + self.get_variable_info(contract, var)[0] + for contract, var in self.target_variables + ] + if int_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 does not yet exist From 6117abdc174c8cc615e8c0130af2c9aeba69be56 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 12:04:46 -0500 Subject: [PATCH 37/70] Add more "unhappy path" tests for unstructured storage --- tests/storage-layout/TEST_unstructured_storage.json | 9 +++++++++ tests/storage-layout/unstructured_storage-0.8.10.sol | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/storage-layout/TEST_unstructured_storage.json b/tests/storage-layout/TEST_unstructured_storage.json index 206bf01acd..5d31ba107b 100644 --- a/tests/storage-layout/TEST_unstructured_storage.json +++ b/tests/storage-layout/TEST_unstructured_storage.json @@ -1,4 +1,13 @@ { + "masterCopy": { + "name": "masterCopy", + "type_string": "address", + "slot": 0, + "size": 160, + "offset": 0, + "value": "0x0000000000000000000000000000000000000000", + "elems": {} + }, "ADMIN_SLOT": { "name": "ADMIN_SLOT", "type_string": "address", diff --git a/tests/storage-layout/unstructured_storage-0.8.10.sol b/tests/storage-layout/unstructured_storage-0.8.10.sol index eb0624da5f..015ebee6f1 100644 --- a/tests/storage-layout/unstructured_storage-0.8.10.sol +++ b/tests/storage-layout/unstructured_storage-0.8.10.sol @@ -68,6 +68,7 @@ contract UnstructuredStorageLayout { 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; @@ -120,7 +121,8 @@ contract UnstructuredStorageLayout { // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" fallback() external { assembly { // solium-disable-line - let nonsense := sload(add(1,1)) + 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) From 8dd932ac889d9704dc1a15b1090ee53dad3cfa52 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 12:50:33 -0500 Subject: [PATCH 38/70] Black and pylint --- tests/test_constant_folding.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/tests/test_constant_folding.py b/tests/test_constant_folding.py index 85c37a8fea..cd4c43ad5c 100644 --- a/tests/test_constant_folding.py +++ b/tests/test_constant_folding.py @@ -63,35 +63,35 @@ def test_constant_folding_binary_expressions(): variable_d = contract.get_state_variable_from_name("d") assert str(variable_d.type) == "bool" - assert ConstantFolding(variable_d.expression, "bool").result().value == 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 ConstantFolding(variable_e.expression, "bool").result().value == 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 ConstantFolding(variable_f.expression, "bool").result().value == 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 ConstantFolding(variable_g.expression, "bool").result().value == 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 ConstantFolding(variable_h.expression, "bool").result().value == 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 ConstantFolding(variable_i.expression, "bool").result().value == 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 ConstantFolding(variable_j.expression, "bool").result().value == 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 ConstantFolding(variable_k.expression, "bool").result().value == 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" @@ -103,6 +103,9 @@ def test_constant_folding_binary_expressions(): 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") + int.from_bytes( + ConstantFolding(IMPLEMENTATION_SLOT.expression, "bytes32").result().value, + byteorder="big", + ) == 24440054405305269366569402256811496959409073762505157381672968839269610695612 - ) \ No newline at end of file + ) From 0bf27fff7913aa14171c0607c55e27592189f378 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 13:03:06 -0500 Subject: [PATCH 39/70] Black and pylint --- slither/visitors/expression/constants_folding.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index 320b9737d2..c8c580921e 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -18,6 +18,7 @@ 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 +from slither.core.declarations.solidity_variables import SolidityFunction class NotConstant(Exception): @@ -63,7 +64,7 @@ def result(self) -> "Literal": value = int(value) # emulate 256-bit wrapping if str(self._type).startswith("uint"): - value = value & (2**256 - 1) + value = value & (2 ** 256 - 1) if str(self._type).startswith("byte"): print(value) print(type(value)) @@ -71,8 +72,6 @@ def result(self) -> "Literal": return Literal(value, self._type) def _post_identifier(self, expression: Identifier) -> None: - from slither.core.declarations.solidity_variables import SolidityFunction - if isinstance(expression.value, Variable): if expression.value.is_constant: expr = expression.value.expression @@ -93,7 +92,7 @@ def _post_identifier(self, expression: Identifier) -> None: else: raise NotConstant - # 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 @@ -117,7 +116,7 @@ def _post_binary_operation(self, expression: BinaryOperation) -> None: and isinstance(left, (int, Fraction)) and isinstance(right, (int, Fraction)) ): - set_val(expression, left**right) # type: ignore + set_val(expression, left ** right) # type: ignore elif ( expression.type == BinaryOperationType.MULTIPLICATION and isinstance(left, (int, Fraction)) From 4828a04fe7b00300ae2412f1b6ed4ec7962764e0 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 13:08:53 -0500 Subject: [PATCH 40/70] Revert moving import to top-level --- slither/visitors/expression/constants_folding.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index c8c580921e..554109dc65 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -18,7 +18,6 @@ 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 -from slither.core.declarations.solidity_variables import SolidityFunction class NotConstant(Exception): @@ -71,7 +70,10 @@ def result(self) -> "Literal": value = int.to_bytes(int(value), 32, "big") return Literal(value, self._type) + # pylint: disable=import-outside-toplevel def _post_identifier(self, expression: Identifier) -> None: + from slither.core.declarations.solidity_variables import SolidityFunction + if isinstance(expression.value, Variable): if expression.value.is_constant: expr = expression.value.expression From 0b2ddbc067d61ed69357a4e9b94ee12bd569ecbd Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 13:27:37 -0500 Subject: [PATCH 41/70] Black again... black on my machine and black in the CI seem to disagree --- slither/visitors/expression/constants_folding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index 554109dc65..11b075d247 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -63,7 +63,7 @@ def result(self) -> "Literal": value = int(value) # emulate 256-bit wrapping if str(self._type).startswith("uint"): - value = value & (2 ** 256 - 1) + value = value & (2**256 - 1) if str(self._type).startswith("byte"): print(value) print(type(value)) @@ -118,7 +118,7 @@ def _post_binary_operation(self, expression: BinaryOperation) -> None: and isinstance(left, (int, Fraction)) and isinstance(right, (int, Fraction)) ): - set_val(expression, left ** right) # type: ignore + set_val(expression, left**right) # type: ignore elif ( expression.type == BinaryOperationType.MULTIPLICATION and isinstance(left, (int, Fraction)) From 73cb16b3b593cb17ed92877a2b86a6766fed637e Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 14:19:44 -0500 Subject: [PATCH 42/70] Remove print statements --- slither/visitors/expression/constants_folding.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index 11b075d247..2b2966fe4c 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -65,8 +65,6 @@ def result(self) -> "Literal": if str(self._type).startswith("uint"): value = value & (2**256 - 1) if str(self._type).startswith("byte"): - print(value) - print(type(value)) value = int.to_bytes(int(value), 32, "big") return Literal(value, self._type) @@ -108,8 +106,6 @@ def _post_binary_operation(self, expression: BinaryOperation) -> None: (Literal, BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion), ): raise NotConstant - print(expression_left) - print(expression_right) left = get_val(expression_left) right = get_val(expression_right) From 16d50d3ba4711020594a05ec5ad627b41054ab7e Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 14:20:11 -0500 Subject: [PATCH 43/70] Update test contract and expected result --- tests/storage-layout/StorageSlot.bin | 2 +- tests/storage-layout/TEST_unstructured_storage.json | 9 +++++++++ tests/storage-layout/UnstructuredStorageLayout.bin | 2 +- tests/storage-layout/unstructured_storage-0.8.10.sol | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/storage-layout/StorageSlot.bin b/tests/storage-layout/StorageSlot.bin index 0b4f28c359..47650d3f46 100644 --- a/tests/storage-layout/StorageSlot.bin +++ b/tests/storage-layout/StorageSlot.bin @@ -1 +1 @@ -60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220751c52d8c02daf668d8eb45369730e244a2652349bdd625cf9e59e646d66b80e64736f6c63430008000033 \ No newline at end of file +60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea26469706673582212201e631dae76a01034b70f60379b7fc6ff16daf343bff8f01f189ed5b3c3c291c164736f6c634300080a0033 \ No newline at end of file diff --git a/tests/storage-layout/TEST_unstructured_storage.json b/tests/storage-layout/TEST_unstructured_storage.json index 5d31ba107b..6dbdbddcac 100644 --- a/tests/storage-layout/TEST_unstructured_storage.json +++ b/tests/storage-layout/TEST_unstructured_storage.json @@ -35,6 +35,15 @@ "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", diff --git a/tests/storage-layout/UnstructuredStorageLayout.bin b/tests/storage-layout/UnstructuredStorageLayout.bin index afa19ca361..9f20de74cd 100644 --- a/tests/storage-layout/UnstructuredStorageLayout.bin +++ b/tests/storage-layout/UnstructuredStorageLayout.bin @@ -1 +1 @@ -608060405234801561001057600080fd5b50610225806100206000396000f3fe608060405234801561001057600080fd5b506004361061002f5760003560e01c8063975057e71461007957610030565b5b7fc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7543660008037600080366000845af43d806000803e816000811461007457816000f35b816000fd5b610081610083565b005b600061008d61016d565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146100c857600080fd5b60007f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050600033905080825560007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b905060007354006763154c764da4af42a8c3cfc25ea29765d59050808255807fc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf755610166600161019b565b5050505050565b6000807f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050805491505090565b806101c87f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd914360001b6101e5565b60000160006101000a81548160ff02191690831515021790555050565b600081905091905056fea26469706673582212200d5986a006a2bea622290309f63af92a4188b6e288be9b88b052c534c185931f64736f6c63430008000033 \ No newline at end of file +608060405234801561001057600080fd5b5061030b806100206000396000f3fe608060405234801561001057600080fd5b506004361061002f5760003560e01c8063975057e71461009757610030565b5b600180035473ffffffffffffffffffffffffffffffffffffffff600054167fc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7543660008037600080366000845af43d806000803e816000811461009257816000f35b816000fd5b61009f6100a1565b005b60006100ab6101a8565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146100e657600080fd5b60007f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050600033905080825560007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b905060007354006763154c764da4af42a8c3cfc25ea29765d59050808255807fc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf75561018460016101d6565b6101a17354006763154c764da4af42a8c3cfc25ea29765d5610220565b5050505050565b6000807f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050805491505090565b806102037f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd914360001b61025e565b60000160006101000a81548160ff02191690831515021790555050565b600060017fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5160001c61025291906102a1565b60001b90508181555050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006102ac82610268565b91506102b783610268565b9250828210156102ca576102c9610272565b5b82820390509291505056fea2646970667358221220f079473c1b94744ac2818f521ccef06187a433d996633e61e51a86dfb60cc6ff64736f6c634300080a0033 \ No newline at end of file diff --git a/tests/storage-layout/unstructured_storage-0.8.10.sol b/tests/storage-layout/unstructured_storage-0.8.10.sol index 015ebee6f1..8e5f4e2f83 100644 --- a/tests/storage-layout/unstructured_storage-0.8.10.sol +++ b/tests/storage-layout/unstructured_storage-0.8.10.sol @@ -115,7 +115,7 @@ contract UnstructuredStorageLayout { } _set_rollback(true); - _set_beacon(address(this)); + _set_beacon(address(0x0054006763154c764da4af42a8c3cfc25ea29765d5)); } // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" From 260db9b05455f1047e66c9d3e3c9fd67d034d695 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 14:21:57 -0500 Subject: [PATCH 44/70] Use constant folding for cases like `BEACON_SLOT = ` `bytes32(uint256(keccak256(bytes)(eip1967.proxy.beacon)) - 1)` --- slither/tools/read_storage/read_storage.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index cab9cdc965..0660730f6a 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -13,7 +13,7 @@ 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.state_variable import StateVariable, Variable from slither.core.variables.structure_variable import StructureVariable from slither.core.expressions import ( AssignmentOperation, @@ -135,8 +135,10 @@ def get_storage_layout(self) -> None: exp = var.expression if isinstance(exp, CallExpression) and str(exp.called) == "keccak256(bytes)": exp = exp.arguments[0] + if isinstance(exp, (BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion)): + exp = ConstantFolding(exp, "bytes32").result() if isinstance(exp, Literal): - if exp.value.startswith("0x"): + if str(exp.value).startswith("0x") or isinstance(exp.value, bytes): slot = coerce_type("int", exp.value) else: slot_str = exp.value From a1b359d4fc144df2f4d8bce075489b7fb007330e Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 14:38:05 -0500 Subject: [PATCH 45/70] Infer storage type from `sstore` in addition to `sload` --- slither/tools/read_storage/read_storage.py | 28 ++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 0660730f6a..6444ec4b88 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -276,16 +276,20 @@ def find_constant_slot_storage_type( size = None funcs = [] for c in self.contracts: - funcs.extend(c.get_functions_reading_from_variable(var)) + 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: - if func.return_type is not None: - rets = func.return_type - for ret in rets: - size, _ = ret.storage_size - if size <= 32: - return str(ret), size * 8 + 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 @@ -306,6 +310,16 @@ def find_constant_slot_storage_type( and exp.expression_right.arguments[0] == var.expression ): return "address", 160 + 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) + ): + type_str = exp.arguments[1].value.type.name + type_size, _ = exp.arguments[1].value.type.storage_size + return type_str, type_size * 8 return storage_type, size def walk_slot_info(self, func: Callable) -> None: From b3f7160e845fab7aeff018d101a3889369b6b25a Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 14:38:30 -0500 Subject: [PATCH 46/70] Pylint --- slither/tools/read_storage/read_storage.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 6444ec4b88..42d598327b 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -13,7 +13,7 @@ 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, Variable +from slither.core.variables.state_variable import StateVariable from slither.core.variables.structure_variable import StructureVariable from slither.core.expressions import ( AssignmentOperation, @@ -129,13 +129,20 @@ def get_storage_layout(self) -> None: elif isinstance(type_, ArrayType): elems = self._all_array_slots(var, contract, type_, info.slot) tmp[var.name].elems = elems - for contract, var in self.constant_slots: + tmp = self.get_unstructured_layout(tmp) + self._slot_info = tmp + + def get_unstructured_layout(self, tmp: dict) -> dict: + for _, var in self.constant_slots: var_name = var.name try: exp = var.expression if isinstance(exp, CallExpression) and str(exp.called) == "keccak256(bytes)": exp = exp.arguments[0] - if isinstance(exp, (BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion)): + if isinstance( + exp, + (BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion), + ): exp = ConstantFolding(exp, "bytes32").result() if isinstance(exp, Literal): if str(exp.value).startswith("0x") or isinstance(exp.value, bytes): @@ -160,7 +167,7 @@ def get_storage_layout(self) -> None: self.log = "" except TypeError: continue - self._slot_info = tmp + return tmp # TODO: remove this pylint exception (montyly) # pylint: disable=too-many-locals From 35c9c00c6c90334a74f4edacbc5c961ccf6d824d Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 14:47:22 -0500 Subject: [PATCH 47/70] Format --- slither/tools/read_storage/read_storage.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 42d598327b..e7642f702f 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -278,15 +278,17 @@ def find_constant_slot_storage_type( size (int): The type's size in bits. """ if not (var.is_constant and var.type == ElementaryType("bytes32")): - return None + return None, None 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.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] From 7bab3693983719e3a2ff7257f6b8619aedbfa208 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 14:56:44 -0500 Subject: [PATCH 48/70] Minor refactoring --- slither/tools/read_storage/read_storage.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index e7642f702f..4e8c722bb6 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -316,9 +316,10 @@ def find_constant_slot_storage_type( and isinstance(exp.expression_left, Identifier) and isinstance(exp.expression_right, CallExpression) and "sload" in str(exp.expression_right.called) - and exp.expression_right.arguments[0] == var.expression + and str(exp.expression_right.arguments[0]) == str(var.expression) ): return "address", 160 + # Look for if ( isinstance(exp, CallExpression) and "sstore" in str(exp.called) @@ -326,9 +327,9 @@ def find_constant_slot_storage_type( and isinstance(exp.arguments[1], Identifier) and str(exp.arguments[0].value.expression) == str(var.expression) ): - type_str = exp.arguments[1].value.type.name - type_size, _ = exp.arguments[1].value.type.storage_size - return type_str, type_size * 8 + 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: From 77ad16b8a8a485704f08f3f52a90d002704e55c9 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 24 Mar 2023 15:27:32 -0500 Subject: [PATCH 49/70] Add comments --- slither/tools/read_storage/read_storage.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 4e8c722bb6..a2f059dea0 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -319,7 +319,7 @@ def find_constant_slot_storage_type( and str(exp.expression_right.arguments[0]) == str(var.expression) ): return "address", 160 - # Look for + # Look for variable storage in assembly stored to a hardcoded slot if ( isinstance(exp, CallExpression) and "sstore" in str(exp.called) @@ -439,6 +439,16 @@ def find_hardcoded_slot_in_asm_str( 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): From b7e1f3ec04d81ab111bea1a1986f0132953bba5d Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 29 Mar 2023 13:13:50 -0500 Subject: [PATCH 50/70] Add unstructured storage test to test_read_storage.py and delete test_unstructured_storage.py --- tests/tools/read_storage/test_read_storage.py | 46 +++++++++++++++ .../read_storage/test_unstructured_storage.py | 58 ------------------- 2 files changed, 46 insertions(+), 58 deletions(-) delete mode 100644 tests/tools/read_storage/test_unstructured_storage.py diff --git a/tests/tools/read_storage/test_read_storage.py b/tests/tools/read_storage/test_read_storage.py index 83a0ab475f..49228c0587 100644 --- a/tests/tools/read_storage/test_read_storage.py +++ b/tests/tools/read_storage/test_read_storage.py @@ -84,3 +84,49 @@ def test_read_storage(web3, ganache) -> None: f.write(str(change.t2)) assert not diff + + +# pylint: disable=too-many-locals +@pytest.mark.usefixtures("web3", "ganache") +def test_unstructured_storage(web3, ganache) -> None: + assert web3.is_connected() + bin_path = Path(TEST_DATA_DIR, "UnstructuredStorageLayout.bin").as_posix() + abi_path = Path(TEST_DATA_DIR, "UnstructuredStorageLayout.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, "unstructured_storage-0.8.10.sol").as_posix()) + contracts = sl.contracts + + srs = SlitherReadStorage(contracts, 100) + srs.rpc = ganache.provider + srs.storage_address = address + srs.get_all_storage_variables() + srs.get_storage_layout() + srs.walk_slot_info(srs.get_slot_values) + actual_file = Path(TEST_DATA_DIR, "unstructured_storage.json").as_posix() + with open(actual_file, "w", encoding="utf-8") as file: + slot_infos_json = srs.to_json() + json.dump(slot_infos_json, file, indent=4) + + expected_file = Path(TEST_DATA_DIR, "TEST_unstructured_storage.json").as_posix() + + with open(expected_file, "r", encoding="utf8") as f: + expected = json.load(f) + with open(actual_file, "r", encoding="utf8") as f: + actual = json.load(f) + + diff = DeepDiff(expected, actual, ignore_order=True, verbose_level=2, view="tree") + if diff: + for change in diff.get("values_changed", []): + path_list = re.findall(r"\['(.*?)'\]", change.path()) + path = "_".join(path_list) + with open(f"{path}_expected.txt", "w", encoding="utf8") as f: + f.write(str(change.t1)) + with open(f"{path}_actual.txt", "w", encoding="utf8") as f: + f.write(str(change.t2)) + + assert not diff diff --git a/tests/tools/read_storage/test_unstructured_storage.py b/tests/tools/read_storage/test_unstructured_storage.py deleted file mode 100644 index 095f585c3c..0000000000 --- a/tests/tools/read_storage/test_unstructured_storage.py +++ /dev/null @@ -1,58 +0,0 @@ -import re -import os -import json - -import pytest -from pathlib import Path -from deepdiff import DeepDiff -from slither import Slither -from slither.tools.read_storage import SlitherReadStorage -from tests.tools.read_storage.test_read_storage import get_source_file, deploy_contract - -TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" - - -# pylint: disable=too-many-locals -@pytest.mark.usefixtures("web3", "ganache") -def test_read_storage(web3, ganache) -> None: - assert web3.is_connected() - bin_path = Path(TEST_DATA_DIR, "UnstructuredStorageLayout.bin").as_posix() - abi_path = Path(TEST_DATA_DIR, "UnstructuredStorageLayout.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, "unstructured_storage-0.8.10.sol").as_posix()) - contracts = sl.contracts - - srs = SlitherReadStorage(contracts, 100) - srs.rpc = ganache.provider - srs.storage_address = address - srs.get_all_storage_variables() - srs.get_storage_layout() - srs.walk_slot_info(srs.get_slot_values) - actual_file = Path(TEST_DATA_DIR, "unstructured_storage.json").as_posix() - with open(actual_file, "w", encoding="utf-8") as file: - slot_infos_json = srs.to_json() - json.dump(slot_infos_json, file, indent=4) - - expected_file = Path(TEST_DATA_DIR, "TEST_unstructured_storage.json").as_posix() - - with open(expected_file, "r", encoding="utf8") as f: - expected = json.load(f) - with open(actual_file, "r", encoding="utf8") as f: - actual = json.load(f) - - diff = DeepDiff(expected, actual, ignore_order=True, verbose_level=2, view="tree") - if diff: - for change in diff.get("values_changed", []): - path_list = re.findall(r"\['(.*?)'\]", change.path()) - path = "_".join(path_list) - with open(f"{path}_expected.txt", "w", encoding="utf8") as f: - f.write(str(change.t1)) - with open(f"{path}_actual.txt", "w", encoding="utf8") as f: - f.write(str(change.t2)) - - assert not diff From ee3df3c672239d9d13a3f2dcf181aaa601b821f2 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 29 Mar 2023 13:25:41 -0500 Subject: [PATCH 51/70] Pylint --- tests/tools/read_storage/test_read_storage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/tools/read_storage/test_read_storage.py b/tests/tools/read_storage/test_read_storage.py index 49228c0587..969f50d12b 100644 --- a/tests/tools/read_storage/test_read_storage.py +++ b/tests/tools/read_storage/test_read_storage.py @@ -1,9 +1,8 @@ import re -import os import json +from pathlib import Path import pytest -from pathlib import Path from deepdiff import DeepDiff from web3.contract import Contract From 2cd62cb277bcf128aebb29ee0837e5a66aa2e81b Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 11:05:50 -0500 Subject: [PATCH 52/70] Update setup.py --- setup.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 1dbce4c764..d2878117af 100644 --- a/setup.py +++ b/setup.py @@ -20,17 +20,25 @@ "web3>=6.0.0", ], extras_require={ - "dev": [ + "lint": [ "black==22.3.0", "pylint==2.13.4", + ], + "test": [ "pytest", "pytest-cov", "pytest-xdist", "deepdiff", "numpy", - "openai", + "coverage[toml]", + ], + "doc": [ "pdoc", ], + "dev": [ + "slither-analyzer[lint,test,doc]", + "openai", + ], }, license="AGPL-3.0", long_description=long_description, @@ -52,4 +60,4 @@ "slither-documentation = slither.tools.documentation.__main__:main", ] }, -) +) \ No newline at end of file From bd95810fd1eb9620ebf1c3b71678fc47f56fc933 Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 13:08:57 -0500 Subject: [PATCH 53/70] Undo renamed directory --- tests/tools/{read_storage => read-storage}/__init__.py | 0 .../{read_storage => read-storage}/test_data/StorageLayout.abi | 0 .../{read_storage => read-storage}/test_data/StorageLayout.bin | 0 .../{read_storage => read-storage}/test_data/StorageSlot.abi | 0 .../{read_storage => read-storage}/test_data/StorageSlot.bin | 0 .../test_data/TEST_storage_layout.json | 0 .../test_data/TEST_unstructured_storage.json | 0 .../test_data/UnstructuredStorageLayout.abi | 0 .../test_data/UnstructuredStorageLayout.bin | 0 .../test_data/storage_layout-0.8.10.sol | 0 .../test_data/unstructured_storage-0.8.10.sol | 0 tests/tools/{read_storage => read-storage}/test_read_storage.py | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename tests/tools/{read_storage => read-storage}/__init__.py (100%) rename tests/tools/{read_storage => read-storage}/test_data/StorageLayout.abi (100%) rename tests/tools/{read_storage => read-storage}/test_data/StorageLayout.bin (100%) rename tests/tools/{read_storage => read-storage}/test_data/StorageSlot.abi (100%) rename tests/tools/{read_storage => read-storage}/test_data/StorageSlot.bin (100%) rename tests/tools/{read_storage => read-storage}/test_data/TEST_storage_layout.json (100%) rename tests/tools/{read_storage => read-storage}/test_data/TEST_unstructured_storage.json (100%) rename tests/tools/{read_storage => read-storage}/test_data/UnstructuredStorageLayout.abi (100%) rename tests/tools/{read_storage => read-storage}/test_data/UnstructuredStorageLayout.bin (100%) rename tests/tools/{read_storage => read-storage}/test_data/storage_layout-0.8.10.sol (100%) rename tests/tools/{read_storage => read-storage}/test_data/unstructured_storage-0.8.10.sol (100%) rename tests/tools/{read_storage => read-storage}/test_read_storage.py (100%) diff --git a/tests/tools/read_storage/__init__.py b/tests/tools/read-storage/__init__.py similarity index 100% rename from tests/tools/read_storage/__init__.py rename to tests/tools/read-storage/__init__.py diff --git a/tests/tools/read_storage/test_data/StorageLayout.abi b/tests/tools/read-storage/test_data/StorageLayout.abi similarity index 100% rename from tests/tools/read_storage/test_data/StorageLayout.abi rename to tests/tools/read-storage/test_data/StorageLayout.abi diff --git a/tests/tools/read_storage/test_data/StorageLayout.bin b/tests/tools/read-storage/test_data/StorageLayout.bin similarity index 100% rename from tests/tools/read_storage/test_data/StorageLayout.bin rename to tests/tools/read-storage/test_data/StorageLayout.bin diff --git a/tests/tools/read_storage/test_data/StorageSlot.abi b/tests/tools/read-storage/test_data/StorageSlot.abi similarity index 100% rename from tests/tools/read_storage/test_data/StorageSlot.abi rename to tests/tools/read-storage/test_data/StorageSlot.abi diff --git a/tests/tools/read_storage/test_data/StorageSlot.bin b/tests/tools/read-storage/test_data/StorageSlot.bin similarity index 100% rename from tests/tools/read_storage/test_data/StorageSlot.bin rename to tests/tools/read-storage/test_data/StorageSlot.bin diff --git a/tests/tools/read_storage/test_data/TEST_storage_layout.json b/tests/tools/read-storage/test_data/TEST_storage_layout.json similarity index 100% rename from tests/tools/read_storage/test_data/TEST_storage_layout.json rename to tests/tools/read-storage/test_data/TEST_storage_layout.json diff --git a/tests/tools/read_storage/test_data/TEST_unstructured_storage.json b/tests/tools/read-storage/test_data/TEST_unstructured_storage.json similarity index 100% rename from tests/tools/read_storage/test_data/TEST_unstructured_storage.json rename to tests/tools/read-storage/test_data/TEST_unstructured_storage.json diff --git a/tests/tools/read_storage/test_data/UnstructuredStorageLayout.abi b/tests/tools/read-storage/test_data/UnstructuredStorageLayout.abi similarity index 100% rename from tests/tools/read_storage/test_data/UnstructuredStorageLayout.abi rename to tests/tools/read-storage/test_data/UnstructuredStorageLayout.abi diff --git a/tests/tools/read_storage/test_data/UnstructuredStorageLayout.bin b/tests/tools/read-storage/test_data/UnstructuredStorageLayout.bin similarity index 100% rename from tests/tools/read_storage/test_data/UnstructuredStorageLayout.bin rename to tests/tools/read-storage/test_data/UnstructuredStorageLayout.bin diff --git a/tests/tools/read_storage/test_data/storage_layout-0.8.10.sol b/tests/tools/read-storage/test_data/storage_layout-0.8.10.sol similarity index 100% rename from tests/tools/read_storage/test_data/storage_layout-0.8.10.sol rename to tests/tools/read-storage/test_data/storage_layout-0.8.10.sol diff --git a/tests/tools/read_storage/test_data/unstructured_storage-0.8.10.sol b/tests/tools/read-storage/test_data/unstructured_storage-0.8.10.sol similarity index 100% rename from tests/tools/read_storage/test_data/unstructured_storage-0.8.10.sol rename to tests/tools/read-storage/test_data/unstructured_storage-0.8.10.sol diff --git a/tests/tools/read_storage/test_read_storage.py b/tests/tools/read-storage/test_read_storage.py similarity index 100% rename from tests/tools/read_storage/test_read_storage.py rename to tests/tools/read-storage/test_read_storage.py From 5649f6651a0d0a2d242493da27a05e59aba4216a Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 13:10:57 -0500 Subject: [PATCH 54/70] Black/pylint --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d2878117af..7e563e25a3 100644 --- a/setup.py +++ b/setup.py @@ -60,4 +60,4 @@ "slither-documentation = slither.tools.documentation.__main__:main", ] }, -) \ No newline at end of file +) From 117bfbe664139c6acb2832d6b7559220ba6bd80f Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 13 Apr 2023 14:33:13 -0500 Subject: [PATCH 55/70] Put unstructured storage handling behind a flag, so it is only used when `--unstructured` is set or when we set `self.unstructured = True` --- slither/tools/read_storage/__main__.py | 8 ++++++++ slither/tools/read_storage/read_storage.py | 14 +++++++++----- tests/tools/read-storage/test_read_storage.py | 1 + 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/slither/tools/read_storage/__main__.py b/slither/tools/read_storage/__main__.py index 1a8901321d..20ecece8ad 100644 --- a/slither/tools/read_storage/__main__.py +++ b/slither/tools/read_storage/__main__.py @@ -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() @@ -128,6 +134,8 @@ def main() -> None: srs = SlitherReadStorage(contracts, args.max_depth) + srs.unstructured = bool(args.unstructured) + try: srs.block = int(args.block) except ValueError: diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index a2f059dea0..88b4484af6 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -69,6 +69,7 @@ def __init__(self, contracts: List[Contract], max_depth: int) -> None: self.rpc: Optional[str] = None self.storage_address: Optional[str] = None self.table: Optional[MyPrettyTable] = None + self.unstructured: bool = False @property def contracts(self) -> List[Contract]: @@ -129,10 +130,12 @@ def get_storage_layout(self) -> None: elif isinstance(type_, ArrayType): elems = self._all_array_slots(var, contract, type_, info.slot) tmp[var.name].elems = elems - tmp = self.get_unstructured_layout(tmp) + if self.unstructured: + tmp.update(self.get_unstructured_layout()) self._slot_info = tmp - def get_unstructured_layout(self, tmp: dict) -> dict: + def get_unstructured_layout(self) -> Dict[str, SlotInfo]: + tmp: Dict[str, SlotInfo] = {} for _, var in self.constant_slots: var_name = var.name try: @@ -370,9 +373,10 @@ def get_all_storage_variables(self, func: Callable = lambda x: x) -> None: self._target_variables.append((contract, var)) elif var.is_constant and var.type == ElementaryType("bytes32"): self._constant_storage_slots.append((contract, var)) - hardcoded_slot = self.find_hardcoded_slot_in_fallback(contract) - if hardcoded_slot is not None: - self._constant_storage_slots.append((contract, hardcoded_slot)) + 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]: """ diff --git a/tests/tools/read-storage/test_read_storage.py b/tests/tools/read-storage/test_read_storage.py index 969f50d12b..a869bada07 100644 --- a/tests/tools/read-storage/test_read_storage.py +++ b/tests/tools/read-storage/test_read_storage.py @@ -101,6 +101,7 @@ def test_unstructured_storage(web3, ganache) -> None: contracts = sl.contracts srs = SlitherReadStorage(contracts, 100) + srs.unstructured = True srs.rpc = ganache.provider srs.storage_address = address srs.get_all_storage_variables() From 1ffe92dc0c236f70efea487c685063341059a318 Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 13 Apr 2023 14:33:32 -0500 Subject: [PATCH 56/70] Handle `NotConstant` exception --- slither/tools/read_storage/read_storage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 88b4484af6..3ab310eaf0 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -26,7 +26,7 @@ CallExpression, ) from slither.utils.myprettytable import MyPrettyTable -from slither.visitors.expression.constants_folding import ConstantFolding +from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant from .utils import coerce_type, get_offset_value, get_storage_data @@ -168,7 +168,7 @@ def get_unstructured_layout(self) -> Dict[str, SlotInfo]: ) logger.info(self.log) self.log = "" - except TypeError: + except (TypeError, NotConstant): continue return tmp From 0a19bce1e87e28f4f6ec4fbfa901236f8f69bc7f Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 13 Apr 2023 14:33:52 -0500 Subject: [PATCH 57/70] Use assertion for precondition --- slither/tools/read_storage/read_storage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 3ab310eaf0..a7463651a5 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -280,8 +280,7 @@ def find_constant_slot_storage_type( type (str): The type of value stored in the slot. size (int): The type's size in bits. """ - if not (var.is_constant and var.type == ElementaryType("bytes32")): - return None, None + assert(var.is_constant and var.type == ElementaryType("bytes32")) storage_type = None size = None funcs = [] From a28c3ca0d32651ea2b9ebd8c4f112804250783cc Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 13 Apr 2023 14:34:32 -0500 Subject: [PATCH 58/70] Better handling of `sload` from hardcoded slot without assuming the loaded type is an address --- slither/tools/read_storage/read_storage.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index a7463651a5..7a78cebf63 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -320,7 +320,9 @@ def find_constant_slot_storage_type( and "sload" in str(exp.expression_right.called) and str(exp.expression_right.arguments[0]) == str(var.expression) ): - 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) From f14dbccffd4847ad40c85857a428b8e2b4da1548 Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 13 Apr 2023 14:35:17 -0500 Subject: [PATCH 59/70] Black --- slither/tools/read_storage/read_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 7a78cebf63..c92c92d97d 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -280,7 +280,7 @@ def find_constant_slot_storage_type( 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")) + assert var.is_constant and var.type == ElementaryType("bytes32") storage_type = None size = None funcs = [] From ca7eac0d3b81b4a3c5d63174a6d347c83ab07703 Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 13 Apr 2023 14:53:28 -0500 Subject: [PATCH 60/70] Assume `sload` type is address iff found in fallback --- slither/tools/read_storage/read_storage.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index c92c92d97d..b5396b4eb1 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -320,6 +320,8 @@ def find_constant_slot_storage_type( 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 From d9ce12611544013d0dbce8428efd1df1ef6824cc Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 17 Apr 2023 11:21:18 -0500 Subject: [PATCH 61/70] Put self._constant_storage_slots behind flag --- slither/tools/read_storage/read_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index b5396b4eb1..18fe8a2ee0 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -374,7 +374,7 @@ def get_all_storage_variables(self, func: Callable = lambda x: x) -> None: if func(var): if not var.is_constant and not var.is_immutable: self._target_variables.append((contract, var)) - elif var.is_constant and var.type == ElementaryType("bytes32"): + 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) From 677849dcae1ea9f1b428c71bbea04337be84057d Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 17 Apr 2023 11:21:29 -0500 Subject: [PATCH 62/70] Handle NotConstant exception --- slither/tools/read_storage/read_storage.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 18fe8a2ee0..3bf0c6df0f 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -472,7 +472,10 @@ def find_hardcoded_slot_in_exp( exp, (BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion), ): - exp = ConstantFolding(exp, "bytes32").result() + try: + exp = ConstantFolding(exp, "bytes32").result() + except NotConstant: + return None if ( isinstance(exp, Literal) and isinstance(exp.type, ElementaryType) From 0adecf6a557d6e1c5c772374a03c6a641bbf7589 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 17 Apr 2023 11:21:59 -0500 Subject: [PATCH 63/70] Better bytes to string conversion --- slither/tools/read_storage/read_storage.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 3bf0c6df0f..6eb4843309 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -489,14 +489,15 @@ def find_hardcoded_slot_in_exp( if str_value.isdecimal(): value = int(value) if isinstance(value, (int, bytes)): - str_value = str(value).replace("b'", "0x").replace("\\x", "").replace("'", "") + if isinstance(value, bytes): + str_value = "0x" + value.hex() + value = int(str_value, 16) exp = Literal(str_value, ElementaryType("bytes32")) - int_value = int(str_value, 16) state_var_slots = [ self.get_variable_info(contract, var)[0] for contract, var in self.target_variables ] - if int_value in state_var_slots: + if value in state_var_slots: return None sv.expression = exp sv.is_constant = True From 906fa8ce087439898ec62296a3b7822468423b37 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 17 Apr 2023 12:32:24 -0500 Subject: [PATCH 64/70] Black --- slither/tools/read_storage/read_storage.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 6eb4843309..14acb5bddb 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -374,7 +374,11 @@ def get_all_storage_variables(self, func: Callable = lambda x: x) -> None: 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"): + 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) From 02e74666b8c123b1ca977da40b3b4b907aa4cf79 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 26 Apr 2023 14:40:06 -0500 Subject: [PATCH 65/70] Pylint after merge --- tests/conftest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3439c991d1..faf7ae960c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,9 +10,9 @@ from contextlib import contextmanager from filelock import FileLock from solc_select import solc_select -from slither import Slither from web3 import Web3 import pytest +from slither import Slither # pylint: disable=too-few-public-methods @@ -127,4 +127,3 @@ def inner(source_code: str, solc_version: str = "0.8.19"): Path(fname).unlink() return inner - From bb58ea24390a385dc1b1567317cfb9e523691c22 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 26 Apr 2023 14:48:47 -0500 Subject: [PATCH 66/70] Disable too-many-locals in test_constant_folding.py --- tests/unit/core/test_constant_folding.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/core/test_constant_folding.py b/tests/unit/core/test_constant_folding.py index 988b2284af..97e576ec2c 100644 --- a/tests/unit/core/test_constant_folding.py +++ b/tests/unit/core/test_constant_folding.py @@ -54,6 +54,7 @@ def test_constant_folding_rational(solc_binary_path): 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(), From 34e3902c9d5d3af2b28feb2f0b182e79b3f2b45e Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 26 Apr 2023 14:57:13 -0500 Subject: [PATCH 67/70] Add missing solc_binary_path --- tests/tools/read-storage/test_read_storage.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/tools/read-storage/test_read_storage.py b/tests/tools/read-storage/test_read_storage.py index 6eb03208c7..9f4273122a 100644 --- a/tests/tools/read-storage/test_read_storage.py +++ b/tests/tools/read-storage/test_read_storage.py @@ -90,6 +90,8 @@ def test_read_storage(web3, ganache, solc_binary_path) -> None: # pylint: disable=too-many-locals @pytest.mark.usefixtures("web3", "ganache") def test_unstructured_storage(web3, ganache) -> None: + solc_path = solc_binary_path(version="0.8.10") + assert web3.is_connected() bin_path = Path(TEST_DATA_DIR, "UnstructuredStorageLayout.bin").as_posix() abi_path = Path(TEST_DATA_DIR, "UnstructuredStorageLayout.abi").as_posix() @@ -99,7 +101,7 @@ def test_unstructured_storage(web3, ganache) -> None: contract.functions.store().transact({"from": ganache.eth_address}) address = contract.address - sl = Slither(Path(TEST_DATA_DIR, "unstructured_storage-0.8.10.sol").as_posix()) + sl = Slither(Path(TEST_DATA_DIR, "unstructured_storage-0.8.10.sol").as_posix(), solc=solc_path) contracts = sl.contracts srs = SlitherReadStorage(contracts, 100) From 846e2af25a04ae26975c65a15f5d99a5b3273eb4 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 26 Apr 2023 15:02:55 -0500 Subject: [PATCH 68/70] Add missing solc_binary_path --- tests/tools/read-storage/test_read_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tools/read-storage/test_read_storage.py b/tests/tools/read-storage/test_read_storage.py index 9f4273122a..6fedb6b037 100644 --- a/tests/tools/read-storage/test_read_storage.py +++ b/tests/tools/read-storage/test_read_storage.py @@ -89,7 +89,7 @@ def test_read_storage(web3, ganache, solc_binary_path) -> None: # pylint: disable=too-many-locals @pytest.mark.usefixtures("web3", "ganache") -def test_unstructured_storage(web3, ganache) -> None: +def test_unstructured_storage(web3, ganache, solc_binary_path) -> None: solc_path = solc_binary_path(version="0.8.10") assert web3.is_connected() From d2dad0f6f549ab7e0b7f03e13da0de8e03dd9e53 Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 27 Apr 2023 09:18:07 -0500 Subject: [PATCH 69/70] Format comments, fix typo --- slither/tools/read_storage/read_storage.py | 26 ++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 14acb5bddb..b1b5084cc7 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -180,7 +180,8 @@ def get_storage_slot( 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. @@ -350,7 +351,8 @@ def walk_slot_info(self, func: Callable) -> None: 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: """ hex_bytes = get_storage_data( @@ -365,7 +367,8 @@ def get_slot_values(self, slot_info: SlotInfo) -> None: logger.info(f"\nValue: {slot_info.value}\n") def get_all_storage_variables(self, func: Callable = lambda x: x) -> None: - """Fetches all storage variables from a list of contracts. + """ + Fetches all storage variables from a list of contracts. kwargs: func (Callable, optional): A criteria to filter functions e.g. name. """ @@ -511,7 +514,8 @@ def find_hardcoded_slot_in_exp( 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: """ @@ -529,7 +533,8 @@ def to_json(self) -> Dict: 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. @@ -571,7 +576,8 @@ def _find_array_slot( 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. @@ -674,7 +680,8 @@ def _find_mapping_slot( 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. @@ -745,7 +752,7 @@ def _find_mapping_slot( ) 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): @@ -851,7 +858,8 @@ def _all_array_slots( 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. From abb7622145496bc87da9670f1a679ea1f92c9a7e Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 8 Jun 2023 13:01:20 -0500 Subject: [PATCH 70/70] Update test_unstructured_storage --- tests/tools/read-storage/test_read_storage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/tools/read-storage/test_read_storage.py b/tests/tools/read-storage/test_read_storage.py index 31c4f5019b..5d631ddce8 100644 --- a/tests/tools/read-storage/test_read_storage.py +++ b/tests/tools/read-storage/test_read_storage.py @@ -104,7 +104,8 @@ def test_unstructured_storage(web3, ganache, solc_binary_path) -> None: sl = Slither(Path(TEST_DATA_DIR, "unstructured_storage-0.8.10.sol").as_posix(), solc=solc_path) contracts = sl.contracts - srs = SlitherReadStorage(contracts, 100) + rpc_info: RpcInfo = RpcInfo(ganache.provider) + srs = SlitherReadStorage(contracts, 100, rpc_info) srs.unstructured = True srs.rpc = ganache.provider srs.storage_address = address