slither.utils.ck

CK Metrics are a suite of six software metrics proposed by Chidamber and Kemerer in 1994. These metrics are used to measure the complexity of a class. https://en.wikipedia.org/wiki/Programming_complexity

  • Response For a Class (RFC) is a metric that measures the number of unique method calls within a class.
  • Number of Children (NOC) is a metric that measures the number of children a class has.
  • Depth of Inheritance Tree (DIT) is a metric that measures the number of parent classes a class has.
  • Coupling Between Object Classes (CBO) is a metric that measures the number of classes a class is coupled to.

Not implemented:

  • Lack of Cohesion of Methods (LCOM) is a metric that measures the lack of cohesion in methods.
  • Weighted Methods per Class (WMC) is a metric that measures the complexity of a class.

During the calculation of the metrics above, there are a number of other intermediate metrics that are calculated. These are also included in the output:

  • State variables: total number of state variables
  • Constants: total number of constants
  • Immutables: total number of immutables
  • Public: total number of public functions
  • External: total number of external functions
  • Internal: total number of internal functions
  • Private: total number of private functions
  • Mutating: total number of state mutating functions
  • View: total number of view functions
  • Pure: total number of pure functions
  • External mutating: total number of external mutating functions
  • No auth or onlyOwner: total number of functions without auth or onlyOwner modifiers
  • No modifiers: total number of functions without modifiers
  • Ext calls: total number of external calls
  1"""
  2    CK Metrics are a suite of six software metrics proposed by Chidamber and Kemerer in 1994.
  3    These metrics are used to measure the complexity of a class.
  4    https://en.wikipedia.org/wiki/Programming_complexity
  5
  6    - Response For a Class (RFC) is a metric that measures the number of unique method calls within a class.
  7    - Number of Children (NOC) is a metric that measures the number of children a class has.
  8    - Depth of Inheritance Tree (DIT) is a metric that measures the number of parent classes a class has.
  9    - Coupling Between Object Classes (CBO) is a metric that measures the number of classes a class is coupled to.
 10
 11    Not implemented:
 12    - Lack of Cohesion of Methods (LCOM) is a metric that measures the lack of cohesion in methods.
 13    - Weighted Methods per Class (WMC) is a metric that measures the complexity of a class.
 14
 15    During the calculation of the metrics above, there are a number of other intermediate metrics that are calculated.
 16    These are also included in the output:
 17     - State variables: total number of state variables
 18     - Constants: total number of constants
 19     - Immutables: total number of immutables
 20     - Public: total number of public functions
 21     - External: total number of external functions
 22     - Internal: total number of internal functions
 23     - Private: total number of private functions
 24     - Mutating: total number of state mutating functions
 25     - View: total number of view functions
 26     - Pure: total number of pure functions
 27     - External mutating: total number of external mutating functions
 28     - No auth or onlyOwner: total number of functions without auth or onlyOwner modifiers
 29     - No modifiers: total number of functions without modifiers
 30     - Ext calls: total number of external calls
 31
 32"""
 33from collections import OrderedDict
 34from typing import Tuple, List, Dict
 35from dataclasses import dataclass, field
 36from slither.utils.colors import bold
 37from slither.core.declarations import Contract
 38from slither.utils.myprettytable import make_pretty_table, MyPrettyTable
 39from slither.utils.martin import MartinMetrics
 40from slither.slithir.operations.high_level_call import HighLevelCall
 41
 42
 43# Utility functions
 44
 45
 46def compute_dit(contract: Contract, depth: int = 0) -> int:
 47    """
 48    Recursively compute the depth of inheritance tree (DIT) of a contract
 49    Args:
 50        contract(core.declarations.contract.Contract): contract to compute DIT for
 51        depth(int): current depth of the contract
 52    Returns:
 53        int: depth of the contract
 54    """
 55    if not contract.inheritance:
 56        return depth
 57    max_dit = depth
 58    for inherited_contract in contract.inheritance:
 59        dit = compute_dit(inherited_contract, depth + 1)
 60        max_dit = max(max_dit, dit)
 61    return max_dit
 62
 63
 64def has_auth(func) -> bool:
 65    """
 66    Check if a function has no auth or only_owner modifiers
 67    Args:
 68        func(core.declarations.function.Function): function to check
 69    Returns:
 70        bool True if it does have auth or only_owner modifiers
 71    """
 72    for modifier in func.modifiers:
 73        if "auth" in modifier.name or "only_owner" in modifier.name:
 74            return True
 75    return False
 76
 77
 78# Utility classes for calculating CK metrics
 79
 80
 81@dataclass
 82# pylint: disable=too-many-instance-attributes
 83class CKContractMetrics:
 84    """Class to hold the CK metrics for a single contract."""
 85
 86    contract: Contract
 87
 88    # Used to calculate CBO - should be passed in as a constructor arg
 89    martin_metrics: Dict
 90
 91    # Used to calculate NOC
 92    dependents: Dict
 93
 94    state_variables: int = 0
 95    constants: int = 0
 96    immutables: int = 0
 97    public: int = 0
 98    external: int = 0
 99    internal: int = 0
