From 8ca68e30174cb37658cb887dee848cd728fea5a3 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Tue, 11 Jun 2024 15:49:21 +0200 Subject: [PATCH 1/8] init corpus modification, working basic corpus version history --- fuzz_utils/modify_corpus/CorpusModifier.py | 246 +++++++++++++++++++ fuzz_utils/modify_corpus/__init__.py | 0 fuzz_utils/parsing/commands/modify_corpus.py | 86 +++++++ fuzz_utils/parsing/commands/restore.py | 37 +++ fuzz_utils/parsing/commands/snapshot.py | 43 ++++ fuzz_utils/parsing/parser.py | 21 ++ pyproject.toml | 3 +- 7 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 fuzz_utils/modify_corpus/CorpusModifier.py create mode 100644 fuzz_utils/modify_corpus/__init__.py create mode 100644 fuzz_utils/parsing/commands/modify_corpus.py create mode 100644 fuzz_utils/parsing/commands/restore.py create mode 100644 fuzz_utils/parsing/commands/snapshot.py diff --git a/fuzz_utils/modify_corpus/CorpusModifier.py b/fuzz_utils/modify_corpus/CorpusModifier.py new file mode 100644 index 0000000..69f76b8 --- /dev/null +++ b/fuzz_utils/modify_corpus/CorpusModifier.py @@ -0,0 +1,246 @@ +"""The CorpusModifier class handles modification of corpus call sequences based on fuzzer config""" +import os +import yaml +import hashlib +import json +import shutil +import copy +import datetime +from slither import Slither +from fuzz_utils.utils.error_handler import handle_exit + +class CorpusModifier: + """ + Handles modifying the corpus based on the fuzzer configuration. + """ + fuzzer_fields: dict + corpora_format: dict = {"echidna": {"coverage": [], "reproducers": []}, "medusa": {"call_sequences": {"immutable": [], "mutable": []}, "test_results": []}} + + def __init__( + self, + config_path: str | None, + corpus_path: str | None, + slither: Slither | None, + fuzzer: str | None, + ) -> None: + self.fuzzer_fields = {} + if slither: + self.slither = slither + if corpus_path: + self.corpus_path = corpus_path + if fuzzer: + self.fuzzer = fuzzer + self.fuzzer_fields["echidna"] = ["maxTimeDelay", "maxBlockDelay", "filterFunctions"] + if config_path: + self.config_path = config_path + self.fuzzer_config = self._fetch_fuzzer_config(self.fuzzer_fields[fuzzer]) + self.corpus_copy = {} + + def modify_corpus(self) -> None: + """Modifies the current corpus and saves the new version""" + # 1. Open the corpus and parse all the files + self.corpus_copy = self._copy_fuzzer_corpus(self.corpora_format[self.fuzzer.lower()], self.corpus_path) + # 2. a) Copy current corpus somewhere in case something goes wrong? + self.save_corpus_to_history() + # 3. Define list of rules to apply + # TODO update this later to be dynamic + rules_list = [self._is_incorrect_delay] + # 4. Apply the rules + self._filter_corpus("filters_calls", rules_list) + # 5. Save the new corpus + self._override_corpus_directory(self.corpus_path, self.corpus_copy) + + def save_corpus_to_history(self) -> None: + """Saves the current corpus directory to the corpus history""" + # 1. Fetch current corpus + corpus_to_save = self._copy_fuzzer_corpus(self.corpora_format[self.fuzzer.lower()], self.corpus_path) + # 2. Check if .fuzz_utils folder already exists, create it if not + directory = self._create_or_fetch_hidden_directory() + # 3. Convert into a string + corpus_str = json.dumps(corpus_to_save, sort_keys=True) + # 4. Hash it + corpus_hash = hashlib.sha256(corpus_str.encode()).hexdigest() + # 5. Check if hash already exists, skip operation and print a message if it does + history: dict + history_path = os.path.join(directory, "history.json") + # TODO check if this can fail + if not os.path.exists(history_path): + with open(history_path, "w", encoding="utf-8") as f: + f.write("{}") + + with open(history_path, "r", encoding="utf-8") as f: + history = json.load(f) + + if corpus_hash in history.keys(): + print(f"The corpus is already saved to history with the hash {corpus_hash}") + return + # 6. Add to history + timestamp = datetime.datetime.now().isoformat() + comment = input("Enter a comment for this save: ") + history[corpus_hash] = {"path": self.corpus_path, "timestamp": timestamp, "content": corpus_to_save, "comment": comment} + # 7. Save history + # TODO check if this can fail + with open(history_path, "w", encoding="utf-8") as f: + json.dump(history, f) + + def restore_corpus_from_history(self, corpus_hash: str) -> None: + """Overrides the current corpus directory with a historical version""" + directory = self._create_or_fetch_hidden_directory() + history_path = os.path.join(directory, "history.json") + fetched_corpus: dict + # TODO check if this can fail + with open(history_path, "r", encoding="utf-8") as f: + history = json.load(f) + + if corpus_hash in history.keys(): + fetched_corpus = history[corpus_hash] + self._override_corpus_directory(fetched_corpus["path"], fetched_corpus["content"]) + else: + handle_exit("The provided hash was not found.") + + def list_historic_corpora(self) -> None: + """Prints all the saved corpora""" + directory = self._create_or_fetch_hidden_directory() + history_path = os.path.join(directory, "history.json") + + with open(history_path, "r", encoding="utf-8") as f: + history = json.load(f) + + if history: + print("History:\n") + for key, value in history.items(): + print(f'{key}: {value["comment"]} (saved at {value["timestamp"]})') + else: + print("No historic corpora were found.") + + + + def _override_corpus_directory(self, directory_path: str, contents_list: list) -> None: + for root, dirs, files in os.walk(directory_path): + for file in files: + file_path = os.path.join(root, file) + os.remove(file_path) + for directory in dirs: + dir_path = os.path.join(root, directory) + shutil.rmtree(dir_path) + + for item in contents_list: + file_path = item["path"] + file_content = item["content"] + + if file_content: + # Ensure the directory exists + directory = os.path.dirname(file_path) + os.makedirs(directory, exist_ok=True) + + # Write the file contents + with open(file_path, "w", encoding="utf-8") as file: + json.dump(file_content, file, indent=4) + + def _create_or_fetch_hidden_directory(self) -> str: + current_directory = os.getcwd() + directory_name = os.path.join(current_directory, ".fuzz_utils") + os.makedirs(directory_name, exist_ok=True) + return directory_name + + + + # TODO only supports Echidna for now + def _is_incorrect_delay(self, call_object: dict) -> bool: + maxTimeDelay = self.fuzzer_config["maxTimeDelay"] if "maxTimeDelay" in self.fuzzer_config else None + maxBlockDelay = self.fuzzer_config["maxBlockDelay"] if "maxBlockDelay" in self.fuzzer_config else None + + if maxTimeDelay: + time_delay = int(call_object["delay"][0], 16) + if time_delay > maxTimeDelay: + return True + if maxBlockDelay: + block_delay = int(call_object["delay"][1], 16) + if block_delay > maxBlockDelay: + return True + + return False + + def _filter_corpus(self, mode: str, rules_list: list) -> None: + for idx, value in enumerate(self.corpus_copy): + sequence_list: list = [] + # A list of files with call sequences in them + for call_sequence in value["content"]: + resulting_sequence = self._filter_call_sequence(mode, rules_list, call_sequence) + + if resulting_sequence: + sequence_list.append(resulting_sequence) + + # Override the old call sequences with the new ones + self.corpus_copy[idx] = {"path": self.corpus_copy[idx]["path"], "content": sequence_list} + + def _filter_call_sequence(self, mode: str, rules_list: list, call_sequence: list) -> dict | None: + def should_skip(call): + return any(rule_fn(call) for rule_fn in rules_list) + + if mode not in {"delete_sequence", "filter_calls"}: + raise ValueError("Invalid mode") + + resulting_sequence: dict = {"name": call_sequence["name"], "content": []} + + for call in call_sequence["content"]: + if should_skip(call): + if mode == "delete_sequence": + return None + else: + resulting_sequence["content"].append(call) + + return resulting_sequence if resulting_sequence["content"] else None + + + def _copy_fuzzer_corpus(self, corpus: dict, current_path: str) -> list | None: + temp_corpus = [] + for key in corpus.keys(): + subdir_path = os.path.join(current_path, key) + if isinstance(corpus[key], dict): + temp_corpus[key] = self._copy_fuzzer_corpus(corpus[key], subdir_path) + else: + temp_corpus.extend(self._fetch_directory_files(subdir_path)) + + return temp_corpus if temp_corpus else None + + def _fetch_directory_files(self, directory: str) -> list: + file_list: list = [] + if os.path.exists(directory): + for entry in os.listdir(directory): + full_path = os.path.join(directory, entry) + + if os.path.isfile(full_path): + try: + with open(full_path, "r", encoding="utf-8") as file: + if self.fuzzer.lower() == "echidna": + file_list.append({"path": full_path, "content": yaml.safe_load(file)}) + else: + file_list.append({"path": full_path, "content": json.load(file)}) + except Exception: # pylint: disable=broad-except + print(f"Fail on {full_path}") + else: + print(f"The provided corpus path does not exists: {directory}. Skipping.") + + return file_list + + def _fetch_fuzzer_config(self, fields: list[str]) -> dict: + filtered_config: dict = {} + complete_config: dict + if os.path.isfile(self.config_path): + try: + with open(self.config_path, "r", encoding="utf-8") as file: + if self.fuzzer.lower() == "echidna": + complete_config = yaml.safe_load(file) + else: + complete_config = json.load(file) + except Exception: # pylint: disable=broad-except + handle_exit(f"Failed to find the fuzzer configuration file at {self.config_path}") + + for key, value in complete_config.items(): + # TODO won't work for Medusa + if key in fields: + filtered_config[key] = value + + return filtered_config + diff --git a/fuzz_utils/modify_corpus/__init__.py b/fuzz_utils/modify_corpus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fuzz_utils/parsing/commands/modify_corpus.py b/fuzz_utils/parsing/commands/modify_corpus.py new file mode 100644 index 0000000..8b6f72d --- /dev/null +++ b/fuzz_utils/parsing/commands/modify_corpus.py @@ -0,0 +1,86 @@ +"""Defines the flags and logic associated with the `modify-corpus` command""" +from argparse import Namespace, ArgumentParser +from slither import Slither +from fuzz_utils.utils.crytic_print import CryticPrint +from fuzz_utils.modify_corpus.CorpusModifier import CorpusModifier +from fuzz_utils.utils.error_handler import handle_exit +from fuzz_utils.parsing.parser_util import check_config_and_set_default_values, open_config + +COMMAND: str = "modify-corpus" + + +def modify_flags(parser: ArgumentParser) -> None: + """The unit test generation parser flags""" + parser.add_argument( + "compilation_path", help="Path to the Echidna/Medusa test harness or Foundry directory." + ) + parser.add_argument( + "-cd", "--corpus-dir", dest="corpus_dir", help="Path to the corpus directory" + ) + parser.add_argument( + "-f", + "--fuzzer", + dest="selected_fuzzer", + help="Define the fuzzer used. Valid inputs: 'echidna', 'medusa'", + ) + parser.add_argument( + "--fuzzer-config", + dest="fuzzer_config", + help="Define the location of the fuzzer config file.", + ) + # TODO add specific commands for modifying the corpus + + +# pylint: disable=too-many-branches +def modify_command(args: Namespace) -> None: + """The execution logic of the `modify-corpus` command""" + config: dict = {} + + # If the config file is defined, read it + if args.config: + config = open_config(args.config, COMMAND) + # Override the config with the CLI values + if args.compilation_path: + config["compilationPath"] = args.compilation_path + if args.selected_fuzzer: + config["fuzzer"] = args.selected_fuzzer.lower() + if args.corpus_dir: + config["corpusDir"] = args.corpus_dir + if args.fuzzer_config: + config["fuzzerConfig"] = args.fuzzer_config + + check_config_and_set_default_values( + config, + ["compilationPath", "fuzzer", "corpusDir"], + [".", "echidna", "corpus"], + ) + + CryticPrint().print_information("Running Slither...") + slither = Slither(args.compilation_path) + + derive_config(slither, config) + + if config["fuzzer"] not in {"echidna", "medusa"}: + handle_exit( + f"\n* The requested fuzzer {config['fuzzer']} is not supported. Supported fuzzers: echidna, medusa." + ) + + corpus_modifier = CorpusModifier(config["fuzzerConfig"], config["corpusDir"], slither, config["fuzzer"]) + corpus_modifier.modify_corpus() + + CryticPrint().print_success("Done!") + + +def derive_config(slither: Slither, config: dict) -> None: + """Derive values for the target contract and inheritance path""" + # Derive target if it is not defined but the compilationPath only contains one contract + if "targetContract" not in config or len(config["targetContract"]) == 0: + if len(slither.contracts_derived) == 1: + config["targetContract"] = slither.contracts_derived[0].name + CryticPrint().print_information( + f"Target contract not specified. Using derived target: {config['targetContract']}." + ) + else: + handle_exit( + "Target contract cannot be determined. Please specify the target with `-c targetName`" + ) diff --git a/fuzz_utils/parsing/commands/restore.py b/fuzz_utils/parsing/commands/restore.py new file mode 100644 index 0000000..19bcb04 --- /dev/null +++ b/fuzz_utils/parsing/commands/restore.py @@ -0,0 +1,37 @@ +"""Defines the flags and logic associated with the `modify-corpus` command""" +from argparse import Namespace, ArgumentParser +from fuzz_utils.utils.crytic_print import CryticPrint +from fuzz_utils.modify_corpus.CorpusModifier import CorpusModifier +from fuzz_utils.utils.error_handler import handle_exit + +COMMAND: str = "modify-corpus" + + +def restore_flags(parser: ArgumentParser) -> None: + """The unit test generation parser flags""" + parser.add_argument( + "-ch", "--hash", dest="corpus_hash", help="Hash of the historic corpus that will be restored." + ) + parser.add_argument( + "-lh", + "--list-history", + dest="list_history", + help="List the corpora that are saved in history.", + default=False, + action="store_true", + ) + + +# pylint: disable=too-many-branches +def restore_command(args: Namespace) -> None: + """The execution logic of the `restore` command""" + + corpus_modifier = CorpusModifier(None, None, None, None) + # Override the config with the CLI values + if args.list_history: + corpus_modifier.list_historic_corpora() + else: + if args.corpus_hash: + corpus_modifier.restore_corpus_from_history(args.corpus_hash) + else: + handle_exit("No hash was provided!") \ No newline at end of file diff --git a/fuzz_utils/parsing/commands/snapshot.py b/fuzz_utils/parsing/commands/snapshot.py new file mode 100644 index 0000000..d38b4c5 --- /dev/null +++ b/fuzz_utils/parsing/commands/snapshot.py @@ -0,0 +1,43 @@ +"""Defines the flags and logic associated with the `modify-corpus` command""" +from argparse import Namespace, ArgumentParser +from fuzz_utils.utils.crytic_print import CryticPrint +from fuzz_utils.modify_corpus.CorpusModifier import CorpusModifier +from fuzz_utils.utils.error_handler import handle_exit +from fuzz_utils.parsing.parser_util import check_config_and_set_default_values + +COMMAND: str = "modify-corpus" + + +def snapshot_flags(parser: ArgumentParser) -> None: + """The unit test generation parser flags""" + parser.add_argument( + "-cd", "--corpus-dir", dest="corpus_dir", help="Path to the corpus directory" + ) + parser.add_argument( + "-f", + "--fuzzer", + dest="selected_fuzzer", + help="Define the fuzzer used. Valid inputs: 'echidna', 'medusa'", + ) + + +# pylint: disable=too-many-branches +def snapshot_command(args: Namespace) -> None: + """The execution logic of the `snapshot` command""" + config: dict = {} + + if args.corpus_dir: + config["corpusDir"] = args.corpus_dir + if args.selected_fuzzer: + config["fuzzer"] = args.selected_fuzzer.lower() + if config["fuzzer"] not in {"echidna", "medusa"}: + handle_exit(f"The provided fuzzer {config['fuzzer']} is not supported.") + + check_config_and_set_default_values( + config, + ["fuzzer", "corpusDir"], + ["echidna", "corpus"], + ) + + corpus_modifier = CorpusModifier(None, config["corpusDir"], None, config["fuzzer"]) + corpus_modifier.save_corpus_to_history() diff --git a/fuzz_utils/parsing/parser.py b/fuzz_utils/parsing/parser.py index 92c176d..0b2d7b1 100644 --- a/fuzz_utils/parsing/parser.py +++ b/fuzz_utils/parsing/parser.py @@ -4,6 +4,9 @@ from fuzz_utils.parsing.commands.generate import generate_command, generate_flags from fuzz_utils.parsing.commands.template import template_command, template_flags from fuzz_utils.parsing.commands.init import init_command, init_flags +from fuzz_utils.parsing.commands.restore import restore_command, restore_flags +from fuzz_utils.parsing.commands.snapshot import snapshot_command, snapshot_flags +from fuzz_utils.parsing.commands.modify_corpus import modify_command, modify_flags parsers: dict[str, dict[str, Any]] = { "init": { @@ -24,6 +27,24 @@ "flags": generate_flags, "subparser": None, }, + "modify-corpus": { + "command": modify_command, + "help": "Modifies the provided corpus.", + "flags": modify_flags, + "subparser": None, + }, + "snapshot": { + "command": snapshot_command, + "help": "Save the provided corpus directory.", + "flags": snapshot_flags, + "subparser": None, + }, + "restore": { + "command": restore_command, + "help": "Restore a corpus directory from a historic hash.", + "flags": restore_flags, + "subparser": None, + }, } diff --git a/pyproject.toml b/pyproject.toml index 6c3d3fe..edaf43e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,8 @@ dependencies = [ "jinja2>=3.1.0", "eth_abi>=5.0.1", "eth_utils>=4.0.0", - "eth_typing>=4.0.0" + "eth_typing>=4.0.0", + "pyyaml>=6.0.1" ] [project.optional-dependencies] From 2fcbcc709c31cb0f1da953baedae478fd3c3dc4b Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 12 Jun 2024 13:37:52 +0200 Subject: [PATCH 2/8] working function filtering and caller modification --- fuzz_utils/modify_corpus/CorpusModifier.py | 173 +++++++++++++------ fuzz_utils/parsing/commands/modify_corpus.py | 55 +++++- fuzz_utils/parsing/commands/restore.py | 3 +- fuzz_utils/parsing/commands/snapshot.py | 2 +- 4 files changed, 174 insertions(+), 59 deletions(-) diff --git a/fuzz_utils/modify_corpus/CorpusModifier.py b/fuzz_utils/modify_corpus/CorpusModifier.py index 69f76b8..05b1a77 100644 --- a/fuzz_utils/modify_corpus/CorpusModifier.py +++ b/fuzz_utils/modify_corpus/CorpusModifier.py @@ -7,48 +7,57 @@ import copy import datetime from slither import Slither +from fuzz_utils.utils.slither_utils import get_target_contract from fuzz_utils.utils.error_handler import handle_exit class CorpusModifier: """ Handles modifying the corpus based on the fuzzer configuration. """ - fuzzer_fields: dict + fuzzer_fields: dict = {"echidna": ["maxTimeDelay", "maxBlockDelay", "filterFunctions"], "medusa": ["blockTimestampDelayMax", "blockNumberDelayMax"]} corpora_format: dict = {"echidna": {"coverage": [], "reproducers": []}, "medusa": {"call_sequences": {"immutable": [], "mutable": []}, "test_results": []}} - + valid_modes: list = ["delete_sequence", "delete_calls"] + fuzzer_config: dict | None = None def __init__( self, - config_path: str | None, - corpus_path: str | None, - slither: Slither | None, - fuzzer: str | None, + config: dict, + slither: Slither | None ) -> None: - self.fuzzer_fields = {} + if config: + self.modifier_config = config if slither: self.slither = slither - if corpus_path: - self.corpus_path = corpus_path - if fuzzer: - self.fuzzer = fuzzer - self.fuzzer_fields["echidna"] = ["maxTimeDelay", "maxBlockDelay", "filterFunctions"] - if config_path: - self.config_path = config_path - self.fuzzer_config = self._fetch_fuzzer_config(self.fuzzer_fields[fuzzer]) + if "corpusDir" in config: + self.corpus_path = config["corpusDir"] + if "fuzzer" in config: + self.fuzzer = config["fuzzer"] + if "fuzzerConfigPath" in config: + self.config_path = config["fuzzerConfigPath"] + self.fuzzer_config = self._fetch_fuzzer_config(self.fuzzer_fields[self.fuzzer]) + if "mode" in config: + self.mode = config["mode"] + if "targetContract" in config: + self.target = get_target_contract(self.slither, config["targetContract"]) self.corpus_copy = {} - + self.rules_list = [self._is_incorrect_delay, self._is_blacklisted_function, self._is_nonexistent_function] + self.modifications_list = [self._modify_invalid_caller] + + def modify_corpus(self) -> None: """Modifies the current corpus and saves the new version""" # 1. Open the corpus and parse all the files - self.corpus_copy = self._copy_fuzzer_corpus(self.corpora_format[self.fuzzer.lower()], self.corpus_path) + self.corpus_copy = self._copy_fuzzer_corpus(self.corpora_format[self.fuzzer], self.corpus_path) # 2. a) Copy current corpus somewhere in case something goes wrong? self.save_corpus_to_history() - # 3. Define list of rules to apply - # TODO update this later to be dynamic - rules_list = [self._is_incorrect_delay] # 4. Apply the rules - self._filter_corpus("filters_calls", rules_list) + new_corpus = self._filter_corpus(self.mode, self.rules_list, self.modifications_list) # 5. Save the new corpus - self._override_corpus_directory(self.corpus_path, self.corpus_copy) + self._override_corpus_directory(self.corpus_path, new_corpus) + + def dry_run(self) -> None: + """Prints the calls that would be modified, without modifying them""" + self.corpus_copy = self._copy_fuzzer_corpus(self.corpora_format[self.fuzzer.lower()], self.corpus_path) + new_corpus = self._filter_corpus(self.mode, self.rules_list, self.modifications_list) def save_corpus_to_history(self) -> None: """Saves the current corpus directory to the corpus history""" @@ -70,7 +79,7 @@ def save_corpus_to_history(self) -> None: with open(history_path, "r", encoding="utf-8") as f: history = json.load(f) - + if corpus_hash in history.keys(): print(f"The corpus is already saved to history with the hash {corpus_hash}") return @@ -82,7 +91,7 @@ def save_corpus_to_history(self) -> None: # TODO check if this can fail with open(history_path, "w", encoding="utf-8") as f: json.dump(history, f) - + def restore_corpus_from_history(self, corpus_hash: str) -> None: """Overrides the current corpus directory with a historical version""" directory = self._create_or_fetch_hidden_directory() @@ -91,7 +100,7 @@ def restore_corpus_from_history(self, corpus_hash: str) -> None: # TODO check if this can fail with open(history_path, "r", encoding="utf-8") as f: history = json.load(f) - + if corpus_hash in history.keys(): fetched_corpus = history[corpus_hash] self._override_corpus_directory(fetched_corpus["path"], fetched_corpus["content"]) @@ -143,54 +152,119 @@ def _create_or_fetch_hidden_directory(self) -> str: os.makedirs(directory_name, exist_ok=True) return directory_name - - - # TODO only supports Echidna for now def _is_incorrect_delay(self, call_object: dict) -> bool: - maxTimeDelay = self.fuzzer_config["maxTimeDelay"] if "maxTimeDelay" in self.fuzzer_config else None - maxBlockDelay = self.fuzzer_config["maxBlockDelay"] if "maxBlockDelay" in self.fuzzer_config else None + if not self.fuzzer_config: + return False - if maxTimeDelay: + maxTimeDelay: int | None + maxBlockDelay: int | None + time_delay: int + block_delay: int + + if self.fuzzer == "echidna": + maxTimeDelay = self.fuzzer_config["maxTimeDelay"] if "maxTimeDelay" in self.fuzzer_config else None + maxBlockDelay = self.fuzzer_config["maxBlockDelay"] if "maxBlockDelay" in self.fuzzer_config else None time_delay = int(call_object["delay"][0], 16) + block_delay = int(call_object["delay"][1], 16) + elif self.fuzzer == "medusa": + maxTimeDelay = self.fuzzer_config["fuzzing"]["blockTimestampDelayMax"] if "blockTimestampDelayMax" in self.fuzzer_config["fuzzing"] else None + maxBlockDelay = self.fuzzer_config["fuzzing"]["blockNumberDelayMax"] if "blockNumberDelayMax" in self.fuzzer_config["fuzzing"] else None + time_delay = call_object["blockTimestampDelay"] + block_delay = call_object["blockNumberDelay"] + else: + raise ValueError(f"Invalid fuzzer: {self.fuzzer}") + + if maxTimeDelay: if time_delay > maxTimeDelay: return True if maxBlockDelay: - block_delay = int(call_object["delay"][1], 16) if block_delay > maxBlockDelay: return True return False - def _filter_corpus(self, mode: str, rules_list: list) -> None: + def _is_nonexistent_function(self, call_object: dict) -> bool: + if "filterFunctions" not in self.modifier_config: + return False + + function_name: str + #contracts = self.slither.contracts_derived + #functions = [y.name for x in contracts for y in x.functions_entry_points] + # TODO enable multiple targets later + functions = [x.name for x in self.target.functions_entry_points] + + if self.fuzzer == "echidna": + function_name = call_object["call"]["contents"][0] + elif self.fuzzer == "medusa": + function_name = call_object["call"]["dataAbiValues"]["methodSignature"].split("(")[0] + else: + raise ValueError(f"Invalid fuzzer: {self.fuzzer}") + print(f"fnName {function_name}, functions list {functions}") + if function_name not in functions: + return True + return False + + def _is_blacklisted_function(self, call_object: dict) -> bool: + if not self.fuzzer_config: + return False + + function_name: str + blacklisted_functions: list | None + if self.fuzzer == "echidna": + function_name = call_object["call"]["contents"][0] + blacklisted_functions: list | None = self.fuzzer_config["filterFunctions"] if "filterFunctions" in self.fuzzer_config else None + else: + raise ValueError("Function blacklisting is only available in Echidna.") + + if blacklisted_functions: + if function_name in blacklisted_functions: # pylint: disable=unsupported-membership-test + return True + return False + + def _modify_invalid_caller(self, call_object: dict) -> dict: + if "modifySenders" not in self.modifier_config: + return call_object + + print("modifySenders run") + caller = call_object["src"] + if caller in self.modifier_config["modifySenders"].keys(): + call_object["src"] = self.modifier_config["modifySenders"][caller] + return call_object + + def _filter_corpus(self, mode: str, rules_list: list, modification_list: list) -> list: + new_corpus: list = [] for idx, value in enumerate(self.corpus_copy): - sequence_list: list = [] # A list of files with call sequences in them - for call_sequence in value["content"]: - resulting_sequence = self._filter_call_sequence(mode, rules_list, call_sequence) + resulting_sequence = self._filter_call_sequence(mode, rules_list, modification_list, value["content"]) - if resulting_sequence: - sequence_list.append(resulting_sequence) + if resulting_sequence: + new_corpus.append({"path": self.corpus_copy[idx]["path"], "content": resulting_sequence}) + return new_corpus - # Override the old call sequences with the new ones - self.corpus_copy[idx] = {"path": self.corpus_copy[idx]["path"], "content": sequence_list} - - def _filter_call_sequence(self, mode: str, rules_list: list, call_sequence: list) -> dict | None: + def _filter_call_sequence(self, mode: str, rules_list: list, modification_list: list, call_sequence: list) -> dict | None: def should_skip(call): return any(rule_fn(call) for rule_fn in rules_list) - if mode not in {"delete_sequence", "filter_calls"}: + def replace_fields(call) -> dict: + for modify_fn in modification_list: + call = modify_fn(call) + return call + + if mode not in self.valid_modes: raise ValueError("Invalid mode") - resulting_sequence: dict = {"name": call_sequence["name"], "content": []} + resulting_sequence: list = [] - for call in call_sequence["content"]: + for call in call_sequence: + # TODO make this remove calls if should_skip(call): if mode == "delete_sequence": return None else: - resulting_sequence["content"].append(call) + call = replace_fields(call) + resulting_sequence.append(call) - return resulting_sequence if resulting_sequence["content"] else None + return resulting_sequence if resulting_sequence else None def _copy_fuzzer_corpus(self, corpus: dict, current_path: str) -> list | None: @@ -230,15 +304,14 @@ def _fetch_fuzzer_config(self, fields: list[str]) -> dict: if os.path.isfile(self.config_path): try: with open(self.config_path, "r", encoding="utf-8") as file: - if self.fuzzer.lower() == "echidna": + if self.fuzzer == "echidna": complete_config = yaml.safe_load(file) else: - complete_config = json.load(file) + complete_config = json.load(file)["fuzzing"] except Exception: # pylint: disable=broad-except handle_exit(f"Failed to find the fuzzer configuration file at {self.config_path}") for key, value in complete_config.items(): - # TODO won't work for Medusa if key in fields: filtered_config[key] = value diff --git a/fuzz_utils/parsing/commands/modify_corpus.py b/fuzz_utils/parsing/commands/modify_corpus.py index 8b6f72d..3fd600a 100644 --- a/fuzz_utils/parsing/commands/modify_corpus.py +++ b/fuzz_utils/parsing/commands/modify_corpus.py @@ -17,6 +17,7 @@ def modify_flags(parser: ArgumentParser) -> None: parser.add_argument( "-cd", "--corpus-dir", dest="corpus_dir", help="Path to the corpus directory" ) + parser.add_argument("-c", "--contract", dest="target_contract", help="Define the contract name") parser.add_argument( "-f", "--fuzzer", @@ -28,6 +29,33 @@ def modify_flags(parser: ArgumentParser) -> None: dest="fuzzer_config", help="Define the location of the fuzzer config file.", ) + parser.add_argument( + "--mode", + dest="filter_mode", + help="Define how the corpus will me modified. Available options are: `delete_sequence` and `delete_calls`", + ) + parser.add_argument( + "--modify-senders", + dest="modify_senders", + nargs="+", + help="Define sender remappings in the format `0xoldSender=0xnewSender,0xold2=0xnew2,...`", + ) + parser.add_argument( + "-ff", + "--filter-functions", + dest="filter_functions", + help="Remove functions that no longer exist from the corpus.", + default=False, + action="store_true", + ) + parser.add_argument( + "-dr", + "--dry-run", + dest="dry_run", + help="Print out any corpus calls that would be modified.", + default=False, + action="store_true", + ) # TODO add specific commands for modifying the corpus @@ -36,23 +64,36 @@ def modify_command(args: Namespace) -> None: """The execution logic of the `modify-corpus` command""" config: dict = {} - # If the config file is defined, read it - if args.config: - config = open_config(args.config, COMMAND) # Override the config with the CLI values if args.compilation_path: config["compilationPath"] = args.compilation_path + if args.target_contract: + config["targetContract"] = args.target_contract if args.selected_fuzzer: config["fuzzer"] = args.selected_fuzzer.lower() if args.corpus_dir: config["corpusDir"] = args.corpus_dir if args.fuzzer_config: - config["fuzzerConfig"] = args.fuzzer_config + config["fuzzerConfigPath"] = args.fuzzer_config + if args.filter_mode: + config["mode"] = args.filter_mode + if args.modify_senders: + config["modifySenders"] = {} + # Process list + for item in args.modify_senders: + assert("=" in item) + senders = item.split("=") + config["modifySenders"][senders[0]] = senders[1] + if args.filter_functions: + config["filterFunctions"] = args.filter_functions + if args.dry_run: + config["dryRun"] = args.dry_run + CryticPrint().print_error("The --dry-run command isn't implemented yet, come back in a bit!") check_config_and_set_default_values( config, - ["compilationPath", "fuzzer", "corpusDir"], - [".", "echidna", "corpus"], + ["compilationPath", "fuzzer", "corpusDir", "mode"], + [".", "echidna", "corpus", "filter_calls"], ) CryticPrint().print_information("Running Slither...") @@ -65,7 +106,7 @@ def modify_command(args: Namespace) -> None: f"\n* The requested fuzzer {config['fuzzer']} is not supported. Supported fuzzers: echidna, medusa." ) - corpus_modifier = CorpusModifier(config["fuzzerConfig"], config["corpusDir"], slither, config["fuzzer"]) + corpus_modifier = CorpusModifier(config, slither) corpus_modifier.modify_corpus() CryticPrint().print_success("Done!") diff --git a/fuzz_utils/parsing/commands/restore.py b/fuzz_utils/parsing/commands/restore.py index 19bcb04..adea575 100644 --- a/fuzz_utils/parsing/commands/restore.py +++ b/fuzz_utils/parsing/commands/restore.py @@ -25,8 +25,9 @@ def restore_flags(parser: ArgumentParser) -> None: # pylint: disable=too-many-branches def restore_command(args: Namespace) -> None: """The execution logic of the `restore` command""" + config: dict = {} - corpus_modifier = CorpusModifier(None, None, None, None) + corpus_modifier = CorpusModifier(config, None) # Override the config with the CLI values if args.list_history: corpus_modifier.list_historic_corpora() diff --git a/fuzz_utils/parsing/commands/snapshot.py b/fuzz_utils/parsing/commands/snapshot.py index d38b4c5..7c0ef50 100644 --- a/fuzz_utils/parsing/commands/snapshot.py +++ b/fuzz_utils/parsing/commands/snapshot.py @@ -39,5 +39,5 @@ def snapshot_command(args: Namespace) -> None: ["echidna", "corpus"], ) - corpus_modifier = CorpusModifier(None, config["corpusDir"], None, config["fuzzer"]) + corpus_modifier = CorpusModifier(config, None) corpus_modifier.save_corpus_to_history() From 5e447bb56b96bdc64d39bd455106f7a3f1ce6888 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 12 Jun 2024 14:53:32 +0200 Subject: [PATCH 3/8] lint and reformat --- fuzz_utils/modify_corpus/CorpusModifier.py | 139 +++++++++++++------ fuzz_utils/parsing/commands/modify_corpus.py | 12 +- fuzz_utils/parsing/commands/restore.py | 8 +- fuzz_utils/parsing/commands/snapshot.py | 1 + 4 files changed, 108 insertions(+), 52 deletions(-) diff --git a/fuzz_utils/modify_corpus/CorpusModifier.py b/fuzz_utils/modify_corpus/CorpusModifier.py index 05b1a77..d4cb3d7 100644 --- a/fuzz_utils/modify_corpus/CorpusModifier.py +++ b/fuzz_utils/modify_corpus/CorpusModifier.py @@ -1,28 +1,34 @@ """The CorpusModifier class handles modification of corpus call sequences based on fuzzer config""" import os -import yaml import hashlib import json import shutil -import copy import datetime +import yaml + from slither import Slither from fuzz_utils.utils.slither_utils import get_target_contract from fuzz_utils.utils.error_handler import handle_exit +from fuzz_utils.utils.crytic_print import CryticPrint +# pylint: disable=too-many-instance-attributes class CorpusModifier: """ Handles modifying the corpus based on the fuzzer configuration. """ - fuzzer_fields: dict = {"echidna": ["maxTimeDelay", "maxBlockDelay", "filterFunctions"], "medusa": ["blockTimestampDelayMax", "blockNumberDelayMax"]} - corpora_format: dict = {"echidna": {"coverage": [], "reproducers": []}, "medusa": {"call_sequences": {"immutable": [], "mutable": []}, "test_results": []}} + + fuzzer_fields: dict = { + "echidna": ["maxTimeDelay", "maxBlockDelay", "filterFunctions"], + "medusa": ["blockTimestampDelayMax", "blockNumberDelayMax"], + } + corpora_format: dict = { + "echidna": {"coverage": [], "reproducers": []}, + "medusa": {"call_sequences": {"immutable": [], "mutable": []}, "test_results": []}, + } valid_modes: list = ["delete_sequence", "delete_calls"] fuzzer_config: dict | None = None - def __init__( - self, - config: dict, - slither: Slither | None - ) -> None: + + def __init__(self, config: dict, slither: Slither | None) -> None: if config: self.modifier_config = config if slither: @@ -38,15 +44,20 @@ def __init__( self.mode = config["mode"] if "targetContract" in config: self.target = get_target_contract(self.slither, config["targetContract"]) - self.corpus_copy = {} - self.rules_list = [self._is_incorrect_delay, self._is_blacklisted_function, self._is_nonexistent_function] + self.corpus_copy: list = [] + self.rules_list = [ + self._is_incorrect_delay, + self._is_blacklisted_function, + self._is_nonexistent_function, + ] self.modifications_list = [self._modify_invalid_caller] - def modify_corpus(self) -> None: """Modifies the current corpus and saves the new version""" # 1. Open the corpus and parse all the files - self.corpus_copy = self._copy_fuzzer_corpus(self.corpora_format[self.fuzzer], self.corpus_path) + self.corpus_copy = self._copy_fuzzer_corpus( + self.corpora_format[self.fuzzer], self.corpus_path + ) # 2. a) Copy current corpus somewhere in case something goes wrong? self.save_corpus_to_history() # 4. Apply the rules @@ -56,13 +67,17 @@ def modify_corpus(self) -> None: def dry_run(self) -> None: """Prints the calls that would be modified, without modifying them""" - self.corpus_copy = self._copy_fuzzer_corpus(self.corpora_format[self.fuzzer.lower()], self.corpus_path) - new_corpus = self._filter_corpus(self.mode, self.rules_list, self.modifications_list) + self.corpus_copy = self._copy_fuzzer_corpus( + self.corpora_format[self.fuzzer.lower()], self.corpus_path + ) + _ = self._filter_corpus(self.mode, self.rules_list, self.modifications_list) def save_corpus_to_history(self) -> None: """Saves the current corpus directory to the corpus history""" # 1. Fetch current corpus - corpus_to_save = self._copy_fuzzer_corpus(self.corpora_format[self.fuzzer.lower()], self.corpus_path) + corpus_to_save = self._copy_fuzzer_corpus( + self.corpora_format[self.fuzzer.lower()], self.corpus_path + ) # 2. Check if .fuzz_utils folder already exists, create it if not directory = self._create_or_fetch_hidden_directory() # 3. Convert into a string @@ -86,12 +101,19 @@ def save_corpus_to_history(self) -> None: # 6. Add to history timestamp = datetime.datetime.now().isoformat() comment = input("Enter a comment for this save: ") - history[corpus_hash] = {"path": self.corpus_path, "timestamp": timestamp, "content": corpus_to_save, "comment": comment} + history[corpus_hash] = { + "path": self.corpus_path, + "timestamp": timestamp, + "content": corpus_to_save, + "comment": comment, + } # 7. Save history # TODO check if this can fail with open(history_path, "w", encoding="utf-8") as f: json.dump(history, f) + CryticPrint().print_information(f"Corpus saved to history with hash: {corpus_hash}") + def restore_corpus_from_history(self, corpus_hash: str) -> None: """Overrides the current corpus directory with a historical version""" directory = self._create_or_fetch_hidden_directory() @@ -120,11 +142,11 @@ def list_historic_corpora(self) -> None: for key, value in history.items(): print(f'{key}: {value["comment"]} (saved at {value["timestamp"]})') else: - print("No historic corpora were found.") - - + print("No historic corpora found.") - def _override_corpus_directory(self, directory_path: str, contents_list: list) -> None: + def _override_corpus_directory( # pylint: disable=no-self-use + self, directory_path: str, contents_list: list + ) -> None: for root, dirs, files in os.walk(directory_path): for file in files: file_path = os.path.join(root, file) @@ -143,10 +165,11 @@ def _override_corpus_directory(self, directory_path: str, contents_list: list) - os.makedirs(directory, exist_ok=True) # Write the file contents - with open(file_path, "w", encoding="utf-8") as file: - json.dump(file_content, file, indent=4) + # TODO fix the mypy errors later on + with open(file_path, "w", encoding="utf-8") as file: # type: ignore[assignment] + json.dump(file_content, file, indent=4) # type: ignore[arg-type] - def _create_or_fetch_hidden_directory(self) -> str: + def _create_or_fetch_hidden_directory(self) -> str: # pylint: disable=no-self-use current_directory = os.getcwd() directory_name = os.path.join(current_directory, ".fuzz_utils") os.makedirs(directory_name, exist_ok=True) @@ -162,18 +185,32 @@ def _is_incorrect_delay(self, call_object: dict) -> bool: block_delay: int if self.fuzzer == "echidna": - maxTimeDelay = self.fuzzer_config["maxTimeDelay"] if "maxTimeDelay" in self.fuzzer_config else None - maxBlockDelay = self.fuzzer_config["maxBlockDelay"] if "maxBlockDelay" in self.fuzzer_config else None + maxTimeDelay = ( + self.fuzzer_config["maxTimeDelay"] if "maxTimeDelay" in self.fuzzer_config else None + ) + maxBlockDelay = ( + self.fuzzer_config["maxBlockDelay"] + if "maxBlockDelay" in self.fuzzer_config + else None + ) time_delay = int(call_object["delay"][0], 16) block_delay = int(call_object["delay"][1], 16) elif self.fuzzer == "medusa": - maxTimeDelay = self.fuzzer_config["fuzzing"]["blockTimestampDelayMax"] if "blockTimestampDelayMax" in self.fuzzer_config["fuzzing"] else None - maxBlockDelay = self.fuzzer_config["fuzzing"]["blockNumberDelayMax"] if "blockNumberDelayMax" in self.fuzzer_config["fuzzing"] else None + maxTimeDelay = ( + self.fuzzer_config["fuzzing"]["blockTimestampDelayMax"] + if "blockTimestampDelayMax" in self.fuzzer_config["fuzzing"] + else None + ) + maxBlockDelay = ( + self.fuzzer_config["fuzzing"]["blockNumberDelayMax"] + if "blockNumberDelayMax" in self.fuzzer_config["fuzzing"] + else None + ) time_delay = call_object["blockTimestampDelay"] block_delay = call_object["blockNumberDelay"] else: raise ValueError(f"Invalid fuzzer: {self.fuzzer}") - + if maxTimeDelay: if time_delay > maxTimeDelay: return True @@ -188,8 +225,8 @@ def _is_nonexistent_function(self, call_object: dict) -> bool: return False function_name: str - #contracts = self.slither.contracts_derived - #functions = [y.name for x in contracts for y in x.functions_entry_points] + # contracts = self.slither.contracts_derived + # functions = [y.name for x in contracts for y in x.functions_entry_points] # TODO enable multiple targets later functions = [x.name for x in self.target.functions_entry_points] @@ -212,12 +249,18 @@ def _is_blacklisted_function(self, call_object: dict) -> bool: blacklisted_functions: list | None if self.fuzzer == "echidna": function_name = call_object["call"]["contents"][0] - blacklisted_functions: list | None = self.fuzzer_config["filterFunctions"] if "filterFunctions" in self.fuzzer_config else None + blacklisted_functions = ( + self.fuzzer_config["filterFunctions"] + if "filterFunctions" in self.fuzzer_config + else None + ) else: raise ValueError("Function blacklisting is only available in Echidna.") if blacklisted_functions: - if function_name in blacklisted_functions: # pylint: disable=unsupported-membership-test + if ( + function_name in blacklisted_functions + ): # pylint: disable=unsupported-membership-test return True return False @@ -235,17 +278,23 @@ def _filter_corpus(self, mode: str, rules_list: list, modification_list: list) - new_corpus: list = [] for idx, value in enumerate(self.corpus_copy): # A list of files with call sequences in them - resulting_sequence = self._filter_call_sequence(mode, rules_list, modification_list, value["content"]) + resulting_sequence = self._filter_call_sequence( + mode, rules_list, modification_list, value["content"] + ) if resulting_sequence: - new_corpus.append({"path": self.corpus_copy[idx]["path"], "content": resulting_sequence}) + new_corpus.append( + {"path": self.corpus_copy[idx]["path"], "content": resulting_sequence} + ) return new_corpus - def _filter_call_sequence(self, mode: str, rules_list: list, modification_list: list, call_sequence: list) -> dict | None: - def should_skip(call): + def _filter_call_sequence( + self, mode: str, rules_list: list, modification_list: list, call_sequence: list + ) -> list | None: + def should_skip(call: dict) -> bool: return any(rule_fn(call) for rule_fn in rules_list) - def replace_fields(call) -> dict: + def replace_fields(call: dict) -> dict: for modify_fn in modification_list: call = modify_fn(call) return call @@ -266,9 +315,8 @@ def replace_fields(call) -> dict: return resulting_sequence if resulting_sequence else None - - def _copy_fuzzer_corpus(self, corpus: dict, current_path: str) -> list | None: - temp_corpus = [] + def _copy_fuzzer_corpus(self, corpus: dict, current_path: str) -> list: + temp_corpus: list = [] for key in corpus.keys(): subdir_path = os.path.join(current_path, key) if isinstance(corpus[key], dict): @@ -276,7 +324,7 @@ def _copy_fuzzer_corpus(self, corpus: dict, current_path: str) -> list | None: else: temp_corpus.extend(self._fetch_directory_files(subdir_path)) - return temp_corpus if temp_corpus else None + return temp_corpus def _fetch_directory_files(self, directory: str) -> list: file_list: list = [] @@ -288,7 +336,9 @@ def _fetch_directory_files(self, directory: str) -> list: try: with open(full_path, "r", encoding="utf-8") as file: if self.fuzzer.lower() == "echidna": - file_list.append({"path": full_path, "content": yaml.safe_load(file)}) + file_list.append( + {"path": full_path, "content": yaml.safe_load(file)} + ) else: file_list.append({"path": full_path, "content": json.load(file)}) except Exception: # pylint: disable=broad-except @@ -308,7 +358,7 @@ def _fetch_fuzzer_config(self, fields: list[str]) -> dict: complete_config = yaml.safe_load(file) else: complete_config = json.load(file)["fuzzing"] - except Exception: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except handle_exit(f"Failed to find the fuzzer configuration file at {self.config_path}") for key, value in complete_config.items(): @@ -316,4 +366,3 @@ def _fetch_fuzzer_config(self, fields: list[str]) -> dict: filtered_config[key] = value return filtered_config - diff --git a/fuzz_utils/parsing/commands/modify_corpus.py b/fuzz_utils/parsing/commands/modify_corpus.py index 3fd600a..2dc95d0 100644 --- a/fuzz_utils/parsing/commands/modify_corpus.py +++ b/fuzz_utils/parsing/commands/modify_corpus.py @@ -4,7 +4,7 @@ from fuzz_utils.utils.crytic_print import CryticPrint from fuzz_utils.modify_corpus.CorpusModifier import CorpusModifier from fuzz_utils.utils.error_handler import handle_exit -from fuzz_utils.parsing.parser_util import check_config_and_set_default_values, open_config +from fuzz_utils.parsing.parser_util import check_config_and_set_default_values COMMAND: str = "modify-corpus" @@ -81,14 +81,16 @@ def modify_command(args: Namespace) -> None: config["modifySenders"] = {} # Process list for item in args.modify_senders: - assert("=" in item) + assert "=" in item senders = item.split("=") config["modifySenders"][senders[0]] = senders[1] if args.filter_functions: config["filterFunctions"] = args.filter_functions if args.dry_run: config["dryRun"] = args.dry_run - CryticPrint().print_error("The --dry-run command isn't implemented yet, come back in a bit!") + CryticPrint().print_error( + "The --dry-run command isn't implemented yet, come back in a bit!" + ) check_config_and_set_default_values( config, @@ -103,8 +105,8 @@ def modify_command(args: Namespace) -> None: if config["fuzzer"] not in {"echidna", "medusa"}: handle_exit( - f"\n* The requested fuzzer {config['fuzzer']} is not supported. Supported fuzzers: echidna, medusa." - ) + f"\n* The requested fuzzer {config['fuzzer']} is not supported. Supported fuzzers: echidna, medusa." + ) corpus_modifier = CorpusModifier(config, slither) corpus_modifier.modify_corpus() diff --git a/fuzz_utils/parsing/commands/restore.py b/fuzz_utils/parsing/commands/restore.py index adea575..9d55d45 100644 --- a/fuzz_utils/parsing/commands/restore.py +++ b/fuzz_utils/parsing/commands/restore.py @@ -10,7 +10,10 @@ def restore_flags(parser: ArgumentParser) -> None: """The unit test generation parser flags""" parser.add_argument( - "-ch", "--hash", dest="corpus_hash", help="Hash of the historic corpus that will be restored." + "-ch", + "--hash", + dest="corpus_hash", + help="Hash of the historic corpus that will be restored.", ) parser.add_argument( "-lh", @@ -33,6 +36,7 @@ def restore_command(args: Namespace) -> None: corpus_modifier.list_historic_corpora() else: if args.corpus_hash: + CryticPrint().print_information(f"Restoring corpus with hash: {args.corpus_hash}") corpus_modifier.restore_corpus_from_history(args.corpus_hash) else: - handle_exit("No hash was provided!") \ No newline at end of file + handle_exit("No hash was provided!") diff --git a/fuzz_utils/parsing/commands/snapshot.py b/fuzz_utils/parsing/commands/snapshot.py index 7c0ef50..f369412 100644 --- a/fuzz_utils/parsing/commands/snapshot.py +++ b/fuzz_utils/parsing/commands/snapshot.py @@ -40,4 +40,5 @@ def snapshot_command(args: Namespace) -> None: ) corpus_modifier = CorpusModifier(config, None) + CryticPrint().print_information(f"Saving corpus {args.corpus_dir} to history...") corpus_modifier.save_corpus_to_history() From 0428fb52a6e01d89243dc708fa2d9fa5a7373f67 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 12 Jun 2024 18:04:54 +0200 Subject: [PATCH 4/8] update sender nonce when modifying medusa corpus calls --- fuzz_utils/modify_corpus/CorpusModifier.py | 56 +++++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/fuzz_utils/modify_corpus/CorpusModifier.py b/fuzz_utils/modify_corpus/CorpusModifier.py index d4cb3d7..da7526e 100644 --- a/fuzz_utils/modify_corpus/CorpusModifier.py +++ b/fuzz_utils/modify_corpus/CorpusModifier.py @@ -5,6 +5,7 @@ import shutil import datetime import yaml +from collections import defaultdict from slither import Slither from fuzz_utils.utils.slither_utils import get_target_contract @@ -197,13 +198,13 @@ def _is_incorrect_delay(self, call_object: dict) -> bool: block_delay = int(call_object["delay"][1], 16) elif self.fuzzer == "medusa": maxTimeDelay = ( - self.fuzzer_config["fuzzing"]["blockTimestampDelayMax"] - if "blockTimestampDelayMax" in self.fuzzer_config["fuzzing"] + self.fuzzer_config["blockTimestampDelayMax"] + if "blockTimestampDelayMax" in self.fuzzer_config else None ) maxBlockDelay = ( - self.fuzzer_config["fuzzing"]["blockNumberDelayMax"] - if "blockNumberDelayMax" in self.fuzzer_config["fuzzing"] + self.fuzzer_config["blockNumberDelayMax"] + if "blockNumberDelayMax" in self.fuzzer_config else None ) time_delay = call_object["blockTimestampDelay"] @@ -255,7 +256,7 @@ def _is_blacklisted_function(self, call_object: dict) -> bool: else None ) else: - raise ValueError("Function blacklisting is only available in Echidna.") + return False if blacklisted_functions: if ( @@ -268,10 +269,18 @@ def _modify_invalid_caller(self, call_object: dict) -> dict: if "modifySenders" not in self.modifier_config: return call_object - print("modifySenders run") - caller = call_object["src"] - if caller in self.modifier_config["modifySenders"].keys(): - call_object["src"] = self.modifier_config["modifySenders"][caller] + caller: str + if self.fuzzer == "echidna": + caller = call_object["src"] + if caller in self.modifier_config["modifySenders"].keys(): + call_object["src"] = self.modifier_config["modifySenders"][caller] + elif self.fuzzer == "medusa": + caller = call_object["call"]["from"] + if caller in self.modifier_config["modifySenders"].keys(): + call_object["call"]["from"] = self.modifier_config["modifySenders"][caller] + else: + raise ValueError(f"Invalid fuzzer: {self.fuzzer}") + return call_object def _filter_corpus(self, mode: str, rules_list: list, modification_list: list) -> list: @@ -299,28 +308,49 @@ def replace_fields(call: dict) -> dict: call = modify_fn(call) return call + def replace_nonce(call: dict, nonces: dict) -> dict: + if self.fuzzer == "medusa": + caller = call["call"]["from"] + call["call"]["nonce"] = nonces[caller] + nonces[caller] += 1 + + return call + + nonces: dict = defaultdict(lambda: 0) + if self.fuzzer == "medusa": + for call in call_sequence: + if call["call"]["from"] in nonces: + continue + else: + nonces[call["call"]["from"]] = call["call"]["nonce"] + if mode not in self.valid_modes: raise ValueError("Invalid mode") resulting_sequence: list = [] for call in call_sequence: - # TODO make this remove calls if should_skip(call): if mode == "delete_sequence": return None else: + # Modify call based on modifier rules, if applicable call = replace_fields(call) + # Only used for Medusa# + call = replace_nonce(call, nonces) + # Append the call to the sequence resulting_sequence.append(call) return resulting_sequence if resulting_sequence else None def _copy_fuzzer_corpus(self, corpus: dict, current_path: str) -> list: temp_corpus: list = [] - for key in corpus.keys(): + + for key, value in corpus.items(): subdir_path = os.path.join(current_path, key) - if isinstance(corpus[key], dict): - temp_corpus[key] = self._copy_fuzzer_corpus(corpus[key], subdir_path) + if isinstance(value, dict): + print(value) + temp_corpus.extend(self._copy_fuzzer_corpus(value, subdir_path)) else: temp_corpus.extend(self._fetch_directory_files(subdir_path)) From 7bc883aa1424ae58e2227041de5278ff1abd95f3 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Thu, 13 Jun 2024 17:17:44 +0200 Subject: [PATCH 5/8] refactor CorpusModifier, add unit tests for delete_calls mode --- fuzz_utils/modify_corpus/CorpusModifier.py | 363 ++-- fuzz_utils/modify_corpus/utils.py | 32 + fuzz_utils/utils/file_manager.py | 26 + tests/conftest.py | 22 +- tests/modifier_expected_results.py | 1480 +++++++++++++++++ tests/test_corpus_modifier.py | 176 ++ .../coverage/-1234458407747697055.txt | 107 ++ .../coverage/-585908729518561292.txt | 56 + .../reproducers/-1469195906620168853.txt | 46 + .../reproducers/-2210631667546636005.txt | 46 + ...-cd75b0ae-6513-431b-81d3-d4bd19581b92.json | 67 + ...-410883c1-42cd-4f17-b326-f13eb6bb65fe.json | 111 ++ ...-9eaced3e-b7ae-4565-a0ff-c2ba04d31b13.json | 23 + ...-39bea42d-9a8e-4e02-9652-76990e1d9043.json | 25 + ...-563145f4-2fb6-4929-9438-d9f229d9c2a4.json | 46 + ...-34e6f348-02b4-4ff1-92ea-dac3b77823c5.json | 46 + .../src/BasicTypesNoCheckUint256.sol | 162 ++ 17 files changed, 2597 insertions(+), 237 deletions(-) create mode 100644 fuzz_utils/modify_corpus/utils.py create mode 100644 tests/modifier_expected_results.py create mode 100644 tests/test_corpus_modifier.py create mode 100644 tests/test_data/echidna-corpora/corpus-basic-modifier/coverage/-1234458407747697055.txt create mode 100644 tests/test_data/echidna-corpora/corpus-basic-modifier/coverage/-585908729518561292.txt create mode 100644 tests/test_data/echidna-corpora/corpus-basic-modifier/reproducers/-1469195906620168853.txt create mode 100644 tests/test_data/echidna-corpora/corpus-basic-modifier/reproducers/-2210631667546636005.txt create mode 100755 tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/immutable/1711369139037582000-cd75b0ae-6513-431b-81d3-d4bd19581b92.json create mode 100755 tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/immutable/1711369139040471000-410883c1-42cd-4f17-b326-f13eb6bb65fe.json create mode 100755 tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/mutable/1711369139036115000-9eaced3e-b7ae-4565-a0ff-c2ba04d31b13.json create mode 100755 tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/mutable/1711369139036682000-39bea42d-9a8e-4e02-9652-76990e1d9043.json create mode 100755 tests/test_data/medusa-corpora/corpus-basic-modifier/test_results/1711369141680378000-563145f4-2fb6-4929-9438-d9f229d9c2a4.json create mode 100755 tests/test_data/medusa-corpora/corpus-basic-modifier/test_results/1711369141706737000-34e6f348-02b4-4ff1-92ea-dac3b77823c5.json create mode 100644 tests/test_data/src/BasicTypesNoCheckUint256.sol diff --git a/fuzz_utils/modify_corpus/CorpusModifier.py b/fuzz_utils/modify_corpus/CorpusModifier.py index da7526e..f3d29ab 100644 --- a/fuzz_utils/modify_corpus/CorpusModifier.py +++ b/fuzz_utils/modify_corpus/CorpusModifier.py @@ -2,15 +2,22 @@ import os import hashlib import json -import shutil import datetime +from collections import defaultdict +from typing import List, Dict, Union, Callable, Tuple + import yaml -from collections import defaultdict from slither import Slither from fuzz_utils.utils.slither_utils import get_target_contract from fuzz_utils.utils.error_handler import handle_exit from fuzz_utils.utils.crytic_print import CryticPrint +from fuzz_utils.modify_corpus.utils import ( + override_corpus_directory, + create_or_fetch_hidden_directory, + save_history, + load_history, +) # pylint: disable=too-many-instance-attributes class CorpusModifier: @@ -18,297 +25,200 @@ class CorpusModifier: Handles modifying the corpus based on the fuzzer configuration. """ - fuzzer_fields: dict = { - "echidna": ["maxTimeDelay", "maxBlockDelay", "filterFunctions"], - "medusa": ["blockTimestampDelayMax", "blockNumberDelayMax"], + fuzzer_fields: Dict[str, Dict[str, str]] = { + "echidna": { + "maxTimeDelay": "max_time_delay", + "maxBlockDelay": "max_block_delay", + "filterFunctions": "filter_functions", + }, + "medusa": { + "blockTimestampDelayMax": "max_time_delay", + "blockNumberDelayMax": "max_block_delay", + }, } - corpora_format: dict = { + corpora_format: Dict[str, Dict[str, Union[List, Dict]]] = { "echidna": {"coverage": [], "reproducers": []}, "medusa": {"call_sequences": {"immutable": [], "mutable": []}, "test_results": []}, } - valid_modes: list = ["delete_sequence", "delete_calls"] - fuzzer_config: dict | None = None - - def __init__(self, config: dict, slither: Slither | None) -> None: - if config: - self.modifier_config = config - if slither: - self.slither = slither - if "corpusDir" in config: - self.corpus_path = config["corpusDir"] - if "fuzzer" in config: - self.fuzzer = config["fuzzer"] - if "fuzzerConfigPath" in config: - self.config_path = config["fuzzerConfigPath"] - self.fuzzer_config = self._fetch_fuzzer_config(self.fuzzer_fields[self.fuzzer]) - if "mode" in config: - self.mode = config["mode"] - if "targetContract" in config: - self.target = get_target_contract(self.slither, config["targetContract"]) - self.corpus_copy: list = [] - self.rules_list = [ + valid_modes: List[str] = ["delete_sequence", "delete_calls"] + fuzzer_config: Union[Dict, None] = None + + def __init__(self, config: Dict, slither: Union[Slither, None]) -> None: + self.modifier_config = config + self.slither = slither + self.corpus_path = config.get("corpusDir", "") + self.fuzzer = config.get("fuzzer", "") + self.config_path = config.get("fuzzerConfigPath", "") + self.mode = config.get("mode", "") + if self.slither: + self.target = ( + get_target_contract(self.slither, config["targetContract"]) + if "targetContract" in config + else None + ) + if self.config_path: + self.fuzzer_config = self._fetch_fuzzer_config(self.fuzzer_fields.get(self.fuzzer, {})) + self.corpus_copy: List[Dict] = [] + + self.rules_list: List[Callable[[Dict], bool]] = [ self._is_incorrect_delay, self._is_blacklisted_function, self._is_nonexistent_function, ] - self.modifications_list = [self._modify_invalid_caller] + self.modifications_list: List[Callable[[Dict], Dict]] = [self._modify_invalid_caller] - def modify_corpus(self) -> None: + def modify_corpus(self) -> Tuple[List, str]: """Modifies the current corpus and saves the new version""" - # 1. Open the corpus and parse all the files self.corpus_copy = self._copy_fuzzer_corpus( self.corpora_format[self.fuzzer], self.corpus_path ) - # 2. a) Copy current corpus somewhere in case something goes wrong? - self.save_corpus_to_history() - # 4. Apply the rules + corpus_hash = self.save_corpus_to_history() new_corpus = self._filter_corpus(self.mode, self.rules_list, self.modifications_list) - # 5. Save the new corpus - self._override_corpus_directory(self.corpus_path, new_corpus) + override_corpus_directory(self.corpus_path, new_corpus) + return new_corpus, corpus_hash def dry_run(self) -> None: """Prints the calls that would be modified, without modifying them""" self.corpus_copy = self._copy_fuzzer_corpus( - self.corpora_format[self.fuzzer.lower()], self.corpus_path + self.corpora_format[self.fuzzer], self.corpus_path ) _ = self._filter_corpus(self.mode, self.rules_list, self.modifications_list) - def save_corpus_to_history(self) -> None: + def save_corpus_to_history(self) -> str: """Saves the current corpus directory to the corpus history""" - # 1. Fetch current corpus corpus_to_save = self._copy_fuzzer_corpus( self.corpora_format[self.fuzzer.lower()], self.corpus_path ) - # 2. Check if .fuzz_utils folder already exists, create it if not - directory = self._create_or_fetch_hidden_directory() - # 3. Convert into a string + directory = create_or_fetch_hidden_directory() corpus_str = json.dumps(corpus_to_save, sort_keys=True) - # 4. Hash it corpus_hash = hashlib.sha256(corpus_str.encode()).hexdigest() - # 5. Check if hash already exists, skip operation and print a message if it does - history: dict history_path = os.path.join(directory, "history.json") - # TODO check if this can fail - if not os.path.exists(history_path): - with open(history_path, "w", encoding="utf-8") as f: - f.write("{}") - - with open(history_path, "r", encoding="utf-8") as f: - history = json.load(f) - if corpus_hash in history.keys(): + history = load_history(history_path) + if corpus_hash in history: print(f"The corpus is already saved to history with the hash {corpus_hash}") - return - # 6. Add to history + return corpus_hash + timestamp = datetime.datetime.now().isoformat() - comment = input("Enter a comment for this save: ") + comment = "test" # input("Enter a comment for this save: ") history[corpus_hash] = { "path": self.corpus_path, "timestamp": timestamp, "content": corpus_to_save, "comment": comment, } - # 7. Save history - # TODO check if this can fail - with open(history_path, "w", encoding="utf-8") as f: - json.dump(history, f) - + save_history(history_path, history) CryticPrint().print_information(f"Corpus saved to history with hash: {corpus_hash}") + return corpus_hash - def restore_corpus_from_history(self, corpus_hash: str) -> None: + def restore_corpus_from_history(self, corpus_hash: str) -> None: # pylint: disable=no-self-use """Overrides the current corpus directory with a historical version""" - directory = self._create_or_fetch_hidden_directory() + directory = create_or_fetch_hidden_directory() history_path = os.path.join(directory, "history.json") - fetched_corpus: dict - # TODO check if this can fail - with open(history_path, "r", encoding="utf-8") as f: - history = json.load(f) - - if corpus_hash in history.keys(): - fetched_corpus = history[corpus_hash] - self._override_corpus_directory(fetched_corpus["path"], fetched_corpus["content"]) - else: - handle_exit("The provided hash was not found.") + history = load_history(history_path) - def list_historic_corpora(self) -> None: + if corpus_hash in history: + fetched_corpus = history[corpus_hash] + override_corpus_directory(fetched_corpus["path"], fetched_corpus["content"]) + else: + handle_exit("The provided hash was not found.") + + def list_historic_corpora(self) -> None: # pylint: disable=no-self-use """Prints all the saved corpora""" - directory = self._create_or_fetch_hidden_directory() + directory = create_or_fetch_hidden_directory() history_path = os.path.join(directory, "history.json") + history = load_history(history_path) - with open(history_path, "r", encoding="utf-8") as f: - history = json.load(f) - - if history: - print("History:\n") - for key, value in history.items(): - print(f'{key}: {value["comment"]} (saved at {value["timestamp"]})') - else: - print("No historic corpora found.") - - def _override_corpus_directory( # pylint: disable=no-self-use - self, directory_path: str, contents_list: list - ) -> None: - for root, dirs, files in os.walk(directory_path): - for file in files: - file_path = os.path.join(root, file) - os.remove(file_path) - for directory in dirs: - dir_path = os.path.join(root, directory) - shutil.rmtree(dir_path) - - for item in contents_list: - file_path = item["path"] - file_content = item["content"] - - if file_content: - # Ensure the directory exists - directory = os.path.dirname(file_path) - os.makedirs(directory, exist_ok=True) - - # Write the file contents - # TODO fix the mypy errors later on - with open(file_path, "w", encoding="utf-8") as file: # type: ignore[assignment] - json.dump(file_content, file, indent=4) # type: ignore[arg-type] - - def _create_or_fetch_hidden_directory(self) -> str: # pylint: disable=no-self-use - current_directory = os.getcwd() - directory_name = os.path.join(current_directory, ".fuzz_utils") - os.makedirs(directory_name, exist_ok=True) - return directory_name + if history: + print("History:\n") + for key, value in history.items(): + print(f'{key}: {value["comment"]} (saved at {value["timestamp"]})') + else: + print("No historic corpora found.") def _is_incorrect_delay(self, call_object: dict) -> bool: if not self.fuzzer_config: return False - maxTimeDelay: int | None - maxBlockDelay: int | None - time_delay: int - block_delay: int - - if self.fuzzer == "echidna": - maxTimeDelay = ( - self.fuzzer_config["maxTimeDelay"] if "maxTimeDelay" in self.fuzzer_config else None - ) - maxBlockDelay = ( - self.fuzzer_config["maxBlockDelay"] - if "maxBlockDelay" in self.fuzzer_config - else None - ) - time_delay = int(call_object["delay"][0], 16) - block_delay = int(call_object["delay"][1], 16) - elif self.fuzzer == "medusa": - maxTimeDelay = ( - self.fuzzer_config["blockTimestampDelayMax"] - if "blockTimestampDelayMax" in self.fuzzer_config - else None - ) - maxBlockDelay = ( - self.fuzzer_config["blockNumberDelayMax"] - if "blockNumberDelayMax" in self.fuzzer_config - else None - ) - time_delay = call_object["blockTimestampDelay"] - block_delay = call_object["blockNumberDelay"] - else: - raise ValueError(f"Invalid fuzzer: {self.fuzzer}") - - if maxTimeDelay: - if time_delay > maxTimeDelay: - return True - if maxBlockDelay: - if block_delay > maxBlockDelay: - return True + max_time_delay = self.fuzzer_config.get("max_time_delay") + max_block_delay = self.fuzzer_config.get("max_block_delay") + time_delay = ( + int(call_object["delay"][0], 16) + if self.fuzzer == "echidna" + else call_object["blockTimestampDelay"] + ) + block_delay = ( + int(call_object["delay"][1], 16) + if self.fuzzer == "echidna" + else call_object["blockNumberDelay"] + ) + if (max_time_delay and time_delay > max_time_delay) or ( + max_block_delay and block_delay > max_block_delay + ): + return True return False def _is_nonexistent_function(self, call_object: dict) -> bool: if "filterFunctions" not in self.modifier_config: return False - function_name: str - # contracts = self.slither.contracts_derived - # functions = [y.name for x in contracts for y in x.functions_entry_points] - # TODO enable multiple targets later + function_name = ( + call_object["call"]["contents"][0] + if self.fuzzer == "echidna" + else call_object["call"]["dataAbiValues"]["methodSignature"].split("(")[0] + ) functions = [x.name for x in self.target.functions_entry_points] - if self.fuzzer == "echidna": - function_name = call_object["call"]["contents"][0] - elif self.fuzzer == "medusa": - function_name = call_object["call"]["dataAbiValues"]["methodSignature"].split("(")[0] - else: - raise ValueError(f"Invalid fuzzer: {self.fuzzer}") - print(f"fnName {function_name}, functions list {functions}") - if function_name not in functions: - return True - return False + return function_name not in functions def _is_blacklisted_function(self, call_object: dict) -> bool: - if not self.fuzzer_config: + if not self.fuzzer_config or self.fuzzer == "medusa": return False - function_name: str - blacklisted_functions: list | None - if self.fuzzer == "echidna": - function_name = call_object["call"]["contents"][0] - blacklisted_functions = ( - self.fuzzer_config["filterFunctions"] - if "filterFunctions" in self.fuzzer_config - else None - ) - else: - return False + function_name = call_object["call"]["contents"][0] + blacklisted_functions = self.fuzzer_config.get("filter_functions", []) - if blacklisted_functions: - if ( - function_name in blacklisted_functions - ): # pylint: disable=unsupported-membership-test - return True - return False + return function_name in blacklisted_functions if blacklisted_functions else False def _modify_invalid_caller(self, call_object: dict) -> dict: if "modifySenders" not in self.modifier_config: return call_object - caller: str - if self.fuzzer == "echidna": - caller = call_object["src"] - if caller in self.modifier_config["modifySenders"].keys(): - call_object["src"] = self.modifier_config["modifySenders"][caller] - elif self.fuzzer == "medusa": - caller = call_object["call"]["from"] - if caller in self.modifier_config["modifySenders"].keys(): - call_object["call"]["from"] = self.modifier_config["modifySenders"][caller] - else: - raise ValueError(f"Invalid fuzzer: {self.fuzzer}") - + caller = call_object["src"] if self.fuzzer == "echidna" else call_object["call"]["from"] + modified_caller = self.modifier_config["modifySenders"].get(caller) + if modified_caller: + if self.fuzzer == "echidna": + call_object["src"] = modified_caller + else: + call_object["call"]["from"] = modified_caller return call_object def _filter_corpus(self, mode: str, rules_list: list, modification_list: list) -> list: - new_corpus: list = [] - for idx, value in enumerate(self.corpus_copy): - # A list of files with call sequences in them + if mode not in self.valid_modes: + raise ValueError("Invalid mode") + + new_corpus: List[Dict] = [] + for value in self.corpus_copy: resulting_sequence = self._filter_call_sequence( mode, rules_list, modification_list, value["content"] ) - if resulting_sequence: - new_corpus.append( - {"path": self.corpus_copy[idx]["path"], "content": resulting_sequence} - ) + new_corpus.append({"path": value["path"], "content": resulting_sequence}) return new_corpus def _filter_call_sequence( self, mode: str, rules_list: list, modification_list: list, call_sequence: list ) -> list | None: - def should_skip(call: dict) -> bool: + def should_skip(call: Dict) -> bool: return any(rule_fn(call) for rule_fn in rules_list) - def replace_fields(call: dict) -> dict: + def replace_fields(call: Dict) -> Dict: for modify_fn in modification_list: call = modify_fn(call) return call - def replace_nonce(call: dict, nonces: dict) -> dict: + def replace_nonce(call: Dict, nonces: Dict) -> Dict: if self.fuzzer == "medusa": caller = call["call"]["from"] call["call"]["nonce"] = nonces[caller] @@ -316,19 +226,13 @@ def replace_nonce(call: dict, nonces: dict) -> dict: return call - nonces: dict = defaultdict(lambda: 0) + nonces: dict = defaultdict(int) if self.fuzzer == "medusa": for call in call_sequence: - if call["call"]["from"] in nonces: - continue - else: + if call["call"]["from"] not in nonces: nonces[call["call"]["from"]] = call["call"]["nonce"] - if mode not in self.valid_modes: - raise ValueError("Invalid mode") - - resulting_sequence: list = [] - + resulting_sequence: List = [] for call in call_sequence: if should_skip(call): if mode == "delete_sequence": @@ -365,34 +269,37 @@ def _fetch_directory_files(self, directory: str) -> list: if os.path.isfile(full_path): try: with open(full_path, "r", encoding="utf-8") as file: - if self.fuzzer.lower() == "echidna": + if self.fuzzer == "echidna": file_list.append( {"path": full_path, "content": yaml.safe_load(file)} ) else: file_list.append({"path": full_path, "content": json.load(file)}) - except Exception: # pylint: disable=broad-except - print(f"Fail on {full_path}") + except (yaml.YAMLError, json.JSONDecodeError, OSError) as e: + handle_exit(f"Failed to read file {full_path}: {e}") else: print(f"The provided corpus path does not exists: {directory}. Skipping.") return file_list - def _fetch_fuzzer_config(self, fields: list[str]) -> dict: - filtered_config: dict = {} - complete_config: dict + def _fetch_fuzzer_config(self, fields: Dict) -> Dict: + filtered_config = {} + complete_config = {} if os.path.isfile(self.config_path): try: with open(self.config_path, "r", encoding="utf-8") as file: - if self.fuzzer == "echidna": - complete_config = yaml.safe_load(file) - else: - complete_config = json.load(file)["fuzzing"] - except Exception: # pylint: disable=broad-except - handle_exit(f"Failed to find the fuzzer configuration file at {self.config_path}") + complete_config = ( + yaml.safe_load(file) + if self.fuzzer == "echidna" + else json.load(file)["fuzzing"] + ) + except (yaml.YAMLError, json.JSONDecodeError, OSError) as e: + handle_exit( + f"Failed to find the fuzzer configuration file at {self.config_path}: {e}" + ) for key, value in complete_config.items(): if key in fields: - filtered_config[key] = value + filtered_config[fields.get(key)] = value return filtered_config diff --git a/fuzz_utils/modify_corpus/utils.py b/fuzz_utils/modify_corpus/utils.py new file mode 100644 index 0000000..43132ae --- /dev/null +++ b/fuzz_utils/modify_corpus/utils.py @@ -0,0 +1,32 @@ +""" Utilities for managing corpus files""" +import os +import json +from typing import Dict +from fuzz_utils.utils.file_manager import clear_directory, write_directory_contents_json + + +def override_corpus_directory(directory_path: str, contents_list: list) -> None: + """Cleares directory contents and then writes new ones""" + clear_directory(directory_path) + write_directory_contents_json(contents_list) + + +def create_or_fetch_hidden_directory() -> str: + """Creates or fetched the .fuzz_utils directory""" + directory_name = os.path.join(os.getcwd(), ".fuzz_utils") + os.makedirs(directory_name, exist_ok=True) + return directory_name + + +def save_history(history_path: str, history: Dict) -> None: + """Save history to history.json""" + with open(history_path, "w", encoding="utf-8") as f: + json.dump(history, f) + + +def load_history(history_path: str) -> Dict: + """Load history.json from file""" + if not os.path.exists(history_path): + return {} + with open(history_path, "r", encoding="utf-8") as f: + return json.load(f) diff --git a/fuzz_utils/utils/file_manager.py b/fuzz_utils/utils/file_manager.py index 4eee052..1426fdf 100644 --- a/fuzz_utils/utils/file_manager.py +++ b/fuzz_utils/utils/file_manager.py @@ -1,5 +1,31 @@ """ Manages creation of files and directories """ import os +import json +import shutil +from typing import List, Dict + + +def clear_directory(directory_path: str) -> None: + """Deletes directory including all the contents""" + for root, dirs, files in os.walk(directory_path): + for file in files: + os.remove(os.path.join(root, file)) + for directory in dirs: + shutil.rmtree(os.path.join(root, directory)) + + +def write_directory_contents_json(contents_list: List[Dict]) -> None: + """ + Takes a list of dictionaries [{`path`: `path/to/file.ext`, `content`: ``}, ...] and + writes all the files, creating directories along the way. + """ + for item in contents_list: + file_path = item["path"] + file_content = item["content"] + if file_content: + os.makedirs(os.path.dirname(file_path), exist_ok=True) + with open(file_path, "w", encoding="utf-8") as file: + json.dump(file_content, file, indent=4) def check_and_create_dirs(base_path: str, dirnames: list[str]) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index f12215c..5e21b8f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -105,7 +105,7 @@ def value_transfer() -> TestGenerator: @pytest.fixture(scope="session") # type: ignore[misc] -def setup_foundry_temp_dir(tmp_path_factory: Any) -> None: +def setup_foundry_temp_dir(tmp_path_factory: Any) -> Any: """Sets up a temporary directory for the tests that contain all the necessary Foundry files""" # Create a temporary directory valid for the session temp_dir = tmp_path_factory.mktemp("foundry_session") @@ -153,18 +153,22 @@ def setup_foundry_temp_dir(tmp_path_factory: Any) -> None: ) os.chdir(temp_dir) + return temp_dir + + +def create_file(temp_dir: Any, file_name: str, out_str: str) -> None: + """Creates a remappings file""" + file_path = os.path.join(temp_dir, file_name) + with open(file_path, "w", encoding="utf-8") as outfile: + outfile.write(out_str) def create_remappings_file(temp_dir: Any, out_str: str | None) -> None: """Creates a remappings file""" - remappings = os.path.join(temp_dir, "remappings.txt") - with open(remappings, "w", encoding="utf-8") as outfile: - if out_str: - outfile.write(out_str) - else: - outfile.write( - "forge-std/=lib/forge-std/src/\nproperties/=lib/properties/contracts/\nsolmate/=lib/solmate/src/\nsrc/=src/" - ) + if not out_str: + out_str = "forge-std/=lib/forge-std/src/\nproperties/=lib/properties/contracts/\nsolmate/=lib/solmate/src/\nsrc/=src/" + + create_file(temp_dir, "remappings.txt", out_str) def copy_directory_contents(src_dir: str, dest_dir: str) -> None: diff --git a/tests/modifier_expected_results.py b/tests/modifier_expected_results.py new file mode 100644 index 0000000..bd233c5 --- /dev/null +++ b/tests/modifier_expected_results.py @@ -0,0 +1,1480 @@ +"""Contains expected data for corpus modifier regression tests""" +# pylint: disable=too-many-lines +expected_results = { + "echidna": { + "time_and_block_call": [ + { + "name": "-2210631667546636005.txt", + "content": [ + { + "call": { + "contents": ["setInt256", [{"contents": [256, "0"], "tag": "AbiInt"}]], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-1469195906620168853.txt", + "content": [ + { + "call": { + "contents": [ + "setUint256", + [{"contents": [256, "0"], "tag": "AbiUInt"}], + ], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_uint256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-585908729518561292.txt", + "content": [ + { + "call": {"contents": ["check_large_positive_int256", []], "tag": "SolCall"}, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000000ffff", + "0x000000000000000000000000000000000000000000000000000000000000b6d1", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000030000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + } + ], + }, + ], + "blacklisted_functions_call": [ + { + "name": "-585908729518561292.txt", + "content": [ + { + "call": {"contents": ["check_large_positive_int256", []], "tag": "SolCall"}, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000000ffff", + "0x000000000000000000000000000000000000000000000000000000000000b6d1", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000030000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_combined_input", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000066815", + "0x0000000000000000000000000000000000000000000000000000000000003c09", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-1234458407747697055.txt", + "content": [ + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": '"\\fEs8\\DLE3\\180\\FSQ,\\156G\\135\\223\\162\\131 cM\\204,4$\\227w\\168$Oz"', + "tag": "AbiString", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000005caa0", + "0x00000000000000000000000000000000000000000000000000000000000030cd", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": '"\\v\\252\\&1l\\134-{G}\\ETXH"', + "tag": "AbiString", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000087adc", + "0x00000000000000000000000000000000000000000000000000000000000013bd", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_address", []], "tag": "SolCall"}, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000001b2da", + "0x0000000000000000000000000000000000000000000000000000000000006b0c", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": { + "contents": [ + "setAddress", + [ + { + "contents": "0x0000000000000000000000000000000000020000", + "tag": "AbiAddress", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000004694f", + "0x000000000000000000000000000000000000000000000000000000000000bb03", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000048a23", + "0x0000000000000000000000000000000000000000000000000000000000008ffb", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000030000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-1469195906620168853.txt", + "content": [ + { + "call": { + "contents": [ + "setUint256", + [{"contents": [256, "0"], "tag": "AbiUInt"}], + ], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + } + ], + }, + { + "name": "-2210631667546636005.txt", + "content": [ + { + "call": { + "contents": ["setInt256", [{"contents": [256, "0"], "tag": "AbiInt"}]], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + ], + "invalid_functions_call": [ + { + "name": "-1234458407747697055.txt", + "content": [ + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": '"\\fEs8\\DLE3\\180\\FSQ,\\156G\\135\\223\\162\\131 cM\\204,4$\\227w\\168$Oz"', + "tag": "AbiString", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000005caa0", + "0x00000000000000000000000000000000000000000000000000000000000030cd", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": '"\\v\\252\\&1l\\134-{G}\\ETXH"', + "tag": "AbiString", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000087adc", + "0x00000000000000000000000000000000000000000000000000000000000013bd", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_address", []], "tag": "SolCall"}, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000001b2da", + "0x0000000000000000000000000000000000000000000000000000000000006b0c", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": { + "contents": [ + "setAddress", + [ + { + "contents": "0x0000000000000000000000000000000000020000", + "tag": "AbiAddress", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000004694f", + "0x000000000000000000000000000000000000000000000000000000000000bb03", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000048a23", + "0x0000000000000000000000000000000000000000000000000000000000008ffb", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000030000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-585908729518561292.txt", + "content": [ + { + "call": {"contents": ["check_large_positive_int256", []], "tag": "SolCall"}, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000000ffff", + "0x000000000000000000000000000000000000000000000000000000000000b6d1", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000030000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_combined_input", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000066815", + "0x0000000000000000000000000000000000000000000000000000000000003c09", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-2210631667546636005.txt", + "content": [ + { + "call": { + "contents": ["setInt256", [{"contents": [256, "0"], "tag": "AbiInt"}]], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-1469195906620168853.txt", + "content": [ + { + "call": { + "contents": [ + "setUint256", + [{"contents": [256, "0"], "tag": "AbiUInt"}], + ], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + } + ], + }, + ], + "modify_senders": [ + { + "name": "-585908729518561292.txt", + "content": [ + { + "call": {"contents": ["check_large_positive_int256", []], "tag": "SolCall"}, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000000ffff", + "0x000000000000000000000000000000000000000000000000000000000000b6d1", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000030000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_uint256", []], "tag": "SolCall"}, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000003340a", + "0x0000000000000000000000000000000000000000000000000000000000007fe1", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_combined_input", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000066815", + "0x0000000000000000000000000000000000000000000000000000000000003c09", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-1234458407747697055.txt", + "content": [ + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": '"\\fEs8\\DLE3\\180\\FSQ,\\156G\\135\\223\\162\\131 cM\\204,4$\\227w\\168$Oz"', + "tag": "AbiString", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000005caa0", + "0x00000000000000000000000000000000000000000000000000000000000030cd", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": '"\\v\\252\\&1l\\134-{G}\\ETXH"', + "tag": "AbiString", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000087adc", + "0x00000000000000000000000000000000000000000000000000000000000013bd", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_address", []], "tag": "SolCall"}, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000001b2da", + "0x0000000000000000000000000000000000000000000000000000000000006b0c", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": { + "contents": [ + "setAddress", + [ + { + "contents": "0x0000000000000000000000000000000000020000", + "tag": "AbiAddress", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000004694f", + "0x000000000000000000000000000000000000000000000000000000000000bb03", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000048a23", + "0x0000000000000000000000000000000000000000000000000000000000008ffb", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000030000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-1469195906620168853.txt", + "content": [ + { + "call": { + "contents": [ + "setUint256", + [{"contents": [256, "0"], "tag": "AbiUInt"}], + ], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_uint256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-2210631667546636005.txt", + "content": [ + { + "call": { + "contents": ["setInt256", [{"contents": [256, "0"], "tag": "AbiInt"}]], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + ], + }, + "medusa": { + "time_and_block_call": [ + { + "name": "1711369139037582000-cd75b0ae-6513-431b-81d3-d4bd19581b92.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + } + ], + }, + { + "name": "1711369139040471000-410883c1-42cd-4f17-b326-f13eb6bb65fe.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x68fe2494", + "dataAbiValues": { + "methodSignature": "check_bytes()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 1, + }, + ], + }, + { + "name": "1711369139036115000-9eaced3e-b7ae-4565-a0ff-c2ba04d31b13.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + } + ], + }, + { + "name": "1711369139036682000-39bea42d-9a8e-4e02-9652-76990e1d9043.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xe30081a0000000000000000000000000647be767dca5dea7641f4edac5754609bd0b5ee2", + "dataAbiValues": { + "methodSignature": "setAddress(address)", + "inputValues": ["0x647BE767Dca5DeA7641f4EDaC5754609bd0b5eE2"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 8, + "blockTimestampDelay": 10, + } + ], + }, + { + "name": "1711369141680378000-563145f4-2fb6-4929-9438-d9f229d9c2a4.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xd3b5d5d4", + "dataAbiValues": { + "methodSignature": "check_uint256()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 9, + "blockTimestampDelay": 80, + }, + ], + }, + { + "name": "1711369141706737000-34e6f348-02b4-4ff1-92ea-dac3b77823c5.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x68fe2494", + "dataAbiValues": { + "methodSignature": "check_bytes()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + }, + ], + }, + ], + "invalid_functions_call": [ + { + "name": "1711369139037582000-cd75b0ae-6513-431b-81d3-d4bd19581b92.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 27285, + "blockTimestampDelay": 570987, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xda359dc80000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001426b87d7a48ed7d5bc05982fac03649d6741c65f4000000000000000000000000", + "dataAbiValues": { + "methodSignature": "setBytes(bytes)", + "inputValues": ["26b87d7a48ed7d5bc05982fac03649d6741c65f4"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 32686, + "blockTimestampDelay": 517396, + }, + ], + }, + { + "name": "1711369139040471000-410883c1-42cd-4f17-b326-f13eb6bb65fe.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x1e26fd330000000000000000000000000000000000000000000000000000000000000001", + "dataAbiValues": { + "methodSignature": "setBool(bool)", + "inputValues": [True], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 1, + "blockTimestampDelay": 175946, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x68fe2494", + "dataAbiValues": { + "methodSignature": "check_bytes()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 1, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xe30081a00000000000000000000000003ce6b28f541002ec458324582e3ab2bfc2b77167", + "dataAbiValues": { + "methodSignature": "setAddress(address)", + "inputValues": ["0x3CE6B28F541002EC458324582e3Ab2bfC2b77167"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 20, + "blockTimestampDelay": 107219, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 2, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x73e69a18", + "dataAbiValues": { + "methodSignature": "check_address()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 40568, + "blockTimestampDelay": 353358, + }, + ], + }, + { + "name": "1711369139036115000-9eaced3e-b7ae-4565-a0ff-c2ba04d31b13.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + } + ], + }, + { + "name": "1711369139036682000-39bea42d-9a8e-4e02-9652-76990e1d9043.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xe30081a0000000000000000000000000647be767dca5dea7641f4edac5754609bd0b5ee2", + "dataAbiValues": { + "methodSignature": "setAddress(address)", + "inputValues": ["0x647BE767Dca5DeA7641f4EDaC5754609bd0b5eE2"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 8, + "blockTimestampDelay": 10, + } + ], + }, + { + "name": "1711369141680378000-563145f4-2fb6-4929-9438-d9f229d9c2a4.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xd2282dc536ac105a7845b109c000df159eb4427dedbcdedf9379aff52bf13cedc54c1928", + "dataAbiValues": { + "methodSignature": "setUint256(uint256)", + "inputValues": ["19050454979278060990829239674756109780367087"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 25421, + "blockTimestampDelay": 401424, + } + ], + }, + { + "name": "1711369141706737000-34e6f348-02b4-4ff1-92ea-dac3b77823c5.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xda359dc800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000033a2e6714916369cbf3a3e32dc69db8f561b7235556e8cf0948ad0e1ad5dab0aa92d9b3da693f7219ea302629a45abdeb1fe003c00000000000000000000000000", + "dataAbiValues": { + "methodSignature": "setBytes(bytes)", + "inputValues": [""], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 38145, + "blockTimestampDelay": 360617, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x68fe2494", + "dataAbiValues": { + "methodSignature": "check_bytes()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + }, + ], + }, + ], + "modify_senders": [ + { + "name": "1711369139037582000-cd75b0ae-6513-431b-81d3-d4bd19581b92.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 27285, + "blockTimestampDelay": 570987, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 2, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xda359dc80000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001426b87d7a48ed7d5bc05982fac03649d6741c65f4000000000000000000000000", + "dataAbiValues": { + "methodSignature": "setBytes(bytes)", + "inputValues": ["26b87d7a48ed7d5bc05982fac03649d6741c65f4"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 32686, + "blockTimestampDelay": 517396, + }, + ], + }, + { + "name": "1711369139040471000-410883c1-42cd-4f17-b326-f13eb6bb65fe.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x1e26fd330000000000000000000000000000000000000000000000000000000000000001", + "dataAbiValues": { + "methodSignature": "setBool(bool)", + "inputValues": [True], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 1, + "blockTimestampDelay": 175946, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x68fe2494", + "dataAbiValues": { + "methodSignature": "check_bytes()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 1, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 2, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xe30081a00000000000000000000000003ce6b28f541002ec458324582e3ab2bfc2b77167", + "dataAbiValues": { + "methodSignature": "setAddress(address)", + "inputValues": ["0x3CE6B28F541002EC458324582e3Ab2bfC2b77167"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 20, + "blockTimestampDelay": 107219, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 2, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x73e69a18", + "dataAbiValues": { + "methodSignature": "check_address()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 40568, + "blockTimestampDelay": 353358, + }, + ], + }, + { + "name": "1711369139036115000-9eaced3e-b7ae-4565-a0ff-c2ba04d31b13.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + } + ], + }, + { + "name": "1711369139036682000-39bea42d-9a8e-4e02-9652-76990e1d9043.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xe30081a0000000000000000000000000647be767dca5dea7641f4edac5754609bd0b5ee2", + "dataAbiValues": { + "methodSignature": "setAddress(address)", + "inputValues": ["0x647BE767Dca5DeA7641f4EDaC5754609bd0b5eE2"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 8, + "blockTimestampDelay": 10, + } + ], + }, + { + "name": "1711369141680378000-563145f4-2fb6-4929-9438-d9f229d9c2a4.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xd2282dc536ac105a7845b109c000df159eb4427dedbcdedf9379aff52bf13cedc54c1928", + "dataAbiValues": { + "methodSignature": "setUint256(uint256)", + "inputValues": ["19050454979278060990829239674756109780367087"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 25421, + "blockTimestampDelay": 401424, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xd3b5d5d4", + "dataAbiValues": { + "methodSignature": "check_uint256()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 9, + "blockTimestampDelay": 80, + }, + ], + }, + { + "name": "1711369141706737000-34e6f348-02b4-4ff1-92ea-dac3b77823c5.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xda359dc800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000033a2e6714916369cbf3a3e32dc69db8f561b7235556e8cf0948ad0e1ad5dab0aa92d9b3da693f7219ea302629a45abdeb1fe003c00000000000000000000000000", + "dataAbiValues": { + "methodSignature": "setBytes(bytes)", + "inputValues": [""], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 38145, + "blockTimestampDelay": 360617, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x68fe2494", + "dataAbiValues": { + "methodSignature": "check_bytes()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + }, + ], + }, + ], + }, +} diff --git a/tests/test_corpus_modifier.py b/tests/test_corpus_modifier.py new file mode 100644 index 0000000..9e7638d --- /dev/null +++ b/tests/test_corpus_modifier.py @@ -0,0 +1,176 @@ +""" Tests for generating compilable test files from an Echidna corpus""" +import copy +from pathlib import Path +from pytest import TempPathFactory +from slither import Slither + +from fuzz_utils.modify_corpus.CorpusModifier import CorpusModifier +from .conftest import create_file +from .modifier_expected_results import expected_results + +TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" +default_config = { + "compilationPath": ".", + "corpusDir": "", + "fuzzer": "", + "fuzzerConfigPath": "", + "targetContract": "BasicTypes", + "mode": "", + "modifySenders": {}, + "filterFunction": False, + "dryRun": False, +} + + +def compare_corpus_with_expected(expected_corpus: list, new_corpus: list) -> None: + """Compares two corpora, failing if they're not the same""" + assert len(expected_corpus) == len(new_corpus) + expected_corpus_sorted = sorted(expected_corpus, key=lambda d: d["name"]) + new_corpus_sorted = sorted(new_corpus, key=lambda d: d["path"].split("/")[-1]) + for idx, item in enumerate(new_corpus_sorted): + name = item["path"].split("/")[-1] + assert expected_corpus_sorted[idx]["name"] == name + assert expected_corpus_sorted[idx]["content"] == item["content"] + + +def test_echidna_delay_delete_calls( + setup_foundry_temp_dir: TempPathFactory, +) -> None: + """Test that correct calls are deleted when filtering by time and block delay""" + config = copy.deepcopy(default_config) + config["mode"] = "delete_calls" + config["corpusDir"] = "echidna-corpora/corpus-basic-modifier" + config["fuzzerConfigPath"] = "echidna.yaml" + config["fuzzer"] = "echidna" + + create_file(setup_foundry_temp_dir, "echidna.yaml", "maxTimeDelay: 65535\nmaxBlockDelay: 46801") + + modifier = CorpusModifier(config, None) + new_corpus, corpus_hash = modifier.modify_corpus() + # print("new corpus", new_corpus) + compare_corpus_with_expected(expected_results["echidna"]["time_and_block_call"], new_corpus) + modifier.restore_corpus_from_history(corpus_hash) + + +def test_echidna_blacklist_delete_calls( + setup_foundry_temp_dir: TempPathFactory, +) -> None: + """Test that correct calls are deleted when filtering blacklisted functions""" + config = copy.deepcopy(default_config) + config["mode"] = "delete_calls" + config["corpusDir"] = "echidna-corpora/corpus-basic-modifier" + config["fuzzerConfigPath"] = "echidna.yaml" + config["fuzzer"] = "echidna" + + create_file(setup_foundry_temp_dir, "echidna.yaml", 'filterFunctions: ["check_uint256"]') + + modifier = CorpusModifier(config, None) + new_corpus, corpus_hash = modifier.modify_corpus() + + compare_corpus_with_expected( + expected_results["echidna"]["blacklisted_functions_call"], new_corpus + ) + modifier.restore_corpus_from_history(corpus_hash) + + +def test_echidna_function_filter_delete_calls( + setup_foundry_temp_dir: TempPathFactory, # pylint: disable=unused-argument +) -> None: + """Test that correct calls are deleted when filtering non-existent functions""" + config = copy.deepcopy(default_config) + config["corpusDir"] = "echidna-corpora/corpus-basic-modifier" + config["targetContract"] = "BasicTypesNoCheckUint256" + config["filterFunctions"] = True + config["mode"] = "delete_calls" + config["fuzzer"] = "echidna" + + slither = Slither(config["compilationPath"]) + modifier = CorpusModifier(config, slither) + + new_corpus, corpus_hash = modifier.modify_corpus() + # print("new corpus", new_corpus) + compare_corpus_with_expected(expected_results["echidna"]["invalid_functions_call"], new_corpus) + modifier.restore_corpus_from_history(corpus_hash) + + +def test_echidna_modify_senders( + setup_foundry_temp_dir: TempPathFactory, # pylint: disable=unused-argument +) -> None: + """Test that correct calls are modified when modifying senders""" + config = copy.deepcopy(default_config) + config["corpusDir"] = "echidna-corpora/corpus-basic-modifier" + config["mode"] = "delete_calls" + config["fuzzer"] = "echidna" + config["modifySenders"] = { + "0x0000000000000000000000000000000000010000": "0x0000000000000000000000000000000000020000" + } + + modifier = CorpusModifier(config, None) + new_corpus, corpus_hash = modifier.modify_corpus() + + compare_corpus_with_expected(expected_results["echidna"]["modify_senders"], new_corpus) + modifier.restore_corpus_from_history(corpus_hash) + + +def test_medusa_delay_delete_calls( + setup_foundry_temp_dir: TempPathFactory, +) -> None: + """Test that correct calls are deleted when filtering by time and block delay""" + config = copy.deepcopy(default_config) + config["mode"] = "delete_calls" + config["corpusDir"] = "medusa-corpora/corpus-basic-modifier" + config["fuzzerConfigPath"] = "medusa.json" + config["fuzzer"] = "medusa" + + create_file( + setup_foundry_temp_dir, + "medusa.json", + '{"fuzzing": {"blockNumberDelayMax": 100,\n"blockTimestampDelayMax": 100}}', + ) + + modifier = CorpusModifier(config, None) + new_corpus, corpus_hash = modifier.modify_corpus() + print("new corpus", new_corpus) + compare_corpus_with_expected(expected_results["medusa"]["time_and_block_call"], new_corpus) + # set1 = set(new_corpus) + # set2 = set(expected_results["medusa"]["time_and_block_call"]) + # print("Diff", set1 ^ set2) + modifier.restore_corpus_from_history(corpus_hash) + + +def test_medusa_function_filter_delete_calls( + setup_foundry_temp_dir: TempPathFactory, # pylint: disable=unused-argument +) -> None: + """Test that correct calls are deleted when filtering non-existent functions""" + config = copy.deepcopy(default_config) + config["corpusDir"] = "medusa-corpora/corpus-basic-modifier" + config["targetContract"] = "BasicTypesNoCheckUint256" + config["filterFunctions"] = True + config["mode"] = "delete_calls" + config["fuzzer"] = "medusa" + + slither = Slither(config["compilationPath"]) + modifier = CorpusModifier(config, slither) + + new_corpus, corpus_hash = modifier.modify_corpus() + compare_corpus_with_expected(expected_results["medusa"]["invalid_functions_call"], new_corpus) + modifier.restore_corpus_from_history(corpus_hash) + + +def test_medusa_modify_senders( + setup_foundry_temp_dir: TempPathFactory, # pylint: disable=unused-argument +) -> None: + """Test that correct calls are modified when modifying senders""" + config = copy.deepcopy(default_config) + config["corpusDir"] = "medusa-corpora/corpus-basic-modifier" + config["mode"] = "delete_calls" + config["fuzzer"] = "medusa" + config["modifySenders"] = { + "0x0000000000000000000000000000000000010000": "0x0000000000000000000000000000000000020000" + } + + modifier = CorpusModifier(config, None) + new_corpus, corpus_hash = modifier.modify_corpus() + + compare_corpus_with_expected(expected_results["medusa"]["modify_senders"], new_corpus) + modifier.restore_corpus_from_history(corpus_hash) diff --git a/tests/test_data/echidna-corpora/corpus-basic-modifier/coverage/-1234458407747697055.txt b/tests/test_data/echidna-corpora/corpus-basic-modifier/coverage/-1234458407747697055.txt new file mode 100644 index 0000000..4368d24 --- /dev/null +++ b/tests/test_data/echidna-corpora/corpus-basic-modifier/coverage/-1234458407747697055.txt @@ -0,0 +1,107 @@ +[ + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": "\"\\fEs8\\DLE3\\180\\FSQ,\\156G\\135\\223\\162\\131 cM\\204,4$\\227w\\168$Oz\"", + "tag": "AbiString" + } + ] + ], + "tag": "SolCall" + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000005caa0", + "0x00000000000000000000000000000000000000000000000000000000000030cd" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": "\"\\v\\252\\&1l\\134-{G}\\ETXH\"", + "tag": "AbiString" + } + ] + ], + "tag": "SolCall" + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000087adc", + "0x00000000000000000000000000000000000000000000000000000000000013bd" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "call": { + "contents": [ + "check_address", + [] + ], + "tag": "SolCall" + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000001b2da", + "0x0000000000000000000000000000000000000000000000000000000000006b0c" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "call": { + "contents": [ + "setAddress", + [ + { + "contents": "0x0000000000000000000000000000000000020000", + "tag": "AbiAddress" + } + ] + ], + "tag": "SolCall" + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000004694f", + "0x000000000000000000000000000000000000000000000000000000000000bb03" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "call": { + "contents": [ + "check_int256", + [] + ], + "tag": "SolCall" + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000048a23", + "0x0000000000000000000000000000000000000000000000000000000000008ffb" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000030000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + } +] \ No newline at end of file diff --git a/tests/test_data/echidna-corpora/corpus-basic-modifier/coverage/-585908729518561292.txt b/tests/test_data/echidna-corpora/corpus-basic-modifier/coverage/-585908729518561292.txt new file mode 100644 index 0000000..8334e8b --- /dev/null +++ b/tests/test_data/echidna-corpora/corpus-basic-modifier/coverage/-585908729518561292.txt @@ -0,0 +1,56 @@ +[ + { + "call": { + "contents": [ + "check_large_positive_int256", + [] + ], + "tag": "SolCall" + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000000ffff", + "0x000000000000000000000000000000000000000000000000000000000000b6d1" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000030000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "call": { + "contents": [ + "check_uint256", + [] + ], + "tag": "SolCall" + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000003340a", + "0x0000000000000000000000000000000000000000000000000000000000007fe1" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "call": { + "contents": [ + "check_combined_input", + [] + ], + "tag": "SolCall" + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000066815", + "0x0000000000000000000000000000000000000000000000000000000000003c09" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + } +] \ No newline at end of file diff --git a/tests/test_data/echidna-corpora/corpus-basic-modifier/reproducers/-1469195906620168853.txt b/tests/test_data/echidna-corpora/corpus-basic-modifier/reproducers/-1469195906620168853.txt new file mode 100644 index 0000000..84a474b --- /dev/null +++ b/tests/test_data/echidna-corpora/corpus-basic-modifier/reproducers/-1469195906620168853.txt @@ -0,0 +1,46 @@ +[ + { + "call": { + "contents": [ + "setUint256", + [ + { + "contents": [ + 256, + "0" + ], + "tag": "AbiUInt" + } + ] + ], + "tag": "SolCall" + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "call": { + "contents": [ + "check_uint256", + [] + ], + "tag": "SolCall" + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + } +] \ No newline at end of file diff --git a/tests/test_data/echidna-corpora/corpus-basic-modifier/reproducers/-2210631667546636005.txt b/tests/test_data/echidna-corpora/corpus-basic-modifier/reproducers/-2210631667546636005.txt new file mode 100644 index 0000000..8db21b2 --- /dev/null +++ b/tests/test_data/echidna-corpora/corpus-basic-modifier/reproducers/-2210631667546636005.txt @@ -0,0 +1,46 @@ +[ + { + "call": { + "contents": [ + "setInt256", + [ + { + "contents": [ + 256, + "0" + ], + "tag": "AbiInt" + } + ] + ], + "tag": "SolCall" + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "call": { + "contents": [ + "check_int256", + [] + ], + "tag": "SolCall" + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000" + } +] \ No newline at end of file diff --git a/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/immutable/1711369139037582000-cd75b0ae-6513-431b-81d3-d4bd19581b92.json b/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/immutable/1711369139037582000-cd75b0ae-6513-431b-81d3-d4bd19581b92.json new file mode 100755 index 0000000..19b4d45 --- /dev/null +++ b/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/immutable/1711369139037582000-cd75b0ae-6513-431b-81d3-d4bd19581b92.json @@ -0,0 +1,67 @@ +[ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0 + }, + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 27285, + "blockTimestampDelay": 570987 + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xda359dc80000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001426b87d7a48ed7d5bc05982fac03649d6741c65f4000000000000000000000000", + "dataAbiValues": { + "methodSignature": "setBytes(bytes)", + "inputValues": [ + "26b87d7a48ed7d5bc05982fac03649d6741c65f4" + ] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 32686, + "blockTimestampDelay": 517396 + } +] \ No newline at end of file diff --git a/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/immutable/1711369139040471000-410883c1-42cd-4f17-b326-f13eb6bb65fe.json b/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/immutable/1711369139040471000-410883c1-42cd-4f17-b326-f13eb6bb65fe.json new file mode 100755 index 0000000..270a8fd --- /dev/null +++ b/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/immutable/1711369139040471000-410883c1-42cd-4f17-b326-f13eb6bb65fe.json @@ -0,0 +1,111 @@ +[ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0 + }, + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x1e26fd330000000000000000000000000000000000000000000000000000000000000001", + "dataAbiValues": { + "methodSignature": "setBool(bool)", + "inputValues": [ + true + ] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 1, + "blockTimestampDelay": 175946 + }, + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x68fe2494", + "dataAbiValues": { + "methodSignature": "check_bytes()", + "inputValues": [] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 1 + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xe30081a00000000000000000000000003ce6b28f541002ec458324582e3ab2bfc2b77167", + "dataAbiValues": { + "methodSignature": "setAddress(address)", + "inputValues": [ + "0x3CE6B28F541002EC458324582e3Ab2bfC2b77167" + ] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 20, + "blockTimestampDelay": 107219 + }, + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 2, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x73e69a18", + "dataAbiValues": { + "methodSignature": "check_address()", + "inputValues": [] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 40568, + "blockTimestampDelay": 353358 + } +] \ No newline at end of file diff --git a/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/mutable/1711369139036115000-9eaced3e-b7ae-4565-a0ff-c2ba04d31b13.json b/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/mutable/1711369139036115000-9eaced3e-b7ae-4565-a0ff-c2ba04d31b13.json new file mode 100755 index 0000000..a375860 --- /dev/null +++ b/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/mutable/1711369139036115000-9eaced3e-b7ae-4565-a0ff-c2ba04d31b13.json @@ -0,0 +1,23 @@ +[ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0 + } +] \ No newline at end of file diff --git a/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/mutable/1711369139036682000-39bea42d-9a8e-4e02-9652-76990e1d9043.json b/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/mutable/1711369139036682000-39bea42d-9a8e-4e02-9652-76990e1d9043.json new file mode 100755 index 0000000..83fc099 --- /dev/null +++ b/tests/test_data/medusa-corpora/corpus-basic-modifier/call_sequences/mutable/1711369139036682000-39bea42d-9a8e-4e02-9652-76990e1d9043.json @@ -0,0 +1,25 @@ +[ + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xe30081a0000000000000000000000000647be767dca5dea7641f4edac5754609bd0b5ee2", + "dataAbiValues": { + "methodSignature": "setAddress(address)", + "inputValues": [ + "0x647BE767Dca5DeA7641f4EDaC5754609bd0b5eE2" + ] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 8, + "blockTimestampDelay": 10 + } +] \ No newline at end of file diff --git a/tests/test_data/medusa-corpora/corpus-basic-modifier/test_results/1711369141680378000-563145f4-2fb6-4929-9438-d9f229d9c2a4.json b/tests/test_data/medusa-corpora/corpus-basic-modifier/test_results/1711369141680378000-563145f4-2fb6-4929-9438-d9f229d9c2a4.json new file mode 100755 index 0000000..37a43fe --- /dev/null +++ b/tests/test_data/medusa-corpora/corpus-basic-modifier/test_results/1711369141680378000-563145f4-2fb6-4929-9438-d9f229d9c2a4.json @@ -0,0 +1,46 @@ +[ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xd2282dc536ac105a7845b109c000df159eb4427dedbcdedf9379aff52bf13cedc54c1928", + "dataAbiValues": { + "methodSignature": "setUint256(uint256)", + "inputValues": [ + "19050454979278060990829239674756109780367087" + ] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 25421, + "blockTimestampDelay": 401424 + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xd3b5d5d4", + "dataAbiValues": { + "methodSignature": "check_uint256()", + "inputValues": [] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 9, + "blockTimestampDelay": 80 + } +] \ No newline at end of file diff --git a/tests/test_data/medusa-corpora/corpus-basic-modifier/test_results/1711369141706737000-34e6f348-02b4-4ff1-92ea-dac3b77823c5.json b/tests/test_data/medusa-corpora/corpus-basic-modifier/test_results/1711369141706737000-34e6f348-02b4-4ff1-92ea-dac3b77823c5.json new file mode 100755 index 0000000..a7b31ce --- /dev/null +++ b/tests/test_data/medusa-corpora/corpus-basic-modifier/test_results/1711369141706737000-34e6f348-02b4-4ff1-92ea-dac3b77823c5.json @@ -0,0 +1,46 @@ +[ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xda359dc800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000033a2e6714916369cbf3a3e32dc69db8f561b7235556e8cf0948ad0e1ad5dab0aa92d9b3da693f7219ea302629a45abdeb1fe003c00000000000000000000000000", + "dataAbiValues": { + "methodSignature": "setBytes(bytes)", + "inputValues": [ + "" + ] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 38145, + "blockTimestampDelay": 360617 + }, + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x68fe2494", + "dataAbiValues": { + "methodSignature": "check_bytes()", + "inputValues": [] + }, + "AccessList": null, + "SkipAccountChecks": false + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0 + } +] \ No newline at end of file diff --git a/tests/test_data/src/BasicTypesNoCheckUint256.sol b/tests/test_data/src/BasicTypesNoCheckUint256.sol new file mode 100644 index 0000000..b363c51 --- /dev/null +++ b/tests/test_data/src/BasicTypesNoCheckUint256.sol @@ -0,0 +1,162 @@ +pragma solidity ^0.8.0; + +// Ran from test directory: echidna . --contract BasicTypes --test-mode assertion --test-limit 100000 --corpus-dir echidna-corpora/corpus-basic --crytic-args "--foundry-ignore-compile" +// Ran from test directory: fuzz-utils ./src/BasicTypes.sol --corpus-dir echidna-corpora/corpus-basic --contract "BasicTypes" --test-directory "./test/" --inheritance-path "../src/" --fuzzer echidna +contract BasicTypesNoCheckUint256 { + + // ------------------------------ + // -- bool -- + // ------------------------------ + bool first; + + function setBool(bool set) public { + first = set; + } + + function check_bool() public { + if (first) { + assert(false); + } + } + + // ------------------------------ + // -- uint -- + // ------------------------------ + uint256 _uint256 = 3; + + function setUint256(uint256 input) public { + _uint256 = input; + } + + function check_large_uint256() public { + if (_uint256 == type(uint256).max) { + assert(false); + } + } + + // ------------------------------ + // -- int -- + // ------------------------------ + int256 _int256 = 3; + + function setInt256(int256 input) public { + _int256 = input; + } + + function check_int256() public { + if (_int256 % 2 == 0) { + assert(false); + } + } + + function check_large_positive_int256() public { + if (_int256 == type(int256).max) { + assert(false); + } + } + + function check_large_negative_int256() public { + if (_int256 == type(int256).min) { + assert(false); + } + } + // ------------------------------ + // -- address -- + // ------------------------------ + address providedAddress; + + function setAddress(address input) public { + require(input != address(0)); + providedAddress = input; + } + + function check_address() public { + if (providedAddress != address(0)) { + assert(false); + } + } + // ------------------------------ + // -- string -- + // ------------------------------ + string providedString; + + function setString(string memory input) public { + require(bytes(input).length > 20); + providedString = input; + } + + function check_string() public { + if (bytes(providedString).length > 20) { + assert(false); + } + } + + function check_specific_string(string memory provided) public { + require(bytes(provided).length > 0); + if (keccak256(bytes(provided)) == keccak256(bytes("TEST_STRING"))) { + assert(false); + } + } + + // ------------------------------ + // -- bytes -- + // ------------------------------ + bytes providedBytes; + bytes32 providedBytes32; + + // TODO bytes32, etc. + function setBytes(bytes memory input) public { + require(input.length > 20); + providedBytes = input; + } + + function check_bytes() public { + if (providedBytes.length > 20) { + assert(false); + } + } + + function check_specific_bytes(bytes memory provided) public { + require(bytes(provided).length > 0); + if (keccak256(bytes(provided)) == keccak256(bytes(hex"1e233952a8b4"))) { + assert(false); + } + } + + /// @notice bytes32 has decoding issues right now + /* function setBytes32(bytes32 input) public { + require(input != bytes32(0)); + providedBytes32 = input; + } + + function check_bytes32() public { + if (providedBytes32 != bytes32(0)) { + assert(false); + } + } */ + + // ------------------------------ + // -- combination -- + // ------------------------------ + bool combBool; + uint256 combUint256; + int256 combInt256; + address combAddress; + string combString; + bytes combBytes; + + function setCombination(bool bool_input, uint256 unsigned_input, int256 signed_input, address address_input, string memory str_input, bytes memory bytes_input) public { + combBool = bool_input; + combUint256 = unsigned_input; + combInt256 = signed_input; + combAddress = address_input; + combString = str_input; + combBytes = bytes_input; + } + + function check_combined_input() public { + if (combBool && combUint256 > 0 && combInt256 < 0 && combAddress != address(0) && bytes(combString).length > 0 && combBytes.length > 0) { + assert(false); + } + } +} \ No newline at end of file From 0d513ea0afa4365ea4017f4d9c6854fb4c82294f Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Thu, 13 Jun 2024 18:11:32 +0200 Subject: [PATCH 6/8] unit tests of delete_sequence --- tests/modifier_expected_results.py | 762 +++++++++++++++++++++++++++-- tests/test_corpus_modifier.py | 126 ++++- 2 files changed, 829 insertions(+), 59 deletions(-) diff --git a/tests/modifier_expected_results.py b/tests/modifier_expected_results.py index bd233c5..dbf6579 100644 --- a/tests/modifier_expected_results.py +++ b/tests/modifier_expected_results.py @@ -456,6 +456,339 @@ ], }, ], + "time_and_block_seq": [ + { + "name": "-2210631667546636005.txt", + "content": [ + { + "call": { + "contents": ["setInt256", [{"contents": [256, "0"], "tag": "AbiInt"}]], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-1469195906620168853.txt", + "content": [ + { + "call": { + "contents": [ + "setUint256", + [{"contents": [256, "0"], "tag": "AbiUInt"}], + ], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_uint256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + ], + "blacklisted_functions_seq": [ + { + "name": "-1234458407747697055.txt", + "content": [ + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": '"\\fEs8\\DLE3\\180\\FSQ,\\156G\\135\\223\\162\\131 cM\\204,4$\\227w\\168$Oz"', + "tag": "AbiString", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000005caa0", + "0x00000000000000000000000000000000000000000000000000000000000030cd", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": '"\\v\\252\\&1l\\134-{G}\\ETXH"', + "tag": "AbiString", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000087adc", + "0x00000000000000000000000000000000000000000000000000000000000013bd", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_address", []], "tag": "SolCall"}, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000001b2da", + "0x0000000000000000000000000000000000000000000000000000000000006b0c", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": { + "contents": [ + "setAddress", + [ + { + "contents": "0x0000000000000000000000000000000000020000", + "tag": "AbiAddress", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000004694f", + "0x000000000000000000000000000000000000000000000000000000000000bb03", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000048a23", + "0x0000000000000000000000000000000000000000000000000000000000008ffb", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000030000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-2210631667546636005.txt", + "content": [ + { + "call": { + "contents": ["setInt256", [{"contents": [256, "0"], "tag": "AbiInt"}]], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + ], + "invalid_functions_seq": [ + { + "name": "-1234458407747697055.txt", + "content": [ + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": '"\\fEs8\\DLE3\\180\\FSQ,\\156G\\135\\223\\162\\131 cM\\204,4$\\227w\\168$Oz"', + "tag": "AbiString", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000005caa0", + "0x00000000000000000000000000000000000000000000000000000000000030cd", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": { + "contents": [ + "check_specific_string", + [ + { + "contents": '"\\v\\252\\&1l\\134-{G}\\ETXH"', + "tag": "AbiString", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000087adc", + "0x00000000000000000000000000000000000000000000000000000000000013bd", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_address", []], "tag": "SolCall"}, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000001b2da", + "0x0000000000000000000000000000000000000000000000000000000000006b0c", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": { + "contents": [ + "setAddress", + [ + { + "contents": "0x0000000000000000000000000000000000020000", + "tag": "AbiAddress", + } + ], + ], + "tag": "SolCall", + }, + "delay": [ + "0x000000000000000000000000000000000000000000000000000000000004694f", + "0x000000000000000000000000000000000000000000000000000000000000bb03", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000048a23", + "0x0000000000000000000000000000000000000000000000000000000000008ffb", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000030000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + { + "name": "-2210631667546636005.txt", + "content": [ + { + "call": { + "contents": ["setInt256", [{"contents": [256, "0"], "tag": "AbiInt"}]], + "tag": "SolCall", + }, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + { + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000010000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + ], "modify_senders": [ { "name": "-585908729518561292.txt", @@ -650,23 +983,202 @@ "value": "0x0000000000000000000000000000000000000000000000000000000000000000", }, { - "call": {"contents": ["check_int256", []], "tag": "SolCall"}, - "delay": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - ], - "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", - "gas": 12500000, - "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", - "src": "0x0000000000000000000000000000000000020000", - "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + "call": {"contents": ["check_int256", []], "tag": "SolCall"}, + "delay": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + "dst": "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", + "gas": 12500000, + "gasprice": "0x0000000000000000000000000000000000000000000000000000000000000000", + "src": "0x0000000000000000000000000000000000020000", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + ], + }, + ], + }, + "medusa": { + "time_and_block_call": [ + { + "name": "1711369139037582000-cd75b0ae-6513-431b-81d3-d4bd19581b92.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + } + ], + }, + { + "name": "1711369139040471000-410883c1-42cd-4f17-b326-f13eb6bb65fe.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x68fe2494", + "dataAbiValues": { + "methodSignature": "check_bytes()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 1, + }, + ], + }, + { + "name": "1711369139036115000-9eaced3e-b7ae-4565-a0ff-c2ba04d31b13.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + } + ], + }, + { + "name": "1711369139036682000-39bea42d-9a8e-4e02-9652-76990e1d9043.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xe30081a0000000000000000000000000647be767dca5dea7641f4edac5754609bd0b5ee2", + "dataAbiValues": { + "methodSignature": "setAddress(address)", + "inputValues": ["0x647BE767Dca5DeA7641f4EDaC5754609bd0b5eE2"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 8, + "blockTimestampDelay": 10, + } + ], + }, + { + "name": "1711369141680378000-563145f4-2fb6-4929-9438-d9f229d9c2a4.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xd3b5d5d4", + "dataAbiValues": { + "methodSignature": "check_uint256()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 9, + "blockTimestampDelay": 80, + }, + ], + }, + { + "name": "1711369141706737000-34e6f348-02b4-4ff1-92ea-dac3b77823c5.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x68fe2494", + "dataAbiValues": { + "methodSignature": "check_bytes()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, }, ], }, ], - }, - "medusa": { - "time_and_block_call": [ + "invalid_functions_call": [ { "name": "1711369139037582000-cd75b0ae-6513-431b-81d3-d4bd19581b92.json", "content": [ @@ -690,7 +1202,49 @@ }, "blockNumberDelay": 0, "blockTimestampDelay": 0, - } + }, + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 27285, + "blockTimestampDelay": 570987, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xda359dc80000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001426b87d7a48ed7d5bc05982fac03649d6741c65f4000000000000000000000000", + "dataAbiValues": { + "methodSignature": "setBytes(bytes)", + "inputValues": ["26b87d7a48ed7d5bc05982fac03649d6741c65f4"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 32686, + "blockTimestampDelay": 517396, + }, ], }, { @@ -717,6 +1271,27 @@ "blockNumberDelay": 0, "blockTimestampDelay": 0, }, + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x1e26fd330000000000000000000000000000000000000000000000000000000000000001", + "dataAbiValues": { + "methodSignature": "setBool(bool)", + "inputValues": [True], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 1, + "blockTimestampDelay": 175946, + }, { "call": { "from": "0x0000000000000000000000000000000000030000", @@ -738,6 +1313,48 @@ "blockNumberDelay": 0, "blockTimestampDelay": 1, }, + { + "call": { + "from": "0x0000000000000000000000000000000000020000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xe30081a00000000000000000000000003ce6b28f541002ec458324582e3ab2bfc2b77167", + "dataAbiValues": { + "methodSignature": "setAddress(address)", + "inputValues": ["0x3CE6B28F541002EC458324582e3Ab2bfC2b77167"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 20, + "blockTimestampDelay": 107219, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 2, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x73e69a18", + "dataAbiValues": { + "methodSignature": "check_address()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 40568, + "blockTimestampDelay": 353358, + }, ], }, { @@ -797,7 +1414,7 @@ "content": [ { "call": { - "from": "0x0000000000000000000000000000000000020000", + "from": "0x0000000000000000000000000000000000010000", "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", "nonce": 0, "value": "0x0", @@ -805,17 +1422,17 @@ "gasPrice": "0x1", "gasFeeCap": "0x0", "gasTipCap": "0x0", - "data": "0xd3b5d5d4", + "data": "0xd2282dc536ac105a7845b109c000df159eb4427dedbcdedf9379aff52bf13cedc54c1928", "dataAbiValues": { - "methodSignature": "check_uint256()", - "inputValues": [], + "methodSignature": "setUint256(uint256)", + "inputValues": ["19050454979278060990829239674756109780367087"], }, "AccessList": None, "SkipAccountChecks": False, }, - "blockNumberDelay": 9, - "blockTimestampDelay": 80, - }, + "blockNumberDelay": 25421, + "blockTimestampDelay": 401424, + } ], }, { @@ -831,6 +1448,27 @@ "gasPrice": "0x1", "gasFeeCap": "0x0", "gasTipCap": "0x0", + "data": "0xda359dc800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000033a2e6714916369cbf3a3e32dc69db8f561b7235556e8cf0948ad0e1ad5dab0aa92d9b3da693f7219ea302629a45abdeb1fe003c00000000000000000000000000", + "dataAbiValues": { + "methodSignature": "setBytes(bytes)", + "inputValues": [""], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 38145, + "blockTimestampDelay": 360617, + }, + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", "data": "0x68fe2494", "dataAbiValues": { "methodSignature": "check_bytes()", @@ -845,7 +1483,61 @@ ], }, ], - "invalid_functions_call": [ + "time_and_block_seq": [ + { + "name": "1711369139036115000-9eaced3e-b7ae-4565-a0ff-c2ba04d31b13.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000010000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 0, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0x8bc5af90", + "dataAbiValues": { + "methodSignature": "check_string()", + "inputValues": [], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 0, + "blockTimestampDelay": 0, + } + ], + }, + { + "name": "1711369139036682000-39bea42d-9a8e-4e02-9652-76990e1d9043.json", + "content": [ + { + "call": { + "from": "0x0000000000000000000000000000000000030000", + "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", + "nonce": 1, + "value": "0x0", + "gasLimit": 12500000, + "gasPrice": "0x1", + "gasFeeCap": "0x0", + "gasTipCap": "0x0", + "data": "0xe30081a0000000000000000000000000647be767dca5dea7641f4edac5754609bd0b5ee2", + "dataAbiValues": { + "methodSignature": "setAddress(address)", + "inputValues": ["0x647BE767Dca5DeA7641f4EDaC5754609bd0b5eE2"], + }, + "AccessList": None, + "SkipAccountChecks": False, + }, + "blockNumberDelay": 8, + "blockTimestampDelay": 10, + } + ], + }, + ], + "invalid_functions_seq": [ { "name": "1711369139037582000-cd75b0ae-6513-431b-81d3-d4bd19581b92.json", "content": [ @@ -1076,32 +1768,6 @@ } ], }, - { - "name": "1711369141680378000-563145f4-2fb6-4929-9438-d9f229d9c2a4.json", - "content": [ - { - "call": { - "from": "0x0000000000000000000000000000000000010000", - "to": "0xa647ff3c36cfab592509e13860ab8c4f28781a66", - "nonce": 0, - "value": "0x0", - "gasLimit": 12500000, - "gasPrice": "0x1", - "gasFeeCap": "0x0", - "gasTipCap": "0x0", - "data": "0xd2282dc536ac105a7845b109c000df159eb4427dedbcdedf9379aff52bf13cedc54c1928", - "dataAbiValues": { - "methodSignature": "setUint256(uint256)", - "inputValues": ["19050454979278060990829239674756109780367087"], - }, - "AccessList": None, - "SkipAccountChecks": False, - }, - "blockNumberDelay": 25421, - "blockTimestampDelay": 401424, - } - ], - }, { "name": "1711369141706737000-34e6f348-02b4-4ff1-92ea-dac3b77823c5.json", "content": [ diff --git a/tests/test_corpus_modifier.py b/tests/test_corpus_modifier.py index 9e7638d..bb1f2c3 100644 --- a/tests/test_corpus_modifier.py +++ b/tests/test_corpus_modifier.py @@ -33,6 +33,9 @@ def compare_corpus_with_expected(expected_corpus: list, new_corpus: list) -> Non assert expected_corpus_sorted[idx]["content"] == item["content"] +# delete_calls mode + + def test_echidna_delay_delete_calls( setup_foundry_temp_dir: TempPathFactory, ) -> None: @@ -47,9 +50,9 @@ def test_echidna_delay_delete_calls( modifier = CorpusModifier(config, None) new_corpus, corpus_hash = modifier.modify_corpus() - # print("new corpus", new_corpus) - compare_corpus_with_expected(expected_results["echidna"]["time_and_block_call"], new_corpus) + modifier.restore_corpus_from_history(corpus_hash) + compare_corpus_with_expected(expected_results["echidna"]["time_and_block_call"], new_corpus) def test_echidna_blacklist_delete_calls( @@ -67,10 +70,10 @@ def test_echidna_blacklist_delete_calls( modifier = CorpusModifier(config, None) new_corpus, corpus_hash = modifier.modify_corpus() + modifier.restore_corpus_from_history(corpus_hash) compare_corpus_with_expected( expected_results["echidna"]["blacklisted_functions_call"], new_corpus ) - modifier.restore_corpus_from_history(corpus_hash) def test_echidna_function_filter_delete_calls( @@ -89,8 +92,71 @@ def test_echidna_function_filter_delete_calls( new_corpus, corpus_hash = modifier.modify_corpus() # print("new corpus", new_corpus) + modifier.restore_corpus_from_history(corpus_hash) compare_corpus_with_expected(expected_results["echidna"]["invalid_functions_call"], new_corpus) + + +# delete_sequence mode + + +def test_echidna_delay_delete_sequence( + setup_foundry_temp_dir: TempPathFactory, +) -> None: + """Test that correct calls are deleted when filtering by time and block delay""" + config = copy.deepcopy(default_config) + config["mode"] = "delete_sequence" + config["corpusDir"] = "echidna-corpora/corpus-basic-modifier" + config["fuzzerConfigPath"] = "echidna.yaml" + config["fuzzer"] = "echidna" + + create_file(setup_foundry_temp_dir, "echidna.yaml", "maxTimeDelay: 65535\nmaxBlockDelay: 46801") + + modifier = CorpusModifier(config, None) + new_corpus, corpus_hash = modifier.modify_corpus() + modifier.restore_corpus_from_history(corpus_hash) + compare_corpus_with_expected(expected_results["echidna"]["time_and_block_seq"], new_corpus) + + +def test_echidna_blacklist_delete_sequence( + setup_foundry_temp_dir: TempPathFactory, +) -> None: + """Test that correct calls are deleted when filtering blacklisted functions""" + config = copy.deepcopy(default_config) + config["mode"] = "delete_sequence" + config["corpusDir"] = "echidna-corpora/corpus-basic-modifier" + config["fuzzerConfigPath"] = "echidna.yaml" + config["fuzzer"] = "echidna" + + create_file(setup_foundry_temp_dir, "echidna.yaml", 'filterFunctions: ["check_uint256"]') + + modifier = CorpusModifier(config, None) + new_corpus, corpus_hash = modifier.modify_corpus() + + modifier.restore_corpus_from_history(corpus_hash) + compare_corpus_with_expected( + expected_results["echidna"]["blacklisted_functions_seq"], new_corpus + ) + + +def test_echidna_function_filter_delete_sequence( + setup_foundry_temp_dir: TempPathFactory, # pylint: disable=unused-argument +) -> None: + """Test that correct calls are deleted when filtering non-existent functions""" + config = copy.deepcopy(default_config) + config["corpusDir"] = "echidna-corpora/corpus-basic-modifier" + config["targetContract"] = "BasicTypesNoCheckUint256" + config["filterFunctions"] = True + config["mode"] = "delete_sequence" + config["fuzzer"] = "echidna" + + slither = Slither(config["compilationPath"]) + modifier = CorpusModifier(config, slither) + + new_corpus, corpus_hash = modifier.modify_corpus() + + modifier.restore_corpus_from_history(corpus_hash) + compare_corpus_with_expected(expected_results["echidna"]["invalid_functions_seq"], new_corpus) def test_echidna_modify_senders( @@ -108,8 +174,8 @@ def test_echidna_modify_senders( modifier = CorpusModifier(config, None) new_corpus, corpus_hash = modifier.modify_corpus() - compare_corpus_with_expected(expected_results["echidna"]["modify_senders"], new_corpus) modifier.restore_corpus_from_history(corpus_hash) + compare_corpus_with_expected(expected_results["echidna"]["modify_senders"], new_corpus) def test_medusa_delay_delete_calls( @@ -130,12 +196,9 @@ def test_medusa_delay_delete_calls( modifier = CorpusModifier(config, None) new_corpus, corpus_hash = modifier.modify_corpus() - print("new corpus", new_corpus) - compare_corpus_with_expected(expected_results["medusa"]["time_and_block_call"], new_corpus) - # set1 = set(new_corpus) - # set2 = set(expected_results["medusa"]["time_and_block_call"]) - # print("Diff", set1 ^ set2) + modifier.restore_corpus_from_history(corpus_hash) + compare_corpus_with_expected(expected_results["medusa"]["time_and_block_call"], new_corpus) def test_medusa_function_filter_delete_calls( @@ -153,8 +216,50 @@ def test_medusa_function_filter_delete_calls( modifier = CorpusModifier(config, slither) new_corpus, corpus_hash = modifier.modify_corpus() + modifier.restore_corpus_from_history(corpus_hash) compare_corpus_with_expected(expected_results["medusa"]["invalid_functions_call"], new_corpus) + + +def test_medusa_delay_delete_sequence( + setup_foundry_temp_dir: TempPathFactory, +) -> None: + """Test that correct calls are deleted when filtering by time and block delay""" + config = copy.deepcopy(default_config) + config["mode"] = "delete_sequence" + config["corpusDir"] = "medusa-corpora/corpus-basic-modifier" + config["fuzzerConfigPath"] = "medusa.json" + config["fuzzer"] = "medusa" + + create_file( + setup_foundry_temp_dir, + "medusa.json", + '{"fuzzing": {"blockNumberDelayMax": 100,\n"blockTimestampDelayMax": 100}}', + ) + + modifier = CorpusModifier(config, None) + new_corpus, corpus_hash = modifier.modify_corpus() + + modifier.restore_corpus_from_history(corpus_hash) + compare_corpus_with_expected(expected_results["medusa"]["time_and_block_seq"], new_corpus) + + +def test_medusa_function_filter_delete_sequence( + setup_foundry_temp_dir: TempPathFactory, # pylint: disable=unused-argument +) -> None: + """Test that correct calls are deleted when filtering non-existent functions""" + config = copy.deepcopy(default_config) + config["corpusDir"] = "medusa-corpora/corpus-basic-modifier" + config["targetContract"] = "BasicTypesNoCheckUint256" + config["filterFunctions"] = True + config["mode"] = "delete_sequence" + config["fuzzer"] = "medusa" + + slither = Slither(config["compilationPath"]) + modifier = CorpusModifier(config, slither) + + new_corpus, corpus_hash = modifier.modify_corpus() modifier.restore_corpus_from_history(corpus_hash) + compare_corpus_with_expected(expected_results["medusa"]["invalid_functions_seq"], new_corpus) def test_medusa_modify_senders( @@ -171,6 +276,5 @@ def test_medusa_modify_senders( modifier = CorpusModifier(config, None) new_corpus, corpus_hash = modifier.modify_corpus() - - compare_corpus_with_expected(expected_results["medusa"]["modify_senders"], new_corpus) modifier.restore_corpus_from_history(corpus_hash) + compare_corpus_with_expected(expected_results["medusa"]["modify_senders"], new_corpus) From 29dca24aec43510e6d6d182414bdcd13bd9234e3 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Thu, 13 Jun 2024 18:24:03 +0200 Subject: [PATCH 7/8] fix path splitting --- tests/test_corpus_modifier.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_corpus_modifier.py b/tests/test_corpus_modifier.py index bb1f2c3..e7dcbb7 100644 --- a/tests/test_corpus_modifier.py +++ b/tests/test_corpus_modifier.py @@ -1,4 +1,5 @@ """ Tests for generating compilable test files from an Echidna corpus""" +import os import copy from pathlib import Path from pytest import TempPathFactory @@ -26,9 +27,9 @@ def compare_corpus_with_expected(expected_corpus: list, new_corpus: list) -> Non """Compares two corpora, failing if they're not the same""" assert len(expected_corpus) == len(new_corpus) expected_corpus_sorted = sorted(expected_corpus, key=lambda d: d["name"]) - new_corpus_sorted = sorted(new_corpus, key=lambda d: d["path"].split("/")[-1]) + new_corpus_sorted = sorted(new_corpus, key=lambda d: os.path.normpath(d["path"]).split(os.path.sep)[-1]) for idx, item in enumerate(new_corpus_sorted): - name = item["path"].split("/")[-1] + name = os.path.normpath(item["path"]).split(os.path.sep)[-1] assert expected_corpus_sorted[idx]["name"] == name assert expected_corpus_sorted[idx]["content"] == item["content"] From e08d458d82af96ecbd36e5d7768d823cba38cd2b Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Thu, 13 Jun 2024 18:26:31 +0200 Subject: [PATCH 8/8] reformat --- tests/test_corpus_modifier.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_corpus_modifier.py b/tests/test_corpus_modifier.py index e7dcbb7..28d9e20 100644 --- a/tests/test_corpus_modifier.py +++ b/tests/test_corpus_modifier.py @@ -27,7 +27,9 @@ def compare_corpus_with_expected(expected_corpus: list, new_corpus: list) -> Non """Compares two corpora, failing if they're not the same""" assert len(expected_corpus) == len(new_corpus) expected_corpus_sorted = sorted(expected_corpus, key=lambda d: d["name"]) - new_corpus_sorted = sorted(new_corpus, key=lambda d: os.path.normpath(d["path"]).split(os.path.sep)[-1]) + new_corpus_sorted = sorted( + new_corpus, key=lambda d: os.path.normpath(d["path"]).split(os.path.sep)[-1] + ) for idx, item in enumerate(new_corpus_sorted): name = os.path.normpath(item["path"]).split(os.path.sep)[-1] assert expected_corpus_sorted[idx]["name"] == name