diff --git a/setup.py b/setup.py index 03fe64c426..86db4fa9ac 100644 --- a/setup.py +++ b/setup.py @@ -14,8 +14,8 @@ install_requires=[ "prettytable>=0.7.2", "pycryptodome>=3.4.6", - "crytic-compile>=0.2.4", - # "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile", + # "crytic-compile>=0.2.4", + "crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile", ], extras_require={ "dev": [ diff --git a/slither/core/scope/scope.py b/slither/core/scope/scope.py index c6d18556e6..2d1c114910 100644 --- a/slither/core/scope/scope.py +++ b/slither/core/scope/scope.py @@ -1,4 +1,7 @@ -from typing import List, Any, Dict, Optional, Union, Set +from typing import List, Any, Dict, Optional, Union, Set, TypeVar, Callable + +from crytic_compile import CompilationUnit +from crytic_compile.source_unit import SourceUnit from crytic_compile.utils.naming import Filename from slither.core.declarations import Contract, Import, Pragma @@ -98,6 +101,117 @@ def get_contract_from_name(self, name: Union[str, Constant]) -> Optional[Contrac return self.contracts.get(name.name, None) return self.contracts.get(name, None) + AbstractReturnType = TypeVar("AbstractReturnType") + + def _generic_source_unit_getter( + self, + crytic_compile_compilation_unit: CompilationUnit, + name: str, + getter: Callable[[SourceUnit], Dict[str, AbstractReturnType]], + ) -> Optional[AbstractReturnType]: + + assert self.filename in crytic_compile_compilation_unit.source_units + + source_unit = crytic_compile_compilation_unit.source_unit(self.filename) + + if name in getter(source_unit): + return getter(source_unit)[name] + + for scope in self.accessible_scopes: + source_unit = crytic_compile_compilation_unit.source_unit(scope.filename) + if name in getter(source_unit): + return getter(source_unit)[name] + + return None + + def bytecode_init( + self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str + ) -> Optional[str]: + """ + Return the init bytecode + + Args: + crytic_compile_compilation_unit: + contract_name: + + Returns: + + """ + getter: Callable[[SourceUnit], Dict[str, str]] = lambda x: x.bytecodes_init + return self._generic_source_unit_getter( + crytic_compile_compilation_unit, contract_name, getter + ) + + def bytecode_runtime( + self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str + ) -> Optional[str]: + """ + Return the runtime bytecode + + Args: + crytic_compile_compilation_unit: + contract_name: + + Returns: + + """ + getter: Callable[[SourceUnit], Dict[str, str]] = lambda x: x.bytecodes_runtime + return self._generic_source_unit_getter( + crytic_compile_compilation_unit, contract_name, getter + ) + + def srcmap_init( + self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str + ) -> Optional[List[str]]: + """ + Return the init scrmap + + Args: + crytic_compile_compilation_unit: + contract_name: + + Returns: + + """ + getter: Callable[[SourceUnit], Dict[str, List[str]]] = lambda x: x.srcmaps_init + return self._generic_source_unit_getter( + crytic_compile_compilation_unit, contract_name, getter + ) + + def srcmap_runtime( + self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str + ) -> Optional[List[str]]: + """ + Return the runtime srcmap + + Args: + crytic_compile_compilation_unit: + contract_name: + + Returns: + + """ + getter: Callable[[SourceUnit], Dict[str, List[str]]] = lambda x: x.srcmaps_runtime + return self._generic_source_unit_getter( + crytic_compile_compilation_unit, contract_name, getter + ) + + def abi(self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str) -> Any: + """ + Return the abi + + Args: + crytic_compile_compilation_unit: + contract_name: + + Returns: + + """ + getter: Callable[[SourceUnit], Dict[str, List[str]]] = lambda x: x.abis + return self._generic_source_unit_getter( + crytic_compile_compilation_unit, contract_name, getter + ) + # region Built in definitions ################################################################################### ################################################################################### diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index fe68051c2e..0c77b69ca9 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -32,7 +32,7 @@ def _detect(self): info = ["Different versions of Solidity are used:\n"] info += [f"\t- Version used: {[str(v) for v in versions]}\n"] - for p in pragma: + for p in sorted(pragma, key=lambda x: x.version): info += ["\t- ", p, "\n"] res = self.generate_result(info) diff --git a/slither/printers/summary/evm.py b/slither/printers/summary/evm.py index 8476deaca1..660d912042 100644 --- a/slither/printers/summary/evm.py +++ b/slither/printers/summary/evm.py @@ -21,14 +21,8 @@ def _extract_evm_info(slither): CFG = load_evm_cfg_builder() for contract in slither.contracts_derived: - contract_bytecode_runtime = ( - contract.compilation_unit.crytic_compile_compilation_unit.bytecode_runtime( - contract.name - ) - ) - contract_srcmap_runtime = ( - contract.compilation_unit.crytic_compile_compilation_unit.srcmap_runtime(contract.name) - ) + contract_bytecode_runtime = contract.scope.bytecode_runtime(contract.name) + contract_srcmap_runtime = contract.scope.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( @@ -38,12 +32,8 @@ def _extract_evm_info(slither): contract.source_mapping.filename.absolute, ) - contract_bytecode_init = ( - contract.compilation_unit.crytic_compile_compilation_unit.bytecode_init(contract.name) - ) - contract_srcmap_init = ( - contract.compilation_unit.crytic_compile_compilation_unit.srcmap_init(contract.name) - ) + contract_bytecode_init = contract.scope.bytecode_init(contract.name) + contract_srcmap_init = contract.scope.srcmap_init(contract.name) cfg_init = CFG(contract_bytecode_init) evm_info["cfg_init", contract.name] = cfg_init diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index c5817f4cd1..0b43184bd6 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -450,19 +450,25 @@ def propagate_type_and_convert_call(result, node): return result -def _convert_type_contract(ir, compilation_unit: "SlitherCompilationUnit"): +def _convert_type_contract(ir: Member) -> Assignment: assert isinstance(ir.variable_left.type, TypeInformation) contract = ir.variable_left.type.type + scope = ir.node.file_scope + if ir.variable_right == "creationCode": - bytecode = compilation_unit.crytic_compile_compilation_unit.bytecode_init(contract.name) + bytecode = scope.bytecode_init( + ir.node.compilation_unit.crytic_compile_compilation_unit, contract.name + ) assignment = Assignment(ir.lvalue, Constant(str(bytecode)), ElementaryType("bytes")) assignment.set_expression(ir.expression) assignment.set_node(ir.node) assignment.lvalue.set_type(ElementaryType("bytes")) return assignment if ir.variable_right == "runtimeCode": - bytecode = compilation_unit.crytic_compile_compilation_unit.bytecode_runtime(contract.name) + bytecode = scope.bytecode_runtime( + ir.node.compilation_unit.crytic_compile_compilation_unit, contract.name + ) assignment = Assignment(ir.lvalue, Constant(str(bytecode)), ElementaryType("bytes")) assignment.set_expression(ir.expression) assignment.set_node(ir.node) @@ -673,7 +679,7 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals if isinstance(ir.variable_left, TemporaryVariable) and isinstance( ir.variable_left.type, TypeInformation ): - return _convert_type_contract(ir, node.function.compilation_unit) + return _convert_type_contract(ir) left = ir.variable_left t = None ir_func = ir.function diff --git a/tests/source_unit/foundry.toml b/tests/source_unit/foundry.toml new file mode 100644 index 0000000000..e6810b2b58 --- /dev/null +++ b/tests/source_unit/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = 'src' +out = 'out' +libs = ['lib'] + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/tests/source_unit/lib/forge-std b/tests/source_unit/lib/forge-std new file mode 160000 index 0000000000..eb980e1d4f --- /dev/null +++ b/tests/source_unit/lib/forge-std @@ -0,0 +1 @@ +Subproject commit eb980e1d4f0e8173ec27da77297ae411840c8ccb diff --git a/tests/source_unit/script/Counter.s.sol b/tests/source_unit/script/Counter.s.sol new file mode 100644 index 0000000000..0e546aba37 --- /dev/null +++ b/tests/source_unit/script/Counter.s.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; + +contract CounterScript is Script { + function setUp() public {} + + function run() public { + vm.broadcast(); + } +} diff --git a/tests/source_unit/src/Counter.sol b/tests/source_unit/src/Counter.sol new file mode 100644 index 0000000000..aded7997b0 --- /dev/null +++ b/tests/source_unit/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/tests/source_unit/src/Counter2.sol b/tests/source_unit/src/Counter2.sol new file mode 100644 index 0000000000..fa830a4467 --- /dev/null +++ b/tests/source_unit/src/Counter2.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { +} diff --git a/tests/source_unit/test/Counter.t.sol b/tests/source_unit/test/Counter.t.sol new file mode 100644 index 0000000000..30235e8a88 --- /dev/null +++ b/tests/source_unit/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function testIncrement() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testSetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/tests/test_source_unit.py b/tests/test_source_unit.py new file mode 100644 index 0000000000..7b653599ed --- /dev/null +++ b/tests/test_source_unit.py @@ -0,0 +1,31 @@ +from slither import Slither + + +def test_contract_info() -> None: + slither = Slither("./tests/source_unit") + + assert len(slither.compilation_units) == 1 + compilation_unit = slither.compilation_units[0] + + for source_unit in compilation_unit.crytic_compile_compilation_unit.source_units.values(): + source_unit.remove_metadata() + + counter_sol = compilation_unit.crytic_compile.filename_lookup( + "tests/source_unit/src/Counter.sol" + ) + assert ( + compilation_unit.scopes[counter_sol].bytecode_init( + compilation_unit.crytic_compile_compilation_unit, "Counter" + ) + == "608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fe" + ) + + counter2_sol = compilation_unit.crytic_compile.filename_lookup( + "tests/source_unit/src/Counter2.sol" + ) + assert ( + compilation_unit.scopes[counter2_sol].bytecode_init( + compilation_unit.crytic_compile_compilation_unit, "Counter" + ) + == "6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfe" + )