100    private: int = 0
101    mutating: int = 0
102    view: int = 0
103    pure: int = 0
104    external_mutating: int = 0
105    no_auth_or_only_owner: int = 0
106    no_modifiers: int = 0
107    ext_calls: int = 0
108    rfc: int = 0
109    noc: int = 0
110    dit: int = 0
111    cbo: int = 0
112
113    def __post_init__(self) -> None:
114        if not hasattr(self.contract, "functions"):
115            return
116        self.count_variables()
117        self.noc = len(self.dependents[self.contract.name])
118        self.dit = compute_dit(self.contract)
119        self.cbo = (
120            self.martin_metrics[self.contract.name].ca + self.martin_metrics[self.contract.name].ce
121        )
122        self.calculate_metrics()
123
124    # pylint: disable=too-many-locals
125    # pylint: disable=too-many-branches
126    def calculate_metrics(self) -> None:
127        """Calculate the metrics for a contract"""
128        rfc = self.public  # initialize with public getter count
129        for func in self.contract.functions:
130            if func.name == "constructor":
131                continue
132            pure = func.pure
133            view = not pure and func.view
134            mutating = not pure and not view
135            external = func.visibility == "external"
136            public = func.visibility == "public"
137            internal = func.visibility == "internal"
138            private = func.visibility == "private"
139            external_public_mutating = external or public and mutating
140            external_no_auth = external_public_mutating and not has_auth(func)
141            external_no_modifiers = external_public_mutating and len(func.modifiers) == 0
142            if external or public:
143                rfc += 1
144
145            high_level_calls = [
146                ir for node in func.nodes for ir in node.irs_ssa if isinstance(ir, HighLevelCall)
147            ]
148
149            # convert irs to string with target function and contract name
150            external_calls = []
151            for high_level_call in high_level_calls:
152                if isinstance(high_level_call.destination, Contract):
153                    destination_contract = high_level_call.destination.name
154                elif isinstance(high_level_call.destination, str):
155                    destination_contract = high_level_call.destination
156                elif not hasattr(high_level_call.destination, "type"):
157                    continue
158                elif isinstance(high_level_call.destination.type, Contract):
159                    destination_contract = high_level_call.destination.type.name
160                elif isinstance(high_level_call.destination.type, str):
161                    destination_contract = high_level_call.destination.type
162                elif not hasattr(high_level_call.destination.type, "type"):
163                    continue
164                elif isinstance(high_level_call.destination.type.type, Contract):
165                    destination_contract = high_level_call.destination.type.type.name
166                elif isinstance(high_level_call.destination.type.type, str):
167                    destination_contract = high_level_call.destination.type.type
168                else:
169                    continue
170                external_calls.append(f"{high_level_call.function_name}{destination_contract}")
171            rfc += len(set(external_calls))
172
173            self.public += public
174            self.external += external
175            self.internal += internal
176            self.private += private
177
178            self.mutating += mutating
179            self.view += view
180            self.pure += pure
181
182            self.external_mutating += external_public_mutating
183            self.no_auth_or_only_owner += external_no_auth
184            self.no_modifiers += external_no_modifiers
185
186            self.ext_calls += len(external_calls)
187            self.rfc = rfc
188
189    def count_variables(self) -> None:
190        """Count the number of variables in a contract"""
191        state_variable_count = 0
192        constant_count = 0
193        immutable_count = 0
194        public_getter_count = 0
195        for variable in self.contract.variables:
196            if variable.is_constant:
197                constant_count += 1
198            elif variable.is_immutable:
199                immutable_count += 1
200            else:
201                state_variable_count += 1
202            if variable.visibility == "Public":
203                public_getter_count += 1
204        self.state_variables = state_variable_count
205        self.constants = constant_count
206        self.immutables = immutable_count
207
208        # initialize RFC with public getter count
209        # self.public is used count public functions not public variables
210        self.rfc = public_getter_count
211
212    def to_dict(self) -> Dict[str, float]:
213        """Return the metrics as a dictionary."""
214        return OrderedDict(
215            {
216                "State variables": self.state_variables,
217                "Constants": self.constants,
218                "Immutables": self.immutables,
219                "Public": self.public,
220                "External": self.external,
221                "Internal": self.internal,
222                "Private": self.private,
223                "Mutating": self.mutating,
224                "View": self.view,
225                "Pure": self.pure,
226                "External mutating": self.external_mutating,
227                "No auth or onlyOwner": self.no_auth_or_only_owner,
228                "No modifiers": self.no_modifiers,
229                "Ext calls": self.ext_calls,
230                "RFC": self.rfc,
231                "NOC": self.noc,
232                "DIT": self.dit,
233                "CBO": self.cbo,
234            }
235        )
236
237
238@dataclass
239class SectionInfo:
240    """Class to hold the information for a section of the report."""
241
242    title: str
243    pretty_table: MyPrettyTable
244    txt: str
245
246
247@dataclass
248# pylint: disable=too-many-instance-attributes
249class CKMetrics:
250    """Class to hold the CK metrics for all contracts. Contains methods useful for reporting.
251
252    There are 5 sections in the report:
253    1. Variable count by type (state, constant, immutable)
254    2. Function count by visibility (public, external, internal, private)
255    3. Function count by mutability (mutating, view, pure)
256    4. External mutating function count by modifier (external mutating, no auth or onlyOwner, no modifiers)
257    5. CK metrics (RFC, NOC, DIT, CBO)
258    """
259
260    contracts: List[Contract] = field(default_factory=list)
261    contract_metrics: OrderedDict = field(default_factory=OrderedDict)
262    title: str = "CK complexity metrics"
263    full_text: str = ""
264    auxiliary1: SectionInfo = field(default=SectionInfo)
265    auxiliary2: SectionInfo = field(default=SectionInfo)
266    auxiliary3: SectionInfo = field(default=SectionInfo)
267    auxiliary4: SectionInfo = field(default=SectionInfo)
268    core: SectionInfo = field(default=SectionInfo)
269    AUXILIARY1_KEYS = (
270        "State variables",
271        "Constants",
272        "Immutables",
273    )
274    AUXILIARY2_KEYS = (
275        "Public",
276        "External",
277        "Internal",
278        "Private",
279    )
280    AUXILIARY3_KEYS = (
281        "Mutating",
282        "View",
283        "Pure",
284    )
285    AUXILIARY4_KEYS = (
286        "External mutating",
287        "No auth or onlyOwner",
288        "No modifiers",
289    )
290    CORE_KEYS = (
291        "Ext calls",
292        "RFC",
293        "NOC",
294        "DIT",
295        "CBO",
296    )
297    SECTIONS: Tuple[Tuple[str, str, Tuple[str]]] = (
298        ("Variables", "auxiliary1", AUXILIARY1_KEYS),
299        ("Function visibility", "auxiliary2", AUXILIARY2_KEYS),
300        ("State mutability", "auxiliary3", AUXILIARY3_KEYS),
301        ("External mutating functions", "auxiliary4", AUXILIARY4_KEYS),
302        ("Core", "core", CORE_KEYS),
303    )
304
305    def __post_init__(self) -> None:
306        martin_metrics = MartinMetrics(self.contracts).contract_metrics
307        dependents = {
308            inherited.name: {
309                contract.name
310                for contract in self.contracts
311                if inherited.name in contract.inheritance
312            }
313            for inherited in self.contracts
314        }
315        for contract in self.contracts:
316            self.contract_metrics[contract.name] = CKContractMetrics(
317                contract=contract, martin_metrics=martin_metrics, dependents=dependents
318            )
319
320        # Create the table and text for each section.
321        data = {
322            contract.name: self.contract_metrics[contract.name].to_dict()
323            for contract in self.contracts
324        }
325
326        subtitle = ""
327        # Update each section
328        for (title, attr, keys) in self.SECTIONS:
329            if attr == "core":
330                # Special handling for core section
331                totals_enabled = False
332                subtitle += bold("RFC: Response For a Class\n")
333                subtitle += bold("NOC: Number of Children\n")
334                subtitle += bold("DIT: Depth of Inheritance Tree\n")
335                subtitle += bold("CBO: Coupling Between Object Classes\n")
336            else:
337                totals_enabled = True
338                subtitle = ""
339
340            pretty_table = make_pretty_table(["Contract", *keys], data, totals=totals_enabled)
341            section_title = f"{self.title} ({title})"
342            txt = f"\n\n{section_title}:\n{subtitle}{pretty_table}\n"
343            self.full_text += txt
344            setattr(
345                self,
346                attr,
347                SectionInfo(title=section_title, pretty_table=pretty_table, txt=txt),
348            )
def compute_dit( contract: slither.core.declarations.contract.Contract, depth: int = 0) -> int:
47def compute_dit(contract: Contract, depth: int = 0) -> int:
48    """
49    Recursively compute the depth of inheritance tree (DIT) of a contract
50    Args:
51        contract(core.declarations.contract.Contract): contract to compute DIT for
52        depth(int): current depth of the contract
53    Returns:
54        int: depth of the contract
55    """
56    if not contract.inheritance:
57        return depth
58    max_dit = depth
59    for inherited_contract in contract.inheritance:
60        dit = compute_dit(inherited_contract, depth + 1)
61        max_dit = max(max_dit, dit)
62    return max_dit

