Merge branch 'dev'

pull/371/head
Josselin 5 years ago
commit 28d9981726
  1. 9
      examples/printers/evm.sol
  2. 39
      examples/scripts/convert_to_evm_ins.py
  3. 445
      examples/scripts/test_evm_api.expected_output
  4. 19
      examples/scripts/test_evm_api.sol
  5. 4
      scripts/travis_test_printers.sh
  6. 2
      slither/analyses/evm/__init__.py
  7. 191
      slither/analyses/evm/convert.py
  8. 15
      slither/analyses/evm/evm_cfg_builder.py
  9. 15
      slither/core/cfg/node.py
  10. 48
      slither/core/declarations/contract.py
  11. 44
      slither/detectors/variables/uninitialized_state_variables.py
  12. 3
      slither/printers/all_printers.py
  13. 25
      slither/printers/summary/contract.py
  14. 109
      slither/printers/summary/evm.py
  15. 9
      slither/solc_parsing/declarations/function.py

@ -0,0 +1,9 @@
pragma solidity >=0.4.24 <0.5.4;
contract Test {
function foo() public returns (address) {
address from = msg.sender;
return(from);
}
}

@ -0,0 +1,39 @@
import sys
from slither.slither import Slither
from slither.evm.convert import SourceToEVM
if len(sys.argv) != 2:
print('python3 function_called.py functions_called.sol')
exit(-1)
# Init slither
slither = Slither(sys.argv[1])
# Get the contract evm instructions
contract = slither.get_contract_from_name('Test')
contract_ins = SourceToEVM.get_evm_instructions(contract)
print("## Contract evm instructions: {} ##".format(contract.name))
for ins in contract_ins:
print(str(ins))
# Get the constructor evm instructions
constructor = contract.constructor
print("## Function evm instructions: {} ##".format(constructor.name))
constructor_ins = SourceToEVM.get_evm_instructions(constructor)
for ins in constructor_ins:
print(str(ins))
# Get the function evm instructions
function = contract.get_function_from_signature('foo()')
print("## Function evm instructions: {} ##".format(function.name))
function_ins = SourceToEVM.get_evm_instructions(function)
for ins in function_ins:
print(str(ins))
# Get the node evm instructions
nodes = function.nodes
for node in nodes:
node_ins = SourceToEVM.get_evm_instructions(node)
print("Node evm instructions: {}".format(str(node)))
for ins in node_ins:
print(str(ins))

