diff --git a/scripts/bytecodecompare/prepare_report.js b/scripts/bytecodecompare/prepare_report.js index 23e490e32..b8e8821fa 100755 --- a/scripts/bytecodecompare/prepare_report.js +++ b/scripts/bytecodecompare/prepare_report.js @@ -16,6 +16,13 @@ function loadSource(sourceFileName, stripSMTPragmas) return source } +function cleanString(string) +{ + if (string !== undefined) + string = string.trim() + return (string !== '' ? string : undefined) +} + let stripSMTPragmas = false let firstFileArgumentIndex = 2 @@ -78,10 +85,15 @@ for (const optimize of [false, true]) let bytecode = '' let metadata = '' - if ('evm' in contractResults && 'bytecode' in contractResults['evm'] && 'object' in contractResults['evm']['bytecode']) - bytecode = contractResults.evm.bytecode.object + if ( + 'evm' in contractResults && + 'bytecode' in contractResults['evm'] && + 'object' in contractResults['evm']['bytecode'] && + cleanString(contractResults.evm.bytecode.object) !== undefined + ) + bytecode = cleanString(contractResults.evm.bytecode.object) - if ('metadata' in contractResults) + if ('metadata' in contractResults && cleanString(contractResults.metadata) !== undefined) metadata = contractResults.metadata console.log(filename + ':' + contractName + ' ' + bytecode) diff --git a/scripts/bytecodecompare/prepare_report.py b/scripts/bytecodecompare/prepare_report.py index ce2f3881f..d8ac15543 100755 --- a/scripts/bytecodecompare/prepare_report.py +++ b/scripts/bytecodecompare/prepare_report.py @@ -13,9 +13,9 @@ from tempfile import TemporaryDirectory from typing import List, Optional, Tuple, Union -CONTRACT_SEPARATOR_PATTERN = re.compile(r'^======= (?P.+):(?P[^:]+) =======$', re.MULTILINE) -BYTECODE_REGEX = re.compile(r'^Binary:\n(?P.*)$', re.MULTILINE) -METADATA_REGEX = re.compile(r'^Metadata:\n(?P\{.*\})$', re.MULTILINE) +CONTRACT_SEPARATOR_PATTERN = re.compile(r'^ *======= +(?:(?P.+) *:)? *(?P[^:]+) +======= *$', re.MULTILINE) +BYTECODE_REGEX = re.compile(r'^ *Binary: *\n(?P.*[0-9a-f$_]+.*)$', re.MULTILINE) +METADATA_REGEX = re.compile(r'^ *Metadata: *\n *(?P\{.*\}) *$', re.MULTILINE) class CompilerInterface(Enum): @@ -32,7 +32,7 @@ class SMTUse(Enum): @dataclass(frozen=True) class ContractReport: contract_name: str - file_name: Path + file_name: Optional[Path] bytecode: Optional[str] metadata: Optional[str] @@ -72,6 +72,11 @@ def load_source(path: Union[Path, str], smt_use: SMTUse) -> str: return file_content +def clean_string(value: Optional[str]) -> Optional[str]: + value = value.strip() if value is not None else None + return value if value != '' else None + + def parse_standard_json_output(source_file_name: Path, standard_json_output: str) -> FileReport: decoded_json_output = json.loads(standard_json_output.strip()) @@ -98,8 +103,8 @@ def parse_standard_json_output(source_file_name: Path, standard_json_output: str file_report.contract_reports.append(ContractReport( contract_name=contract_name, file_name=Path(file_name), - bytecode=contract_results.get('evm', {}).get('bytecode', {}).get('object'), - metadata=contract_results.get('metadata'), + bytecode=clean_string(contract_results.get('evm', {}).get('bytecode', {}).get('object')), + metadata=clean_string(contract_results.get('metadata')), )) return file_report @@ -122,10 +127,10 @@ def parse_cli_output(source_file_name: Path, cli_output: str) -> FileReport: assert file_report.contract_reports is not None file_report.contract_reports.append(ContractReport( - contract_name=contract_name, - file_name=Path(file_name), - bytecode=bytecode_match['bytecode'] if bytecode_match is not None else None, - metadata=metadata_match['metadata'] if metadata_match is not None else None, + contract_name=contract_name.strip(), + file_name=Path(file_name.strip()) if file_name is not None else None, + bytecode=clean_string(bytecode_match['bytecode'] if bytecode_match is not None else None), + metadata=clean_string(metadata_match['metadata'] if metadata_match is not None else None), )) return file_report diff --git a/test/scripts/fixtures/solc_0.4.0_cli_output.txt b/test/scripts/fixtures/solc_0.4.0_cli_output.txt new file mode 100644 index 000000000..2000013c5 --- /dev/null +++ b/test/scripts/fixtures/solc_0.4.0_cli_output.txt @@ -0,0 +1,8 @@ +contract.sol:1:1: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.4.0;". +contract C {} +^ +Spanning multiple lines. + +======= C ======= +Binary: +6060604052600c8060106000396000f360606040526008565b600256 diff --git a/test/scripts/fixtures/solc_0.4.8_cli_output.txt b/test/scripts/fixtures/solc_0.4.8_cli_output.txt new file mode 100644 index 000000000..619e5c39e --- /dev/null +++ b/test/scripts/fixtures/solc_0.4.8_cli_output.txt @@ -0,0 +1,10 @@ +contract.sol:1:1: Warning: Source file does not specify required compiler version!Consider adding "pragma solidity ^0.4.8 +contract C {} +^ +Spanning multiple lines. + +======= C ======= +Binary: +6060604052346000575b60358060166000396000f30060606040525b60005600a165627a7a72305820ccf9337430b4c4f7d6ad41efb10a94411a2af6a9f173ef52daeadd31f4bf11890029 +Metadata: +{"compiler":{"version":"0.4.8+commit.60cc1668.mod.Darwin.appleclang"},"language":"Solidity","output":{"abi":[],"devdoc":{"methods":{}},"userdoc":{"methods":{}}},"settings":{"compilationTarget":{"contract.sol":"C"},"libraries":{},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"contract.sol":{"keccak256":"0xbe86d3681a198587296ad6d4a834606197e1a8f8944922c501631b04e21eeba2","urls":["bzzr://af16957d3d86013309d64d3cc572d007b1d8b08a821f2ff366840deb54a78524"]}},"version":1} diff --git a/test/scripts/test_bytecodecompare_prepare_report.py b/test/scripts/test_bytecodecompare_prepare_report.py index c1566c0e9..46c140962 100644 --- a/test/scripts/test_bytecodecompare_prepare_report.py +++ b/test/scripts/test_bytecodecompare_prepare_report.py @@ -41,6 +41,9 @@ STACK_TOO_DEEP_CLI_OUTPUT = load_fixture('stack_too_deep_cli_output.txt') CODE_GENERATION_ERROR_JSON_OUTPUT = load_fixture('code_generation_error_json_output.json') CODE_GENERATION_ERROR_CLI_OUTPUT = load_fixture('code_generation_error_cli_output.txt') +SOLC_0_4_0_CLI_OUTPUT = load_fixture('solc_0.4.0_cli_output.txt') +SOLC_0_4_8_CLI_OUTPUT = load_fixture('solc_0.4.8_cli_output.txt') + class PrepareReportTestBase(unittest.TestCase): def setUp(self): @@ -334,6 +337,34 @@ class TestParseStandardJSONOutput(PrepareReportTestBase): class TestParseCLIOutput(PrepareReportTestBase): + def test_parse_standard_json_output_should_report_missing_if_value_is_just_whitespace(self): + compiler_output = dedent("""\ + { + "contracts": { + "contract.sol": { + "A": { + "evm": {"bytecode": {"object": ""}}, + "metadata": "" + }, + "B": { + "evm": {"bytecode": {"object": " "}}, + "metadata": " " + } + } + } + } + """) + + expected_report = FileReport( + file_name=Path('contract.sol'), + contract_reports=[ + ContractReport(contract_name='A', file_name=Path('contract.sol'), bytecode=None, metadata=None), + ContractReport(contract_name='B', file_name=Path('contract.sol'), bytecode=None, metadata=None), + ] + ) + + self.assertEqual(parse_standard_json_output(Path('contract.sol'), compiler_output), expected_report) + def test_parse_cli_output(self): expected_report = FileReport( file_name=Path('syntaxTests/scoping/library_inherited2.sol'), @@ -431,3 +462,107 @@ class TestParseCLIOutput(PrepareReportTestBase): expected_report = FileReport(file_name=Path('file.sol'), contract_reports=None) self.assertEqual(parse_cli_output(Path('file.sol'), CODE_GENERATION_ERROR_CLI_OUTPUT), expected_report) + + def test_parse_cli_output_should_handle_output_from_solc_0_4_0(self): + expected_report = FileReport( + file_name=Path('contract.sol'), + contract_reports=[ + ContractReport( + contract_name='C', + file_name=None, + bytecode='6060604052600c8060106000396000f360606040526008565b600256', + metadata=None, + ) + ] + ) + + self.assertEqual(parse_cli_output(Path('contract.sol'), SOLC_0_4_0_CLI_OUTPUT), expected_report) + + def test_parse_cli_output_should_handle_output_from_solc_0_4_8(self): + expected_report = FileReport( + file_name=Path('contract.sol'), + contract_reports=[ + # pragma pylint: disable=line-too-long + ContractReport( + contract_name='C', + file_name=None, + bytecode='6060604052346000575b60358060166000396000f30060606040525b60005600a165627a7a72305820ccf9337430b4c4f7d6ad41efb10a94411a2af6a9f173ef52daeadd31f4bf11890029', + metadata='{"compiler":{"version":"0.4.8+commit.60cc1668.mod.Darwin.appleclang"},"language":"Solidity","output":{"abi":[],"devdoc":{"methods":{}},"userdoc":{"methods":{}}},"settings":{"compilationTarget":{"contract.sol":"C"},"libraries":{},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"contract.sol":{"keccak256":"0xbe86d3681a198587296ad6d4a834606197e1a8f8944922c501631b04e21eeba2","urls":["bzzr://af16957d3d86013309d64d3cc572d007b1d8b08a821f2ff366840deb54a78524"]}},"version":1}', + ) + # pragma pylint: enable=line-too-long + ] + ) + + self.assertEqual(parse_cli_output(Path('contract.sol'), SOLC_0_4_8_CLI_OUTPUT), expected_report) + + def test_parse_cli_output_should_handle_leading_and_trailing_spaces(self): + compiler_output = ( + ' ======= contract.sol : C ======= \n' + ' Binary: \n' + ' 60806040523480156 \n' + ' Metadata: \n' + ' { } \n' + ) + + expected_report = FileReport( + file_name=Path('contract.sol'), + contract_reports=[ContractReport(contract_name='C', file_name=Path('contract.sol'), bytecode='60806040523480156', metadata='{ }')] + ) + + self.assertEqual(parse_cli_output(Path('contract.sol'), compiler_output), expected_report) + + def test_parse_cli_output_should_handle_empty_bytecode_and_metadata_lines(self): + compiler_output = dedent("""\ + ======= contract.sol:C ======= + Binary: + 60806040523480156 + Metadata: + + + ======= contract.sol:D ======= + Binary: + + Metadata: + {} + + + ======= contract.sol:E ======= + Binary: + + Metadata: + + + """) + + expected_report = FileReport( + file_name=Path('contract.sol'), + contract_reports=[ + ContractReport(contract_name='C', file_name=Path('contract.sol'), bytecode='60806040523480156', metadata=None), + ContractReport(contract_name='D', file_name=Path('contract.sol'), bytecode=None, metadata='{}'), + ContractReport(contract_name='E', file_name=Path('contract.sol'), bytecode=None, metadata=None), + ] + ) + + self.assertEqual(parse_cli_output(Path('contract.sol'), compiler_output), expected_report) + + def test_parse_cli_output_should_handle_link_references_in_bytecode(self): + compiler_output = dedent("""\ + ======= contract.sol:C ======= + Binary: + 73123456789012345678901234567890123456789073__$fb58009a6b1ecea3b9d99bedd645df4ec3$__5050 + ======= contract.sol:D ======= + Binary: + __$fb58009a6b1ecea3b9d99bedd645df4ec3$__ + """) + + # pragma pylint: disable=line-too-long + expected_report = FileReport( + file_name=Path('contract.sol'), + contract_reports=[ + ContractReport(contract_name='C', file_name=Path('contract.sol'), bytecode='73123456789012345678901234567890123456789073__$fb58009a6b1ecea3b9d99bedd645df4ec3$__5050', metadata=None), + ContractReport(contract_name='D', file_name=Path('contract.sol'), bytecode='__$fb58009a6b1ecea3b9d99bedd645df4ec3$__', metadata=None), + ] + ) + # pragma pylint: enable=line-too-long + + self.assertEqual(parse_cli_output(Path('contract.sol'), compiler_output), expected_report)