def generate_test_contract( contract: Contract, type_property: str, output_dir: Path, property_file: Path, initialization_recommendation: str, ) -> Tuple[str, str]: test_contract_name = f"Test{contract.name}{type_property}" properties_name = f"Properties{contract.name}{type_property}" content = "" content += f'import "./{property_file}";\n' content += f"contract {test_contract_name} is {properties_name} {{\n" content += "\tconstructor() public{\n" content += "\t\t// Existing addresses:\n" content += "\t\t// - crytic_owner: If the contract has an owner, it must be crytic_owner\n" content += "\t\t// - crytic_user: Legitimate user\n" content += "\t\t// - crytic_attacker: Attacker\n" content += "\t\t// \n" content += initialization_recommendation content += "\t\t// \n" content += "\t\t// \n" content += "\t\t// Update the following if totalSupply and balanceOf are external functions or state variables:\n\n" content += "\t\tinitialTotalSupply = totalSupply();\n" content += "\t\tinitialBalance_owner = balanceOf(crytic_owner);\n" content += "\t\tinitialBalance_user = balanceOf(crytic_user);\n" content += "\t\tinitialBalance_attacker = balanceOf(crytic_attacker);\n" content += "\t}\n}\n" filename = f"{test_contract_name}.sol" write_file(output_dir, filename, content, allow_overwrite=False) return filename, test_contract_name
def generate_solidity_interface(output_dir: Path, addresses: Addresses): content = f""" contract CryticInterface{{ address internal crytic_owner = address({addresses.owner}); address internal crytic_user = address({addresses.user}); address internal crytic_attacker = address({addresses.attacker}); uint internal initialTotalSupply; uint internal initialBalance_owner; uint internal initialBalance_user; uint internal initialBalance_attacker; }}""" # Static file, we discard if it exists as it should never change write_file(output_dir, "interfaces.sol", content, discard_if_exist=True)
def generate_echidna_config(output_dir: Path, addresses: Addresses) -> str: """ Generate the echidna configuration file :param output_dir: :param addresses: :return: """ content = 'prefix: crytic_\n' content += f'deployer: "{addresses.owner}"\n' content += f'sender: ["{addresses.user}", "{addresses.attacker}"]\n' content += f'psender: "{addresses.user}"\n' content += 'coverage: true\n' filename = 'echidna_config.yaml' write_file(output_dir, filename, content) return filename
def generate_solidity_properties(contract: Contract, type_property: str, solidity_properties: str, output_dir: Path) -> Path: solidity_import = f'import "./interfaces.sol";\n' solidity_import += f'import "../{contract.source_mapping["filename_short"]}";' test_contract_name = f'Properties{contract.name}{type_property}' solidity_content = f'{solidity_import}\ncontract {test_contract_name} is CryticInterface,{contract.name}' solidity_content += f'{{\n\n{solidity_properties}\n}}\n' filename = f'{test_contract_name}.sol' write_file(output_dir, filename, solidity_content) return Path(filename)
def generate_migration(test_contract: str, output_dir: Path, owner_address: str): """ Generate migration file :param test_contract: :param output_dir: :param owner_address: :return: """ content = f"""{test_contract} = artifacts.require("{test_contract}"); module.exports = function(deployer) {{ deployer.deploy({test_contract}, {{from: "{owner_address}"}}); }}; """ output_dir = Path(output_dir, "migrations") output_dir.mkdir(exist_ok=True) migration_files = [ js_file for js_file in output_dir.iterdir() if js_file.suffix == ".js" and PATTERN_TRUFFLE_MIGRATION.match(js_file.name) ] idx = len(migration_files) filename = f"{idx + 1}_{test_contract}.js" potential_previous_filename = f"{idx}_{test_contract}.js" for m in migration_files: if m.name == potential_previous_filename: write_file(output_dir, potential_previous_filename, content) return if test_contract in m.name: logger.error(f"Potential conflicts with {m.name}") write_file(output_dir, filename, content)
def generate_unit_test( test_contract: str, filename: str, unit_tests: List[Property], output_dir: Path, addresses: Addresses, assert_message: str = "", ): """ Generate unit tests files :param test_contract: :param filename: :param unit_tests: :param output_dir: :param addresses: :param assert_message: :return: """ content = f'{test_contract} = artifacts.require("{test_contract}");\n\n' content += _helpers() content += f'contract("{test_contract}", accounts => {{\n' content += f'\tlet owner = "{addresses.owner}";\n' content += f'\tlet user = "******";\n' content += f'\tlet attacker = "{addresses.attacker}";\n' for unit_test in unit_tests: content += f'\tit("{unit_test.description}", async () => {{\n' content += f"\t\tlet instance = await {test_contract}.deployed();\n" callers = _extract_caller(unit_test.caller) if unit_test.return_type == PropertyReturn.SUCCESS: for caller in callers: content += f"\t\tlet test_{caller} = await instance.{unit_test.name[:-2]}.call({{from: {caller}}});\n" if assert_message: content += f'\t\tassert.equal(test_{caller}, true, "{assert_message}");\n' else: content += f"\t\tassert.equal(test_{caller}, true);\n" elif unit_test.return_type == PropertyReturn.FAIL: for caller in callers: content += f"\t\tlet test_{caller} = await instance.{unit_test.name[:-2]}.call({{from: {caller}}});\n" if assert_message: content += f'\t\tassert.equal(test_{caller}, false, "{assert_message}");\n' else: content += f"\t\tassert.equal(test_{caller}, false);\n" elif unit_test.return_type == PropertyReturn.FAIL_OR_THROW: for caller in callers: content += f"\t\tawait catchRevertThrowReturnFalse(instance.{unit_test.name[:-2]}.call({{from: {caller}}}));\n" elif unit_test.return_type == PropertyReturn.THROW: callers = _extract_caller(unit_test.caller) for caller in callers: content += f"\t\tawait catchRevertThrow(instance.{unit_test.name[:-2]}.call({{from: {caller}}}));\n" content += "\t});\n" content += "});\n" output_dir = Path(output_dir, "test") output_dir.mkdir(exist_ok=True) output_dir = Path(output_dir, "crytic") output_dir.mkdir(exist_ok=True) write_file(output_dir, filename, content) return output_dir