slither.utils.code_generation

  1# Functions for generating Solidity code
  2from typing import TYPE_CHECKING, Optional
  3
  4from slither.utils.type import (
  5    convert_type_for_solidity_signature_to_string,
  6    export_nested_types_from_variable,
  7    export_return_type_from_variable,
  8)
  9from slither.core.solidity_types import (
 10    Type,
 11    UserDefinedType,
 12    MappingType,
 13    ArrayType,
 14    ElementaryType,
 15    TypeAlias,
 16)
 17from slither.core.declarations import Structure, StructureContract, Enum, Contract
 18
 19if TYPE_CHECKING:
 20    from slither.core.declarations import FunctionContract, CustomErrorContract
 21    from slither.core.variables.state_variable import StateVariable
 22    from slither.core.variables.local_variable import LocalVariable
 23    from slither.core.variables.structure_variable import StructureVariable
 24
 25
 26# pylint: disable=too-many-arguments,too-many-locals,too-many-branches
 27def generate_interface(
 28    contract: "Contract",
 29    unroll_structs: bool = True,
 30    include_events: bool = True,
 31    include_errors: bool = True,
 32    include_enums: bool = True,
 33    include_structs: bool = True,
 34) -> str:
 35    """
 36    Generates code for a Solidity interface to the contract.
 37    Args:
 38        contract: A Contract object.
 39        unroll_structs: Whether to use structures' underlying types instead of the user-defined type (default: True).
 40        include_events: Whether to include event signatures in the interface (default: True).
 41        include_errors: Whether to include custom error signatures in the interface (default: True).
 42        include_enums: Whether to include enum definitions in the interface (default: True).
 43        include_structs: Whether to include struct definitions in the interface (default: True).
 44
 45    Returns:
 46        A string with the code for an interface, with function stubs for all public or external functions and
 47        state variables, as well as any events, custom errors and/or structs declared in the contract.
 48    """
 49    interface = f"interface I{contract.name} {{\n"
 50    if include_events:
 51        for event in contract.events:
 52            name, args = event.signature
 53            interface += f"    event {name}({', '.join(args)});\n"
 54    if include_errors:
 55        for error in contract.custom_errors:
 56            interface += f"    error {generate_custom_error_interface(error, unroll_structs)};\n"
 57    if include_enums:
 58        for enum in contract.enums:
 59            interface += f"    enum {enum.name} {{ {', '.join(enum.values)} }}\n"
 60    if include_structs:
 61        # Include structures defined in this contract and at the top level
 62        structs = contract.structures + contract.compilation_unit.structures_top_level
 63        # Function signatures may reference other structures as well
 64        # Include structures defined in libraries used for them
 65        for _for in contract.using_for.keys():
 66            if (
 67                isinstance(_for, UserDefinedType)
 68                and isinstance(_for.type, StructureContract)
 69                and _for.type not in structs
 70            ):
 71                structs.append(_for.type)
 72        # Include any other structures used as function arguments/returns
 73        for func in contract.functions_entry_points:
 74            for arg in func.parameters + func.returns:
 75                _type = arg.type
 76                if isinstance(_type, ArrayType):
 77                    _type = _type.type
 78                while isinstance(_type, MappingType):
 79                    _type = _type.type_to
 80                if isinstance(_type, UserDefinedType):
 81                    _type = _type.type
 82                if isinstance(_type, Structure) and _type not in structs:
 83                    structs.append(_type)
 84        for struct in structs:
 85            interface += generate_struct_interface_str(struct, indent=4)
 86            for elem in struct.elems_ordered:
 87                if (
 88                    isinstance(elem.type, UserDefinedType)
 89                    and isinstance(elem.type.type, StructureContract)
 90                    and elem.type.type not in structs
 91                ):
 92                    structs.append(elem.type.type)
 93    for var in contract.state_variables_entry_points:
 94        # if any(func.name == var.name for func in contract.functions_entry_points):
 95        #     # ignore public variables that override a public function
 96        #     continue
 97        var_sig = generate_interface_variable_signature(var, unroll_structs)
 98        if var_sig is not None and var_sig != "":
 99            interface += f"    function {var_sig};\n"
