mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #14350 from ethereum/bytecode-report-presets
Bytecode report presets
This commit is contained in:
commit
aca4c86a23
@ -4,6 +4,10 @@ const fs = require('fs')
|
||||
|
||||
const compiler = require('solc')
|
||||
|
||||
SETTINGS_PRESETS = {
|
||||
'legacy-optimize': {optimize: true},
|
||||
'legacy-no-optimize': {optimize: false},
|
||||
}
|
||||
|
||||
function loadSource(sourceFileName, stripSMTPragmas)
|
||||
{
|
||||
@ -23,96 +27,112 @@ function cleanString(string)
|
||||
return (string !== '' ? string : undefined)
|
||||
}
|
||||
|
||||
|
||||
let inputFiles = []
|
||||
let stripSMTPragmas = false
|
||||
let firstFileArgumentIndex = 2
|
||||
let presets = []
|
||||
|
||||
if (process.argv.length >= 3 && process.argv[2] === '--strip-smt-pragmas')
|
||||
for (let i = 2; i < process.argv.length; ++i)
|
||||
{
|
||||
stripSMTPragmas = true
|
||||
firstFileArgumentIndex = 3
|
||||
if (process.argv[i] === '--strip-smt-pragmas')
|
||||
stripSMTPragmas = true
|
||||
else if (process.argv[i] === '--preset')
|
||||
{
|
||||
if (i + 1 === process.argv.length)
|
||||
throw Error("Option --preset was used, but no preset name given.")
|
||||
|
||||
presets.push(process.argv[i + 1])
|
||||
++i;
|
||||
}
|
||||
else
|
||||
inputFiles.push(process.argv[i])
|
||||
}
|
||||
|
||||
for (const optimize of [false, true])
|
||||
if (presets.length === 0)
|
||||
presets = ['legacy-no-optimize', 'legacy-optimize']
|
||||
|
||||
for (const preset of presets)
|
||||
if (!(preset in SETTINGS_PRESETS))
|
||||
throw Error(`Invalid preset name: ${preset}.`)
|
||||
|
||||
for (const preset of presets)
|
||||
{
|
||||
for (const filename of process.argv.slice(firstFileArgumentIndex))
|
||||
settings = SETTINGS_PRESETS[preset]
|
||||
|
||||
for (const filename of inputFiles)
|
||||
{
|
||||
if (filename !== undefined)
|
||||
let input = {
|
||||
language: 'Solidity',
|
||||
sources: {
|
||||
[filename]: {content: loadSource(filename, stripSMTPragmas)}
|
||||
},
|
||||
settings: {
|
||||
optimizer: {enabled: settings.optimize},
|
||||
outputSelection: {'*': {'*': ['evm.bytecode.object', 'metadata']}}
|
||||
}
|
||||
}
|
||||
if (!stripSMTPragmas)
|
||||
input['settings']['modelChecker'] = {engine: 'none'}
|
||||
|
||||
let serializedOutput
|
||||
let result
|
||||
const serializedInput = JSON.stringify(input)
|
||||
|
||||
let internalCompilerError = false
|
||||
try
|
||||
{
|
||||
let input = {
|
||||
language: 'Solidity',
|
||||
sources: {
|
||||
[filename]: {content: loadSource(filename, stripSMTPragmas)}
|
||||
},
|
||||
settings: {
|
||||
optimizer: {enabled: optimize},
|
||||
outputSelection: {'*': {'*': ['evm.bytecode.object', 'metadata']}}
|
||||
}
|
||||
}
|
||||
if (!stripSMTPragmas)
|
||||
input['settings']['modelChecker'] = {engine: 'none'}
|
||||
serializedOutput = compiler.compile(serializedInput)
|
||||
}
|
||||
catch (exception)
|
||||
{
|
||||
internalCompilerError = true
|
||||
}
|
||||
|
||||
let serializedOutput
|
||||
let result
|
||||
const serializedInput = JSON.stringify(input)
|
||||
if (!internalCompilerError)
|
||||
{
|
||||
result = JSON.parse(serializedOutput)
|
||||
|
||||
let internalCompilerError = false
|
||||
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'])
|
||||
// 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
|
||||
// an error in both cases.
|
||||
if (['UnimplementedFeatureError', 'CompilerError', 'CodeGenerationError'].includes(error['type']))
|
||||
{
|
||||
internalCompilerError = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
internalCompilerError ||
|
||||
!('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])
|
||||
if ('errors' in result)
|
||||
for (const error of result['errors'])
|
||||
// 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
|
||||
// an error in both cases.
|
||||
if (['UnimplementedFeatureError', 'CompilerError', 'CodeGenerationError'].includes(error['type']))
|
||||
{
|
||||
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'] &&
|
||||
cleanString(contractResults.evm.bytecode.object) !== undefined
|
||||
)
|
||||
bytecode = cleanString(contractResults.evm.bytecode.object)
|
||||
|
||||
if ('metadata' in contractResults && cleanString(contractResults.metadata) !== undefined)
|
||||
metadata = contractResults.metadata
|
||||
|
||||
console.log(filename + ':' + contractName + ' ' + bytecode)
|
||||
console.log(filename + ':' + contractName + ' ' + metadata)
|
||||
internalCompilerError = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
internalCompilerError ||
|
||||
!('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'] &&
|
||||
cleanString(contractResults.evm.bytecode.object) !== undefined
|
||||
)
|
||||
bytecode = cleanString(contractResults.evm.bytecode.object)
|
||||
|
||||
if ('metadata' in contractResults && cleanString(contractResults.metadata) !== undefined)
|
||||
metadata = contractResults.metadata
|
||||
|
||||
console.log(filename + ':' + contractName + ' ' + bytecode)
|
||||
console.log(filename + ':' + contractName + ' ' + metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,12 +26,29 @@ class CompilerInterface(Enum):
|
||||
STANDARD_JSON = 'standard-json'
|
||||
|
||||
|
||||
class SettingsPreset(Enum):
|
||||
LEGACY_OPTIMIZE = 'legacy-optimize'
|
||||
LEGACY_NO_OPTIMIZE = 'legacy-no-optimize'
|
||||
|
||||
|
||||
class SMTUse(Enum):
|
||||
PRESERVE = 'preserve'
|
||||
DISABLE = 'disable'
|
||||
STRIP_PRAGMAS = 'strip-pragmas'
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CompilerSettings:
|
||||
optimize: bool
|
||||
|
||||
@staticmethod
|
||||
def from_preset(preset: SettingsPreset):
|
||||
return {
|
||||
SettingsPreset.LEGACY_OPTIMIZE: CompilerSettings(optimize=True),
|
||||
SettingsPreset.LEGACY_NO_OPTIMIZE: CompilerSettings(optimize=False),
|
||||
}[preset]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ContractReport:
|
||||
contract_name: str
|
||||
@ -190,13 +207,15 @@ def parse_cli_output(source_file_name: Path, cli_output: str) -> FileReport:
|
||||
def prepare_compiler_input(
|
||||
compiler_path: Path,
|
||||
source_file_name: Path,
|
||||
optimize: bool,
|
||||
force_no_optimize_yul: bool,
|
||||
interface: CompilerInterface,
|
||||
preset: SettingsPreset,
|
||||
smt_use: SMTUse,
|
||||
metadata_option_supported: bool,
|
||||
) -> Tuple[List[str], str]:
|
||||
|
||||
settings = CompilerSettings.from_preset(preset)
|
||||
|
||||
if interface == CompilerInterface.STANDARD_JSON:
|
||||
json_input: dict = {
|
||||
'language': 'Solidity',
|
||||
@ -204,7 +223,7 @@ def prepare_compiler_input(
|
||||
str(source_file_name): {'content': load_source(source_file_name, smt_use)}
|
||||
},
|
||||
'settings': {
|
||||
'optimizer': {'enabled': optimize},
|
||||
'optimizer': {'enabled': settings.optimize},
|
||||
'outputSelection': {'*': {'*': ['evm.bytecode.object', 'metadata']}},
|
||||
}
|
||||
}
|
||||
@ -220,7 +239,7 @@ def prepare_compiler_input(
|
||||
compiler_options = [str(source_file_name), '--bin']
|
||||
if metadata_option_supported:
|
||||
compiler_options.append('--metadata')
|
||||
if optimize:
|
||||
if settings.optimize:
|
||||
compiler_options.append('--optimize')
|
||||
elif force_no_optimize_yul:
|
||||
compiler_options.append('--no-optimize-yul')
|
||||
@ -259,9 +278,9 @@ def detect_metadata_cli_option_support(compiler_path: Path):
|
||||
def run_compiler(
|
||||
compiler_path: Path,
|
||||
source_file_name: Path,
|
||||
optimize: bool,
|
||||
force_no_optimize_yul: bool,
|
||||
interface: CompilerInterface,
|
||||
preset: SettingsPreset,
|
||||
smt_use: SMTUse,
|
||||
metadata_option_supported: bool,
|
||||
tmp_dir: Path,
|
||||
@ -272,9 +291,9 @@ def run_compiler(
|
||||
(command_line, compiler_input) = prepare_compiler_input(
|
||||
compiler_path,
|
||||
Path(source_file_name.name),
|
||||
optimize,
|
||||
force_no_optimize_yul,
|
||||
interface,
|
||||
preset,
|
||||
smt_use,
|
||||
metadata_option_supported,
|
||||
)
|
||||
@ -295,9 +314,9 @@ def run_compiler(
|
||||
(command_line, compiler_input) = prepare_compiler_input(
|
||||
compiler_path.absolute(),
|
||||
Path(source_file_name.name),
|
||||
optimize,
|
||||
force_no_optimize_yul,
|
||||
interface,
|
||||
preset,
|
||||
smt_use,
|
||||
metadata_option_supported,
|
||||
)
|
||||
@ -324,6 +343,7 @@ def generate_report(
|
||||
source_file_names: List[str],
|
||||
compiler_path: Path,
|
||||
interface: CompilerInterface,
|
||||
presets: List[SettingsPreset],
|
||||
smt_use: SMTUse,
|
||||
force_no_optimize_yul: bool,
|
||||
report_file_path: Path,
|
||||
@ -335,16 +355,16 @@ def generate_report(
|
||||
|
||||
try:
|
||||
with open(report_file_path, mode='w', encoding='utf8', newline='\n') as report_file:
|
||||
for optimize in [False, True]:
|
||||
for preset in presets:
|
||||
with TemporaryDirectory(prefix='prepare_report-') as tmp_dir:
|
||||
for source_file_name in sorted(source_file_names):
|
||||
try:
|
||||
report = run_compiler(
|
||||
compiler_path,
|
||||
Path(source_file_name),
|
||||
optimize,
|
||||
force_no_optimize_yul,
|
||||
interface,
|
||||
preset,
|
||||
smt_use,
|
||||
metadata_option_supported,
|
||||
Path(tmp_dir),
|
||||
@ -358,7 +378,7 @@ def generate_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"'{source_file_name}' with preset={preset}\n\n"
|
||||
f"COMPILER STDOUT:\n{exception.stdout}\n"
|
||||
f"COMPILER STDERR:\n{exception.stderr}\n",
|
||||
file=sys.stderr
|
||||
@ -367,7 +387,7 @@ def generate_report(
|
||||
except:
|
||||
print(
|
||||
f"\n\nInterrupted by an exception while processing file "
|
||||
f"'{source_file_name}' with optimize={optimize}\n",
|
||||
f"'{source_file_name}' with preset={preset}\n\n",
|
||||
file=sys.stderr
|
||||
)
|
||||
raise
|
||||
@ -390,6 +410,15 @@ def commandline_parser() -> ArgumentParser:
|
||||
choices=[c.value for c in CompilerInterface],
|
||||
help="Compiler interface to use.",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--preset',
|
||||
dest='presets',
|
||||
default=None,
|
||||
nargs='+',
|
||||
action='append',
|
||||
choices=[p.value for p in SettingsPreset],
|
||||
help="Predefined set of settings to pass to the compiler. More than one can be selected.",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--smt-use',
|
||||
dest='smt_use',
|
||||
@ -418,10 +447,19 @@ def commandline_parser() -> ArgumentParser:
|
||||
|
||||
if __name__ == "__main__":
|
||||
options = commandline_parser().parse_args()
|
||||
|
||||
if options.presets is None:
|
||||
# NOTE: Can't put it in add_argument()'s default because then it would be always present.
|
||||
# See https://github.com/python/cpython/issues/60603
|
||||
presets = [[SettingsPreset.LEGACY_NO_OPTIMIZE.value, SettingsPreset.LEGACY_OPTIMIZE.value]]
|
||||
else:
|
||||
presets = options.presets
|
||||
|
||||
generate_report(
|
||||
glob("*.sol"),
|
||||
Path(options.compiler_path),
|
||||
CompilerInterface(options.interface),
|
||||
[SettingsPreset(p) for preset_group in presets for p in preset_group],
|
||||
SMTUse(options.smt_use),
|
||||
options.force_no_optimize_yul,
|
||||
Path(options.report_file),
|
||||
|
@ -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
|
||||
# pragma pylint: disable=import-error
|
||||
from bytecodecompare.prepare_report import CompilerInterface, FileReport, ContractReport, SMTUse, Statistics
|
||||
from bytecodecompare.prepare_report import CompilerInterface, FileReport, ContractReport, SettingsPreset, SMTUse, Statistics
|
||||
from bytecodecompare.prepare_report import load_source, parse_cli_output, parse_standard_json_output, prepare_compiler_input
|
||||
# pragma pylint: enable=import-error
|
||||
|
||||
@ -224,7 +224,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase):
|
||||
(command_line, compiler_input) = prepare_compiler_input(
|
||||
Path('solc'),
|
||||
SMT_SMOKE_TEST_SOL_PATH,
|
||||
optimize=True,
|
||||
preset=SettingsPreset.LEGACY_OPTIMIZE,
|
||||
force_no_optimize_yul=False,
|
||||
interface=CompilerInterface.STANDARD_JSON,
|
||||
smt_use=SMTUse.DISABLE,
|
||||
@ -238,7 +238,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase):
|
||||
(command_line, compiler_input) = prepare_compiler_input(
|
||||
Path('solc'),
|
||||
SMT_SMOKE_TEST_SOL_PATH,
|
||||
optimize=True,
|
||||
preset=SettingsPreset.LEGACY_OPTIMIZE,
|
||||
force_no_optimize_yul=False,
|
||||
interface=CompilerInterface.CLI,
|
||||
smt_use=SMTUse.DISABLE,
|
||||
@ -273,7 +273,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase):
|
||||
(command_line, compiler_input) = prepare_compiler_input(
|
||||
Path('solc'),
|
||||
SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_PATH,
|
||||
optimize=True,
|
||||
preset=SettingsPreset.LEGACY_OPTIMIZE,
|
||||
force_no_optimize_yul=False,
|
||||
interface=CompilerInterface.STANDARD_JSON,
|
||||
smt_use=SMTUse.DISABLE,
|
||||
@ -287,7 +287,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase):
|
||||
(_command_line, compiler_input) = prepare_compiler_input(
|
||||
Path('solc'),
|
||||
SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_PATH,
|
||||
optimize=True,
|
||||
preset=SettingsPreset.LEGACY_OPTIMIZE,
|
||||
force_no_optimize_yul=True,
|
||||
interface=CompilerInterface.CLI,
|
||||
smt_use=SMTUse.DISABLE,
|
||||
@ -300,7 +300,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase):
|
||||
(command_line, compiler_input) = prepare_compiler_input(
|
||||
Path('solc'),
|
||||
SMT_SMOKE_TEST_SOL_PATH,
|
||||
optimize=False,
|
||||
preset=SettingsPreset.LEGACY_NO_OPTIMIZE,
|
||||
force_no_optimize_yul=True,
|
||||
interface=CompilerInterface.CLI,
|
||||
smt_use=SMTUse.DISABLE,
|
||||
@ -317,7 +317,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase):
|
||||
(command_line, compiler_input) = prepare_compiler_input(
|
||||
Path('solc'),
|
||||
SMT_SMOKE_TEST_SOL_PATH,
|
||||
optimize=True,
|
||||
preset=SettingsPreset.LEGACY_OPTIMIZE,
|
||||
force_no_optimize_yul=False,
|
||||
interface=CompilerInterface.CLI,
|
||||
smt_use=SMTUse.PRESERVE,
|
||||
|
Loading…
Reference in New Issue
Block a user