mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #10679 from ethereum/backwards-compatibility-for-bytecode-comparison
Backwards compatibility for bytecode comparison
This commit is contained in:
commit
358324ed3e
@ -4,29 +4,73 @@ const fs = require('fs')
|
|||||||
|
|
||||||
const compiler = require('./solc-js/wrapper.js')(require('./solc-js/soljson.js'))
|
const compiler = require('./solc-js/wrapper.js')(require('./solc-js/soljson.js'))
|
||||||
|
|
||||||
|
|
||||||
|
function loadSource(sourceFileName, stripSMTPragmas)
|
||||||
|
{
|
||||||
|
source = fs.readFileSync(sourceFileName).toString()
|
||||||
|
|
||||||
|
if (stripSMTPragmas)
|
||||||
|
// NOTE: replace() with string parameter replaces only the first occurrence.
|
||||||
|
return source.replace('pragma experimental SMTChecker;', '');
|
||||||
|
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanString(string)
|
||||||
|
{
|
||||||
|
if (string !== undefined)
|
||||||
|
string = string.trim()
|
||||||
|
return (string !== '' ? string : undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let stripSMTPragmas = false
|
||||||
|
let firstFileArgumentIndex = 2
|
||||||
|
|
||||||
|
if (process.argv.length >= 3 && process.argv[2] === '--strip-smt-pragmas')
|
||||||
|
{
|
||||||
|
stripSMTPragmas = true
|
||||||
|
firstFileArgumentIndex = 3
|
||||||
|
}
|
||||||
|
|
||||||
for (const optimize of [false, true])
|
for (const optimize of [false, true])
|
||||||
{
|
{
|
||||||
for (const filename of process.argv.slice(2))
|
for (const filename of process.argv.slice(firstFileArgumentIndex))
|
||||||
{
|
{
|
||||||
if (filename !== undefined)
|
if (filename !== undefined)
|
||||||
{
|
{
|
||||||
const input = {
|
let input = {
|
||||||
language: 'Solidity',
|
language: 'Solidity',
|
||||||
sources: {
|
sources: {
|
||||||
[filename]: {content: fs.readFileSync(filename).toString()}
|
[filename]: {content: loadSource(filename, stripSMTPragmas)}
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
optimizer: {enabled: optimize},
|
optimizer: {enabled: optimize},
|
||||||
outputSelection: {'*': {'*': ['evm.bytecode.object', 'metadata']}},
|
outputSelection: {'*': {'*': ['evm.bytecode.object', 'metadata']}}
|
||||||
"modelChecker": {"engine": "none"}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!stripSMTPragmas)
|
||||||
|
input['settings']['modelChecker'] = {engine: 'none'}
|
||||||
|
|
||||||
const result = JSON.parse(compiler.compile(JSON.stringify(input)))
|
let serializedOutput
|
||||||
|
let result
|
||||||
|
const serializedInput = JSON.stringify(input)
|
||||||
|
|
||||||
let internalCompilerError = false
|
let internalCompilerError = false
|
||||||
if ('errors' in result)
|
try
|
||||||
{
|
{
|
||||||
|
serializedOutput = compiler.compile(serializedInput)
|
||||||
|
}
|
||||||
|
catch (exception)
|
||||||
|
{
|
||||||
|
internalCompilerError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!internalCompilerError)
|
||||||
|
{
|
||||||
|
result = JSON.parse(serializedOutput)
|
||||||
|
|
||||||
|
if ('errors' in result)
|
||||||
for (const error of result['errors'])
|
for (const error of result['errors'])
|
||||||
// JSON interface still returns contract metadata in case of an internal compiler error while
|
// JSON interface still returns contract metadata in case of an internal compiler error while
|
||||||
// CLI interface does not. To make reports comparable we must force this case to be detected as
|
// CLI interface does not. To make reports comparable we must force this case to be detected as
|
||||||
@ -39,10 +83,10 @@ for (const optimize of [false, true])
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
internalCompilerError ||
|
||||||
!('contracts' in result) ||
|
!('contracts' in result) ||
|
||||||
Object.keys(result['contracts']).length === 0 ||
|
Object.keys(result['contracts']).length === 0 ||
|
||||||
Object.keys(result['contracts']).every(file => Object.keys(result['contracts'][file]).length === 0) ||
|
Object.keys(result['contracts']).every(file => Object.keys(result['contracts'][file]).length === 0)
|
||||||
internalCompilerError
|
|
||||||
)
|
)
|
||||||
// NOTE: do not exit here because this may be run on source which cannot be compiled
|
// NOTE: do not exit here because this may be run on source which cannot be compiled
|
||||||
console.log(filename + ': <ERROR>')
|
console.log(filename + ': <ERROR>')
|
||||||
@ -55,10 +99,15 @@ for (const optimize of [false, true])
|
|||||||
let bytecode = '<NO BYTECODE>'
|
let bytecode = '<NO BYTECODE>'
|
||||||
let metadata = '<NO METADATA>'
|
let metadata = '<NO METADATA>'
|
||||||
|
|
||||||
if ('evm' in contractResults && 'bytecode' in contractResults['evm'] && 'object' in contractResults['evm']['bytecode'])
|
if (
|
||||||
bytecode = contractResults.evm.bytecode.object
|
'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
|
metadata = contractResults.metadata
|
||||||
|
|
||||||
console.log(filename + ':' + contractName + ' ' + bytecode)
|
console.log(filename + ':' + contractName + ' ' + bytecode)
|
||||||
|
@ -13,9 +13,12 @@ from tempfile import TemporaryDirectory
|
|||||||
from typing import List, Optional, Tuple, Union
|
from typing import List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
|
||||||
CONTRACT_SEPARATOR_PATTERN = re.compile(r'^======= (?P<file_name>.+):(?P<contract_name>[^:]+) =======$', re.MULTILINE)
|
CONTRACT_SEPARATOR_PATTERN = re.compile(
|
||||||
BYTECODE_REGEX = re.compile(r'^Binary:\n(?P<bytecode>.*)$', re.MULTILINE)
|
r'^ *======= +(?:(?P<file_name>.+) *:)? *(?P<contract_name>[^:]+) +======= *$',
|
||||||
METADATA_REGEX = re.compile(r'^Metadata:\n(?P<metadata>\{.*\})$', re.MULTILINE)
|
re.MULTILINE
|
||||||
|
)
|
||||||
|
BYTECODE_REGEX = re.compile(r'^ *Binary: *\n(?P<bytecode>.*[0-9a-f$_]+.*)$', re.MULTILINE)
|
||||||
|
METADATA_REGEX = re.compile(r'^ *Metadata: *\n *(?P<metadata>\{.*\}) *$', re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
class CompilerInterface(Enum):
|
class CompilerInterface(Enum):
|
||||||
@ -23,10 +26,16 @@ class CompilerInterface(Enum):
|
|||||||
STANDARD_JSON = 'standard-json'
|
STANDARD_JSON = 'standard-json'
|
||||||
|
|
||||||
|
|
||||||
|
class SMTUse(Enum):
|
||||||
|
PRESERVE = 'preserve'
|
||||||
|
DISABLE = 'disable'
|
||||||
|
STRIP_PRAGMAS = 'strip-pragmas'
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class ContractReport:
|
class ContractReport:
|
||||||
contract_name: str
|
contract_name: str
|
||||||
file_name: Path
|
file_name: Optional[Path]
|
||||||
bytecode: Optional[str]
|
bytecode: Optional[str]
|
||||||
metadata: Optional[str]
|
metadata: Optional[str]
|
||||||
|
|
||||||
@ -54,15 +63,23 @@ class FileReport:
|
|||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
def load_source(path: Union[Path, str]) -> str:
|
def load_source(path: Union[Path, str], smt_use: SMTUse) -> str:
|
||||||
# NOTE: newline='' disables newline conversion.
|
# NOTE: newline='' disables newline conversion.
|
||||||
# We want the file exactly as is because changing even a single byte in the source affects metadata.
|
# We want the file exactly as is because changing even a single byte in the source affects metadata.
|
||||||
with open(path, mode='r', encoding='utf8', newline='') as source_file:
|
with open(path, mode='r', encoding='utf8', newline='') as source_file:
|
||||||
file_content = source_file.read()
|
file_content = source_file.read()
|
||||||
|
|
||||||
|
if smt_use == SMTUse.STRIP_PRAGMAS:
|
||||||
|
return file_content.replace('pragma experimental SMTChecker;', '', 1)
|
||||||
|
|
||||||
return file_content
|
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:
|
def parse_standard_json_output(source_file_name: Path, standard_json_output: str) -> FileReport:
|
||||||
decoded_json_output = json.loads(standard_json_output.strip())
|
decoded_json_output = json.loads(standard_json_output.strip())
|
||||||
|
|
||||||
@ -89,8 +106,8 @@ def parse_standard_json_output(source_file_name: Path, standard_json_output: str
|
|||||||
file_report.contract_reports.append(ContractReport(
|
file_report.contract_reports.append(ContractReport(
|
||||||
contract_name=contract_name,
|
contract_name=contract_name,
|
||||||
file_name=Path(file_name),
|
file_name=Path(file_name),
|
||||||
bytecode=contract_results.get('evm', {}).get('bytecode', {}).get('object'),
|
bytecode=clean_string(contract_results.get('evm', {}).get('bytecode', {}).get('object')),
|
||||||
metadata=contract_results.get('metadata'),
|
metadata=clean_string(contract_results.get('metadata')),
|
||||||
))
|
))
|
||||||
|
|
||||||
return file_report
|
return file_report
|
||||||
@ -113,55 +130,92 @@ def parse_cli_output(source_file_name: Path, cli_output: str) -> FileReport:
|
|||||||
|
|
||||||
assert file_report.contract_reports is not None
|
assert file_report.contract_reports is not None
|
||||||
file_report.contract_reports.append(ContractReport(
|
file_report.contract_reports.append(ContractReport(
|
||||||
contract_name=contract_name,
|
contract_name=contract_name.strip(),
|
||||||
file_name=Path(file_name),
|
file_name=Path(file_name.strip()) if file_name is not None else None,
|
||||||
bytecode=bytecode_match['bytecode'] if bytecode_match is not None else None,
|
bytecode=clean_string(bytecode_match['bytecode'] if bytecode_match is not None else None),
|
||||||
metadata=metadata_match['metadata'] if metadata_match is not None else None,
|
metadata=clean_string(metadata_match['metadata'] if metadata_match is not None else None),
|
||||||
))
|
))
|
||||||
|
|
||||||
return file_report
|
return file_report
|
||||||
|
|
||||||
|
|
||||||
def prepare_compiler_input(
|
def prepare_compiler_input( # pylint: disable=too-many-arguments
|
||||||
compiler_path: Path,
|
compiler_path: Path,
|
||||||
source_file_name: Path,
|
source_file_name: Path,
|
||||||
optimize: bool,
|
optimize: bool,
|
||||||
interface: CompilerInterface
|
force_no_optimize_yul: bool,
|
||||||
|
interface: CompilerInterface,
|
||||||
|
smt_use: SMTUse,
|
||||||
|
metadata_option_supported: bool,
|
||||||
) -> Tuple[List[str], str]:
|
) -> Tuple[List[str], str]:
|
||||||
|
|
||||||
if interface == CompilerInterface.STANDARD_JSON:
|
if interface == CompilerInterface.STANDARD_JSON:
|
||||||
json_input: dict = {
|
json_input: dict = {
|
||||||
'language': 'Solidity',
|
'language': 'Solidity',
|
||||||
'sources': {
|
'sources': {
|
||||||
str(source_file_name): {'content': load_source(source_file_name)}
|
str(source_file_name): {'content': load_source(source_file_name, smt_use)}
|
||||||
},
|
},
|
||||||
'settings': {
|
'settings': {
|
||||||
'optimizer': {'enabled': optimize},
|
'optimizer': {'enabled': optimize},
|
||||||
'outputSelection': {'*': {'*': ['evm.bytecode.object', 'metadata']}},
|
'outputSelection': {'*': {'*': ['evm.bytecode.object', 'metadata']}},
|
||||||
'modelChecker': {'engine': 'none'},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if smt_use == SMTUse.DISABLE:
|
||||||
|
json_input['settings']['modelChecker'] = {'engine': 'none'}
|
||||||
|
|
||||||
command_line = [str(compiler_path), '--standard-json']
|
command_line = [str(compiler_path), '--standard-json']
|
||||||
compiler_input = json.dumps(json_input)
|
compiler_input = json.dumps(json_input)
|
||||||
else:
|
else:
|
||||||
assert interface == CompilerInterface.CLI
|
assert interface == CompilerInterface.CLI
|
||||||
|
|
||||||
compiler_options = [str(source_file_name), '--bin', '--metadata', '--model-checker-engine', 'none']
|
compiler_options = [str(source_file_name), '--bin']
|
||||||
|
if metadata_option_supported:
|
||||||
|
compiler_options.append('--metadata')
|
||||||
if optimize:
|
if optimize:
|
||||||
compiler_options.append('--optimize')
|
compiler_options.append('--optimize')
|
||||||
|
elif force_no_optimize_yul:
|
||||||
|
compiler_options.append('--no-optimize-yul')
|
||||||
|
if smt_use == SMTUse.DISABLE:
|
||||||
|
compiler_options += ['--model-checker-engine', 'none']
|
||||||
|
|
||||||
command_line = [str(compiler_path)] + compiler_options
|
command_line = [str(compiler_path)] + compiler_options
|
||||||
compiler_input = load_source(source_file_name)
|
compiler_input = load_source(source_file_name, smt_use)
|
||||||
|
|
||||||
return (command_line, compiler_input)
|
return (command_line, compiler_input)
|
||||||
|
|
||||||
|
|
||||||
def run_compiler(
|
def detect_metadata_cli_option_support(compiler_path: Path):
|
||||||
|
process = subprocess.run(
|
||||||
|
[str(compiler_path.absolute()), '--metadata', '-'],
|
||||||
|
input="contract C {}",
|
||||||
|
encoding='utf8',
|
||||||
|
capture_output=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
negative_response = "unrecognised option '--metadata'".strip()
|
||||||
|
if (process.returncode == 0) != (process.stderr.strip() != negative_response):
|
||||||
|
# If the error is other than expected or there's an error message but no error, don't try
|
||||||
|
# to guess. Just fail.
|
||||||
|
print(
|
||||||
|
f"Compiler exit code: {process.returncode}\n"
|
||||||
|
f"Compiler output:\n{process.stderr}\n",
|
||||||
|
file=sys.stderr
|
||||||
|
)
|
||||||
|
raise Exception("Failed to determine if the compiler supports the --metadata option.")
|
||||||
|
|
||||||
|
return process.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def run_compiler( # pylint: disable=too-many-arguments
|
||||||
compiler_path: Path,
|
compiler_path: Path,
|
||||||
source_file_name: Path,
|
source_file_name: Path,
|
||||||
optimize: bool,
|
optimize: bool,
|
||||||
|
force_no_optimize_yul: bool,
|
||||||
interface: CompilerInterface,
|
interface: CompilerInterface,
|
||||||
|
smt_use: SMTUse,
|
||||||
|
metadata_option_supported: bool,
|
||||||
tmp_dir: Path,
|
tmp_dir: Path,
|
||||||
) -> FileReport:
|
) -> FileReport:
|
||||||
|
|
||||||
@ -170,7 +224,10 @@ def run_compiler(
|
|||||||
compiler_path,
|
compiler_path,
|
||||||
Path(source_file_name.name),
|
Path(source_file_name.name),
|
||||||
optimize,
|
optimize,
|
||||||
interface
|
force_no_optimize_yul,
|
||||||
|
interface,
|
||||||
|
smt_use,
|
||||||
|
metadata_option_supported,
|
||||||
)
|
)
|
||||||
|
|
||||||
process = subprocess.run(
|
process = subprocess.run(
|
||||||
@ -190,7 +247,10 @@ def run_compiler(
|
|||||||
compiler_path.absolute(),
|
compiler_path.absolute(),
|
||||||
Path(source_file_name.name),
|
Path(source_file_name.name),
|
||||||
optimize,
|
optimize,
|
||||||
interface
|
force_no_optimize_yul,
|
||||||
|
interface,
|
||||||
|
smt_use,
|
||||||
|
metadata_option_supported,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a copy that we can use directly with the CLI interface
|
# Create a copy that we can use directly with the CLI interface
|
||||||
@ -211,13 +271,30 @@ def run_compiler(
|
|||||||
return parse_cli_output(Path(source_file_name), process.stdout)
|
return parse_cli_output(Path(source_file_name), process.stdout)
|
||||||
|
|
||||||
|
|
||||||
def generate_report(source_file_names: List[str], compiler_path: Path, interface: CompilerInterface):
|
def generate_report(
|
||||||
|
source_file_names: List[str],
|
||||||
|
compiler_path: Path,
|
||||||
|
interface: CompilerInterface,
|
||||||
|
smt_use: SMTUse,
|
||||||
|
force_no_optimize_yul: bool
|
||||||
|
):
|
||||||
|
metadata_option_supported = detect_metadata_cli_option_support(compiler_path)
|
||||||
|
|
||||||
with open('report.txt', mode='w', encoding='utf8', newline='\n') as report_file:
|
with open('report.txt', mode='w', encoding='utf8', newline='\n') as report_file:
|
||||||
for optimize in [False, True]:
|
for optimize in [False, True]:
|
||||||
with TemporaryDirectory(prefix='prepare_report-') as tmp_dir:
|
with TemporaryDirectory(prefix='prepare_report-') as tmp_dir:
|
||||||
for source_file_name in sorted(source_file_names):
|
for source_file_name in sorted(source_file_names):
|
||||||
try:
|
try:
|
||||||
report = run_compiler(compiler_path, Path(source_file_name), optimize, interface, Path(tmp_dir))
|
report = run_compiler(
|
||||||
|
compiler_path,
|
||||||
|
Path(source_file_name),
|
||||||
|
optimize,
|
||||||
|
force_no_optimize_yul,
|
||||||
|
interface,
|
||||||
|
smt_use,
|
||||||
|
metadata_option_supported,
|
||||||
|
Path(tmp_dir),
|
||||||
|
)
|
||||||
report_file.write(report.format_report())
|
report_file.write(report.format_report())
|
||||||
except subprocess.CalledProcessError as exception:
|
except subprocess.CalledProcessError as exception:
|
||||||
print(
|
print(
|
||||||
@ -250,7 +327,21 @@ def commandline_parser() -> ArgumentParser:
|
|||||||
dest='interface',
|
dest='interface',
|
||||||
default=CompilerInterface.STANDARD_JSON.value,
|
default=CompilerInterface.STANDARD_JSON.value,
|
||||||
choices=[c.value for c in CompilerInterface],
|
choices=[c.value for c in CompilerInterface],
|
||||||
help="Compiler interface to use."
|
help="Compiler interface to use.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--smt-use',
|
||||||
|
dest='smt_use',
|
||||||
|
default=SMTUse.DISABLE.value,
|
||||||
|
choices=[s.value for s in SMTUse],
|
||||||
|
help="What to do about contracts that use the experimental SMT checker."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--force-no-optimize-yul',
|
||||||
|
dest='force_no_optimize_yul',
|
||||||
|
default=False,
|
||||||
|
action='store_true',
|
||||||
|
help="Explicitly disable Yul optimizer in CLI runs without optimization to work around a bug in solc 0.6.0 and 0.6.1."
|
||||||
)
|
)
|
||||||
return parser;
|
return parser;
|
||||||
|
|
||||||
@ -261,4 +352,6 @@ if __name__ == "__main__":
|
|||||||
glob("*.sol"),
|
glob("*.sol"),
|
||||||
Path(options.compiler_path),
|
Path(options.compiler_path),
|
||||||
CompilerInterface(options.interface),
|
CompilerInterface(options.interface),
|
||||||
|
SMTUse(options.smt_use),
|
||||||
|
options.force_no_optimize_yul,
|
||||||
)
|
)
|
||||||
|
8
test/scripts/fixtures/solc_0.4.0_cli_output.txt
Normal file
8
test/scripts/fixtures/solc_0.4.0_cli_output.txt
Normal file
@ -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
|
10
test/scripts/fixtures/solc_0.4.8_cli_output.txt
Normal file
10
test/scripts/fixtures/solc_0.4.8_cli_output.txt
Normal file
@ -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}
|
@ -9,7 +9,7 @@ from unittest_helpers import FIXTURE_DIR, LIBSOLIDITY_TEST_DIR, load_fixture, lo
|
|||||||
|
|
||||||
# NOTE: This test file file only works with scripts/ added to PYTHONPATH so pylint can't find the imports
|
# NOTE: This test file file only works with scripts/ added to PYTHONPATH so pylint can't find the imports
|
||||||
# pragma pylint: disable=import-error
|
# pragma pylint: disable=import-error
|
||||||
from bytecodecompare.prepare_report import CompilerInterface, FileReport, ContractReport
|
from bytecodecompare.prepare_report import CompilerInterface, FileReport, ContractReport, SMTUse
|
||||||
from bytecodecompare.prepare_report import load_source, parse_cli_output, parse_standard_json_output, prepare_compiler_input
|
from bytecodecompare.prepare_report import load_source, parse_cli_output, parse_standard_json_output, prepare_compiler_input
|
||||||
# pragma pylint: enable=import-error
|
# pragma pylint: enable=import-error
|
||||||
|
|
||||||
@ -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_JSON_OUTPUT = load_fixture('code_generation_error_json_output.json')
|
||||||
CODE_GENERATION_ERROR_CLI_OUTPUT = load_fixture('code_generation_error_cli_output.txt')
|
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):
|
class PrepareReportTestBase(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -98,48 +101,58 @@ class TestFileReport(PrepareReportTestBase):
|
|||||||
|
|
||||||
|
|
||||||
class TestLoadSource(PrepareReportTestBase):
|
class TestLoadSource(PrepareReportTestBase):
|
||||||
def test_load_source(self):
|
def test_load_source_should_strip_smt_pragmas_if_requested(self):
|
||||||
self.assertEqual(load_source(SMT_SMOKE_TEST_SOL_PATH), SMT_SMOKE_TEST_SOL_CODE)
|
expected_file_content = (
|
||||||
|
|
||||||
def test_load_source_preserves_lf_newlines(self):
|
|
||||||
expected_output = (
|
|
||||||
"pragma experimental SMTChecker;\n"
|
|
||||||
"\n"
|
"\n"
|
||||||
"contract C {\n"
|
"contract C {\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(load_source(SMT_CONTRACT_WITH_LF_NEWLINES_SOL_PATH), expected_output)
|
self.assertEqual(load_source(SMT_SMOKE_TEST_SOL_PATH, SMTUse.STRIP_PRAGMAS), expected_file_content)
|
||||||
|
|
||||||
|
def test_load_source_should_not_strip_smt_pragmas_if_not_requested(self):
|
||||||
|
self.assertEqual(load_source(SMT_SMOKE_TEST_SOL_PATH, SMTUse.DISABLE), SMT_SMOKE_TEST_SOL_CODE)
|
||||||
|
self.assertEqual(load_source(SMT_SMOKE_TEST_SOL_PATH, SMTUse.PRESERVE), SMT_SMOKE_TEST_SOL_CODE)
|
||||||
|
|
||||||
|
def test_load_source_preserves_lf_newlines(self):
|
||||||
|
expected_output = (
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
"contract C {\n"
|
||||||
|
"}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(load_source(SMT_CONTRACT_WITH_LF_NEWLINES_SOL_PATH, SMTUse.STRIP_PRAGMAS), expected_output)
|
||||||
|
|
||||||
def test_load_source_preserves_crlf_newlines(self):
|
def test_load_source_preserves_crlf_newlines(self):
|
||||||
expected_output = (
|
expected_output = (
|
||||||
"pragma experimental SMTChecker;\r\n"
|
"\r\n"
|
||||||
"\r\n"
|
"\r\n"
|
||||||
"contract C {\r\n"
|
"contract C {\r\n"
|
||||||
"}\r\n"
|
"}\r\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(load_source(SMT_CONTRACT_WITH_CRLF_NEWLINES_SOL_PATH), expected_output)
|
self.assertEqual(load_source(SMT_CONTRACT_WITH_CRLF_NEWLINES_SOL_PATH, SMTUse.STRIP_PRAGMAS), expected_output)
|
||||||
|
|
||||||
def test_load_source_preserves_cr_newlines(self):
|
def test_load_source_preserves_cr_newlines(self):
|
||||||
expected_output = (
|
expected_output = (
|
||||||
"pragma experimental SMTChecker;\r"
|
"\r"
|
||||||
"\r"
|
"\r"
|
||||||
"contract C {\r"
|
"contract C {\r"
|
||||||
"}\r"
|
"}\r"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(load_source(SMT_CONTRACT_WITH_CR_NEWLINES_SOL_PATH), expected_output)
|
self.assertEqual(load_source(SMT_CONTRACT_WITH_CR_NEWLINES_SOL_PATH, SMTUse.STRIP_PRAGMAS), expected_output)
|
||||||
|
|
||||||
def test_load_source_preserves_mixed_newlines(self):
|
def test_load_source_preserves_mixed_newlines(self):
|
||||||
expected_output = (
|
expected_output = (
|
||||||
"pragma experimental SMTChecker;\n"
|
"\n"
|
||||||
"\n"
|
"\n"
|
||||||
"contract C {\r"
|
"contract C {\r"
|
||||||
"}\r\n"
|
"}\r\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(load_source(SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_PATH), expected_output)
|
self.assertEqual(load_source(SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_PATH, SMTUse.STRIP_PRAGMAS), expected_output)
|
||||||
|
|
||||||
|
|
||||||
class TestPrepareCompilerInput(PrepareReportTestBase):
|
class TestPrepareCompilerInput(PrepareReportTestBase):
|
||||||
@ -160,7 +173,10 @@ class TestPrepareCompilerInput(PrepareReportTestBase):
|
|||||||
Path('solc'),
|
Path('solc'),
|
||||||
SMT_SMOKE_TEST_SOL_PATH,
|
SMT_SMOKE_TEST_SOL_PATH,
|
||||||
optimize=True,
|
optimize=True,
|
||||||
|
force_no_optimize_yul=False,
|
||||||
interface=CompilerInterface.STANDARD_JSON,
|
interface=CompilerInterface.STANDARD_JSON,
|
||||||
|
smt_use=SMTUse.DISABLE,
|
||||||
|
metadata_option_supported=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(command_line, ['solc', '--standard-json'])
|
self.assertEqual(command_line, ['solc', '--standard-json'])
|
||||||
@ -171,12 +187,15 @@ class TestPrepareCompilerInput(PrepareReportTestBase):
|
|||||||
Path('solc'),
|
Path('solc'),
|
||||||
SMT_SMOKE_TEST_SOL_PATH,
|
SMT_SMOKE_TEST_SOL_PATH,
|
||||||
optimize=True,
|
optimize=True,
|
||||||
|
force_no_optimize_yul=False,
|
||||||
interface=CompilerInterface.CLI,
|
interface=CompilerInterface.CLI,
|
||||||
|
smt_use=SMTUse.DISABLE,
|
||||||
|
metadata_option_supported=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
command_line,
|
command_line,
|
||||||
['solc', str(SMT_SMOKE_TEST_SOL_PATH), '--bin', '--metadata', '--model-checker-engine', 'none', '--optimize']
|
['solc', str(SMT_SMOKE_TEST_SOL_PATH), '--bin', '--metadata', '--optimize', '--model-checker-engine', 'none']
|
||||||
)
|
)
|
||||||
self.assertEqual(compiler_input, SMT_SMOKE_TEST_SOL_CODE)
|
self.assertEqual(compiler_input, SMT_SMOKE_TEST_SOL_CODE)
|
||||||
|
|
||||||
@ -203,7 +222,10 @@ class TestPrepareCompilerInput(PrepareReportTestBase):
|
|||||||
Path('solc'),
|
Path('solc'),
|
||||||
SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_PATH,
|
SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_PATH,
|
||||||
optimize=True,
|
optimize=True,
|
||||||
|
force_no_optimize_yul=False,
|
||||||
interface=CompilerInterface.STANDARD_JSON,
|
interface=CompilerInterface.STANDARD_JSON,
|
||||||
|
smt_use=SMTUse.DISABLE,
|
||||||
|
metadata_option_supported=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(command_line, ['solc', '--standard-json'])
|
self.assertEqual(command_line, ['solc', '--standard-json'])
|
||||||
@ -214,11 +236,48 @@ class TestPrepareCompilerInput(PrepareReportTestBase):
|
|||||||
Path('solc'),
|
Path('solc'),
|
||||||
SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_PATH,
|
SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_PATH,
|
||||||
optimize=True,
|
optimize=True,
|
||||||
|
force_no_optimize_yul=True,
|
||||||
interface=CompilerInterface.CLI,
|
interface=CompilerInterface.CLI,
|
||||||
|
smt_use=SMTUse.DISABLE,
|
||||||
|
metadata_option_supported=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(compiler_input, SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_CODE)
|
self.assertEqual(compiler_input, SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_CODE)
|
||||||
|
|
||||||
|
def test_prepare_compiler_input_for_cli_should_handle_force_no_optimize_yul_flag(self):
|
||||||
|
(command_line, compiler_input) = prepare_compiler_input(
|
||||||
|
Path('solc'),
|
||||||
|
SMT_SMOKE_TEST_SOL_PATH,
|
||||||
|
optimize=False,
|
||||||
|
force_no_optimize_yul=True,
|
||||||
|
interface=CompilerInterface.CLI,
|
||||||
|
smt_use=SMTUse.DISABLE,
|
||||||
|
metadata_option_supported=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
command_line,
|
||||||
|
['solc', str(SMT_SMOKE_TEST_SOL_PATH), '--bin', '--metadata', '--no-optimize-yul', '--model-checker-engine', 'none'],
|
||||||
|
)
|
||||||
|
self.assertEqual(compiler_input, SMT_SMOKE_TEST_SOL_CODE)
|
||||||
|
|
||||||
|
def test_prepare_compiler_input_for_cli_should_not_use_metadata_option_if_not_supported(self):
|
||||||
|
(command_line, compiler_input) = prepare_compiler_input(
|
||||||
|
Path('solc'),
|
||||||
|
SMT_SMOKE_TEST_SOL_PATH,
|
||||||
|
optimize=True,
|
||||||
|
force_no_optimize_yul=False,
|
||||||
|
interface=CompilerInterface.CLI,
|
||||||
|
smt_use=SMTUse.PRESERVE,
|
||||||
|
metadata_option_supported=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
command_line,
|
||||||
|
['solc', str(SMT_SMOKE_TEST_SOL_PATH), '--bin', '--optimize'],
|
||||||
|
)
|
||||||
|
self.assertEqual(compiler_input, SMT_SMOKE_TEST_SOL_CODE)
|
||||||
|
|
||||||
|
|
||||||
class TestParseStandardJSONOutput(PrepareReportTestBase):
|
class TestParseStandardJSONOutput(PrepareReportTestBase):
|
||||||
def test_parse_standard_json_output(self):
|
def test_parse_standard_json_output(self):
|
||||||
@ -320,6 +379,34 @@ class TestParseStandardJSONOutput(PrepareReportTestBase):
|
|||||||
|
|
||||||
|
|
||||||
class TestParseCLIOutput(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):
|
def test_parse_cli_output(self):
|
||||||
expected_report = FileReport(
|
expected_report = FileReport(
|
||||||
file_name=Path('syntaxTests/scoping/library_inherited2.sol'),
|
file_name=Path('syntaxTests/scoping/library_inherited2.sol'),
|
||||||
@ -417,3 +504,109 @@ class TestParseCLIOutput(PrepareReportTestBase):
|
|||||||
expected_report = FileReport(file_name=Path('file.sol'), contract_reports=None)
|
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)
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user