crytic / tealer

Static Analyzer for Teal
GNU Affero General Public License v3.0
62 stars 14 forks source link

Have a uniform output directory and filenames #159

Open S3v3ru5 opened 1 year ago

S3v3ru5 commented 1 year ago

Detectors and printers output .dot files. All these files are saved in the current directory by default. User can use --dest option to select the destination directory. When user uses this, all the files that are generated in that run will be saved in the selected directory. Most of the times users will not use the --dest option and even if used, user has to select the directory for every run of the tool.

We can use a standard directory structure for tealer output files.

All files will be saved in the default directory tealer-export. Assuming the users run tealer on multiple contracts, each contract should have a separate sub-directory.

Each of the detectors could generate one or more dot files to represent the output. It makes sense to have a separate directory to save the output files of each of the detectors.

Printers either

Printers generating multiple files will have a separate directory. Printers generating the single files will save in the main directory.

Directory structure:

root_directory = "tealer-export"
contract_directory = f"teal.contract_name"
main_directory = f"{root_directory}/{contract_directory}"

printer_directory = f"{main_directory}" if printer_outputs_single_file else f"{main_directory}/{printer.NAME}"
detector_directory = f"{main_directory}/{detector.NAME}"

# filenames of detectors

for index, output in enumerate(outputs):
      file_location = f"{detector_directory}/{detector.NAME}-{index}.{file-extension}" # file-extension is dot

Contract Name:

# __main__.py fetch_contract function
def fetch_contract(args: argparse.Namespace) -> Tuple[str, str]:
    program: str = args.program
    network: str = args.network
    b32_regex = "[A-Z2-7]+"
    if program.isdigit():
        # is a number so a app id
        print(f'Fetching application using id "{program}"')
        # contract_name = f"app_{appid}", ex: app_991196662
        return get_application_using_app_id(network, int(program)), f"app_{program}"
    if len(program) == 52 and re.fullmatch(b32_regex, program) is not None:
        # is a txn id: base32 encoded. length after encoding == 52
        print(f'Fetching logic-sig contract that signed the transaction "{program}"')
        # contract_name = f"txn_{txn_id[:8].lower()}"
        return logic_sig_from_txn_id(network, program), f"txn_{program[:8].lower()}"
    if len(program) == 58 and re.fullmatch(b32_regex, program) is not None:
        # is a address. base32 encoded. length after encoding == 58
        print(f'Fetching logic-sig of contract account "{program}"')
        # contract_name = f"lsig_{address[:8].lower()}"
        return logic_sig_from_contract_account(network, program), f"lsig_{program[:8].lower()}"
    # file path
    print(f'Reading contract from file: "{program}"')
    try:
        contract_name = program[:-len(".teal")] if program.endswith(".teal") else program
        contract_name = contract_name.lower()
        with open(program, encoding="utf-8") as f:
            return f.read(), contract_name
    except FileNotFoundError as e:
        raise TealerException from e

Its better to remove --dest option if we use this directory structure.