@ -0,0 +1,445 @@
## Contract evm instructions: Test ##
PUSH1 0x80
PUSH1 0x40
MSTORE
CALLVALUE
DUP1
ISZERO
PUSH2 0x10
JUMPI
PUSH1 0x0
DUP1
REVERT
JUMPDEST
POP
CALLER
PUSH1 0x0
DUP1
PUSH2 0x100
EXP
DUP2
SLOAD
DUP2
PUSH20 0xffffffffffffffffffffffffffffffffffffffff
MUL
NOT
AND
SWAP1
DUP4
PUSH20 0xffffffffffffffffffffffffffffffffffffffff
AND
MUL
OR
SWAP1
SSTORE
POP
PUSH1 0xfa
DUP1
PUSH2 0x5f
PUSH1 0x0
CODECOPY
PUSH1 0x0
RETURN
STOP
PUSH1 0x80
PUSH1 0x40
MSTORE
PUSH1 0x4
CALLDATASIZE
LT
PUSH1 0x49
JUMPI
PUSH1 0x0
CALLDATALOAD
PUSH29 0x100000000000000000000000000000000000000000000000000000000
SWAP1
DIV
PUSH4 0xffffffff
AND
DUP1
PUSH4 0x14ba3f12
EQ
PUSH1 0x4e
JUMPI
DUP1
PUSH4 0xc2985578
EQ
PUSH1 0x8c
JUMPI
JUMPDEST
PUSH1 0x0
DUP1
REVERT
JUMPDEST
CALLVALUE
DUP1
ISZERO
PUSH1 0x59
JUMPI
PUSH1 0x0
DUP1
REVERT
JUMPDEST
POP
PUSH1 0x76
PUSH1 0x4
DUP1
CALLDATASIZE
SUB
DUP2
ADD
SWAP1
DUP1
DUP1
CALLDATALOAD
SWAP1
PUSH1 0x20
ADD
SWAP1
SWAP3
SWAP2
SWAP1
POP
POP
POP
PUSH1 0xb4
JUMP
JUMPDEST
PUSH1 0x40
MLOAD
DUP1
DUP3
DUP2
MSTORE
PUSH1 0x20
ADD
SWAP2
POP
POP
PUSH1 0x40
MLOAD
DUP1
SWAP2
SUB
SWAP1
RETURN
JUMPDEST
CALLVALUE
DUP1
ISZERO
PUSH1 0x97
JUMPI
PUSH1 0x0
DUP1
REVERT
JUMPDEST
POP
PUSH1 0x9e
PUSH1 0xc1
JUMP
JUMPDEST
PUSH1 0x40
MLOAD
DUP1
DUP3
DUP2
MSTORE
PUSH1 0x20
ADD
SWAP2
POP
POP
PUSH1 0x40
MLOAD
DUP1
SWAP2
SUB
SWAP1
RETURN
JUMPDEST
PUSH1 0x0
PUSH1 0xa
DUP3
ADD
SWAP1
POP
SWAP2
SWAP1
POP
JUMP
JUMPDEST
PUSH1 0x0
DUP1
PUSH1 0xa
DUP2
ADD
SWAP2
POP
POP
SWAP1
JUMP
STOP
PUSH1 0x80
PUSH1 0x40
MSTORE
PUSH1 0x4
CALLDATASIZE
LT
PUSH1 0x49
JUMPI
PUSH1 0x0
CALLDATALOAD
PUSH29 0x100000000000000000000000000000000000000000000000000000000
SWAP1
DIV
PUSH4 0xffffffff
AND
DUP1
PUSH4 0x14ba3f12
EQ
PUSH1 0x4e
JUMPI
DUP1
PUSH4 0xc2985578
EQ
PUSH1 0x8c
JUMPI
JUMPDEST
PUSH1 0x0
DUP1
REVERT
JUMPDEST
CALLVALUE
DUP1
ISZERO
PUSH1 0x59
JUMPI
PUSH1 0x0
DUP1
REVERT
JUMPDEST
POP
PUSH1 0x76
PUSH1 0x4
DUP1
CALLDATASIZE
SUB
DUP2
ADD
SWAP1
DUP1
DUP1
CALLDATALOAD
SWAP1
PUSH1 0x20
ADD
SWAP1
SWAP3
SWAP2
SWAP1
POP
POP
POP
PUSH1 0xb4
JUMP
JUMPDEST
PUSH1 0x40
MLOAD
DUP1
DUP3
DUP2
MSTORE
PUSH1 0x20
ADD
SWAP2
POP
POP
PUSH1 0x40
MLOAD
DUP1
SWAP2
SUB
SWAP1
RETURN
JUMPDEST
CALLVALUE
DUP1
ISZERO
PUSH1 0x97
JUMPI
PUSH1 0x0
DUP1
REVERT
JUMPDEST
POP
PUSH1 0x9e
PUSH1 0xc1
JUMP
JUMPDEST
PUSH1 0x40
MLOAD
DUP1
DUP3
DUP2
MSTORE
PUSH1 0x20
ADD
SWAP2
POP
POP
PUSH1 0x40
MLOAD
DUP1
SWAP2
SUB
SWAP1
RETURN
JUMPDEST
PUSH1 0x0
PUSH1 0xa
DUP3
ADD
SWAP1
POP
SWAP2
SWAP1
POP
JUMP
JUMPDEST
PUSH1 0x0
DUP1
PUSH1 0xa
DUP2
ADD
SWAP2
POP
POP
SWAP1
JUMP
STOP
## Function evm instructions: constructor ##
PUSH1 0x80
PUSH1 0x40
MSTORE
CALLVALUE
DUP1
ISZERO
PUSH2 0x10
JUMPI
PUSH1 0x0
DUP1
REVERT
JUMPDEST
POP
CALLER
PUSH1 0x0
DUP1
PUSH2 0x100
EXP
DUP2
SLOAD
DUP2
PUSH20 0xffffffffffffffffffffffffffffffffffffffff
MUL
NOT
AND
SWAP1
DUP4
PUSH20 0xffffffffffffffffffffffffffffffffffffffff
AND
MUL
OR
SWAP1
SSTORE
POP
PUSH1 0xfa
DUP1
PUSH2 0x5f
PUSH1 0x0
CODECOPY
PUSH1 0x0
RETURN
## Function evm instructions: foo ##
JUMPDEST
CALLVALUE
DUP1
ISZERO
PUSH1 0x97
JUMPI
PUSH1 0x0
DUP1
REVERT
JUMPDEST
POP
PUSH1 0x9e
PUSH1 0xc1
JUMP
JUMPDEST
PUSH1 0x40
MLOAD
DUP1
DUP3
DUP2
MSTORE
PUSH1 0x20
ADD
SWAP2
POP
POP
PUSH1 0x40
MLOAD
DUP1
SWAP2
SUB
SWAP1
RETURN
JUMPDEST
PUSH1 0x0
DUP1
PUSH1 0xa
DUP2
ADD
SWAP2
POP
POP
SWAP1
JUMP
Node evm instructions: ENTRY_POINT None
JUMPDEST
CALLVALUE
POP
PUSH1 0x9e
PUSH1 0xc1
JUMP
JUMPDEST
PUSH1 0x40
MLOAD
DUP1
DUP3
DUP2
MSTORE
PUSH1 0x20
ADD
SWAP2
POP
POP
PUSH1 0x40
MLOAD
DUP1
SWAP2
SUB
SWAP1
RETURN
JUMPDEST
PUSH1 0x0
POP
SWAP1
JUMP
Node evm instructions: NEW VARIABLE None
DUP1
Node evm instructions: RETURN (i + 10)
PUSH1 0xa
DUP2
ADD
SWAP2
POP

