diff --git a/crytic_compile/crytic_compile.py b/crytic_compile/crytic_compile.py index 7b81bbaa..e437afef 100644 --- a/crytic_compile/crytic_compile.py +++ b/crytic_compile/crytic_compile.py @@ -63,13 +63,14 @@ def _extract_libraries(libraries_str: Optional[str]) -> Optional[Dict[str, int]] if not libraries_str: return None - - pattern = r"\((?P\w+),\s*(?P0x[0-9a-fA-F]{2})\),?" + # Extract tuple like (libname1, 0x00) + pattern = r"\((?P\w+),\s*(?P0x[0-9a-fA-F]{2,40})\),?" matches = re.findall(pattern, libraries_str) if not matches: - logging.info(f"Libraries {libraries_str} could not be parsed") - return None + raise ValueError( + f"Invalid library linking directive\nGot:\n{libraries_str}\nExpected format:\n(libname1, 0x00),(libname2, 0x02)" + ) ret: Dict[str, int] = {} for key, value in matches: diff --git a/tests/library_linking.sol b/tests/library_linking.sol new file mode 100644 index 00000000..a8704e7e --- /dev/null +++ b/tests/library_linking.sol @@ -0,0 +1,16 @@ +library NeedsLinkingA { + function testA() external pure returns (uint) { + return type(uint).max; + } +} +library NeedsLinkingB { + function testB() external pure returns (uint) { + return type(uint).min; + } +} +contract TestLibraryLinking { + function test() external { + NeedsLinkingA.testA(); + NeedsLinkingB.testB(); + } +} diff --git a/tests/test_library_linking.py b/tests/test_library_linking.py new file mode 100644 index 00000000..d863f4f9 --- /dev/null +++ b/tests/test_library_linking.py @@ -0,0 +1,54 @@ +""" +Test library linking +""" +import re +from pathlib import Path +import pytest +from crytic_compile.crytic_compile import CryticCompile + +TEST_DIR = Path(__file__).resolve().parent + +LIBRARY_PLACEHOLDER_REGEX = r"__.{36}__" + + +def test_library_linking() -> None: + """Test that the placeholder is not present in the bytecode when the libraries are provided""" + cc = CryticCompile( + Path(TEST_DIR / "library_linking.sol").as_posix(), + compile_libraries="(NeedsLinkingA, 0xdead),(NeedsLinkingB, 0x000000000000000000000000000000000000beef)", + ) + for compilation_unit in cc.compilation_units.values(): + for source_unit in compilation_unit.source_units.values(): + assert ( + len(re.findall(r"__.{36}__", source_unit.bytecode_init("TestLibraryLinking"))) == 2 + ) + assert ( + len(re.findall(r"__.{36}__", source_unit.bytecode_runtime("TestLibraryLinking"))) + == 2 + ) + libraries = compilation_unit.crytic_compile.libraries + assert ( + len( + re.findall( + r"__.{36}__", source_unit.bytecode_init("TestLibraryLinking", libraries) + ) + ) + == 0 + ) + assert ( + len( + re.findall( + r"__.{36}__", source_unit.bytecode_runtime("TestLibraryLinking", libraries) + ) + ) + == 0 + ) + + +def test_library_linking_validation() -> None: + """Test that invalid compile libraries argument raises an error""" + with pytest.raises(ValueError): + CryticCompile( + Path(TEST_DIR / "library_linking.sol").as_posix(), + compile_libraries="(NeedsLinkingA, 0x)", + )