100    for func in contract.functions_entry_points:
101        if func.is_constructor or func.is_fallback or func.is_receive or not func.is_implemented:
102            continue
103        interface += (
104            f"    function {generate_interface_function_signature(func, unroll_structs)};\n"
105        )
106    interface += "}\n\n"
107    return interface
108
109
110def generate_interface_variable_signature(
111    var: "StateVariable", unroll_structs: bool = True
112) -> Optional[str]:
113    if var.visibility in ["private", "internal"]:
114        return None
115    if isinstance(var.type, UserDefinedType) and isinstance(var.type.type, Structure):
116        for elem in var.type.type.elems_ordered:
117            if isinstance(elem.type, MappingType):
118                return ""
119    if unroll_structs:
120        params = [
121            convert_type_for_solidity_signature_to_string(x).replace("(", "").replace(")", "")
122            for x in export_nested_types_from_variable(var)
123        ]
124        returns = [
125            convert_type_for_solidity_signature_to_string(x).replace("(", "").replace(")", "")
126            for x in export_return_type_from_variable(var)
127        ]
128    else:
129        _, params, _ = var.signature
130        params = [p + " memory" if p in ["bytes", "string"] else p for p in params]
131        returns = []
132        _type = var.type
133        while isinstance(_type, MappingType):
134            _type = _type.type_to
135        while isinstance(_type, (ArrayType, UserDefinedType)):
136            _type = _type.type
137        if isinstance(_type, TypeAlias):
138            _type = _type.type
139        if isinstance(_type, Structure):
140            if any(isinstance(elem.type, MappingType) for elem in _type.elems_ordered):
141                return ""
142        ret = str(_type)
143        if isinstance(_type, Structure) or (isinstance(_type, Type) and _type.is_dynamic):
144            ret += " memory"
145        elif isinstance(_type, Contract):
146            ret = "address"
147        returns.append(ret)
148    return f"{var.name}({','.join(params)}) external returns ({', '.join(returns)})"
149
150
151def generate_interface_function_signature(
152    func: "FunctionContract", unroll_structs: bool = True
153) -> Optional[str]:
154    """
155    Generates a string of the form:
156        func_name(type1,type2) external {payable/view/pure} returns (type3)
157
158    Args:
159        func: A FunctionContract object
160        unroll_structs: Determines whether structs are unrolled into underlying types (default: True)
161
162    Returns:
163        The function interface as a str (contains the return values).
164        Returns None if the function is private or internal, or is a constructor/fallback/receive.
165    """
166
167    def format_var(var: "LocalVariable", unroll: bool) -> str:
168        if unroll:
169            return (
170                convert_type_for_solidity_signature_to_string(var.type)
171                .replace("(", "")
172                .replace(")", "")
173            )
174        if var.type.is_dynamic:
175            return f"{_handle_dynamic_struct_elem(var.type)} {var.location}"
176        if isinstance(var.type, ArrayType) and isinstance(
177            var.type.type, (UserDefinedType, ElementaryType)
178        ):
179            return (
180                convert_type_for_solidity_signature_to_string(var.type)
181                .replace("(", "")
182                .replace(")", "")
183                + f" {var.location}"
184            )
185        if isinstance(var.type, UserDefinedType):
186            if isinstance(var.type.type, Structure):
187                return f"{str(var.type.type)} memory"
188            if isinstance(var.type.type, Enum):
189                return str(var.type.type)
190            if isinstance(var.type.type, Contract):
191                return "address"
192        if isinstance(var.type, TypeAlias):
193            return str(var.type.type)
194        return str(var.type)
195
196    name, _, _ = func.signature
197    if (
198        func not in func.contract.functions_entry_points
199        or func.is_constructor
200        or func.is_fallback
201        or func.is_receive
202    ):
203        return None
204    view = " view" if func.view and not func.pure else ""
205    pure = " pure" if func.pure else ""
206    payable = " payable" if func.payable else ""
207    # Make sure the function doesn't return a struct with nested mappings
208    for ret in func.returns:
209        if isinstance(ret.type, UserDefinedType) and isinstance(ret.type.type, Structure):
210            for elem in ret.type.type.elems_ordered:
211                if isinstance(elem.type, MappingType):
212                    return ""
213    returns = [format_var(ret, unroll_structs) for ret in func.returns]
214    parameters = [format_var(param, unroll_structs) for param in func.parameters]
215    _interface_signature_str = (
216        name + "(" + ",".join(parameters) + ") external" + payable + pure + view
217    )
218    if len(returns) > 0:
219        _interface_signature_str += " returns (" + ",".join(returns) + ")"
220    return _interface_signature_str
221
222
223def generate_struct_interface_str(struct: "Structure", indent: int = 0) -> str:
224    """
225    Generates code for a structure declaration in an interface of the form:
226        struct struct_name {
227            elem1_type elem1_name;
228            elem2_type elem2_name;
229            ...        ...
230        }
231    Args:
232        struct: A Structure object.
233        indent: Number of spaces to indent the code block with.
234
235    Returns:
236        The structure declaration code as a string.
237    """
238    spaces = ""
239    for _ in range(0, indent):
240        spaces += " "
241    definition = f"{spaces}struct {struct.name} {{\n"
242    for elem in struct.elems_ordered:
243        if elem.type.is_dynamic:
244            definition += f"{spaces}    {_handle_dynamic_struct_elem(elem.type)} {elem.name};\n"
245        elif isinstance(elem.type, UserDefinedType):
246            if isinstance(elem.type.type, Structure):
247                definition += f"{spaces}    {elem.type.type} {elem.name};\n"
248            else:
249                definition += f"{spaces}    {convert_type_for_solidity_signature_to_string(elem.type)} {elem.name};\n"
250        elif isinstance(elem.type, TypeAlias):
251            definition += f"{spaces}    {elem.type.type} {elem.name};\n"
252        else:
253            definition += f"{spaces}    {elem.type} {elem.name};\n"
254    definition += f"{spaces}}}\n"
255    return definition
256
257
258def _handle_dynamic_struct_elem(elem_type: Type) -> str:
259    assert elem_type.is_dynamic
260    if isinstance(elem_type, ElementaryType):
261        return f"{elem_type}"
262    if isinstance(elem_type, ArrayType):
263        base_type = elem_type.type
264        if isinstance(base_type, UserDefinedType):
265            if isinstance(base_type.type, Contract):
266                return "address[]"
267            if isinstance(base_type.type, Enum):
268                return convert_type_for_solidity_signature_to_string(elem_type)
269            return f"{base_type.type.name}[]"
270        return f"{base_type}[]"
271    if isinstance(elem_type, MappingType):
272        type_to = elem_type.type_to
273        type_from = elem_type.type_from
274        if isinstance(type_from, UserDefinedType) and isinstance(type_from.type, Contract):
275            type_from = ElementaryType("address")
276        if isinstance(type_to, MappingType):
277            return f"mapping({type_from} => {_handle_dynamic_struct_elem(type_to)})"
278        if isinstance(type_to, UserDefinedType):
279            if isinstance(type_to.type, Contract):
280                return f"mapping({type_from} => address)"
281            return f"mapping({type_from} => {type_to.type.name})"
282        return f"{elem_type}"
283    return ""
284
285
286def generate_custom_error_interface(
287    error: "CustomErrorContract", unroll_structs: bool = True
288) -> str:
289    args = [
290        convert_type_for_solidity_signature_to_string(arg.type).replace("(", "").replace(")", "")
291        if unroll_structs
292        else str(arg.type.type)
293        if isinstance(arg.type, UserDefinedType) and isinstance(arg.type.type, (Structure, Enum))
294        else str(arg.type)
295        for arg in error.parameters
296    ]
297    return f"{error.name}({', '.join(args)})"
def generate_interface( contract: slither.core.declarations.contract.Contract, unroll_structs: bool = True, include_events: bool = True, include_errors: bool = True, include_enums: bool = True, include_structs: bool = True) -> str:
 28def generate_interface(
 29    contract: "Contract",
 30    unroll_structs: bool = True,
 31    include_events: bool = True,
 32    include_errors: bool = True,
 33    include_enums: bool = True,
 34    include_structs: bool = True,
 35) -> str:
 36    """
 37    Generates code for a Solidity interface to the contract.
 38    Args:
 39        contract: A Contract object.
 40        unroll_structs: Whether to use structures' underlying types instead of the user-defined type (default: True).
 41        include_events: Whether to include event signatures in the interface (default: True).
 42        include_errors: Whether to include custom error signatures in the interface (default: True).
 43        include_enums: Whether to include enum definitions in the interface (default: True).
 44        include_structs: Whether to include struct definitions in the interface (default: True).
 45
 46    Returns:
 47        A string with the code for an interface, with function stubs for all public or external functions and
 48        state variables, as well as any events, custom errors and/or structs declared in the contract.
 49    """
 50    interface = f"interface I{contract.name} {{\n"
 51    if include_events:
 52        for event in contract.events:
 53            name, args = event.signature
 54            interface += f"    event {name}({', '.join(args)});\n"
 55    if include_errors:
 56        for error in contract.custom_errors:
 57            interface += f"    error {generate_custom_error_interface(error, unroll_structs)};\n"
 58    if include_enums:
 59        for enum in contract.enums:
 60            interface += f"    enum {enum.name} {{ {', '.join(enum.values)} }}\n"
 61    if include_structs:
 62        # Include structures defined in this contract and at the top level
 63        structs = contract.structures + contract.compilation_unit.structures_top_level
 64        # Function signatures may reference other structures as well
 65        # Include structures defined in libraries used for them
 66        for _for in contract.using_for.keys():
 67            if (
 68                isinstance(_for, UserDefinedType)
 69                and isinstance(_for.type, StructureContract)
 70                and _for.type not in structs
 71            ):
 72                structs.append(_for.type)
 73        # Include any other structures used as function arguments/returns
 74        for func in contract.functions_entry_points:
 75            for arg in func.parameters + func.returns:
 76                _type = arg.type
 77                if isinstance(_type, ArrayType):
 78                    _type = _type.type
 79                while isinstance(_type, MappingType):
 80                    _type = _type.type_to
 81                if isinstance(_type, UserDefinedType):
 82                    _type = _type.type
 83                if isinstance(_type, Structure) and _type not in structs:
 84                    structs.append(_type)
 85        for struct in structs:
 86            interface += generate_struct_interface_str(struct, indent=4)
 87            for elem in struct.elems_ordered:
 88                if (
 89                    isinstance(elem.type, UserDefinedType)
 90                    and isinstance(elem.type.type, StructureContract)
 91                    and elem.type.type not in structs
 92                ):
 93                    structs.append(elem.type.type)
 94    for var in contract.state_variables_entry_points:
 95        # if any(func.name == var.name for func in contract.functions_entry_points):
 96        #     # ignore public variables that override a public function
 97        #     continue
 98        var_sig = generate_interface_variable_signature(var, unroll_structs)
 99        if var_sig is not None and var_sig != "":