@ -0,0 +1,19 @@
pragma solidity >=0.4.24 <0.5.4;
contract Test {
address owner;
constructor () public {
owner = msg.sender;
}
function foo() public returns (uint) {
uint i;
return(i+10);
}
function foobar(uint i) public returns (uint) {
return(i+10);
}
}

@ -2,6 +2,9 @@
### Test printer ### Test printer
# Needed for evm printer
pip install evm-cfg-builder
slither "tests/*.json" --print all slither "tests/*.json" --print all
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@ -9,3 +12,4 @@ if [ $? -ne 0 ]; then
exit 1 exit 1
fi fi
slither examples/scripts/test_evm_api.sol --print evm --solc solc-0.5.1

@ -0,0 +1,2 @@
from .convert import generate_source_to_evm_ins_mapping
from .evm_cfg_builder import load_evm_cfg_builder

@ -0,0 +1,191 @@
import logging
from slither.core.declarations import (Contract, Function)
from slither.core.cfg.node import Node
from slither.utils.function import get_function_id
from slither.exceptions import SlitherError
from .evm_cfg_builder import load_evm_cfg_builder
logger = logging.getLogger('ConvertToEVM')
KEY_EVM_INS = "EVM_INSTRUCTIONS"
def get_evm_instructions(obj):
assert isinstance(obj, (Function, Contract, Node))
if KEY_EVM_INS not in obj.context:
CFG = load_evm_cfg_builder()
slither = obj.slither
if not slither.crytic_compile:
raise SlitherError('EVM features require to compile with crytic-compile')
contract_info = {}
function_info = {}
node_info = {}
if isinstance(obj, Node):
contract_info['contract'] = obj.function.contract
elif isinstance(obj, Function):
contract_info['contract'] = obj.contract
else:
contract_info['contract'] = obj
# Get contract runtime bytecode, srcmap and cfg
contract_info['bytecode_runtime'] = slither.crytic_compile.bytecode_runtime(
contract_info['contract'].name)
contract_info['srcmap_runtime'] = slither.crytic_compile.srcmap_runtime(
contract_info['contract'].name)
contract_info['cfg'] = CFG(contract_info['bytecode_runtime'])
# Get contract init bytecode, srcmap and cfg
contract_info['bytecode_init'] = slither.crytic_compile.bytecode_init(contract_info['contract'].name)
contract_info['srcmap_init'] = slither.crytic_compile.srcmap_init(contract_info['contract'].name)
contract_info['cfg_init'] = CFG(contract_info['bytecode_init'])
# Get evm instructions
if isinstance(obj, Contract):
# Get evm instructions for contract
obj.context[KEY_EVM_INS] = _get_evm_instructions_contract(contract_info)
elif isinstance(obj, Function):
# Get evm instructions for function
function_info['function'] = obj
function_info['contract_info'] = contract_info
obj.context[KEY_EVM_INS] = _get_evm_instructions_function(function_info)
else:
# Get evm instructions for node
node_info['node'] = obj
# CFG and srcmap depend on function being constructor or not
if node_info['node'].function.is_constructor:
cfg = contract_info['cfg_init']
srcmap = contract_info['srcmap_init']
else:
cfg = contract_info['cfg']
srcmap = contract_info['srcmap_runtime']
node_info['cfg'] = cfg
node_info['srcmap'] = srcmap
node_info['contract'] = contract_info['contract']
node_info['slither'] = slither
obj.context[KEY_EVM_INS] = _get_evm_instructions_node(node_info)
return obj.context.get(KEY_EVM_INS, [])
def _get_evm_instructions_contract(contract_info):
# Combine the instructions of constructor and the rest of the contract
return contract_info['cfg_init'].instructions + contract_info['cfg'].instructions
def _get_evm_instructions_function(function_info):
function = function_info['function']
# CFG depends on function being constructor or not
if function.is_constructor:
cfg = function_info['contract_info']['cfg_init']
# _dispatcher is the only function recognised by evm-cfg-builder in bytecode_init.
# _dispatcher serves the role of the constructor in init code,
# given that there are no other functions.
# Todo: Could rename it appropriately in evm-cfg-builder
# by detecting that init bytecode is being parsed.
name = "_dispatcher"
hash = ""
else:
cfg = function_info['contract_info']['cfg']
name = function.name
# Get first four bytes of function singature's keccak-256 hash used as function selector
hash = str(hex(get_function_id(function.full_name)))
function_evm = _get_function_evm(cfg, name, hash)
if function_evm is None:
logger.error("Function " + function.name + " not found in the EVM code")
raise SlitherError("Function " + function.name + " not found in the EVM code")
function_ins = []
for basic_block in sorted(function_evm.basic_blocks, key=lambda x: x.start.pc):
for ins in basic_block.instructions:
function_ins.append(ins)
return function_ins
def _get_evm_instructions_node(node_info):
# Get evm instructions for node's contract
contract_pcs = generate_source_to_evm_ins_mapping(node_info['cfg'].instructions,
node_info['srcmap'],
node_info['slither'],
node_info['contract'].source_mapping['filename_absolute'])
contract_file = node_info['slither'].source_code[node_info['contract'].source_mapping['filename_absolute']].encode(
'utf-8')
# Get evm instructions corresponding to node's source line number
node_source_line = contract_file[0:node_info['node'].source_mapping['start']].count("\n".encode("utf-8")) \
+ 1
node_pcs = contract_pcs.get(node_source_line, [])
node_ins = []
for pc in node_pcs:
node_ins.append(node_info['cfg'].get_instruction_at(pc))
return node_ins
def _get_function_evm(cfg, function_name, function_hash):
for function_evm in cfg.functions:
# Match function hash
if function_evm.name[:2] == "0x" and function_evm.name == function_hash:
return function_evm
# Match function name
elif function_evm.name[:2] != "0x" and function_evm.name.split('(')[0] == function_name:
return function_evm
return None
def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither, filename):
"""
Generate Solidity source to EVM instruction mapping using evm_cfg_builder:cfg.instructions
and solc:srcmap_runtime
Returns: Solidity source to EVM instruction mapping
"""
source_to_evm_mapping = {}
file_source = slither.source_code[filename].encode('utf-8')
prev_mapping = []
for idx, mapping in enumerate(srcmap_runtime):
# Parse srcmap_runtime according to its format
# See https://solidity.readthedocs.io/en/v0.5.9/miscellaneous.html#source-mappings
# In order to compress these source mappings especially for bytecode, the following rules are used:
# If a field is empty, the value of the preceding element is used.
# If a : is missing, all following fields are considered empty.
mapping_item = mapping.split(':')
mapping_item += prev_mapping[len(mapping_item):]
for i in range(len(mapping_item)):
if mapping_item[i] == '':
mapping_item[i] = int(prev_mapping[i])
offset, length, file_id, _ = mapping_item
prev_mapping = mapping_item
if file_id == '-1':
# Internal compiler-generated code snippets to be ignored
# See https://github.com/ethereum/solidity/issues/6119#issuecomment-467797635
continue
offset = int(offset)
line_number = file_source[0:offset].count("\n".encode("utf-8")) + 1
# Append evm instructions to the corresponding source line number
# Note: Some evm instructions in mapping are not necessarily in program execution order
# Note: The order depends on how solc creates the srcmap_runtime
source_to_evm_mapping.setdefault(line_number, []).append(evm_instructions[idx].pc)
return source_to_evm_mapping

