Merge pull request #10675 from ethereum/refactor-bytecode-comparison-scripts

Refactor bytecode comparison scripts
This commit is contained in:
chriseth 2021-01-11 18:57:47 +01:00 committed by GitHub
commit d43693ecca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 508 additions and 88 deletions

View File

@ -0,0 +1,54 @@
#!/usr/bin/env node
const process = require('process')
const fs = require('fs')
const compiler = require('./solc-js/wrapper.js')(require('./solc-js/soljson.js'))
for (const optimize of [false, true])
{
for (const filename of process.argv.slice(2))
{
if (filename !== undefined)
{
const input = {
language: 'Solidity',
sources: {
[filename]: {content: fs.readFileSync(filename).toString()}
},
settings: {
optimizer: {enabled: optimize},
outputSelection: {'*': {'*': ['evm.bytecode.object', 'metadata']}},
"modelChecker": {"engine": "none"}
}
}
const result = JSON.parse(compiler.compile(JSON.stringify(input)))
if (
!('contracts' in result) ||
Object.keys(result['contracts']).length === 0 ||
Object.keys(result['contracts']).every(file => Object.keys(result['contracts'][file]).length === 0)
)
// NOTE: do not exit here because this may be run on source which cannot be compiled
console.log(filename + ': <ERROR>')
else
for (const contractFile in result['contracts'])
for (const contractName in result['contracts'][contractFile])
{
const contractResults = result['contracts'][contractFile][contractName]
let bytecode = '<NO BYTECODE>'
let metadata = '<NO METADATA>'
if ('evm' in contractResults && 'bytecode' in contractResults['evm'] && 'object' in contractResults['evm']['bytecode'])
bytecode = contractResults.evm.bytecode.object
if ('metadata' in contractResults)
metadata = contractResults.metadata
console.log(filename + ':' + contractName + ' ' + bytecode)
console.log(filename + ':' + contractName + ' ' + metadata)
}
}
}
}

View File

