mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #10675 from ethereum/refactor-bytecode-comparison-scripts
Refactor bytecode comparison scripts
This commit is contained in:
commit
d43693ecca
54
scripts/bytecodecompare/prepare_report.js
Executable file
54
scripts/bytecodecompare/prepare_report.js
Executable 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 = {
|
||||
'language': 'Solidity',
|
||||
'sources': sources,
|
||||
'settings': {
|
||||
'optimizer': {
|
||||
'enabled': optimize
|
||||
},
|
||||
'outputSelection': {'*': {'*': ['evm.bytecode.object', 'metadata']}},
|
||||
'modelChecker': { "engine": 'none' }
|
||||
}
|
||||
@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': {
|
||||
str(source_file_name): {'content': load_source(source_file_name)}
|
||||
},
|
||||
'settings': {
|
||||
'optimizer': {'enabled': optimize},
|
||||
'outputSelection': {'*': {'*': ['evm.bytecode.object', 'metadata']}},
|
||||
'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'))
|
||||
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")
|
||||
}
|
||||
|
||||
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:
|
||||
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),
|
||||
)
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
44
test/scripts/fixtures/unknown_pragma_sol_json_output.json
Normal file
44
test/scripts/fixtures/unknown_pragma_sol_json_output.json
Normal 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": {}
|
||||
}
|
189
test/scripts/test_bytecodecompare_prepare_report.py
Normal file
189
test/scripts/test_bytecodecompare_prepare_report.py
Normal 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)
|
15
test/scripts/unittest_helpers.py
Normal file
15
test/scripts/unittest_helpers.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user