@ -0,0 +1,15 @@
import logging
from slither.exceptions import SlitherError
logger = logging.getLogger('ConvertToEVM')
def load_evm_cfg_builder():
try:
# Avoiding the addition of evm_cfg_builder as permanent dependency
from evm_cfg_builder.cfg import CFG
return CFG
except ImportError:
logger.error("To use evm features, you need to install evm-cfg-builder")
logger.error("Documentation: https://github.com/crytic/evm_cfg_builder")
logger.error("Installation: pip install evm-cfg-builder")
raise SlitherError("evm-cfg-builder not installed.")

@ -204,6 +204,8 @@ class Node(SourceMapping, ChildFunction):
self._can_reenter = None self._can_reenter = None
self._can_send_eth = None self._can_send_eth = None
self._asm_source_code = None
################################################################################### ###################################################################################
################################################################################### ###################################################################################
# region General's properties # region General's properties
@ -515,6 +517,19 @@ class Node(SourceMapping, ChildFunction):
return True return True
return False return False
# endregion
###################################################################################
###################################################################################
# region EVM
###################################################################################
###################################################################################
@property
def inline_asm(self):
return self._asm_source_code
def add_inline_asm(self, asm):
self._asm_source_code = asm
# endregion # endregion

@ -2,6 +2,7 @@
Contract module Contract module
""" """
import logging import logging
from slither.core.children.child_slither import ChildSlither from slither.core.children.child_slither import ChildSlither
from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.declarations.function import Function from slither.core.declarations.function import Function
@ -42,6 +43,9 @@ class Contract(ChildSlither, SourceMapping):
self._signatures = None self._signatures = None
self._is_upgradeable = None
self._is_upgradeable_proxy = None
self._initial_state_variables = [] # ssa self._initial_state_variables = [] # ssa
@ -811,6 +815,50 @@ class Contract(ChildSlither, SourceMapping):
for function in self.functions + self.modifiers: for function in self.functions + self.modifiers:
function.update_read_write_using_ssa() function.update_read_write_using_ssa()
# endregion
###################################################################################
###################################################################################
# region Upgradeability
###################################################################################
###################################################################################
@property
def is_upgradeable(self):
if self._is_upgradeable is None:
self._is_upgradeable = False
initializable = self.slither.get_contract_from_name('Initializable')
if initializable:
if initializable in self.inheritance:
self._is_upgradeable = True
else:
for c in self.inheritance + [self]:
# This might lead to false positive
if 'upgradeable' in c.name.lower() or 'upgradable' in c.name.lower():
self._is_upgradeable = True
break
return self._is_upgradeable
@property
def is_upgradeable_proxy(self):
from slither.core.cfg.node import NodeType
from slither.slithir.operations import LowLevelCall
if self._is_upgradeable_proxy is None:
self._is_upgradeable_proxy = False
for f in self.functions:
if f.is_fallback:
for node in f.nodes:
for ir in node.irs:
if isinstance(ir, LowLevelCall) and ir.function_name == 'delegatecall':
self._is_upgradeable_proxy = True
return self._is_upgradeable_proxy
if node.type == NodeType.ASSEMBLY:
inline_asm = node.inline_asm
if inline_asm:
if 'delegatecall' in inline_asm:
self._is_upgradeable_proxy = True
return self._is_upgradeable_proxy
return self._is_upgradeable_proxy
# endregion # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################