@ -1,43 +1,149 @@
#!/usr/bin/env python3
import sys
import glob
import subprocess
import json
from argparse import ArgumentParser
from dataclasses import dataclass
from glob import glob
from pathlib import Path
from typing import List, Optional, Tuple, Union
SOLC_BIN = sys.argv[1]
REPORT_FILE = open("report.txt", mode="w", encoding='utf8', newline='\n')
for optimize in [False, True]:
for f in sorted(glob.glob("*.sol")):
sources = {}
sources[f] = {'content': open(f, mode='r', encoding='utf8').read()}
input_json = {
@dataclass(frozen=True)
class ContractReport:
contract_name: str
file_name: Path
bytecode: Optional[str]
metadata: Optional[str]
@dataclass
class FileReport:
file_name: Path
contract_reports: Optional[List[ContractReport]]
def format_report(self) -> str:
report = ""
if self.contract_reports is None:
return f"{self.file_name}: <ERROR>\n"
for contract_report in self.contract_reports:
bytecode = contract_report.bytecode if contract_report.bytecode is not None else '<NO BYTECODE>'
metadata = contract_report.metadata if contract_report.metadata is not None else '<NO METADATA>'
# NOTE: Ignoring contract_report.file_name because it should always be either the same
# as self.file_name (for Standard JSON) or just the '<stdin>' placeholder (for CLI).
report += f"{self.file_name}:{contract_report.contract_name} {bytecode}\n"
report += f"{self.file_name}:{contract_report.contract_name} {metadata}\n"
return report
def load_source(path: Union[Path, str]) -> str:
with open(path, mode='r', encoding='utf8') as source_file:
file_content = source_file.read()
return file_content
def parse_standard_json_output(source_file_name: Path, standard_json_output: str) -> FileReport:
decoded_json_output = json.loads(standard_json_output.strip())
if (
'contracts' not in decoded_json_output or
len(decoded_json_output['contracts']) == 0 or
all(len(file_results) == 0 for file_name, file_results in decoded_json_output['contracts'].items())
):
return FileReport(file_name=source_file_name, contract_reports=None)
file_report = FileReport(file_name=source_file_name, contract_reports=[])
for file_name, file_results in sorted(decoded_json_output['contracts'].items()):
for contract_name, contract_results in sorted(file_results.items()):
assert file_report.contract_reports is not None
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'),
))
return file_report
def prepare_compiler_input(compiler_path: Path, source_file_name: Path, optimize: bool) -> Tuple[List[str], str]:
json_input: dict = {
'language': 'Solidity',
'sources': sources,
'settings': {
'optimizer': {
'enabled': optimize
'sources': {
str(source_file_name): {'content': load_source(source_file_name)}
},
'settings': {
'optimizer': {'enabled': optimize},
'outputSelection': {'*': {'*': ['evm.bytecode.object', 'metadata']}},
'modelChecker': { "engine": 'none' }
'modelChecker': {'engine': 'none'},
}
}
args = [SOLC_BIN, '--standard-json']
if optimize:
args += ['--optimize']
proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = proc.communicate(json.dumps(input_json).encode('utf-8'))
command_line = [str(compiler_path), '--standard-json']
compiler_input = json.dumps(json_input)
return (command_line, compiler_input)
def run_compiler(compiler_path: Path, source_file_name: Path, optimize: bool) -> FileReport:
(command_line, compiler_input) = prepare_compiler_input(compiler_path, Path(Path(source_file_name).name), optimize)
process = subprocess.run(
command_line,
input=compiler_input,
encoding='utf8',
capture_output=True,
check=False,
)
return parse_standard_json_output(Path(source_file_name), process.stdout)
def generate_report(source_file_names: List[str], compiler_path: Path):
with open('report.txt', mode='w', encoding='utf8', newline='\n') as report_file:
for optimize in [False, True]:
for source_file_name in sorted(source_file_names):
try:
result = json.loads(out.decode('utf-8').strip())
for filename in sorted(result['contracts'].keys()):
for contractName in sorted(result['contracts'][filename].keys()):
contractData = result['contracts'][filename][contractName]
if 'evm' in contractData and 'bytecode' in contractData['evm']:
REPORT_FILE.write(filename + ':' + contractName + ' ' +
contractData['evm']['bytecode']['object'] + '\n')
else:
REPORT_FILE.write(filename + ':' + contractName + ' NO BYTECODE\n')
REPORT_FILE.write(filename + ':' + contractName + ' ' + contractData['metadata'] + '\n')
except KeyError:
REPORT_FILE.write(f + ": ERROR\n")
report = run_compiler(Path(compiler_path), Path(source_file_name), optimize)
report_file.write(report.format_report())
except subprocess.CalledProcessError as exception:
print(
f"\n\nInterrupted by an exception while processing file "
f"'{source_file_name}' with optimize={optimize}\n\n"
f"COMPILER STDOUT:\n{exception.stdout}\n"
f"COMPILER STDERR:\n{exception.stderr}\n",
file=sys.stderr
)
raise
except:
print(
f"\n\nInterrupted by an exception while processing file "
f"'{source_file_name}' with optimize={optimize}\n",
file=sys.stderr
)
raise
def commandline_parser() -> ArgumentParser:
script_description = (
"Generates a report listing bytecode and metadata obtained by compiling all the "
"*.sol files found in the current working directory using the provided binary."
)
parser = ArgumentParser(description=script_description)
parser.add_argument(dest='compiler_path', help="Solidity compiler executable")
return parser;
if __name__ == "__main__":
options = commandline_parser().parse_args()
generate_report(
glob("*.sol"),
Path(options.compiler_path),
)

View File

@ -46,60 +46,9 @@ TMPDIR=$(mktemp -d)
git clone --depth 1 https://github.com/ethereum/solc-js.git solc-js
( cd solc-js; npm install )
cp "$REPO_ROOT/emscripten_build/libsolc/soljson.js" solc-js/
cat > solc <<EOF
#!/usr/bin/env node
var process = require('process')
var fs = require('fs')
var compiler = require('./solc-js/wrapper.js')(require('./solc-js/soljson.js'))
for (var optimize of [false, true])
{
for (var filename of process.argv.slice(2))
{
if (filename !== undefined)
{
var inputs = {}
inputs[filename] = { content: fs.readFileSync(filename).toString() }
var input = {
language: 'Solidity',
sources: inputs,
settings: {
optimizer: { enabled: optimize },
outputSelection: { '*': { '*': ['evm.bytecode.object', 'metadata'] } },
"modelChecker": { "engine": "none" }
}
}
var result = JSON.parse(compiler.compile(JSON.stringify(input)))
if (
!('contracts' in result) ||
Object.keys(result['contracts']).length === 0 ||
!result['contracts'][filename] ||
Object.keys(result['contracts'][filename]).length === 0
)
{
// NOTE: do not exit here because this may be run on source which cannot be compiled
console.log(filename + ': ERROR')
}
else
{
for (var contractName in result['contracts'][filename])
{
var contractData = result['contracts'][filename][contractName];
if (contractData.evm !== undefined && contractData.evm.bytecode !== undefined)
console.log(filename + ':' + contractName + ' ' + contractData.evm.bytecode.object)
else
console.log(filename + ':' + contractName + ' NO BYTECODE')
console.log(filename + ':' + contractName + ' ' + contractData.metadata)
}
}
}
}
}
EOF
cp ""$REPO_ROOT"/scripts/bytecodecompare/prepare_report.js" .
echo "Running the compiler..."
chmod +x solc
./solc *.sol > report.txt
./prepare_report.js *.sol > report.txt
echo "Finished running the compiler."
else
"$REPO_ROOT/scripts/bytecodecompare/prepare_report.py" "$BUILD_DIR/solc/solc"

View File

@ -0,0 +1,63 @@
{
"contracts": {
"syntaxTests/scoping/library_inherited2.sol": {
"A": {
"evm": {
"bytecode": {
"object": "6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea264697066735822122086e727f29d40b264a19bbfcad38d64493dca4bab5dbba8c82ffdaae389d2bba064736f6c63430008000033"
}
},
"metadata": "{\"compiler\":{\"version\":\"0.8.0+commit.c7dfd78e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"syntaxTests/scoping/library_inherited2.sol\":\"A\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"syntaxTests/scoping/library_inherited2.sol\":{\"keccak256\":\"0xd0619f00638fdfea187368965615dbd599fead93dd14b6558725e85ec7011d96\",\"urls\":[\"bzz-raw://ec7af066be66a223f0d25ba3bf9ba6dc103e1a57531a66a38a5ca2b6ce172f55\",\"dweb:/ipfs/QmW1NrqQNhnY1Tkgr3Z9oM8buCGLUJCJVCDTVejJTT5Vet\"]}},\"version\":1}"
},
"B": {
"evm": {
"bytecode": {
"object": "608060405234801561001057600080fd5b506101cc806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630423a13214610030575b600080fd5b61004a6004803603810190610045919061009d565b610060565b60405161005791906100d5565b60405180910390f35b600061006b82610072565b9050919050565b6000602a8261008191906100f0565b9050919050565b6000813590506100978161017f565b92915050565b6000602082840312156100af57600080fd5b60006100bd84828501610088565b91505092915050565b6100cf81610146565b82525050565b60006020820190506100ea60008301846100c6565b92915050565b60006100fb82610146565b915061010683610146565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0382111561013b5761013a610150565b5b828201905092915050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b61018881610146565b811461019357600080fd5b5056fea2646970667358221220104c345633313efe410492448844d96d78452c3044ce126b5e041b7fbeaa790064736f6c63430008000033"
}
},
"metadata": "{\"compiler\":{\"version\":\"0.8.0+commit.c7dfd78e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"bar\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"syntaxTests/scoping/library_inherited2.sol\":\"B\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"syntaxTests/scoping/library_inherited2.sol\":{\"keccak256\":\"0xd0619f00638fdfea187368965615dbd599fead93dd14b6558725e85ec7011d96\",\"urls\":[\"bzz-raw://ec7af066be66a223f0d25ba3bf9ba6dc103e1a57531a66a38a5ca2b6ce172f55\",\"dweb:/ipfs/QmW1NrqQNhnY1Tkgr3Z9oM8buCGLUJCJVCDTVejJTT5Vet\"]}},\"version\":1}"
},
"Lib": {
"evm": {
"bytecode": {
"object": "60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea26469706673582212207f9515e2263fa71a7984707e2aefd82241fac15c497386ca798b526f14f8ba6664736f6c63430008000033"
}
},
"metadata": "{\"compiler\":{\"version\":\"0.8.0+commit.c7dfd78e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"syntaxTests/scoping/library_inherited2.sol\":\"Lib\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"syntaxTests/scoping/library_inherited2.sol\":{\"keccak256\":\"0xd0619f00638fdfea187368965615dbd599fead93dd14b6558725e85ec7011d96\",\"urls\":[\"bzz-raw://ec7af066be66a223f0d25ba3bf9ba6dc103e1a57531a66a38a5ca2b6ce172f55\",\"dweb:/ipfs/QmW1NrqQNhnY1Tkgr3Z9oM8buCGLUJCJVCDTVejJTT5Vet\"]}},\"version\":1}"
}
}
},
"errors": [
{
"component": "general",
"errorCode": "1878",
"formattedMessage": "Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing \"SPDX-License-Identifier: <SPDX-License>\" to each source file. Use \"SPDX-License-Identifier: UNLICENSED\" for non-open-source code. Please see https://spdx.org for more information.\n--> syntaxTests/scoping/library_inherited2.sol\n\n",
"message": "SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing \"SPDX-License-Identifier: <SPDX-License>\" to each source file. Use \"SPDX-License-Identifier: UNLICENSED\" for non-open-source code. Please see https://spdx.org for more information.",
"severity": "warning",
"sourceLocation": {
"end": -1,
"file": "syntaxTests/scoping/library_inherited2.sol",
"start": -1
},
"type": "Warning"
},
{
"component": "general",
"errorCode": "3420",
"formattedMessage": "Warning: Source file does not specify required compiler version! Consider adding \"pragma solidity ^0.8.0;\"\n--> syntaxTests/scoping/library_inherited2.sol\n\n",
"message": "Source file does not specify required compiler version! Consider adding \"pragma solidity ^0.8.0;\"",
"severity": "warning",
"sourceLocation": {
"end": -1,
"file": "syntaxTests/scoping/library_inherited2.sol",
"start": -1
},
"type": "Warning"
}
],
"sources": {
"syntaxTests/scoping/library_inherited2.sol": {
"id": 0
}
}
}

View File

@ -0,0 +1,44 @@
{
"errors": [
{
"component": "general",
"errorCode": "1878",
"formattedMessage": "Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing \"SPDX-License-Identifier: <SPDX-License>\" to each source file. Use \"SPDX-License-Identifier: UNLICENSED\" for non-open-source code. Please see https://spdx.org for more information.\n--> syntaxTests/pragma/unknown_pragma.sol\n\n",
"message": "SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing \"SPDX-License-Identifier: <SPDX-License>\" to each source file. Use \"SPDX-License-Identifier: UNLICENSED\" for non-open-source code. Please see https://spdx.org for more information.",
"severity": "warning",
"sourceLocation": {
"end": -1,
"file": "syntaxTests/pragma/unknown_pragma.sol",
"start": -1
},
"type": "Warning"
},
{
"component": "general",
"errorCode": "4936",
"formattedMessage": "SyntaxError: Unknown pragma \"thisdoesntexist\"\n --> syntaxTests/pragma/unknown_pragma.sol:1:1:\n |\n1 | pragma thisdoesntexist;\n | ^^^^^^^^^^^^^^^^^^^^^^^\n\n",
"message": "Unknown pragma \"thisdoesntexist\"",
"severity": "error",
"sourceLocation": {
"end": 23,
"file": "syntaxTests/pragma/unknown_pragma.sol",
"start": 0
},
"type": "SyntaxError"
},
{
"component": "general",
"errorCode": "3420",
"formattedMessage": "Warning: Source file does not specify required compiler version! Consider adding \"pragma solidity ^0.8.0;\"\n--> syntaxTests/pragma/unknown_pragma.sol\n\n",
"message": "Source file does not specify required compiler version! Consider adding \"pragma solidity ^0.8.0;\"",
"severity": "warning",
"sourceLocation": {
"end": -1,
"file": "syntaxTests/pragma/unknown_pragma.sol",
"start": -1
},
"type": "Warning"
}
],
"sources": {}
}

View File

@ -0,0 +1,189 @@
#!/usr/bin/env python
import json
import unittest
from pathlib import Path
from textwrap import dedent
from unittest_helpers import LIBSOLIDITY_TEST_DIR, load_fixture, load_libsolidity_test_case
# NOTE: This test file file only works with scripts/ added to PYTHONPATH so pylint can't find the imports
# pragma pylint: disable=import-error
from bytecodecompare.prepare_report import FileReport, ContractReport
from bytecodecompare.prepare_report import load_source, parse_standard_json_output, prepare_compiler_input
# pragma pylint: enable=import-error
SMT_SMOKE_TEST_SOL_PATH = LIBSOLIDITY_TEST_DIR / 'smtCheckerTests/simple/smoke_test.sol'
SMT_SMOKE_TEST_SOL_CODE = load_libsolidity_test_case(SMT_SMOKE_TEST_SOL_PATH)
SYNTAX_SMOKE_TEST_SOL_PATH = LIBSOLIDITY_TEST_DIR / 'syntaxTests/smoke_test.sol'
SYNTAX_SMOKE_TEST_SOL_CODE = load_libsolidity_test_case(SYNTAX_SMOKE_TEST_SOL_PATH)
LIBRARY_INHERITED2_SOL_JSON_OUTPUT = load_fixture('library_inherited2_sol_json_output.json')
UNKNOWN_PRAGMA_SOL_JSON_OUTPUT = load_fixture('unknown_pragma_sol_json_output.json')
class TestPrepareReport_FileReport(unittest.TestCase):
def test_format_report(self):
report = FileReport(
file_name=Path('syntaxTests/scoping/library_inherited2.sol'),
contract_reports=[
ContractReport(
contract_name='A',
file_name=Path('syntaxTests/smoke_test.sol'),
bytecode=None,
metadata=None,
),
ContractReport(
contract_name='B',
file_name=Path('syntaxTests/smoke_test.sol'),
bytecode=None,
metadata='{"language":"Solidity"}',
),
ContractReport(
contract_name='Lib',
file_name=Path('syntaxTests/smoke_test.sol'),
bytecode='60566050600b828282398051',
metadata=None,
),
]
)
expected_output = dedent("""\
syntaxTests/scoping/library_inherited2.sol:A <NO BYTECODE>
syntaxTests/scoping/library_inherited2.sol:A <NO METADATA>
syntaxTests/scoping/library_inherited2.sol:B <NO BYTECODE>
syntaxTests/scoping/library_inherited2.sol:B {"language":"Solidity"}
syntaxTests/scoping/library_inherited2.sol:Lib 60566050600b828282398051
syntaxTests/scoping/library_inherited2.sol:Lib <NO METADATA>
""")
self.assertEqual(report.format_report(), expected_output)
def test_format_report_should_print_error_if_contract_report_list_is_missing(self):
report = FileReport(file_name=Path('file.sol'), contract_reports=None)
expected_output = dedent("""\
file.sol: <ERROR>
""")
self.assertEqual(report.format_report(), expected_output)
def test_format_report_should_not_print_anything_if_contract_report_list_is_empty(self):
report = FileReport(file_name=Path('file.sol'), contract_reports=[])
self.assertEqual(report.format_report(), '')
class TestPrepareReport(unittest.TestCase):
def setUp(self):
self.maxDiff = 10000
def test_load_source(self):
self.assertEqual(load_source(SMT_SMOKE_TEST_SOL_PATH), SMT_SMOKE_TEST_SOL_CODE)
def test_prepare_compiler_input(self):
expected_compiler_input = {
'language': 'Solidity',
'sources': {
str(SMT_SMOKE_TEST_SOL_PATH): {'content': SMT_SMOKE_TEST_SOL_CODE},
},
'settings': {
'optimizer': {'enabled': True},
'outputSelection': {'*': {'*': ['evm.bytecode.object', 'metadata']}},
'modelChecker': {'engine': 'none'},
}
}
(command_line, compiler_input) = prepare_compiler_input(
Path('solc'),
SMT_SMOKE_TEST_SOL_PATH,
optimize=True,
)
self.assertEqual(command_line, ['solc', '--standard-json'])
self.assertEqual(json.loads(compiler_input), expected_compiler_input)
def test_parse_standard_json_output(self):
expected_report = FileReport(
file_name=Path('syntaxTests/scoping/library_inherited2.sol'),
contract_reports=[
# pragma pylint: disable=line-too-long
ContractReport(
contract_name='A',
file_name=Path('syntaxTests/scoping/library_inherited2.sol'),
bytecode='6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea264697066735822122086e727f29d40b264a19bbfcad38d64493dca4bab5dbba8c82ffdaae389d2bba064736f6c63430008000033',
metadata='{"compiler":{"version":"0.8.0+commit.c7dfd78e"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"compilationTarget":{"syntaxTests/scoping/library_inherited2.sol":"A"},"evmVersion":"istanbul","libraries":{},"metadata":{"bytecodeHash":"ipfs"},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"syntaxTests/scoping/library_inherited2.sol":{"keccak256":"0xd0619f00638fdfea187368965615dbd599fead93dd14b6558725e85ec7011d96","urls":["bzz-raw://ec7af066be66a223f0d25ba3bf9ba6dc103e1a57531a66a38a5ca2b6ce172f55","dweb:/ipfs/QmW1NrqQNhnY1Tkgr3Z9oM8buCGLUJCJVCDTVejJTT5Vet"]}},"version":1}',
),
ContractReport(
contract_name='B',
file_name=Path('syntaxTests/scoping/library_inherited2.sol'),
bytecode='608060405234801561001057600080fd5b506101cc806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630423a13214610030575b600080fd5b61004a6004803603810190610045919061009d565b610060565b60405161005791906100d5565b60405180910390f35b600061006b82610072565b9050919050565b6000602a8261008191906100f0565b9050919050565b6000813590506100978161017f565b92915050565b6000602082840312156100af57600080fd5b60006100bd84828501610088565b91505092915050565b6100cf81610146565b82525050565b60006020820190506100ea60008301846100c6565b92915050565b60006100fb82610146565b915061010683610146565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0382111561013b5761013a610150565b5b828201905092915050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b61018881610146565b811461019357600080fd5b5056fea2646970667358221220104c345633313efe410492448844d96d78452c3044ce126b5e041b7fbeaa790064736f6c63430008000033',
metadata='{"compiler":{"version":"0.8.0+commit.c7dfd78e"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"bar","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"compilationTarget":{"syntaxTests/scoping/library_inherited2.sol":"B"},"evmVersion":"istanbul","libraries":{},"metadata":{"bytecodeHash":"ipfs"},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"syntaxTests/scoping/library_inherited2.sol":{"keccak256":"0xd0619f00638fdfea187368965615dbd599fead93dd14b6558725e85ec7011d96","urls":["bzz-raw://ec7af066be66a223f0d25ba3bf9ba6dc103e1a57531a66a38a5ca2b6ce172f55","dweb:/ipfs/QmW1NrqQNhnY1Tkgr3Z9oM8buCGLUJCJVCDTVejJTT5Vet"]}},"version":1}',
),
ContractReport(
contract_name='Lib',
file_name=Path('syntaxTests/scoping/library_inherited2.sol'),
bytecode='60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea26469706673582212207f9515e2263fa71a7984707e2aefd82241fac15c497386ca798b526f14f8ba6664736f6c63430008000033',
metadata='{"compiler":{"version":"0.8.0+commit.c7dfd78e"},"language":"Solidity","output":{"abi":[],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"compilationTarget":{"syntaxTests/scoping/library_inherited2.sol":"Lib"},"evmVersion":"istanbul","libraries":{},"metadata":{"bytecodeHash":"ipfs"},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"syntaxTests/scoping/library_inherited2.sol":{"keccak256":"0xd0619f00638fdfea187368965615dbd599fead93dd14b6558725e85ec7011d96","urls":["bzz-raw://ec7af066be66a223f0d25ba3bf9ba6dc103e1a57531a66a38a5ca2b6ce172f55","dweb:/ipfs/QmW1NrqQNhnY1Tkgr3Z9oM8buCGLUJCJVCDTVejJTT5Vet"]}},"version":1}',
),
# pragma pylint: enable=line-too-long
]
)
report = parse_standard_json_output(
Path('syntaxTests/scoping/library_inherited2.sol'),
LIBRARY_INHERITED2_SOL_JSON_OUTPUT,
)
self.assertEqual(report, expected_report)
def test_parse_standard_json_output_should_report_error_on_compiler_errors(self):
expected_report = FileReport(file_name=Path('syntaxTests/pragma/unknown_pragma.sol'), contract_reports=None)
report = parse_standard_json_output(Path('syntaxTests/pragma/unknown_pragma.sol'), UNKNOWN_PRAGMA_SOL_JSON_OUTPUT)
self.assertEqual(report, expected_report)
def test_parse_standard_json_output_should_report_error_on_empty_json(self):
expected_report = FileReport(file_name=Path('file.sol'), contract_reports=None)
self.assertEqual(parse_standard_json_output(Path('file.sol'), '{}'), expected_report)
def test_parse_standard_json_output_should_report_error_if_contracts_is_empty(self):
compiler_output = '{"contracts": {}}'
expected_report = FileReport(file_name=Path('contract.sol'), contract_reports=None)
self.assertEqual(parse_standard_json_output(Path('contract.sol'), compiler_output), expected_report)
def test_parse_standard_json_output_should_report_error_if_every_file_has_no_contracts(self):
compiler_output = dedent("""\
{
"contracts": {
"contract1.sol": {},
"contract2.sol": {}
}
}
""")
expected_report = FileReport(file_name=Path('contract.sol'), contract_reports=None)
self.assertEqual(parse_standard_json_output(Path('contract.sol'), compiler_output), expected_report)
def test_parse_standard_json_output_should_not_report_error_if_there_is_at_least_one_file_with_contracts(self):
compiler_output = dedent("""\
{
"contracts": {
"contract1.sol": {"A": {}},
"contract2.sol": {}
}
}
""")
expected_report = FileReport(
file_name=Path('contract.sol'),
contract_reports=[ContractReport(contract_name='A', file_name=Path('contract1.sol'), bytecode=None, metadata=None)]
)
self.assertEqual(parse_standard_json_output(Path('contract.sol'), compiler_output), expected_report)

View File

@ -0,0 +1,15 @@
from pathlib import Path
from typing import Union
LIBSOLIDITY_TEST_DIR = Path(__file__).parent.parent / 'libsolidity'
FIXTURE_DIR = Path(__file__).parent / 'fixtures'
def load_file(path: Union[Path, str]) -> str:
with open(path, 'r', encoding='utf-8') as f:
return f.read()
def load_fixture(relative_path: Union[Path, str]) -> str:
return load_file(FIXTURE_DIR / relative_path)
def load_libsolidity_test_case(relative_path: Union[Path, str]) -> str:
return load_file(LIBSOLIDITY_TEST_DIR / relative_path)