Recursively compute the depth of inheritance tree (DIT) of a contract Args: contract(core.declarations.contract.Contract): contract to compute DIT for depth(int): current depth of the contract Returns: int: depth of the contract

def has_auth(func) -> bool:
65def has_auth(func) -> bool:
66    """
67    Check if a function has no auth or only_owner modifiers
68    Args:
69        func(core.declarations.function.Function): function to check
70    Returns:
71        bool True if it does have auth or only_owner modifiers
72    """
73    for modifier in func.modifiers:
74        if "auth" in modifier.name or "only_owner" in modifier.name:
75            return True
76    return False

Check if a function has no auth or only_owner modifiers Args: func(core.declarations.function.Function): function to check Returns: bool True if it does have auth or only_owner modifiers

class CKContractMetrics:
 84class CKContractMetrics:
 85    """Class to hold the CK metrics for a single contract."""
 86
 87    contract: Contract
 88
 89    # Used to calculate CBO - should be passed in as a constructor arg
 90    martin_metrics: Dict
 91
 92    # Used to calculate NOC
 93    dependents: Dict
 94
 95    state_variables: int = 0
 96    constants: int = 0
 97    immutables: int = 0
 98    public: int = 0
 99    external: int = 0
100    internal: int = 0
101    private: int = 0
102    mutating: int = 0
103    view: int = 0
104    pure: int = 0
105    external_mutating: int = 0
106    no_auth_or_only_owner: int = 0
107    no_modifiers: int = 0
108    ext_calls: int = 0
109    rfc: int = 0
110    noc: int = 0
111    dit: int = 0
112    cbo: int = 0
113
114    def __post_init__(self) -> None:
115        if not hasattr(self.contract, "functions"):
116            return
117        self.count_variables()
118        self.noc = len(self.dependents[self.contract.name])
119        self.dit = compute_dit(self.contract)
120        self.cbo = (
121            self.martin_metrics[self.contract.name].ca + self.martin_metrics[self.contract.name].ce
122        )
123        self.calculate_metrics()
124
125    # pylint: disable=too-many-locals
126    # pylint: disable=too-many-branches
127    def calculate_metrics(self) -> None:
128        """Calculate the metrics for a contract"""
129        rfc = self.public  # initialize with public getter count
130        for func in self.contract.functions:
131            if func.name == "constructor":
132                continue
133            pure = func.pure
134            view = not pure and func.view
135            mutating = not pure and not view
136            external = func.visibility == "external"
137            public = func.visibility == "public"
138            internal = func.visibility == "internal"
139            private = func.visibility == "private"
140            external_public_mutating = external or public and mutating
141            external_no_auth = external_public_mutating and not has_auth(func)
142            external_no_modifiers = external_public_mutating and len(func.modifiers) == 0
143            if external or public:
144                rfc += 1
145
146            high_level_calls = [
147                ir for node in func.nodes for ir in node.irs_ssa if isinstance(ir, HighLevelCall)
148            ]
149
150            # convert irs to string with target function and contract name
151            external_calls = []
152            for high_level_call in high_level_calls:
153                if isinstance(high_level_call.destination, Contract):
154                    destination_contract = high_level_call.destination.name
155                elif isinstance(high_level_call.destination, str):
156                    destination_contract = high_level_call.destination
157                elif not hasattr(high_level_call.destination, "type"):
158                    continue
159                elif isinstance(high_level_call.destination.type, Contract):
160                    destination_contract = high_level_call.destination.type.name
161                elif isinstance(high_level_call.destination.type, str):
162                    destination_contract = high_level_call.destination.type
163                elif not hasattr(high_level_call.destination.type, "type"):
164                    continue
165                elif isinstance(high_level_call.destination.type.type, Contract):
166                    destination_contract = high_level_call.destination.type.type.name
167                elif isinstance(high_level_call.destination.type.type, str):
168                    destination_contract = high_level_call.destination.type.type
169                else:
170                    continue
171                external_calls.append(f"{high_level_call.function_name}{destination_contract}")
172            rfc += len(set(external_calls))
173
174            self.public += public
175            self.external += external
176            self.internal += internal
177            self.private += private
178
179            self.mutating += mutating
180            self.view += view
181            self.pure += pure
182
183            self.external_mutating += external_public_mutating
184            self.no_auth_or_only_owner += external_no_auth
185            self.no_modifiers += external_no_modifiers
186
187            self.ext_calls += len(external_calls)
188            self.rfc = rfc
189
190    def count_variables(self) -> None:
191        """Count the number of variables in a contract"""
192        state_variable_count = 0
193        constant_count = 0
194        immutable_count = 0
195        public_getter_count = 0
196        for variable in self.contract.variables:
197            if variable.is_constant:
198                constant_count += 1
199            elif variable.is_immutable:
200                immutable_count += 1
201            else:
202                state_variable_count += 1
203            if variable.visibility == "Public":
204                public_getter_count += 1
205        self.state_variables = state_variable_count
206        self.constants = constant_count
207        self.immutables = immutable_count
208
209        # initialize RFC with public getter count
210        # self.public is used count public functions not public variables
211        self.rfc = public_getter_count
212
213    def to_dict(self) -> Dict[str, float]:
214        """Return the metrics as a dictionary."""
215        return OrderedDict(
216            {
217                "State variables": self.state_variables,
218                "Constants": self.constants,
219                "Immutables": self.immutables,
220                "Public": self.public,
221                "External": self.external,
222                "Internal": self.internal,
223                "Private": self.private,
224                "Mutating": self.mutating,
225                "View": self.view,
226                "Pure": self.pure,
227                "External mutating": self.external_mutating,
228                "No auth or onlyOwner": self.no_auth_or_only_owner,
229                "No modifiers": self.no_modifiers,
230                "Ext calls": self.ext_calls,
231                "RFC": self.rfc,
232                "NOC": self.noc,
233                "DIT": self.dit,
234                "CBO": self.cbo,
235            }
236        )