@ -44,7 +44,7 @@ Initialize all the variables. If a variable is meant to be initialized to zero,
''' '''
@staticmethod @staticmethod
def written_variables(contract): def _written_variables(contract):
ret = [] ret = []
for f in contract.all_functions_called + contract.modifiers: for f in contract.all_functions_called + contract.modifiers:
for n in f.nodes: for n in f.nodes:
@ -57,24 +57,48 @@ Initialize all the variables. If a variable is meant to be initialized to zero,
for param in ir.function.parameters: for param in ir.function.parameters:
if param.location == 'storage': if param.location == 'storage':
ret.append(ir.arguments[idx]) ret.append(ir.arguments[idx])
idx = idx+1 idx = idx + 1
return ret return ret
def _variable_written_in_proxy(self):
# Hack to memoize without having it define in the init
if hasattr(self, '__variables_written_in_proxy'):
return self.__variables_written_in_proxy
variables_written_in_proxy = []
for c in self.slither.contracts:
if c.is_upgradeable_proxy:
variables_written_in_proxy += self._written_variables(c)
self.__variables_written_in_proxy = list(set([v.name for v in variables_written_in_proxy]))
return self.__variables_written_in_proxy
def _written_variables_in_proxy(self, contract):
variables = []
if contract.is_upgradeable:
variables_name_written_in_proxy = self._variable_written_in_proxy()
if variables_name_written_in_proxy:
variables_in_contract = [contract.get_state_variable_from_name(v) for v in variables_name_written_in_proxy]
variables_in_contract = [v for v in variables_in_contract if v]
variables += variables_in_contract
return list(set(variables))
@staticmethod @staticmethod
def read_variables(contract): def _read_variables(contract):
ret = [] ret = []
for f in contract.all_functions_called + contract.modifiers: for f in contract.all_functions_called + contract.modifiers:
ret += f.state_variables_read ret += f.state_variables_read
return ret return ret
def detect_uninitialized(self, contract): def _detect_uninitialized(self, contract):
written_variables = self.written_variables(contract) written_variables = self._written_variables(contract)
read_variables = self.read_variables(contract) written_variables += self._written_variables_in_proxy(contract)
read_variables = self._read_variables(contract)
return [(variable, contract.get_functions_reading_from_variable(variable)) return [(variable, contract.get_functions_reading_from_variable(variable))
for variable in contract.state_variables if variable not in written_variables and\ for variable in contract.state_variables if variable not in written_variables and \
not variable.expression and\ not variable.expression and \
variable in read_variables] variable in read_variables]
def _detect(self): def _detect(self):
""" Detect uninitialized state variables """ Detect uninitialized state variables
@ -85,7 +109,7 @@ Initialize all the variables. If a variable is meant to be initialized to zero,
""" """
results = [] results = []
for c in self.slither.contracts_derived: for c in self.slither.contracts_derived:
ret = self.detect_uninitialized(c) ret = self._detect_uninitialized(c)
for variable, functions in ret: for variable, functions in ret:
info = [variable, " is never initialized. It is used in:\n"] info = [variable, " is never initialized. It is used in:\n"]

