diff --git a/slither/core/variables/__init__.py b/slither/core/variables/__init__.py index 638f0f3a4e..53872853aa 100644 --- a/slither/core/variables/__init__.py +++ b/slither/core/variables/__init__.py @@ -1,2 +1,8 @@ from .state_variable import StateVariable from .variable import Variable +from .local_variable_init_from_tuple import LocalVariableInitFromTuple +from .local_variable import LocalVariable +from .top_level_variable import TopLevelVariable +from .event_variable import EventVariable +from .function_type_variable import FunctionTypeVariable +from .structure_variable import StructureVariable diff --git a/slither/printers/summary/ck.py b/slither/printers/summary/ck.py index f7a8510391..78da23756b 100644 --- a/slither/printers/summary/ck.py +++ b/slither/printers/summary/ck.py @@ -32,6 +32,7 @@ """ from slither.printers.abstract_printer import AbstractPrinter from slither.utils.ck import CKMetrics +from slither.utils.output import Output class CK(AbstractPrinter): @@ -40,7 +41,7 @@ class CK(AbstractPrinter): WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#ck" - def output(self, _filename): + def output(self, _filename: str) -> Output: if len(self.contracts) == 0: return self.generate_output("No contract found") diff --git a/slither/printers/summary/halstead.py b/slither/printers/summary/halstead.py index 8144e467f7..d3c3557db9 100644 --- a/slither/printers/summary/halstead.py +++ b/slither/printers/summary/halstead.py @@ -25,6 +25,7 @@ """ from slither.printers.abstract_printer import AbstractPrinter from slither.utils.halstead import HalsteadMetrics +from slither.utils.output import Output class Halstead(AbstractPrinter): @@ -33,7 +34,7 @@ class Halstead(AbstractPrinter): WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#halstead" - def output(self, _filename): + def output(self, _filename: str) -> Output: if len(self.contracts) == 0: return self.generate_output("No contract found") diff --git a/slither/printers/summary/martin.py b/slither/printers/summary/martin.py index c49e63fcb5..a0f1bbfcb1 100644 --- a/slither/printers/summary/martin.py +++ b/slither/printers/summary/martin.py @@ -11,6 +11,7 @@ """ from slither.printers.abstract_printer import AbstractPrinter from slither.utils.martin import MartinMetrics +from slither.utils.output import Output class Martin(AbstractPrinter): @@ -19,7 +20,7 @@ class Martin(AbstractPrinter): WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#martin" - def output(self, _filename): + def output(self, _filename: str) -> Output: if len(self.contracts) == 0: return self.generate_output("No contract found") diff --git a/slither/utils/ck.py b/slither/utils/ck.py index 7b0d1afd90..ffba663ad2 100644 --- a/slither/utils/ck.py +++ b/slither/utils/ck.py @@ -110,7 +110,7 @@ class CKContractMetrics: dit: int = 0 cbo: int = 0 - def __post_init__(self): + def __post_init__(self) -> None: if not hasattr(self.contract, "functions"): return self.count_variables() @@ -123,7 +123,7 @@ def __post_init__(self): # pylint: disable=too-many-locals # pylint: disable=too-many-branches - def calculate_metrics(self): + def calculate_metrics(self) -> None: """Calculate the metrics for a contract""" rfc = self.public # initialize with public getter count for func in self.contract.functions: @@ -186,7 +186,7 @@ def calculate_metrics(self): self.ext_calls += len(external_calls) self.rfc = rfc - def count_variables(self): + def count_variables(self) -> None: """Count the number of variables in a contract""" state_variable_count = 0 constant_count = 0 @@ -302,7 +302,7 @@ class CKMetrics: ("Core", "core", CORE_KEYS), ) - def __post_init__(self): + def __post_init__(self) -> None: martin_metrics = MartinMetrics(self.contracts).contract_metrics dependents = { inherited.name: { @@ -323,6 +323,7 @@ def __post_init__(self): for contract in self.contracts } + subtitle = "" # Update each section for (title, attr, keys) in self.SECTIONS: if attr == "core": diff --git a/slither/utils/encoding.py b/slither/utils/encoding.py new file mode 100644 index 0000000000..288b581505 --- /dev/null +++ b/slither/utils/encoding.py @@ -0,0 +1,202 @@ +from typing import Union + +from slither.core import variables +from slither.core.declarations import ( + SolidityVariable, + SolidityVariableComposed, + Structure, + Enum, + Contract, +) +from slither.core import solidity_types +from slither.slithir import operations +from slither.slithir import variables as SlitherIRVariable + + +# pylint: disable=too-many-branches +def ntype(_type: Union[solidity_types.Type, str]) -> str: + if isinstance(_type, solidity_types.ElementaryType): + _type = str(_type) + elif isinstance(_type, solidity_types.ArrayType): + if isinstance(_type.type, solidity_types.ElementaryType): + _type = str(_type) + else: + _type = "user_defined_array" + elif isinstance(_type, Structure): + _type = str(_type) + elif isinstance(_type, Enum): + _type = str(_type) + elif isinstance(_type, solidity_types.MappingType): + _type = str(_type) + elif isinstance(_type, solidity_types.UserDefinedType): + if isinstance(_type.type, Contract): + _type = f"contract({_type.type.name})" + elif isinstance(_type.type, Structure): + _type = f"struct({_type.type.name})" + elif isinstance(_type.type, Enum): + _type = f"enum({_type.type.name})" + else: + _type = str(_type) + + _type = _type.replace(" memory", "") + _type = _type.replace(" storage ref", "") + + if "struct" in _type: + return "struct" + if "enum" in _type: + return "enum" + if "tuple" in _type: + return "tuple" + if "contract" in _type: + return "contract" + if "mapping" in _type: + return "mapping" + return _type.replace(" ", "_") + + +# pylint: disable=too-many-branches +def encode_var_for_compare(var: Union[variables.Variable, SolidityVariable]) -> str: + + # variables + if isinstance(var, SlitherIRVariable.Constant): + return f"constant({ntype(var.type)},{var.value})" + if isinstance(var, SolidityVariableComposed): + return f"solidity_variable_composed({var.name})" + if isinstance(var, SolidityVariable): + return f"solidity_variable{var.name}" + if isinstance(var, SlitherIRVariable.TemporaryVariable): + return "temporary_variable" + if isinstance(var, SlitherIRVariable.ReferenceVariable): + return f"reference({ntype(var.type)})" + if isinstance(var, variables.LocalVariable): + return f"local_solc_variable({ntype(var.type)},{var.location})" + if isinstance(var, variables.StateVariable): + if not (var.is_constant or var.is_immutable): + try: + slot, _ = var.contract.compilation_unit.storage_layout_of(var.contract, var) + except KeyError: + slot = var.name + else: + slot = var.name + return f"state_solc_variable({ntype(var.type)},{slot})" + if isinstance(var, variables.LocalVariableInitFromTuple): + return "local_variable_init_tuple" + if isinstance(var, SlitherIRVariable.TupleVariable): + return "tuple_variable" + + # default + return "" + + +# pylint: disable=too-many-branches +def encode_ir_for_upgradeability_compare(ir: operations.Operation) -> str: + # operations + if isinstance(ir, operations.Assignment): + return f"({encode_var_for_compare(ir.lvalue)}):=({encode_var_for_compare(ir.rvalue)})" + if isinstance(ir, operations.Index): + return f"index({ntype(ir.variable_right.type)})" + if isinstance(ir, operations.Member): + return "member" # .format(ntype(ir._type)) + if isinstance(ir, operations.Length): + return "length" + if isinstance(ir, operations.Binary): + return f"binary({encode_var_for_compare(ir.variable_left)}{ir.type}{encode_var_for_compare(ir.variable_right)})" + if isinstance(ir, operations.Unary): + return f"unary({str(ir.type)})" + if isinstance(ir, operations.Condition): + return f"condition({encode_var_for_compare(ir.value)})" + if isinstance(ir, operations.NewStructure): + return "new_structure" + if isinstance(ir, operations.NewContract): + return "new_contract" + if isinstance(ir, operations.NewArray): + return f"new_array({ntype(ir.array_type)})" + if isinstance(ir, operations.NewElementaryType): + return f"new_elementary({ntype(ir.type)})" + if isinstance(ir, operations.Delete): + return f"delete({encode_var_for_compare(ir.lvalue)},{encode_var_for_compare(ir.variable)})" + if isinstance(ir, operations.SolidityCall): + return f"solidity_call({ir.function.full_name})" + if isinstance(ir, operations.InternalCall): + return f"internal_call({ntype(ir.type_call)})" + if isinstance(ir, operations.EventCall): # is this useful? + return "event" + if isinstance(ir, operations.LibraryCall): + return "library_call" + if isinstance(ir, operations.InternalDynamicCall): + return "internal_dynamic_call" + if isinstance(ir, operations.HighLevelCall): # TODO: improve + return "high_level_call" + if isinstance(ir, operations.LowLevelCall): # TODO: improve + return "low_level_call" + if isinstance(ir, operations.TypeConversion): + return f"type_conversion({ntype(ir.type)})" + if isinstance(ir, operations.Return): # this can be improved using values + return "return" # .format(ntype(ir.type)) + if isinstance(ir, operations.Transfer): + return f"transfer({encode_var_for_compare(ir.call_value)})" + if isinstance(ir, operations.Send): + return f"send({encode_var_for_compare(ir.call_value)})" + if isinstance(ir, operations.Unpack): # TODO: improve + return "unpack" + if isinstance(ir, operations.InitArray): # TODO: improve + return "init_array" + + # default + return "" + + +def encode_ir_for_halstead(ir: operations.Operation) -> str: + # operations + if isinstance(ir, operations.Assignment): + return "assignment" + if isinstance(ir, operations.Index): + return "index" + if isinstance(ir, operations.Member): + return "member" # .format(ntype(ir._type)) + if isinstance(ir, operations.Length): + return "length" + if isinstance(ir, operations.Binary): + return f"binary({str(ir.type)})" + if isinstance(ir, operations.Unary): + return f"unary({str(ir.type)})" + if isinstance(ir, operations.Condition): + return f"condition({encode_var_for_compare(ir.value)})" + if isinstance(ir, operations.NewStructure): + return "new_structure" + if isinstance(ir, operations.NewContract): + return "new_contract" + if isinstance(ir, operations.NewArray): + return f"new_array({ntype(ir.array_type)})" + if isinstance(ir, operations.NewElementaryType): + return f"new_elementary({ntype(ir.type)})" + if isinstance(ir, operations.Delete): + return "delete" + if isinstance(ir, operations.SolidityCall): + return f"solidity_call({ir.function.full_name})" + if isinstance(ir, operations.InternalCall): + return f"internal_call({ntype(ir.type_call)})" + if isinstance(ir, operations.EventCall): # is this useful? + return "event" + if isinstance(ir, operations.LibraryCall): + return "library_call" + if isinstance(ir, operations.InternalDynamicCall): + return "internal_dynamic_call" + if isinstance(ir, operations.HighLevelCall): # TODO: improve + return "high_level_call" + if isinstance(ir, operations.LowLevelCall): # TODO: improve + return "low_level_call" + if isinstance(ir, operations.TypeConversion): + return f"type_conversion({ntype(ir.type)})" + if isinstance(ir, operations.Return): # this can be improved using values + return "return" # .format(ntype(ir.type)) + if isinstance(ir, operations.Transfer): + return "transfer" + if isinstance(ir, operations.Send): + return "send" + if isinstance(ir, operations.Unpack): # TODO: improve + return "unpack" + if isinstance(ir, operations.InitArray): # TODO: improve + return "init_array" + # default + raise NotImplementedError(f"encode_ir_for_halstead: {ir}") diff --git a/slither/utils/halstead.py b/slither/utils/halstead.py index 64dd1f6a1a..9ec952e486 100644 --- a/slither/utils/halstead.py +++ b/slither/utils/halstead.py @@ -25,13 +25,17 @@ """ import math +from collections import OrderedDict from dataclasses import dataclass, field from typing import Tuple, List, Dict -from collections import OrderedDict + from slither.core.declarations import Contract from slither.slithir.variables.temporary import TemporaryVariable +from slither.utils.encoding import encode_ir_for_halstead from slither.utils.myprettytable import make_pretty_table, MyPrettyTable -from slither.utils.upgradeability import encode_ir_for_halstead + + +# pylint: disable=too-many-branches @dataclass @@ -55,7 +59,7 @@ class HalsteadContractMetrics: T: float = 0 B: float = 0 - def __post_init__(self): + def __post_init__(self) -> None: """Operators and operands can be passed in as constructor args to avoid computing them based on the contract. Useful for computing metrics for ALL_CONTRACTS""" @@ -85,7 +89,7 @@ def to_dict(self) -> Dict[str, float]: } ) - def populate_operators_and_operands(self): + def populate_operators_and_operands(self) -> None: """Populate the operators and operands lists.""" operators = [] operands = [] @@ -104,7 +108,7 @@ def populate_operators_and_operands(self): self.all_operators.extend(operators) self.all_operands.extend(operands) - def compute_metrics(self, all_operators=None, all_operands=None): + def compute_metrics(self, all_operators=None, all_operands=None) -> None: """Compute the Halstead metrics.""" if all_operators is None: all_operators = self.all_operators @@ -183,17 +187,17 @@ class HalsteadMetrics: ("Extended 2/2", "extended2", EXTENDED2_KEYS), ) - def __post_init__(self): + def __post_init__(self) -> None: # Compute the metrics for each contract and for all contracts. self.update_contract_metrics() self.add_all_contracts_metrics() self.update_reporting_sections() - def update_contract_metrics(self): + def update_contract_metrics(self) -> None: for contract in self.contracts: self.contract_metrics[contract.name] = HalsteadContractMetrics(contract=contract) - def add_all_contracts_metrics(self): + def add_all_contracts_metrics(self) -> None: # If there are more than 1 contract, compute the metrics for all contracts. if len(self.contracts) <= 1: return @@ -211,7 +215,7 @@ def add_all_contracts_metrics(self): None, all_operators=all_operators, all_operands=all_operands ) - def update_reporting_sections(self): + def update_reporting_sections(self) -> None: # Create the table and text for each section. data = { contract.name: self.contract_metrics[contract.name].to_dict() diff --git a/slither/utils/martin.py b/slither/utils/martin.py index fb62a4c584..c336227fd2 100644 --- a/slither/utils/martin.py +++ b/slither/utils/martin.py @@ -26,12 +26,12 @@ class MartinContractMetrics: i: float = 0.0 d: float = 0.0 - def __post_init__(self): + def __post_init__(self) -> None: if self.ce + self.ca > 0: self.i = float(self.ce / (self.ce + self.ca)) self.d = float(abs(self.i - self.abstractness)) - def to_dict(self): + def to_dict(self) -> Dict: return { "Dependents": self.ca, "Dependencies": self.ce, @@ -65,12 +65,12 @@ class MartinMetrics: ) SECTIONS: Tuple[Tuple[str, str, Tuple[str]]] = (("Core", "core", CORE_KEYS),) - def __post_init__(self): + def __post_init__(self) -> None: self.update_abstractness() self.update_coupling() self.update_reporting_sections() - def update_reporting_sections(self): + def update_reporting_sections(self) -> None: # Create the table and text for each section. data = { contract.name: self.contract_metrics[contract.name].to_dict() @@ -98,7 +98,7 @@ def update_reporting_sections(self): SectionInfo(title=section_title, pretty_table=pretty_table, txt=txt), ) - def update_abstractness(self) -> float: + def update_abstractness(self) -> None: abstract_contract_count = 0 for c in self.contracts: if not c.is_fully_implemented: @@ -106,7 +106,7 @@ def update_abstractness(self) -> float: self.abstractness = float(abstract_contract_count / len(self.contracts)) # pylint: disable=too-many-branches - def update_coupling(self) -> Dict: + def update_coupling(self) -> None: dependencies = {} for contract in self.contracts: external_calls = [] diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 5cc3dba08c..bbb253175e 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -1,66 +1,28 @@ -from typing import Optional, Tuple, List, Union +from typing import Optional, Tuple, List + +from slither.analyses.data_dependency.data_dependency import get_dependencies +from slither.core.cfg.node import Node, NodeType from slither.core.declarations import ( Contract, - Structure, - Enum, - SolidityVariableComposed, - SolidityVariable, Function, ) -from slither.core.solidity_types import ( - Type, - ElementaryType, - ArrayType, - MappingType, - UserDefinedType, -) -from slither.core.variables.local_variable import LocalVariable -from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple -from slither.core.variables.state_variable import StateVariable -from slither.analyses.data_dependency.data_dependency import get_dependencies -from slither.core.variables.variable import Variable from slither.core.expressions import ( Literal, Identifier, CallExpression, AssignmentOperation, ) -from slither.core.cfg.node import Node, NodeType +from slither.core.solidity_types import ( + ElementaryType, +) +from slither.core.variables.local_variable import LocalVariable +from slither.core.variables.state_variable import StateVariable +from slither.core.variables.variable import Variable from slither.slithir.operations import ( - Operation, - Assignment, - Index, - Member, - Length, - Binary, - Unary, - Condition, - NewArray, - NewStructure, - NewContract, - NewElementaryType, - SolidityCall, - Delete, - EventCall, - LibraryCall, - InternalDynamicCall, - HighLevelCall, LowLevelCall, - TypeConversion, - Return, - Transfer, - Send, - Unpack, - InitArray, - InternalCall, -) -from slither.slithir.variables import ( - TemporaryVariable, - TupleVariable, - Constant, - ReferenceVariable, ) from slither.tools.read_storage.read_storage import SlotInfo, SlitherReadStorage +from slither.utils.encoding import encode_ir_for_upgradeability_compare class TaintedExternalContract: @@ -385,201 +347,13 @@ def is_function_modified(f1: Function, f2: Function) -> bool: if len(node_f1.irs) != len(node_f2.irs): return True for i, ir in enumerate(node_f1.irs): - if encode_ir_for_compare(ir) != encode_ir_for_compare(node_f2.irs[i]): + if encode_ir_for_upgradeability_compare(ir) != encode_ir_for_upgradeability_compare( + node_f2.irs[i] + ): return True return False -# pylint: disable=too-many-branches -def ntype(_type: Union[Type, str]) -> str: - if isinstance(_type, ElementaryType): - _type = str(_type) - elif isinstance(_type, ArrayType): - if isinstance(_type.type, ElementaryType): - _type = str(_type) - else: - _type = "user_defined_array" - elif isinstance(_type, Structure): - _type = str(_type) - elif isinstance(_type, Enum): - _type = str(_type) - elif isinstance(_type, MappingType): - _type = str(_type) - elif isinstance(_type, UserDefinedType): - if isinstance(_type.type, Contract): - _type = f"contract({_type.type.name})" - elif isinstance(_type.type, Structure): - _type = f"struct({_type.type.name})" - elif isinstance(_type.type, Enum): - _type = f"enum({_type.type.name})" - else: - _type = str(_type) - - _type = _type.replace(" memory", "") - _type = _type.replace(" storage ref", "") - - if "struct" in _type: - return "struct" - if "enum" in _type: - return "enum" - if "tuple" in _type: - return "tuple" - if "contract" in _type: - return "contract" - if "mapping" in _type: - return "mapping" - return _type.replace(" ", "_") - - -# pylint: disable=too-many-branches -def encode_ir_for_compare(ir: Operation) -> str: - # operations - if isinstance(ir, Assignment): - return f"({encode_var_for_compare(ir.lvalue)}):=({encode_var_for_compare(ir.rvalue)})" - if isinstance(ir, Index): - return f"index({ntype(ir.variable_right.type)})" - if isinstance(ir, Member): - return "member" # .format(ntype(ir._type)) - if isinstance(ir, Length): - return "length" - if isinstance(ir, Binary): - return f"binary({encode_var_for_compare(ir.variable_left)}{ir.type}{encode_var_for_compare(ir.variable_right)})" - if isinstance(ir, Unary): - return f"unary({str(ir.type)})" - if isinstance(ir, Condition): - return f"condition({encode_var_for_compare(ir.value)})" - if isinstance(ir, NewStructure): - return "new_structure" - if isinstance(ir, NewContract): - return "new_contract" - if isinstance(ir, NewArray): - return f"new_array({ntype(ir.array_type)})" - if isinstance(ir, NewElementaryType): - return f"new_elementary({ntype(ir.type)})" - if isinstance(ir, Delete): - return f"delete({encode_var_for_compare(ir.lvalue)},{encode_var_for_compare(ir.variable)})" - if isinstance(ir, SolidityCall): - return f"solidity_call({ir.function.full_name})" - if isinstance(ir, InternalCall): - return f"internal_call({ntype(ir.type_call)})" - if isinstance(ir, EventCall): # is this useful? - return "event" - if isinstance(ir, LibraryCall): - return "library_call" - if isinstance(ir, InternalDynamicCall): - return "internal_dynamic_call" - if isinstance(ir, HighLevelCall): # TODO: improve - return "high_level_call" - if isinstance(ir, LowLevelCall): # TODO: improve - return "low_level_call" - if isinstance(ir, TypeConversion): - return f"type_conversion({ntype(ir.type)})" - if isinstance(ir, Return): # this can be improved using values - return "return" # .format(ntype(ir.type)) - if isinstance(ir, Transfer): - return f"transfer({encode_var_for_compare(ir.call_value)})" - if isinstance(ir, Send): - return f"send({encode_var_for_compare(ir.call_value)})" - if isinstance(ir, Unpack): # TODO: improve - return "unpack" - if isinstance(ir, InitArray): # TODO: improve - return "init_array" - - # default - return "" - - -# pylint: disable=too-many-branches -def encode_ir_for_halstead(ir: Operation) -> str: - # operations - if isinstance(ir, Assignment): - return "assignment" - if isinstance(ir, Index): - return "index" - if isinstance(ir, Member): - return "member" # .format(ntype(ir._type)) - if isinstance(ir, Length): - return "length" - if isinstance(ir, Binary): - return f"binary({str(ir.type)})" - if isinstance(ir, Unary): - return f"unary({str(ir.type)})" - if isinstance(ir, Condition): - return f"condition({encode_var_for_compare(ir.value)})" - if isinstance(ir, NewStructure): - return "new_structure" - if isinstance(ir, NewContract): - return "new_contract" - if isinstance(ir, NewArray): - return f"new_array({ntype(ir.array_type)})" - if isinstance(ir, NewElementaryType): - return f"new_elementary({ntype(ir.type)})" - if isinstance(ir, Delete): - return "delete" - if isinstance(ir, SolidityCall): - return f"solidity_call({ir.function.full_name})" - if isinstance(ir, InternalCall): - return f"internal_call({ntype(ir.type_call)})" - if isinstance(ir, EventCall): # is this useful? - return "event" - if isinstance(ir, LibraryCall): - return "library_call" - if isinstance(ir, InternalDynamicCall): - return "internal_dynamic_call" - if isinstance(ir, HighLevelCall): # TODO: improve - return "high_level_call" - if isinstance(ir, LowLevelCall): # TODO: improve - return "low_level_call" - if isinstance(ir, TypeConversion): - return f"type_conversion({ntype(ir.type)})" - if isinstance(ir, Return): # this can be improved using values - return "return" # .format(ntype(ir.type)) - if isinstance(ir, Transfer): - return "transfer" - if isinstance(ir, Send): - return "send" - if isinstance(ir, Unpack): # TODO: improve - return "unpack" - if isinstance(ir, InitArray): # TODO: improve - return "init_array" - # default - raise NotImplementedError(f"encode_ir_for_halstead: {ir}") - - -# pylint: disable=too-many-branches -def encode_var_for_compare(var: Variable) -> str: - - # variables - if isinstance(var, Constant): - return f"constant({ntype(var.type)},{var.value})" - if isinstance(var, SolidityVariableComposed): - return f"solidity_variable_composed({var.name})" - if isinstance(var, SolidityVariable): - return f"solidity_variable{var.name}" - if isinstance(var, TemporaryVariable): - return "temporary_variable" - if isinstance(var, ReferenceVariable): - return f"reference({ntype(var.type)})" - if isinstance(var, LocalVariable): - return f"local_solc_variable({ntype(var.type)},{var.location})" - if isinstance(var, StateVariable): - if not (var.is_constant or var.is_immutable): - try: - slot, _ = var.contract.compilation_unit.storage_layout_of(var.contract, var) - except KeyError: - slot = var.name - else: - slot = var.name - return f"state_solc_variable({ntype(var.type)},{slot})" - if isinstance(var, LocalVariableInitFromTuple): - return "local_variable_init_tuple" - if isinstance(var, TupleVariable): - return "tuple_variable" - - # default - return "" - - def get_proxy_implementation_slot(proxy: Contract) -> Optional[SlotInfo]: """ Gets information about the storage slot where a proxy's implementation address is stored.