From 6a3ae82b56592769fa426fc8d264292f7bf16c91 Mon Sep 17 00:00:00 2001 From: webthethird Date: Sat, 25 Mar 2023 17:50:18 -0500 Subject: [PATCH 01/19] Add option to skip unrolling user-defined-types --- slither/utils/code_generation.py | 48 +++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index 951bf4702c..ae03e20905 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -2,12 +2,14 @@ from typing import TYPE_CHECKING, Optional from slither.utils.type import convert_type_for_solidity_signature_to_string +from slither.core.solidity_types.user_defined_type import UserDefinedType +from slither.core.declarations import Structure, Enum if TYPE_CHECKING: - from slither.core.declarations import FunctionContract, Structure, Contract + from slither.core.declarations import FunctionContract, Contract, CustomErrorContract -def generate_interface(contract: "Contract") -> str: +def generate_interface(contract: "Contract", unroll_structs: bool = True) -> str: """ Generates code for a Solidity interface to the contract. Args: @@ -22,13 +24,7 @@ def generate_interface(contract: "Contract") -> str: name, args = event.signature interface += f" event {name}({', '.join(args)});\n" for error in contract.custom_errors: - args = [ - convert_type_for_solidity_signature_to_string(arg.type) - .replace("(", "") - .replace(")", "") - for arg in error.parameters - ] - interface += f" error {error.name}({', '.join(args)});\n" + interface += generate_custom_error_interface(error, unroll_structs) for enum in contract.enums: interface += f" enum {enum.name} {{ {', '.join(enum.values)} }}\n" for struct in contract.structures: @@ -38,12 +34,16 @@ def generate_interface(contract: "Contract") -> str: for func in contract.functions_entry_points: if func.is_constructor or func.is_fallback or func.is_receive: continue - interface += f" function {generate_interface_function_signature(func)};\n" + interface += ( + f" function {generate_interface_function_signature(func, unroll_structs)};\n" + ) interface += "}\n\n" return interface -def generate_interface_function_signature(func: "FunctionContract") -> Optional[str]: +def generate_interface_function_signature( + func: "FunctionContract", unroll_structs: bool = True +) -> Optional[str]: """ Generates a string of the form: func_name(type1,type2) external {payable/view/pure} returns (type3) @@ -56,7 +56,7 @@ def generate_interface_function_signature(func: "FunctionContract") -> Optional[ Returns None if the function is private or internal, or is a constructor/fallback/receive. """ - name, parameters, return_vars = func.signature + name, _, _ = func.signature if ( func not in func.contract.functions_entry_points or func.is_constructor @@ -69,16 +69,24 @@ def generate_interface_function_signature(func: "FunctionContract") -> Optional[ payable = " payable" if func.payable else "" returns = [ convert_type_for_solidity_signature_to_string(ret.type).replace("(", "").replace(")", "") + if unroll_structs + else f"{str(ret.type.type)} memory" + if isinstance(ret.type, UserDefinedType) and isinstance(ret.type.type, (Structure, Enum)) + else str(ret.type) for ret in func.returns ] parameters = [ convert_type_for_solidity_signature_to_string(param.type).replace("(", "").replace(")", "") + if unroll_structs + else f"{str(param.type.type)} memory" + if isinstance(param.type, UserDefinedType) and isinstance(param.type.type, (Structure, Enum)) + else str(param.type) for param in func.parameters ] _interface_signature_str = ( name + "(" + ",".join(parameters) + ") external" + payable + pure + view ) - if len(return_vars) > 0: + if len(returns) > 0: _interface_signature_str += " returns (" + ",".join(returns) + ")" return _interface_signature_str @@ -102,3 +110,17 @@ def generate_struct_interface_str(struct: "Structure") -> str: definition += f" {elem.type} {elem.name};\n" definition += " }\n" return definition + + +def generate_custom_error_interface(error: "CustomErrorContract", unroll_structs: bool = True) -> str: + args = [ + convert_type_for_solidity_signature_to_string(arg.type) + .replace("(", "") + .replace(")", "") + if unroll_structs + else str(arg.type.type) + if isinstance(arg.type, UserDefinedType) and isinstance(arg.type.type, (Structure, Enum)) + else str(arg.type) + for arg in error.parameters + ] + return f" error {error.name}({', '.join(args)});\n" From b5cd4642e0a55fc06e1f4535d0512f2d8812b27a Mon Sep 17 00:00:00 2001 From: webthethird Date: Sat, 25 Mar 2023 17:50:45 -0500 Subject: [PATCH 02/19] Test option to skip unrolling user-defined-types --- .../TEST_generated_code_not_unrolled.sol | 24 +++++++++++++++++++ tests/test_code_generation.py | 8 +++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/code_generation/TEST_generated_code_not_unrolled.sol diff --git a/tests/code_generation/TEST_generated_code_not_unrolled.sol b/tests/code_generation/TEST_generated_code_not_unrolled.sol new file mode 100644 index 0000000000..9595252595 --- /dev/null +++ b/tests/code_generation/TEST_generated_code_not_unrolled.sol @@ -0,0 +1,24 @@ +interface ITestContract { + event NoParams(); + event Anonymous(); + event OneParam(address); + event OneParamIndexed(address); + error ErrorWithEnum(SomeEnum); + error ErrorSimple(); + error ErrorWithArgs(uint256, uint256); + error ErrorWithStruct(St); + enum SomeEnum { ONE, TWO, THREE } + struct St { + uint256 v; + } + function stateA() external returns (uint256); + function owner() external returns (address); + function structs(address,uint256) external returns (uint256); + function err0() external; + function err1() external; + function err2(uint256,uint256) external; + function newSt(uint256) external returns (St memory); + function getSt(uint256) external view returns (St memory); + function removeSt(St memory) external; +} + diff --git a/tests/test_code_generation.py b/tests/test_code_generation.py index 13d1c8fb0f..d69a7836d2 100644 --- a/tests/test_code_generation.py +++ b/tests/test_code_generation.py @@ -23,3 +23,11 @@ def test_interface_generation() -> None: expected = file.read() assert actual == expected + + actual = generate_interface(sl.get_contract_from_name("TestContract")[0], unroll_structs=False) + expected_path = os.path.join(CODE_TEST_ROOT, "TEST_generated_code_not_unrolled.sol") + + with open(expected_path, "r", encoding="utf-8") as file: + expected = file.read() + + assert actual == expected From c3f42c62e66fa6682c0b1985d6226e47e540eac8 Mon Sep 17 00:00:00 2001 From: webthethird Date: Sat, 25 Mar 2023 17:54:53 -0500 Subject: [PATCH 03/19] Black --- slither/utils/code_generation.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index ae03e20905..f0c433ac30 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -79,7 +79,8 @@ def generate_interface_function_signature( convert_type_for_solidity_signature_to_string(param.type).replace("(", "").replace(")", "") if unroll_structs else f"{str(param.type.type)} memory" - if isinstance(param.type, UserDefinedType) and isinstance(param.type.type, (Structure, Enum)) + if isinstance(param.type, UserDefinedType) + and isinstance(param.type.type, (Structure, Enum)) else str(param.type) for param in func.parameters ] @@ -112,11 +113,11 @@ def generate_struct_interface_str(struct: "Structure") -> str: return definition -def generate_custom_error_interface(error: "CustomErrorContract", unroll_structs: bool = True) -> str: +def generate_custom_error_interface( + error: "CustomErrorContract", unroll_structs: bool = True +) -> str: args = [ - convert_type_for_solidity_signature_to_string(arg.type) - .replace("(", "") - .replace(")", "") + convert_type_for_solidity_signature_to_string(arg.type).replace("(", "").replace(")", "") if unroll_structs else str(arg.type.type) if isinstance(arg.type, UserDefinedType) and isinstance(arg.type.type, (Structure, Enum)) From 36e1ac1e011aab8fa22b47e6de5ff4d728f647a6 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 27 Mar 2023 10:37:36 -0500 Subject: [PATCH 04/19] Better handling of state variable signatures especially contract-type variables and functions that return them --- slither/utils/code_generation.py | 51 ++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index f0c433ac30..fc7578040e 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -1,19 +1,25 @@ # Functions for generating Solidity code from typing import TYPE_CHECKING, Optional -from slither.utils.type import convert_type_for_solidity_signature_to_string -from slither.core.solidity_types.user_defined_type import UserDefinedType -from slither.core.declarations import Structure, Enum +from slither.utils.type import ( + convert_type_for_solidity_signature_to_string, + export_nested_types_from_variable, + export_return_type_from_variable, +) +from slither.core.solidity_types import UserDefinedType, MappingType, ArrayType +from slither.core.declarations import Structure, Enum, Contract if TYPE_CHECKING: - from slither.core.declarations import FunctionContract, Contract, CustomErrorContract + from slither.core.declarations import FunctionContract, CustomErrorContract + from slither.core.variables import StateVariable def generate_interface(contract: "Contract", unroll_structs: bool = True) -> str: """ Generates code for a Solidity interface to the contract. Args: - contract: A Contract object + contract: A Contract object. + unroll_structs: Specifies whether to use structures' underlying types instead of the user-defined type. Returns: A string with the code for an interface, with function stubs for all public or external functions and @@ -30,7 +36,7 @@ def generate_interface(contract: "Contract", unroll_structs: bool = True) -> str for struct in contract.structures: interface += generate_struct_interface_str(struct) for var in contract.state_variables_entry_points: - interface += f" function {var.signature_str.replace('returns', 'external returns ')};\n" + interface += generate_interface_variable_signature(var, unroll_structs) for func in contract.functions_entry_points: if func.is_constructor or func.is_fallback or func.is_receive: continue @@ -41,6 +47,35 @@ def generate_interface(contract: "Contract", unroll_structs: bool = True) -> str return interface +def generate_interface_variable_signature( + var: "StateVariable", unroll_structs: bool = True +) -> Optional[str]: + if unroll_structs: + params = [ + convert_type_for_solidity_signature_to_string(x).replace("(", "").replace(")", "") + for x in export_nested_types_from_variable(var) + ] + returns = [ + convert_type_for_solidity_signature_to_string(x).replace("(", "").replace(")", "") + for x in export_return_type_from_variable(var) + ] + else: + _, params, _ = var.signature + returns = [] + _type = var.type + while isinstance(_type, MappingType): + _type = _type.type_to + while isinstance(_type, (ArrayType, UserDefinedType)): + _type = _type.type + ret = str(_type) + if isinstance(_type, Structure): + ret += " memory" + elif isinstance(_type, Contract): + ret = "address" + returns.append(ret) + return f" function {var.name}({','.join(params)}) external returns ({', '.join(returns)});\n" + + def generate_interface_function_signature( func: "FunctionContract", unroll_structs: bool = True ) -> Optional[str]: @@ -72,6 +107,8 @@ def generate_interface_function_signature( if unroll_structs else f"{str(ret.type.type)} memory" if isinstance(ret.type, UserDefinedType) and isinstance(ret.type.type, (Structure, Enum)) + else "address" + if isinstance(ret.type, UserDefinedType) and isinstance(ret.type.type, Contract) else str(ret.type) for ret in func.returns ] @@ -81,6 +118,8 @@ def generate_interface_function_signature( else f"{str(param.type.type)} memory" if isinstance(param.type, UserDefinedType) and isinstance(param.type.type, (Structure, Enum)) + else "address" + if isinstance(param.type, UserDefinedType) and isinstance(param.type.type, Contract) else str(param.type) for param in func.parameters ] From 380301b08da55fdd23efb7838be4f5e108acbcdc Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 27 Mar 2023 10:38:57 -0500 Subject: [PATCH 05/19] More robust testing including ArrayType vars and user-defined contract types --- tests/code_generation/CodeGeneration.sol | 13 +++++++++---- tests/code_generation/TEST_generated_code.sol | 5 ++++- .../TEST_generated_code_not_unrolled.sol | 5 ++++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/code_generation/CodeGeneration.sol b/tests/code_generation/CodeGeneration.sol index c15017abd5..749e23f32e 100644 --- a/tests/code_generation/CodeGeneration.sol +++ b/tests/code_generation/CodeGeneration.sol @@ -8,7 +8,9 @@ contract TestContract is I { uint public stateA; uint private stateB; address public immutable owner = msg.sender; - mapping(address => mapping(uint => St)) public structs; + mapping(address => mapping(uint => St)) public structsMap; + St[] public structsArray; + I public otherI; event NoParams(); event Anonymous() anonymous; @@ -44,13 +46,16 @@ contract TestContract is I { function newSt(uint x) public returns (St memory) { St memory st; st.v = x; - structs[msg.sender][x] = st; + structsMap[msg.sender][x] = st; return st; } function getSt(uint x) public view returns (St memory) { - return structs[msg.sender][x]; + return structsMap[msg.sender][x]; } function removeSt(St memory st) public { - delete structs[msg.sender][st.v]; + delete structsMap[msg.sender][st.v]; + } + function setOtherI(I _i) public { + otherI = _i; } } \ No newline at end of file diff --git a/tests/code_generation/TEST_generated_code.sol b/tests/code_generation/TEST_generated_code.sol index 62e08bd74c..cbddea2efc 100644 --- a/tests/code_generation/TEST_generated_code.sol +++ b/tests/code_generation/TEST_generated_code.sol @@ -13,12 +13,15 @@ interface ITestContract { } function stateA() external returns (uint256); function owner() external returns (address); - function structs(address,uint256) external returns (uint256); + function structsMap(address,uint256) external returns (uint256); + function structsArray(uint256) external returns (uint256); + function otherI() external returns (address); function err0() external; function err1() external; function err2(uint256,uint256) external; function newSt(uint256) external returns (uint256); function getSt(uint256) external view returns (uint256); function removeSt(uint256) external; + function setOtherI(address) external; } diff --git a/tests/code_generation/TEST_generated_code_not_unrolled.sol b/tests/code_generation/TEST_generated_code_not_unrolled.sol index 9595252595..b8e8c5d046 100644 --- a/tests/code_generation/TEST_generated_code_not_unrolled.sol +++ b/tests/code_generation/TEST_generated_code_not_unrolled.sol @@ -13,12 +13,15 @@ interface ITestContract { } function stateA() external returns (uint256); function owner() external returns (address); - function structs(address,uint256) external returns (uint256); + function structsMap(address,uint256) external returns (St memory); + function structsArray(uint256) external returns (St memory); + function otherI() external returns (address); function err0() external; function err1() external; function err2(uint256,uint256) external; function newSt(uint256) external returns (St memory); function getSt(uint256) external view returns (St memory); function removeSt(St memory) external; + function setOtherI(address) external; } From 45aca8be103b1f57866692c9d3e04ac0a7a217cc Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 27 Mar 2023 11:19:34 -0500 Subject: [PATCH 06/19] Handle nested Structures --- slither/utils/code_generation.py | 8 +++++++- tests/code_generation/CodeGeneration.sol | 4 ++++ tests/code_generation/TEST_generated_code.sol | 3 +++ .../code_generation/TEST_generated_code_not_unrolled.sol | 3 +++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index fc7578040e..11459d05d9 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -147,7 +147,13 @@ def generate_struct_interface_str(struct: "Structure") -> str: """ definition = f" struct {struct.name} {{\n" for elem in struct.elems_ordered: - definition += f" {elem.type} {elem.name};\n" + if isinstance(elem.type, UserDefinedType): + if isinstance(elem.type.type, (Structure, Enum)): + definition += f" {elem.type.type} {elem.name};\n" + elif isinstance(elem.type.type, Contract): + definition += f" address {elem.name};\n" + else: + definition += f" {elem.type} {elem.name};\n" definition += " }\n" return definition diff --git a/tests/code_generation/CodeGeneration.sol b/tests/code_generation/CodeGeneration.sol index 749e23f32e..6f1f63c72f 100644 --- a/tests/code_generation/CodeGeneration.sol +++ b/tests/code_generation/CodeGeneration.sol @@ -25,6 +25,10 @@ contract TestContract is I { uint v; } + struct Nested{ + St st; + } + function err0() public { revert ErrorSimple(); } diff --git a/tests/code_generation/TEST_generated_code.sol b/tests/code_generation/TEST_generated_code.sol index cbddea2efc..373fba9ca2 100644 --- a/tests/code_generation/TEST_generated_code.sol +++ b/tests/code_generation/TEST_generated_code.sol @@ -11,6 +11,9 @@ interface ITestContract { struct St { uint256 v; } + struct Nested { + St st; + } function stateA() external returns (uint256); function owner() external returns (address); function structsMap(address,uint256) external returns (uint256); diff --git a/tests/code_generation/TEST_generated_code_not_unrolled.sol b/tests/code_generation/TEST_generated_code_not_unrolled.sol index b8e8c5d046..0cc4dc0404 100644 --- a/tests/code_generation/TEST_generated_code_not_unrolled.sol +++ b/tests/code_generation/TEST_generated_code_not_unrolled.sol @@ -11,6 +11,9 @@ interface ITestContract { struct St { uint256 v; } + struct Nested { + St st; + } function stateA() external returns (uint256); function owner() external returns (address); function structsMap(address,uint256) external returns (St memory); From 552a24d8579a0efe4ee0bf5e3f490875ac49db75 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 27 Mar 2023 11:25:18 -0500 Subject: [PATCH 07/19] Make events, errors, enums and structs optional but include all by default --- slither/utils/code_generation.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index 11459d05d9..718410fb7f 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -14,7 +14,14 @@ from slither.core.variables import StateVariable -def generate_interface(contract: "Contract", unroll_structs: bool = True) -> str: +def generate_interface( + contract: "Contract", + unroll_structs: bool = True, + skip_events: bool = False, + skip_errors: bool = False, + skip_enums: bool = False, + skip_structs: bool = False +) -> str: """ Generates code for a Solidity interface to the contract. Args: @@ -26,15 +33,19 @@ def generate_interface(contract: "Contract", unroll_structs: bool = True) -> str state variables, as well as any events, custom errors and/or structs declared in the contract. """ interface = f"interface I{contract.name} {{\n" - for event in contract.events: - name, args = event.signature - interface += f" event {name}({', '.join(args)});\n" - for error in contract.custom_errors: - interface += generate_custom_error_interface(error, unroll_structs) - for enum in contract.enums: - interface += f" enum {enum.name} {{ {', '.join(enum.values)} }}\n" - for struct in contract.structures: - interface += generate_struct_interface_str(struct) + if not skip_events: + for event in contract.events: + name, args = event.signature + interface += f" event {name}({', '.join(args)});\n" + if not skip_errors: + for error in contract.custom_errors: + interface += generate_custom_error_interface(error, unroll_structs) + if not skip_enums: + for enum in contract.enums: + interface += f" enum {enum.name} {{ {', '.join(enum.values)} }}\n" + if not skip_structs: + for struct in contract.structures: + interface += generate_struct_interface_str(struct) for var in contract.state_variables_entry_points: interface += generate_interface_variable_signature(var, unroll_structs) for func in contract.functions_entry_points: From f61c59ae6c1de66b91131fc035e57a32e5bbc069 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 27 Mar 2023 11:43:16 -0500 Subject: [PATCH 08/19] Handle arrays of user-defined types in params/returns --- slither/utils/code_generation.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index 718410fb7f..e7647bfcc3 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -15,12 +15,12 @@ def generate_interface( - contract: "Contract", - unroll_structs: bool = True, - skip_events: bool = False, - skip_errors: bool = False, - skip_enums: bool = False, - skip_structs: bool = False + contract: "Contract", + unroll_structs: bool = True, + skip_events: bool = False, + skip_errors: bool = False, + skip_enums: bool = False, + skip_structs: bool = False, ) -> str: """ Generates code for a Solidity interface to the contract. @@ -116,6 +116,7 @@ def generate_interface_function_signature( returns = [ convert_type_for_solidity_signature_to_string(ret.type).replace("(", "").replace(")", "") if unroll_structs + or (isinstance(ret.type, ArrayType) and isinstance(ret.type.type, UserDefinedType)) else f"{str(ret.type.type)} memory" if isinstance(ret.type, UserDefinedType) and isinstance(ret.type.type, (Structure, Enum)) else "address" @@ -126,6 +127,7 @@ def generate_interface_function_signature( parameters = [ convert_type_for_solidity_signature_to_string(param.type).replace("(", "").replace(")", "") if unroll_structs + or (isinstance(param.type, ArrayType) and isinstance(param.type.type, UserDefinedType)) else f"{str(param.type.type)} memory" if isinstance(param.type, UserDefinedType) and isinstance(param.type.type, (Structure, Enum)) From 0cac8ccb07b99ef897555a655babd5c9be695668 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 27 Mar 2023 12:20:32 -0500 Subject: [PATCH 09/19] Handle array's data location in params/returns --- slither/utils/code_generation.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index e7647bfcc3..f1099a66ab 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -6,7 +6,7 @@ export_nested_types_from_variable, export_return_type_from_variable, ) -from slither.core.solidity_types import UserDefinedType, MappingType, ArrayType +from slither.core.solidity_types import UserDefinedType, MappingType, ArrayType, ElementaryType from slither.core.declarations import Structure, Enum, Contract if TYPE_CHECKING: @@ -14,6 +14,7 @@ from slither.core.variables import StateVariable +# pylint: disable=too-many-arguments def generate_interface( contract: "Contract", unroll_structs: bool = True, @@ -116,7 +117,12 @@ def generate_interface_function_signature( returns = [ convert_type_for_solidity_signature_to_string(ret.type).replace("(", "").replace(")", "") if unroll_structs - or (isinstance(ret.type, ArrayType) and isinstance(ret.type.type, UserDefinedType)) + else convert_type_for_solidity_signature_to_string(ret.type) + .replace("(", "") + .replace(")", "") + + f" {ret.location}" + if isinstance(ret.type, ArrayType) + and isinstance(ret.type.type, (UserDefinedType, ElementaryType)) else f"{str(ret.type.type)} memory" if isinstance(ret.type, UserDefinedType) and isinstance(ret.type.type, (Structure, Enum)) else "address" @@ -127,7 +133,12 @@ def generate_interface_function_signature( parameters = [ convert_type_for_solidity_signature_to_string(param.type).replace("(", "").replace(")", "") if unroll_structs - or (isinstance(param.type, ArrayType) and isinstance(param.type.type, UserDefinedType)) + else convert_type_for_solidity_signature_to_string(param.type) + .replace("(", "") + .replace(")", "") + + f" {param.location}" + if isinstance(param.type, ArrayType) + and isinstance(param.type.type, (UserDefinedType, ElementaryType)) else f"{str(param.type.type)} memory" if isinstance(param.type, UserDefinedType) and isinstance(param.type.type, (Structure, Enum)) From 216ccdc9594d85114e592868d4b004b13bc2fa21 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 27 Mar 2023 13:31:06 -0500 Subject: [PATCH 10/19] Refactor to avoid assumptions re: indentation, semicolon and new line --- slither/utils/code_generation.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index f1099a66ab..34c489b74c 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -40,15 +40,15 @@ def generate_interface( interface += f" event {name}({', '.join(args)});\n" if not skip_errors: for error in contract.custom_errors: - interface += generate_custom_error_interface(error, unroll_structs) + interface += f" error {generate_custom_error_interface(error, unroll_structs)};\n" if not skip_enums: for enum in contract.enums: interface += f" enum {enum.name} {{ {', '.join(enum.values)} }}\n" if not skip_structs: for struct in contract.structures: - interface += generate_struct_interface_str(struct) + interface += generate_struct_interface_str(struct, indent=4) for var in contract.state_variables_entry_points: - interface += generate_interface_variable_signature(var, unroll_structs) + interface += f" function {generate_interface_variable_signature(var, unroll_structs)};\n" for func in contract.functions_entry_points: if func.is_constructor or func.is_fallback or func.is_receive: continue @@ -85,7 +85,7 @@ def generate_interface_variable_signature( elif isinstance(_type, Contract): ret = "address" returns.append(ret) - return f" function {var.name}({','.join(params)}) external returns ({', '.join(returns)});\n" + return f"{var.name}({','.join(params)}) external returns ({', '.join(returns)})" def generate_interface_function_signature( @@ -155,7 +155,7 @@ def generate_interface_function_signature( return _interface_signature_str -def generate_struct_interface_str(struct: "Structure") -> str: +def generate_struct_interface_str(struct: "Structure", indent: int = 0) -> str: """ Generates code for a structure declaration in an interface of the form: struct struct_name { @@ -164,21 +164,25 @@ def generate_struct_interface_str(struct: "Structure") -> str: ... ... } Args: - struct: A Structure object + struct: A Structure object. + indent: Number of spaces to indent the code block with. Returns: The structure declaration code as a string. """ - definition = f" struct {struct.name} {{\n" + spaces = "" + for _ in range(0, indent): + spaces += " " + definition = f"{spaces}struct {struct.name} {{\n" for elem in struct.elems_ordered: if isinstance(elem.type, UserDefinedType): if isinstance(elem.type.type, (Structure, Enum)): - definition += f" {elem.type.type} {elem.name};\n" + definition += f"{spaces} {elem.type.type} {elem.name};\n" elif isinstance(elem.type.type, Contract): - definition += f" address {elem.name};\n" + definition += f"{spaces} address {elem.name};\n" else: - definition += f" {elem.type} {elem.name};\n" - definition += " }\n" + definition += f"{spaces} {elem.type} {elem.name};\n" + definition += f"{spaces}\n" return definition @@ -193,4 +197,4 @@ def generate_custom_error_interface( else str(arg.type) for arg in error.parameters ] - return f" error {error.name}({', '.join(args)});\n" + return f"{error.name}({', '.join(args)})" From 40239751c264ea850335b9789e7fbe23b42e47b3 Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 28 Mar 2023 12:23:45 -0500 Subject: [PATCH 11/19] Fix handling of dynamic `string` and `bytes` --- slither/utils/code_generation.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index 34c489b74c..cd72e6c8fe 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -6,7 +6,7 @@ export_nested_types_from_variable, export_return_type_from_variable, ) -from slither.core.solidity_types import UserDefinedType, MappingType, ArrayType, ElementaryType +from slither.core.solidity_types import Type, UserDefinedType, MappingType, ArrayType, ElementaryType from slither.core.declarations import Structure, Enum, Contract if TYPE_CHECKING: @@ -73,6 +73,7 @@ def generate_interface_variable_signature( ] else: _, params, _ = var.signature + params = [p + " memory" if p in ["bytes", "string"] else p for p in params] returns = [] _type = var.type while isinstance(_type, MappingType): @@ -80,7 +81,7 @@ def generate_interface_variable_signature( while isinstance(_type, (ArrayType, UserDefinedType)): _type = _type.type ret = str(_type) - if isinstance(_type, Structure): + if isinstance(_type, Structure) or (isinstance(_type, Type) and _type.is_dynamic): ret += " memory" elif isinstance(_type, Contract): ret = "address" @@ -127,6 +128,8 @@ def generate_interface_function_signature( if isinstance(ret.type, UserDefinedType) and isinstance(ret.type.type, (Structure, Enum)) else "address" if isinstance(ret.type, UserDefinedType) and isinstance(ret.type.type, Contract) + else f"{ret.type} {ret.location}" + if ret.type.is_dynamic else str(ret.type) for ret in func.returns ] @@ -144,6 +147,8 @@ def generate_interface_function_signature( and isinstance(param.type.type, (Structure, Enum)) else "address" if isinstance(param.type, UserDefinedType) and isinstance(param.type.type, Contract) + else f"{param.type} {param.location}" + if param.type.is_dynamic else str(param.type) for param in func.parameters ] @@ -182,7 +187,7 @@ def generate_struct_interface_str(struct: "Structure", indent: int = 0) -> str: definition += f"{spaces} address {elem.name};\n" else: definition += f"{spaces} {elem.type} {elem.name};\n" - definition += f"{spaces}\n" + definition += f"{spaces}}}\n" return definition From 111559bf750ce58d307806e36fbebb671ad7c973 Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 13:15:20 -0500 Subject: [PATCH 12/19] Black --- slither/utils/code_generation.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index cd72e6c8fe..c22a725260 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -6,7 +6,13 @@ export_nested_types_from_variable, export_return_type_from_variable, ) -from slither.core.solidity_types import Type, UserDefinedType, MappingType, ArrayType, ElementaryType +from slither.core.solidity_types import ( + Type, + UserDefinedType, + MappingType, + ArrayType, + ElementaryType, +) from slither.core.declarations import Structure, Enum, Contract if TYPE_CHECKING: From ea10acad827b7a80085edd1c3020e66fa464ff17 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 10 Apr 2023 11:49:02 -0500 Subject: [PATCH 13/19] Fix missing import --- tests/unit/utils/test_code_generation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/utils/test_code_generation.py b/tests/unit/utils/test_code_generation.py index 99f8689a44..ee70fee871 100644 --- a/tests/unit/utils/test_code_generation.py +++ b/tests/unit/utils/test_code_generation.py @@ -1,3 +1,4 @@ +import os from pathlib import Path from solc_select import solc_select From 89fda0667e57bc541e99644ddf1733052ee66801 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 10 Apr 2023 11:49:51 -0500 Subject: [PATCH 14/19] Pylint --- tests/unit/utils/test_code_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/utils/test_code_generation.py b/tests/unit/utils/test_code_generation.py index ee70fee871..3684b10b46 100644 --- a/tests/unit/utils/test_code_generation.py +++ b/tests/unit/utils/test_code_generation.py @@ -24,7 +24,7 @@ def test_interface_generation() -> None: assert actual == expected actual = generate_interface(sl.get_contract_from_name("TestContract")[0], unroll_structs=False) - expected_path = os.path.join(CODE_TEST_ROOT, "TEST_generated_code_not_unrolled.sol") + expected_path = os.path.join(TEST_DATA_DIR, "TEST_generated_code_not_unrolled.sol") with open(expected_path, "r", encoding="utf-8") as file: expected = file.read() From 97307cd1f0ea9157d15a6b9bb9044420cca55e94 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 17 Apr 2023 14:39:14 -0500 Subject: [PATCH 15/19] Only include 'view' in func sig if not 'pure' --- slither/utils/code_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index c22a725260..b2da93764e 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -118,7 +118,7 @@ def generate_interface_function_signature( or func.is_receive ): return None - view = " view" if func.view else "" + view = " view" if func.view and not func.pure else "" pure = " pure" if func.pure else "" payable = " payable" if func.payable else "" returns = [ From 9809da2f6e5034fecccf40c022cd72f044ae3829 Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 18 Apr 2023 10:19:17 -0500 Subject: [PATCH 16/19] Change arguments from `skip_` to `include_`, and invert conditions. --- slither/utils/code_generation.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index b2da93764e..f7d0a8db61 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -24,33 +24,37 @@ def generate_interface( contract: "Contract", unroll_structs: bool = True, - skip_events: bool = False, - skip_errors: bool = False, - skip_enums: bool = False, - skip_structs: bool = False, + include_events: bool = True, + include_errors: bool = True, + include_enums: bool = True, + include_structs: bool = True, ) -> str: """ Generates code for a Solidity interface to the contract. Args: contract: A Contract object. - unroll_structs: Specifies whether to use structures' underlying types instead of the user-defined type. + unroll_structs: Whether to use structures' underlying types instead of the user-defined type (default: True). + include_events: Whether to include event signatures in the interface (default: True). + include_errors: Whether to include custom error signatures in the interface (default: True). + include_enums: Whether to include enum definitions in the interface (default: True). + include_structs: Whether to include struct definitions in the interface (default: True). Returns: A string with the code for an interface, with function stubs for all public or external functions and state variables, as well as any events, custom errors and/or structs declared in the contract. """ interface = f"interface I{contract.name} {{\n" - if not skip_events: + if include_events: for event in contract.events: name, args = event.signature interface += f" event {name}({', '.join(args)});\n" - if not skip_errors: + if include_errors: for error in contract.custom_errors: interface += f" error {generate_custom_error_interface(error, unroll_structs)};\n" - if not skip_enums: + if include_enums: for enum in contract.enums: interface += f" enum {enum.name} {{ {', '.join(enum.values)} }}\n" - if not skip_structs: + if include_structs: for struct in contract.structures: interface += generate_struct_interface_str(struct, indent=4) for var in contract.state_variables_entry_points: From 1bbf3f7cf888cb00ee22f0a260e307b4402800cc Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 18 Apr 2023 10:19:38 -0500 Subject: [PATCH 17/19] Check visibility in generate_interface_variable_signature --- slither/utils/code_generation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index f7d0a8db61..1abc1c1d80 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -72,6 +72,8 @@ def generate_interface( def generate_interface_variable_signature( var: "StateVariable", unroll_structs: bool = True ) -> Optional[str]: + if var.visibility in ["private", "internal"]: + return None if unroll_structs: params = [ convert_type_for_solidity_signature_to_string(x).replace("(", "").replace(")", "") From 4f87672be71c2a2ac95589479dc6c563a6a7d5f7 Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 18 Apr 2023 17:14:40 -0500 Subject: [PATCH 18/19] Use inner function to clean up generate_interface_function_signature --- slither/utils/code_generation.py | 71 ++++++++++++++------------------ 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index 1abc1c1d80..0699bb262e 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -1,5 +1,5 @@ # Functions for generating Solidity code -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, List from slither.utils.type import ( convert_type_for_solidity_signature_to_string, @@ -17,7 +17,8 @@ if TYPE_CHECKING: from slither.core.declarations import FunctionContract, CustomErrorContract - from slither.core.variables import StateVariable + from slither.core.variables.state_variable import StateVariable + from slither.core.variables.local_variable import LocalVariable # pylint: disable=too-many-arguments @@ -110,12 +111,39 @@ def generate_interface_function_signature( Args: func: A FunctionContract object + unroll_structs: Determines whether structs are unrolled into underlying types (default: True) Returns: The function interface as a str (contains the return values). Returns None if the function is private or internal, or is a constructor/fallback/receive. """ + def format_params_or_returns(variables: List["LocalVariable"], unroll: bool) -> List[str]: + if unroll: + return [ + convert_type_for_solidity_signature_to_string(var.type) + .replace("(", "") + .replace(")", "") + for var in variables + ] + return [ + convert_type_for_solidity_signature_to_string(var.type) + .replace("(", "") + .replace(")", "") + + f" {var.location}" + if isinstance(var.type, ArrayType) + and isinstance(var.type.type, (UserDefinedType, ElementaryType)) + else f"{str(var.type.type)} memory" + if isinstance(var.type, UserDefinedType) + and isinstance(var.type.type, (Structure, Enum)) + else "address" + if isinstance(var.type, UserDefinedType) and isinstance(var.type.type, Contract) + else f"{var.type} {var.location}" + if var.type.is_dynamic + else str(var.type) + for var in variables + ] + name, _, _ = func.signature if ( func not in func.contract.functions_entry_points @@ -127,43 +155,8 @@ def generate_interface_function_signature( view = " view" if func.view and not func.pure else "" pure = " pure" if func.pure else "" payable = " payable" if func.payable else "" - returns = [ - convert_type_for_solidity_signature_to_string(ret.type).replace("(", "").replace(")", "") - if unroll_structs - else convert_type_for_solidity_signature_to_string(ret.type) - .replace("(", "") - .replace(")", "") - + f" {ret.location}" - if isinstance(ret.type, ArrayType) - and isinstance(ret.type.type, (UserDefinedType, ElementaryType)) - else f"{str(ret.type.type)} memory" - if isinstance(ret.type, UserDefinedType) and isinstance(ret.type.type, (Structure, Enum)) - else "address" - if isinstance(ret.type, UserDefinedType) and isinstance(ret.type.type, Contract) - else f"{ret.type} {ret.location}" - if ret.type.is_dynamic - else str(ret.type) - for ret in func.returns - ] - parameters = [ - convert_type_for_solidity_signature_to_string(param.type).replace("(", "").replace(")", "") - if unroll_structs - else convert_type_for_solidity_signature_to_string(param.type) - .replace("(", "") - .replace(")", "") - + f" {param.location}" - if isinstance(param.type, ArrayType) - and isinstance(param.type.type, (UserDefinedType, ElementaryType)) - else f"{str(param.type.type)} memory" - if isinstance(param.type, UserDefinedType) - and isinstance(param.type.type, (Structure, Enum)) - else "address" - if isinstance(param.type, UserDefinedType) and isinstance(param.type.type, Contract) - else f"{param.type} {param.location}" - if param.type.is_dynamic - else str(param.type) - for param in func.parameters - ] + returns = format_params_or_returns(func.returns, unroll_structs) + parameters = format_params_or_returns(func.parameters, unroll_structs) _interface_signature_str = ( name + "(" + ",".join(parameters) + ") external" + payable + pure + view ) From 80c47c370c944ce653c0dd0225ff605b27a24f40 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 19 Apr 2023 10:07:21 -0500 Subject: [PATCH 19/19] Use a more legible inner function with list comprehension outside --- slither/utils/code_generation.py | 47 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/slither/utils/code_generation.py b/slither/utils/code_generation.py index 0699bb262e..bb8344d8fb 100644 --- a/slither/utils/code_generation.py +++ b/slither/utils/code_generation.py @@ -1,5 +1,5 @@ # Functions for generating Solidity code -from typing import TYPE_CHECKING, Optional, List +from typing import TYPE_CHECKING, Optional from slither.utils.type import ( convert_type_for_solidity_signature_to_string, @@ -118,31 +118,30 @@ def generate_interface_function_signature( Returns None if the function is private or internal, or is a constructor/fallback/receive. """ - def format_params_or_returns(variables: List["LocalVariable"], unroll: bool) -> List[str]: + def format_var(var: "LocalVariable", unroll: bool) -> str: if unroll: - return [ + return ( convert_type_for_solidity_signature_to_string(var.type) .replace("(", "") .replace(")", "") - for var in variables - ] - return [ - convert_type_for_solidity_signature_to_string(var.type) - .replace("(", "") - .replace(")", "") - + f" {var.location}" - if isinstance(var.type, ArrayType) - and isinstance(var.type.type, (UserDefinedType, ElementaryType)) - else f"{str(var.type.type)} memory" - if isinstance(var.type, UserDefinedType) - and isinstance(var.type.type, (Structure, Enum)) - else "address" - if isinstance(var.type, UserDefinedType) and isinstance(var.type.type, Contract) - else f"{var.type} {var.location}" - if var.type.is_dynamic - else str(var.type) - for var in variables - ] + ) + if isinstance(var.type, ArrayType) and isinstance( + var.type.type, (UserDefinedType, ElementaryType) + ): + return ( + convert_type_for_solidity_signature_to_string(var.type) + .replace("(", "") + .replace(")", "") + + f" {var.location}" + ) + if isinstance(var.type, UserDefinedType): + if isinstance(var.type.type, (Structure, Enum)): + return f"{str(var.type.type)} memory" + if isinstance(var.type.type, Contract): + return "address" + if var.type.is_dynamic: + return f"{var.type} {var.location}" + return str(var.type) name, _, _ = func.signature if ( @@ -155,8 +154,8 @@ def format_params_or_returns(variables: List["LocalVariable"], unroll: bool) -> view = " view" if func.view and not func.pure else "" pure = " pure" if func.pure else "" payable = " payable" if func.payable else "" - returns = format_params_or_returns(func.returns, unroll_structs) - parameters = format_params_or_returns(func.parameters, unroll_structs) + returns = [format_var(ret, unroll_structs) for ret in func.returns] + parameters = [format_var(param, unroll_structs) for param in func.parameters] _interface_signature_str = ( name + "(" + ",".join(parameters) + ") external" + payable + pure + view )