@ -14,4 +14,5 @@ from .summary.data_depenency import DataDependency
from .summary.modifier_calls import Modifiers from .summary.modifier_calls import Modifiers
from .summary.require_calls import RequireOrAssert from .summary.require_calls import RequireOrAssert
from .summary.constructor_calls import ConstructorPrinter from .summary.constructor_calls import ConstructorPrinter
from .guidance.echidna import Echidna from .guidance.echidna import Echidna
from .summary.evm import PrinterEVM

@ -24,8 +24,27 @@ class ContractSummary(AbstractPrinter):
all_contracts = [] all_contracts = []
for c in self.contracts: for c in self.contracts:
txt += blue("\n+ Contract %s\n" % c.name)
additional_fields = output.Output('') is_upgradeable_proxy = c.is_upgradeable_proxy
is_upgradeable = c.is_upgradeable
additional_txt_info = ''
if is_upgradeable_proxy:
additional_txt_info += ' (Upgradeable Proxy)'
if is_upgradeable:
additional_txt_info += ' (Upgradeable)'
if c in self.slither.contracts_derived:
additional_txt_info += ' (Most derived contract)'
txt += blue(f"\n+ Contract {c.name}{additional_txt_info}\n")
additional_fields = output.Output('', additional_fields={
'is_upgradeable_proxy': is_upgradeable_proxy,
'is_upgradeable': is_upgradeable,
'is_most_derived': c in self.slither.contracts_derived
})
# Order the function with # Order the function with
# contract_declarer -> list_functions # contract_declarer -> list_functions
@ -49,7 +68,7 @@ class ContractSummary(AbstractPrinter):
txt += " - {}  ({})\n".format(function, function.visibility) txt += " - {}  ({})\n".format(function, function.visibility)
additional_fields.add(function, additional_fields={"visibility": additional_fields.add(function, additional_fields={"visibility":
function.visibility}) function.visibility,})
all_contracts.append((c, additional_fields.data)) all_contracts.append((c, additional_fields.data))

