Support Solidity compilation of a file handle (#873)
This preserves the current behavior of using a string value as Solidity source code, but now also supports instances of the `file` type, and will use the `name` property of the file object to pass to the subprocess invocation of solc. This implementation also now ensures resource cleanup for the stdout and stderr pipe handles. Make use of compiling via file handles in multi_tx_analysis This will enable import statements in Solidity code to work for the Manticore CLI out of the box Note, Manticore still wants the source code, so if we compiled using a file handle, rather than a source string, we need to read in the source fixes #705
This commit is contained in:
parent
92cfab3a42
commit
4a98110f4a
@ -658,13 +658,12 @@ class ManticoreEVM(Manticore):
|
||||
return bytecode
|
||||
|
||||
@staticmethod
|
||||
def _compile(source_code, contract_name):
|
||||
""" Compile a Solidity contract, used internally
|
||||
def _run_solc(source_file):
|
||||
''' Compile a source file with the Solidity compiler
|
||||
|
||||
:param source_code: a solidity source code
|
||||
:param contract_name: a string with the name of the contract to analyze
|
||||
:return: name, source_code, bytecode, srcmap, srcmap_runtime, hashes
|
||||
"""
|
||||
:param source_file: a file object for the source file
|
||||
:return: output, warnings
|
||||
'''
|
||||
solc = "solc"
|
||||
|
||||
#check solc version
|
||||
@ -679,43 +678,74 @@ class ManticoreEVM(Manticore):
|
||||
#logger.warning("Unsupported solc version %s", installed_version)
|
||||
pass
|
||||
|
||||
with tempfile.NamedTemporaryFile() as temp:
|
||||
temp.write(source_code)
|
||||
temp.flush()
|
||||
p = Popen([solc, '--combined-json', 'abi,srcmap,srcmap-runtime,bin,hashes,bin-runtime', '--allow-paths', '.', temp.name], stdout=PIPE, stderr=PIPE)
|
||||
|
||||
solc_invocation = [
|
||||
solc,
|
||||
'--combined-json', 'abi,srcmap,srcmap-runtime,bin,hashes,bin-runtime',
|
||||
'--allow-paths', '.',
|
||||
source_file.name
|
||||
]
|
||||
p = Popen(solc_invocation, stdout=PIPE, stderr=PIPE)
|
||||
with p.stdout as stdout, p.stderr as stderr:
|
||||
try:
|
||||
output = json.loads(p.stdout.read())
|
||||
return json.loads(stdout.read()), stderr.read()
|
||||
except ValueError:
|
||||
raise Exception('Solidity compilation error:\n\n{}'.format(p.stderr.read()))
|
||||
raise Exception('Solidity compilation error:\n\n{}'.format(stderr.read()))
|
||||
|
||||
contracts = output.get('contracts', [])
|
||||
if len(contracts) != 1 and contract_name is None:
|
||||
raise Exception('Solidity file must contain exactly one contract or you must use contract parameter to specify which one.')
|
||||
@staticmethod
|
||||
def _compile(source_code, contract_name):
|
||||
""" Compile a Solidity contract, used internally
|
||||
|
||||
name, contract = None, None
|
||||
if contract_name is None:
|
||||
name, contract = contracts.items()[0]
|
||||
else:
|
||||
for n, c in contracts.items():
|
||||
if n.split(":")[1] == contract_name:
|
||||
name, contract = n, c
|
||||
break
|
||||
:param source_code: solidity source as either a string or a file handle
|
||||
:param contract_name: a string with the name of the contract to analyze
|
||||
:return: name, source_code, bytecode, runtime, srcmap, srcmap_runtime, hashes, abi, warnings
|
||||
"""
|
||||
try:
|
||||
file_type = file # Python 2
|
||||
except NameError:
|
||||
from io import IOBase
|
||||
file_type = IOBase # Python 3
|
||||
|
||||
assert(name is not None)
|
||||
name = name.split(':')[1]
|
||||
if isinstance(source_code, str):
|
||||
with tempfile.NamedTemporaryFile() as temp:
|
||||
temp.write(source_code)
|
||||
temp.flush()
|
||||
output, warnings = ManticoreEVM._run_solc(temp)
|
||||
elif isinstance(source_code, file_type):
|
||||
output, warnings = ManticoreEVM._run_solc(source_code)
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
if contract['bin'] == '':
|
||||
raise Exception('Solidity failed to compile your contract.')
|
||||
|
||||
bytecode = contract['bin'].decode('hex')
|
||||
srcmap = contract['srcmap'].split(';')
|
||||
srcmap_runtime = contract['srcmap-runtime'].split(';')
|
||||
hashes = contract['hashes']
|
||||
abi = json.loads(contract['abi'])
|
||||
runtime = contract['bin-runtime'].decode('hex')
|
||||
warnings = p.stderr.read()
|
||||
return name, source_code, bytecode, runtime, srcmap, srcmap_runtime, hashes, abi, warnings
|
||||
contracts = output.get('contracts', [])
|
||||
if len(contracts) != 1 and contract_name is None:
|
||||
raise Exception('Solidity file must contain exactly one contract or you must use contract parameter to specify which one.')
|
||||
|
||||
name, contract = None, None
|
||||
if contract_name is None:
|
||||
name, contract = contracts.items()[0]
|
||||
else:
|
||||
for n, c in contracts.items():
|
||||
if n.split(":")[1] == contract_name:
|
||||
name, contract = n, c
|
||||
break
|
||||
|
||||
assert(name is not None)
|
||||
# capture source code if file handle was passed as arg
|
||||
if isinstance(source_code, file_type):
|
||||
source_path = name.split(':')[0]
|
||||
with open(source_path) as f:
|
||||
source_code = f.read()
|
||||
name = name.split(':')[1]
|
||||
|
||||
if contract['bin'] == '':
|
||||
raise Exception('Solidity failed to compile your contract.')
|
||||
|
||||
bytecode = contract['bin'].decode('hex')
|
||||
srcmap = contract['srcmap'].split(';')
|
||||
srcmap_runtime = contract['srcmap-runtime'].split(';')
|
||||
hashes = contract['hashes']
|
||||
abi = json.loads(contract['abi'])
|
||||
runtime = contract['bin-runtime'].decode('hex')
|
||||
return name, source_code, bytecode, runtime, srcmap, srcmap_runtime, hashes, abi, warnings
|
||||
|
||||
def __init__(self, procs=1, **kwargs):
|
||||
''' A Manticore EVM manager
|
||||
@ -992,11 +1022,9 @@ class ManticoreEVM(Manticore):
|
||||
return status
|
||||
|
||||
def multi_tx_analysis(self, solidity_filename, contract_name=None, tx_limit=None, tx_use_coverage=True, tx_account="attacker"):
|
||||
with open(solidity_filename) as f:
|
||||
source_code = f.read()
|
||||
|
||||
owner_account = self.create_account(balance=1000)
|
||||
contract_account = self.solidity_create_contract(source_code, contract_name=contract_name, owner=owner_account)
|
||||
with open(solidity_filename) as f:
|
||||
contract_account = self.solidity_create_contract(f, contract_name=contract_name, owner=owner_account)
|
||||
attacker_account = self.create_account(balance=1000)
|
||||
|
||||
if tx_account == "attacker":
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import shutil
|
||||
import struct
|
||||
import tempfile
|
||||
import unittest
|
||||
import os
|
||||
import struct
|
||||
|
||||
from manticore.core.plugin import Plugin
|
||||
from manticore.core.smtlib import ConstraintSet, operators
|
||||
@ -365,3 +366,32 @@ class EthHelpersTest(unittest.TestCase):
|
||||
inner_func(None, self.bv, 123)
|
||||
|
||||
|
||||
class EthSolidityCompilerTest(unittest.TestCase):
|
||||
def test_run_solc(self):
|
||||
source_a = '''
|
||||
import "./B.sol";
|
||||
contract A {
|
||||
function callB(B _b) public { _b.fromA(); }
|
||||
function fromB() public { revert(); }
|
||||
}
|
||||
'''
|
||||
source_b = '''
|
||||
import "./A.sol";
|
||||
contract B {
|
||||
function callA(A _a) public { _a.fromB(); }
|
||||
function fromA() public { revert(); }
|
||||
}
|
||||
'''
|
||||
d = tempfile.mkdtemp()
|
||||
try:
|
||||
with open(os.path.join(d, 'A.sol'), 'w') as a, open(os.path.join(d, 'B.sol'), 'w') as b:
|
||||
a.write(source_a)
|
||||
a.flush()
|
||||
b.write(source_b)
|
||||
b.flush()
|
||||
output, warnings = ManticoreEVM._run_solc(a)
|
||||
source_list = output.get('sourceList', [])
|
||||
self.assertIn(a.name, source_list)
|
||||
self.assertIn(b.name, source_list)
|
||||
finally:
|
||||
shutil.rmtree(d)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user