Class to hold the CK metrics for a single contract.

CKContractMetrics( contract: slither.core.declarations.contract.Contract, martin_metrics: Dict, dependents: Dict, state_variables: int = 0, constants: int = 0, immutables: int = 0, public: int = 0, external: int = 0, internal: int = 0, private: int = 0, mutating: int = 0, view: int = 0, pure: int = 0, external_mutating: int = 0, no_auth_or_only_owner: int = 0, no_modifiers: int = 0, ext_calls: int = 0, rfc: int = 0, noc: int = 0, dit: int = 0, cbo: int = 0)
martin_metrics: Dict
dependents: Dict
state_variables: int = 0
constants: int = 0
immutables: int = 0
public: int = 0
external: int = 0
internal: int = 0
private: int = 0
mutating: int = 0
view: int = 0
pure: int = 0
external_mutating: int = 0
no_auth_or_only_owner: int = 0
no_modifiers: int = 0
ext_calls: int = 0
rfc: int = 0
noc: int = 0
dit: int = 0
cbo: int = 0
def calculate_metrics(self) -> None:
127    def calculate_metrics(self) -> None:
128        """Calculate the metrics for a contract"""
129        rfc = self.public  # initialize with public getter count
130        for func in self.contract.functions:
131            if func.name == "constructor":
132                continue
133            pure = func.pure
134            view = not pure and func.view
135            mutating = not pure and not view
136            external = func.visibility == "external"
137            public = func.visibility == "public"
138            internal = func.visibility == "internal"
139            private = func.visibility == "private"
140            external_public_mutating = external or public and mutating
141            external_no_auth = external_public_mutating and not has_auth(func)
142            external_no_modifiers = external_public_mutating and len(func.modifiers) == 0
143            if external or public:
144                rfc += 1
145
146            high_level_calls = [
147                ir for node in func.nodes for ir in node.irs_ssa if isinstance(ir, HighLevelCall)
148            ]
149
150            # convert irs to string with target function and contract name
151            external_calls = []
152            for high_level_call in high_level_calls:
153                if isinstance(high_level_call.destination, Contract):
154                    destination_contract = high_level_call.destination.name
155                elif isinstance(high_level_call.destination, str):
156                    destination_contract = high_level_call.destination
157                elif not hasattr(high_level_call.destination, "type"):
158                    continue
159                elif isinstance(high_level_call.destination.type, Contract):
160                    destination_contract = high_level_call.destination.type.name
161                elif isinstance(high_level_call.destination.type, str):
162                    destination_contract = high_level_call.destination.type
163                elif not hasattr(high_level_call.destination.type, "type"):
164                    continue
165                elif isinstance(high_level_call.destination.type.type, Contract):
166                    destination_contract = high_level_call.destination.type.type.name
167                elif isinstance(high_level_call.destination.type.type, str):
168                    destination_contract = high_level_call.destination.type.type
169                else:
170                    continue
171                external_calls.append(f"{high_level_call.function_name}{destination_contract}")
172            rfc += len(set(external_calls))
173
174            self.public += public
175            self.external += external
176            self.internal += internal
177            self.private += private
178
179            self.mutating += mutating
180            self.view += view
181            self.pure += pure
182
183            self.external_mutating += external_public_mutating
184            self.no_auth_or_only_owner += external_no_auth
185            self.no_modifiers += external_no_modifiers
186
187            self.ext_calls += len(external_calls)
188            self.rfc = rfc