@ -0,0 +1,109 @@
"""
Module printing evm mapping of the contract
"""
from slither.printers.abstract_printer import AbstractPrinter
from slither.analyses.evm import generate_source_to_evm_ins_mapping, load_evm_cfg_builder
from slither.utils.colors import blue, green, magenta, red
class PrinterEVM(AbstractPrinter):
ARGUMENT = 'evm'
HELP = 'Print the evm instructions of nodes in functions'
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#evm'
def output(self, _filename):
"""
_filename is not used
Args:
_filename(string)
"""
txt = ""
if not self.slither.crytic_compile:
txt = 'The EVM printer requires to compile with crytic-compile'
self.info(red(txt))
res = self.generate_output(txt)
return res
evm_info = self._extract_evm_info(self.slither)
for contract in self.slither.contracts_derived:
txt += blue('Contract {}\n'.format(contract.name))
contract_file = self.slither.source_code[contract.source_mapping['filename_absolute']].encode('utf-8')
contract_file_lines = open(contract.source_mapping['filename_absolute'], 'r').readlines()
contract_pcs = {}
contract_cfg = {}
for function in contract.functions:
txt += blue(f'\tFunction {function.canonical_name}\n')
# CFG and source mapping depend on function being constructor or not
if function.is_constructor:
contract_cfg = evm_info['cfg_init', contract.name]
contract_pcs = evm_info['mapping_init', contract.name]
else:
contract_cfg = evm_info['cfg', contract.name]
contract_pcs = evm_info['mapping', contract.name]
for node in function.nodes:
txt += green("\t\tNode: " + str(node) + "\n")
node_source_line = contract_file[0:node.source_mapping['start']].count("\n".encode("utf-8")) + 1
txt += green('\t\tSource line {}: {}\n'.format(node_source_line,
contract_file_lines[node_source_line - 1].rstrip()))
txt += magenta('\t\tEVM Instructions:\n')
node_pcs = contract_pcs.get(node_source_line, [])
for pc in node_pcs:
txt += magenta('\t\t\t0x{:x}: {}\n'.format(int(pc), contract_cfg.get_instruction_at(pc)))
for modifier in contract.modifiers:
txt += blue(f'\tModifier {modifier.canonical_name}\n')
for node in modifier.nodes:
txt += green("\t\tNode: " + str(node) + "\n")
node_source_line = contract_file[0:node.source_mapping['start']].count("\n".encode("utf-8")) + 1
txt += green('\t\tSource line {}: {}\n'.format(node_source_line,
contract_file_lines[node_source_line - 1].rstrip()))
txt += magenta('\t\tEVM Instructions:\n')
node_pcs = contract_pcs.get(node_source_line, [])
for pc in node_pcs:
txt += magenta('\t\t\t0x{:x}: {}\n'.format(int(pc), contract_cfg.get_instruction_at(pc)))
self.info(txt)
res = self.generate_output(txt)
return res
def _extract_evm_info(self, slither):
"""
Extract evm information for all derived contracts using evm_cfg_builder
Returns: evm CFG and Solidity source to Program Counter (pc) mapping
"""
evm_info = {}
CFG = load_evm_cfg_builder()
for contract in slither.contracts_derived:
contract_bytecode_runtime = slither.crytic_compile.bytecode_runtime(contract.name)
contract_srcmap_runtime = slither.crytic_compile.srcmap_runtime(contract.name)
cfg = CFG(contract_bytecode_runtime)
evm_info['cfg', contract.name] = cfg
evm_info['mapping', contract.name] = generate_source_to_evm_ins_mapping(
cfg.instructions,
contract_srcmap_runtime,
slither,
contract.source_mapping['filename_absolute'])
contract_bytecode_init = slither.crytic_compile.bytecode_init(contract.name)
contract_srcmap_init = slither.crytic_compile.srcmap_init(contract.name)
cfg_init = CFG(contract_bytecode_init)
evm_info['cfg_init', contract.name] = cfg_init
evm_info['mapping_init', contract.name] = generate_source_to_evm_ins_mapping(
cfg_init.instructions,
contract_srcmap_init, slither,
contract.source_mapping['filename_absolute'])
return evm_info

@ -680,10 +680,13 @@ class FunctionSolc(Function):
elif name == 'Block': elif name == 'Block':
node = self._parse_block(statement, node) node = self._parse_block(statement, node)
elif name == 'InlineAssembly': elif name == 'InlineAssembly':
break_node = self._new_node(NodeType.ASSEMBLY, statement['src']) asm_node = self._new_node(NodeType.ASSEMBLY, statement['src'])
self._contains_assembly = True self._contains_assembly = True
link_nodes(node, break_node) # Added with solc 0.4.12
node = break_node if 'operations' in statement:
asm_node.add_inline_asm(statement['operations'])
link_nodes(node, asm_node)
node = asm_node
elif name == 'DoWhileStatement': elif name == 'DoWhileStatement':
node = self._parse_dowhile(statement, node) node = self._parse_dowhile(statement, node)
# For Continue / Break / Return / Throw # For Continue / Break / Return / Throw

Loading…
Cancel
Save