import json import re import shutil import subprocess from time import sleep from pathlib import Path from typing import Generator import pytest from deepdiff import DeepDiff from web3 import Web3 from web3.contract import Contract from slither import Slither from slither.tools.read_storage import SlitherReadStorage TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" # pylint: disable=too-few-public-methods class GanacheInstance: def __init__(self, provider: str, eth_address: str, eth_privkey: str): self.provider = provider self.eth_address = eth_address self.eth_privkey = eth_privkey @pytest.fixture(scope="module", name="web3") def fixture_web3(ganache: GanacheInstance): w3 = Web3(Web3.HTTPProvider(ganache.provider, request_kwargs={"timeout": 30})) return w3 @pytest.fixture(scope="module", name="ganache") def fixture_ganache() -> Generator[GanacheInstance, None, None]: """Fixture that runs ganache""" if not shutil.which("ganache"): raise Exception( "ganache was not found in PATH, you can install it with `npm install -g ganache`" ) # Address #1 when ganache is run with `--wallet.seed test`, it starts with 1000 ETH eth_address = "0xae17D2dD99e07CA3bF2571CCAcEAA9e2Aefc2Dc6" eth_privkey = "0xe48ba530a63326818e116be262fd39ae6dcddd89da4b1f578be8afd4e8894b8d" eth = int(1e18 * 1e6) port = 8545 with subprocess.Popen( f"""ganache --port {port} --chain.networkId 1 --chain.chainId 1 --account {eth_privkey},{eth} """.replace( "\n", " " ), shell=True, ) as p: sleep(3) yield GanacheInstance(f"http://127.0.0.1:{port}", eth_address, eth_privkey) p.kill() p.wait() def get_source_file(file_path) -> str: with open(file_path, "r", encoding="utf8") as f: 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, use_solc_version) -> None: solc_path = next(use_solc_version(version="0.8.10")) assert web3.is_connected() bin_path = Path(TEST_DATA_DIR, "StorageLayout.bin").as_posix() abi_path = Path(TEST_DATA_DIR, "StorageLayout.abi").as_posix() bytecode = get_source_file(bin_path) abi = get_source_file(abi_path) contract = deploy_contract(web3, ganache, bytecode, abi) contract.functions.store().transact({"from": ganache.eth_address}) address = contract.address sl = Slither(Path(TEST_DATA_DIR, "storage_layout-0.8.10.sol").as_posix(), solc=solc_path) 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, "storage_layout.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_storage_layout.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