Calculate the metrics for a contract

def count_variables(self) -> None:
190    def count_variables(self) -> None:
191        """Count the number of variables in a contract"""
192        state_variable_count = 0
193        constant_count = 0
194        immutable_count = 0
195        public_getter_count = 0
196        for variable in self.contract.variables:
197            if variable.is_constant:
198                constant_count += 1
199            elif variable.is_immutable:
200                immutable_count += 1
201            else:
202                state_variable_count += 1
203            if variable.visibility == "Public":
204                public_getter_count += 1
205        self.state_variables = state_variable_count
206        self.constants = constant_count
207        self.immutables = immutable_count
208
209        # initialize RFC with public getter count
210        # self.public is used count public functions not public variables
211        self.rfc = public_getter_count

Count the number of variables in a contract

def to_dict(self) -> Dict[str, float]:
213    def to_dict(self) -> Dict[str, float]:
214        """Return the metrics as a dictionary."""
215        return OrderedDict(
216            {
217                "State variables": self.state_variables,
218                "Constants": self.constants,
219                "Immutables": self.immutables,
220                "Public": self.public,
221                "External": self.external,
222                "Internal": self.internal,
223                "Private": self.private,
224                "Mutating": self.mutating,
225                "View": self.view,
226                "Pure": self.pure,
227                "External mutating": self.external_mutating,
228                "No auth or onlyOwner": self.no_auth_or_only_owner,
229                "No modifiers": self.no_modifiers,
230                "Ext calls": self.ext_calls,
231                "RFC": self.rfc,
232                "NOC": self.noc,
233                "DIT": self.dit,
234                "CBO": self.cbo,
235            }
236        )