100            interface += f"    function {var_sig};\n"
101    for func in contract.functions_entry_points:
102        if func.is_constructor or func.is_fallback or func.is_receive or not func.is_implemented:
103            continue
104        interface += (
105            f"    function {generate_interface_function_signature(func, unroll_structs)};\n"
106        )
107    interface += "}\n\n"
108    return interface

Generates code for a Solidity interface to the contract. Args: contract: A Contract object. 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.

def generate_interface_variable_signature( var: slither.core.variables.state_variable.StateVariable, unroll_structs: bool = True) -> Union[str, NoneType]:
111def generate_interface_variable_signature(
112    var: "StateVariable", unroll_structs: bool = True
113) -> Optional[str]:
114    if var.visibility in ["private", "internal"]:
115        return None
116    if isinstance(var.type, UserDefinedType) and isinstance(var.type.type, Structure):
117        for elem in var.type.type.elems_ordered:
118            if isinstance(elem.type, MappingType):
119                return ""
120    if unroll_structs:
121        params = [
122            convert_type_for_solidity_signature_to_string(x).replace("(", "").replace(")", "")
123            for x in export_nested_types_from_variable(var)
124        ]
125        returns = [
126            convert_type_for_solidity_signature_to_string(x).replace("(", "").replace(")", "")
127            for x in export_return_type_from_variable(var)
128        ]
129    else:
130        _, params, _ = var.signature
131        params = [p + " memory" if p in ["bytes", "string"] else p for p in params]
132        returns = []
133        _type = var.type
134        while isinstance(_type, MappingType):
135            _type = _type.type_to
136        while isinstance(_type, (ArrayType, UserDefinedType)):
137            _type = _type.type
138        if isinstance(_type, TypeAlias):
139            _type = _type.type
140        if isinstance(_type, Structure):
141            if any(isinstance(elem.type, MappingType) for elem in _type.elems_ordered):
142                return ""
143        ret = str(_type)
144        if isinstance(_type, Structure) or (isinstance(_type, Type) and _type.is_dynamic):
145            ret += " memory"
146        elif isinstance(_type, Contract):
147            ret = "address"
148        returns.append(ret)
149    return f"{var.name}({','.join(params)}) external returns ({', '.join(returns)})"
def generate_interface_function_signature( func: slither.core.declarations.function_contract.FunctionContract, unroll_structs: bool = True) -> Union[str, NoneType]:
152def generate_interface_function_signature(
153    func: "FunctionContract", unroll_structs: bool = True
154) -> Optional[str]:
155    """
156    Generates a string of the form:
157        func_name(type1,type2) external {payable/view/pure} returns (type3)
158
159    Args:
160        func: A FunctionContract object
161        unroll_structs: Determines whether structs are unrolled into underlying types (default: True)
162
163    Returns:
164        The function interface as a str (contains the return values).
165        Returns None if the function is private or internal, or is a constructor/fallback/receive.
166    """
167
168    def format_var(var: "LocalVariable", unroll: bool) -> str:
169        if unroll:
170            return (
171                convert_type_for_solidity_signature_to_string(var.type)
172                .replace("(", "")
173                .replace(")", "")
174            )
175        if var.type.is_dynamic:
176            return f"{_handle_dynamic_struct_elem(var.type)} {var.location}"
177        if isinstance(var.type, ArrayType) and isinstance(
178            var.type.type, (UserDefinedType, ElementaryType)
179        ):
180            return (
181                convert_type_for_solidity_signature_to_string(var.type)
182                .replace("(", "")
183                .replace(")", "")
184                + f" {var.location}"
185            )
186        if isinstance(var.type, UserDefinedType):
187            if isinstance(var.type.type, Structure):
188                return f"{str(var.type.type)} memory"
189            if isinstance(var.type.type, Enum):
190                return str(var.type.type)
191            if isinstance(var.type.type, Contract):
192                return "address"
193        if isinstance(var.type, TypeAlias):
194            return str(var.type.type)
195        return str(var.type)
196
197    name, _, _ = func.signature
198    if (
199        func not in func.contract.functions_entry_points
200        or func.is_constructor
201        or func.is_fallback
202        or func.is_receive
203    ):
204        return None
205    view = " view" if func.view and not func.pure else ""
206    pure = " pure" if func.pure else ""
207    payable = " payable" if func.payable else ""
208    # Make sure the function doesn't return a struct with nested mappings
209    for ret in func.returns:
210        if isinstance(ret.type, UserDefinedType) and isinstance(ret.type.type, Structure):
211            for elem in ret.type.type.elems_ordered:
212                if isinstance(elem.type, MappingType):
213                    return ""
214    returns = [format_var(ret, unroll_structs) for ret in func.returns]
215    parameters = [format_var(param, unroll_structs) for param in func.parameters]
216    _interface_signature_str = (
217        name + "(" + ",".join(parameters) + ") external" + payable + pure + view
218    )
219    if len(returns) > 0:
220        _interface_signature_str += " returns (" + ",".join(returns) + ")"
221    return _interface_signature_str

