Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions scripts/bytecodecompare/prepare_report.js
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
178 changes: 142 additions & 36 deletions scripts/bytecodecompare/prepare_report.py
Original file line number Diff line number Diff line change
@@ -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),
)
55 changes: 2 additions & 53 deletions scripts/bytecodecompare/storebytecode.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
63 changes: 63 additions & 0 deletions test/scripts/fixtures/library_inherited2_sol_json_output.json
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Loading