Return the metrics as a dictionary.

class SectionInfo:
240class SectionInfo:
241    """Class to hold the information for a section of the report."""
242
243    title: str
244    pretty_table: MyPrettyTable
245    txt: str

Class to hold the information for a section of the report.

SectionInfo( title: str, pretty_table: slither.utils.myprettytable.MyPrettyTable, txt: str)
title: str
txt: str
class CKMetrics:
250class CKMetrics:
251    """Class to hold the CK metrics for all contracts. Contains methods useful for reporting.
252
253    There are 5 sections in the report:
254    1. Variable count by type (state, constant, immutable)
255    2. Function count by visibility (public, external, internal, private)
256    3. Function count by mutability (mutating, view, pure)
257    4. External mutating function count by modifier (external mutating, no auth or onlyOwner, no modifiers)
258    5. CK metrics (RFC, NOC, DIT, CBO)
259    """
260
261    contracts: List[Contract] = field(default_factory=list)
262    contract_metrics: OrderedDict = field(default_factory=OrderedDict)
263    title: str = "CK complexity metrics"
264    full_text: str = ""
265    auxiliary1: SectionInfo = field(default=SectionInfo)
266    auxiliary2: SectionInfo = field(default=SectionInfo)
267    auxiliary3: SectionInfo = field(default=SectionInfo)
268    auxiliary4: SectionInfo = field(default=SectionInfo)
269    core: SectionInfo = field(default=SectionInfo)
270    AUXILIARY1_KEYS = (
271        "State variables",
272        "Constants",
273        "Immutables",
274    )
275    AUXILIARY2_KEYS = (
276        "Public",
277        "External",
278        "Internal",
279        "Private",
280    )
281    AUXILIARY3_KEYS = (
282        "Mutating",
283        "View",
284        "Pure",
285    )
286    AUXILIARY4_KEYS = (
287        "External mutating",
288        "No auth or onlyOwner",
289        "No modifiers",
290    )
291    CORE_KEYS = (
292        "Ext calls",
293        "RFC",
294        "NOC",
295        "DIT",
296        "CBO",
297    )
298    SECTIONS: Tuple[Tuple[str, str, Tuple[str]]] = (
299        ("Variables", "auxiliary1", AUXILIARY1_KEYS),
300        ("Function visibility", "auxiliary2", AUXILIARY2_KEYS),
301        ("State mutability", "auxiliary3", AUXILIARY3_KEYS),
302        ("External mutating functions", "auxiliary4", AUXILIARY4_KEYS),
303        ("Core", "core", CORE_KEYS),
304    )
305
306    def __post_init__(self) -> None:
307        martin_metrics = MartinMetrics(self.contracts).contract_metrics
308        dependents = {
309            inherited.name: {
310                contract.name
311                for contract in self.contracts
312                if inherited.name in contract.inheritance
313            }
314            for inherited in self.contracts
315        }
316        for contract in self.contracts:
317            self.contract_metrics[contract.name] = CKContractMetrics(
318                contract=contract, martin_metrics=martin_metrics, dependents=dependents
319            )
320
321        # Create the table and text for each section.
322        data = {
323            contract.name: self.contract_metrics[contract.name].to_dict()
324            for contract in self.contracts
325        }
326
327        subtitle = ""
328        # Update each section
329        for (title, attr, keys) in self.SECTIONS:
330            if attr == "core":
331                # Special handling for core section
332                totals_enabled = False
333                subtitle += bold("RFC: Response For a Class\n")
334                subtitle += bold("NOC: Number of Children\n")
335                subtitle += bold("DIT: Depth of Inheritance Tree\n")
336                subtitle += bold("CBO: Coupling Between Object Classes\n")
337            else:
338                totals_enabled = True
339                subtitle = ""
340
341            pretty_table = make_pretty_table(["Contract", *keys], data, totals=totals_enabled)
342            section_title = f"{self.title} ({title})"
343            txt = f"\n\n{section_title}:\n{subtitle}{pretty_table}\n"
344            self.full_text += txt
345            setattr(
346                self,
347                attr,
348                SectionInfo(title=section_title, pretty_table=pretty_table, txt=txt),
349            )