Generates a string of the form: func_name(type1,type2) external {payable/view/pure} returns (type3)

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 generate_struct_interface_str( struct: slither.core.declarations.structure.Structure, indent: int = 0) -> str:
224def generate_struct_interface_str(struct: "Structure", indent: int = 0) -> str:
225    """
226    Generates code for a structure declaration in an interface of the form:
227        struct struct_name {
228            elem1_type elem1_name;
229            elem2_type elem2_name;
230            ...        ...
231        }
232    Args:
233        struct: A Structure object.
234        indent: Number of spaces to indent the code block with.
235
236    Returns:
237        The structure declaration code as a string.
238    """
239    spaces = ""
240    for _ in range(0, indent):
241        spaces += " "
242    definition = f"{spaces}struct {struct.name} {{\n"
243    for elem in struct.elems_ordered:
244        if elem.type.is_dynamic:
245            definition += f"{spaces}    {_handle_dynamic_struct_elem(elem.type)} {elem.name};\n"
246        elif isinstance(elem.type, UserDefinedType):
247            if isinstance(elem.type.type, Structure):
248                definition += f"{spaces}    {elem.type.type} {elem.name};\n"
249            else:
250                definition += f"{spaces}    {convert_type_for_solidity_signature_to_string(elem.type)} {elem.name};\n"
251        elif isinstance(elem.type, TypeAlias):
252            definition += f"{spaces}    {elem.type.type} {elem.name};\n"
253        else:
254            definition += f"{spaces}    {elem.type} {elem.name};\n"
255    definition += f"{spaces}}}\n"
256    return definition

Generates code for a structure declaration in an interface of the form: struct struct_name { elem1_type elem1_name; elem2_type elem2_name; ... ... } Args: struct: A Structure object. indent: Number of spaces to indent the code block with.

Returns: The structure declaration code as a string.

def generate_custom_error_interface( error: slither.core.declarations.custom_error_contract.CustomErrorContract, unroll_structs: bool = True) -> str:
287def generate_custom_error_interface(
288    error: "CustomErrorContract", unroll_structs: bool = True
289) -> str:
290    args = [
291        convert_type_for_solidity_signature_to_string(arg.type).replace("(", "").replace(")", "")
292        if unroll_structs
293        else str(arg.type.type)
294        if isinstance(arg.type, UserDefinedType) and isinstance(arg.type.type, (Structure, Enum))
295        else str(arg.type)
296        for arg in error.parameters
297    ]
298    return f"{error.name}({', '.join(args)})"