Class to hold the CK metrics for all contracts. Contains methods useful for reporting.

There are 5 sections in the report:

  1. Variable count by type (state, constant, immutable)
  2. Function count by visibility (public, external, internal, private)
  3. Function count by mutability (mutating, view, pure)
  4. External mutating function count by modifier (external mutating, no auth or onlyOwner, no modifiers)
  5. CK metrics (RFC, NOC, DIT, CBO)
CKMetrics( contracts: List[slither.core.declarations.contract.Contract] = <factory>, contract_metrics: collections.OrderedDict = <factory>, title: str = 'CK complexity metrics', full_text: str = '', auxiliary1: SectionInfo = <class 'SectionInfo'>, auxiliary2: SectionInfo = <class 'SectionInfo'>, auxiliary3: SectionInfo = <class 'SectionInfo'>, auxiliary4: SectionInfo = <class 'SectionInfo'>, core: SectionInfo = <class 'SectionInfo'>, SECTIONS: Tuple[Tuple[str, str, Tuple[str]]] = (('Variables', 'auxiliary1', ('State variables', 'Constants', 'Immutables')), ('Function visibility', 'auxiliary2', ('Public', 'External', 'Internal', 'Private')), ('State mutability', 'auxiliary3', ('Mutating', 'View', 'Pure')), ('External mutating functions', 'auxiliary4', ('External mutating', 'No auth or onlyOwner', 'No modifiers')), ('Core', 'core', ('Ext calls', 'RFC', 'NOC', 'DIT', 'CBO'))))
contract_metrics: collections.OrderedDict
title: str = 'CK complexity metrics'
full_text: str = ''
auxiliary1: SectionInfo = <class 'SectionInfo'>
auxiliary2: SectionInfo = <class 'SectionInfo'>
auxiliary3: SectionInfo = <class 'SectionInfo'>
auxiliary4: SectionInfo = <class 'SectionInfo'>
core: SectionInfo = <class 'SectionInfo'>
AUXILIARY1_KEYS = ('State variables', 'Constants', 'Immutables')
AUXILIARY2_KEYS = ('Public', 'External', 'Internal', 'Private')
AUXILIARY3_KEYS = ('Mutating', 'View', 'Pure')
AUXILIARY4_KEYS = ('External mutating', 'No auth or onlyOwner', 'No modifiers')
CORE_KEYS = ('Ext calls', 'RFC', 'NOC', 'DIT', 'CBO')
SECTIONS: Tuple[Tuple[str, str, Tuple[str]]] = (('Variables', 'auxiliary1', ('State variables', 'Constants', 'Immutables')), ('Function visibility', 'auxiliary2', ('Public', 'External', 'Internal', 'Private')), ('State mutability', 'auxiliary3', ('Mutating', 'View', 'Pure')), ('External mutating functions', 'auxiliary4', ('External mutating', 'No auth or onlyOwner', 'No modifiers')), ('Core', 'core', ('Ext calls', 'RFC', 'NOC', 'DIT', 'CBO')))