|
1 | 1 | from collections.abc import Sequence
|
2 | 2 | from fnmatch import fnmatch
|
3 | 3 | from statistics import mean, median
|
4 |
| -from typing import TYPE_CHECKING |
| 4 | +from typing import TYPE_CHECKING, Any, Optional, Union |
5 | 5 |
|
| 6 | +from eth_utils import is_0x_prefixed, to_hex |
6 | 7 | from rich.box import SIMPLE
|
7 | 8 | from rich.table import Table
|
8 | 9 |
|
|
13 | 14 | from ape.types.trace import ContractFunctionPath, GasReport
|
14 | 15 |
|
15 | 16 | USER_ASSERT_TAG = "USER_ASSERT"
|
| 17 | +DEFAULT_WRAP_THRESHOLD = 50 |
| 18 | + |
| 19 | + |
| 20 | +def prettify_function( |
| 21 | + method: str, |
| 22 | + calldata: Any, |
| 23 | + contract: Optional[str] = None, |
| 24 | + returndata: Optional[Any] = None, |
| 25 | + stylize: bool = False, |
| 26 | + is_create: bool = False, |
| 27 | + depth: int = 0, |
| 28 | +) -> str: |
| 29 | + """ |
| 30 | + Prettify the given method call-string to a displayable, prettier string. |
| 31 | + Useful for displaying traces and decoded calls. |
| 32 | +
|
| 33 | + Args: |
| 34 | + method (str): the method call-string to prettify. |
| 35 | + calldata (Any): Arguments to the method. |
| 36 | + contract (str | None): The contract name called. |
| 37 | + returndata (Any): Returned values from the method. |
| 38 | + stylize (bool): ``True`` to use rich styling. |
| 39 | + is_create (bool): Set to ``True`` if creating a contract for better styling. |
| 40 | + depth (int): The depth in the trace (or output) this function gets displayed. |
| 41 | +
|
| 42 | + Returns: |
| 43 | + str |
| 44 | + """ |
| 45 | + if "(" in method: |
| 46 | + # Only show short name, not ID name |
| 47 | + # (it is the full signature when multiple methods have the same name). |
| 48 | + method = method.split("(")[0].strip() or method |
| 49 | + |
| 50 | + if stylize: |
| 51 | + method = f"[{TraceStyles.METHODS}]{method}[/]" |
| 52 | + if contract: |
| 53 | + contract = f"[{TraceStyles.CONTRACTS}]{contract}[/]" |
| 54 | + |
| 55 | + arguments_str = prettify_inputs(calldata, stylize=stylize) |
| 56 | + if is_create and is_0x_prefixed(arguments_str): |
| 57 | + # Un-enriched CREATE calldata is a massive hex. |
| 58 | + arguments_str = "()" |
| 59 | + |
| 60 | + signature = f"{method}{arguments_str}" |
| 61 | + if not is_create and returndata not in ((), [], None, {}, ""): |
| 62 | + if return_str := _get_outputs_str(returndata, stylize=stylize, depth=depth): |
| 63 | + signature = f"{signature} -> {return_str}" |
| 64 | + |
| 65 | + if contract: |
| 66 | + signature = f"{contract}.{signature}" |
| 67 | + |
| 68 | + return signature |
| 69 | + |
| 70 | + |
| 71 | +def prettify_inputs(inputs: Any, stylize: bool = False) -> str: |
| 72 | + """ |
| 73 | + Prettify the inputs to a function or event (or alike). |
| 74 | +
|
| 75 | + Args: |
| 76 | + inputs (Any): the inputs to prettify. |
| 77 | + stylize (bool): ``True`` to use rich styling. |
| 78 | +
|
| 79 | + Returns: |
| 80 | + str |
| 81 | + """ |
| 82 | + color = TraceStyles.INPUTS if stylize else None |
| 83 | + if inputs in ["0x", None, (), [], {}]: |
| 84 | + return "()" |
| 85 | + |
| 86 | + elif isinstance(inputs, dict): |
| 87 | + return prettify_dict(inputs, color=color) |
| 88 | + |
| 89 | + elif isinstance(inputs, bytes): |
| 90 | + return to_hex(inputs) |
| 91 | + |
| 92 | + return f"({inputs})" |
| 93 | + |
| 94 | + |
| 95 | +def _get_outputs_str(outputs: Any, stylize: bool = False, depth: int = 0) -> Optional[str]: |
| 96 | + if outputs in ["0x", None, (), [], {}]: |
| 97 | + return None |
| 98 | + |
| 99 | + elif isinstance(outputs, dict): |
| 100 | + color = TraceStyles.OUTPUTS if stylize else None |
| 101 | + return prettify_dict(outputs, color=color) |
| 102 | + |
| 103 | + elif isinstance(outputs, (list, tuple)): |
| 104 | + return ( |
| 105 | + f"[{TraceStyles.OUTPUTS}]{prettify_list(outputs)}[/]" |
| 106 | + if stylize |
| 107 | + else prettify_list(outputs, depth=depth) |
| 108 | + ) |
| 109 | + |
| 110 | + return f"[{TraceStyles.OUTPUTS}]{outputs}[/]" if stylize else str(outputs) |
| 111 | + |
| 112 | + |
| 113 | +def prettify_list( |
| 114 | + ls: Union[list, tuple], |
| 115 | + depth: int = 0, |
| 116 | + indent: int = 2, |
| 117 | + wrap_threshold: int = DEFAULT_WRAP_THRESHOLD, |
| 118 | +) -> str: |
| 119 | + """ |
| 120 | + Prettify a list of values for displaying. |
| 121 | +
|
| 122 | + Args: |
| 123 | + ls (list): the list to prettify. |
| 124 | + depth (int): The depth the list appears in a tree structure (for traces). |
| 125 | +
|
| 126 | + Returns: |
| 127 | + str |
| 128 | + """ |
| 129 | + if not isinstance(ls, (list, tuple)) or len(str(ls)) < wrap_threshold: |
| 130 | + return str(ls) |
| 131 | + |
| 132 | + elif ls and isinstance(ls[0], (list, tuple)): |
| 133 | + # List of lists |
| 134 | + sub_lists = [prettify_list(i) for i in ls] |
| 135 | + |
| 136 | + # Use multi-line if exceeds threshold OR any of the sub-lists use multi-line |
| 137 | + extra_chars_len = (len(sub_lists) - 1) * 2 |
| 138 | + use_multiline = len(str(sub_lists)) + extra_chars_len > wrap_threshold or any( |
| 139 | + ["\n" in ls for ls in sub_lists] |
| 140 | + ) |
| 141 | + |
| 142 | + if not use_multiline: |
| 143 | + # Happens for lists like '[[0], [1]]' that are short. |
| 144 | + return f"[{', '.join(sub_lists)}]" |
| 145 | + |
| 146 | + value = "[\n" |
| 147 | + num_sub_lists = len(sub_lists) |
| 148 | + index = 0 |
| 149 | + spacing = indent * " " * 2 |
| 150 | + for formatted_list in sub_lists: |
| 151 | + if "\n" in formatted_list: |
| 152 | + # Multi-line sub list. Append 1 more spacing to each line. |
| 153 | + indented_item = f"\n{spacing}".join(formatted_list.splitlines()) |
| 154 | + value = f"{value}{spacing}{indented_item}" |
| 155 | + else: |
| 156 | + # Single line sub-list |
| 157 | + value = f"{value}{spacing}{formatted_list}" |
| 158 | + |
| 159 | + if index < num_sub_lists - 1: |
| 160 | + value = f"{value}," |
| 161 | + |
| 162 | + value = f"{value}\n" |
| 163 | + index += 1 |
| 164 | + |
| 165 | + value = f"{value}]" |
| 166 | + return value |
| 167 | + |
| 168 | + return _list_to_multiline_str(ls, depth=depth) |
| 169 | + |
| 170 | + |
| 171 | +def prettify_dict( |
| 172 | + dictionary: dict, |
| 173 | + color: Optional[str] = None, |
| 174 | + indent: int = 2, |
| 175 | + wrap_threshold: int = DEFAULT_WRAP_THRESHOLD, |
| 176 | +) -> str: |
| 177 | + """ |
| 178 | + Prettify a dictionary. |
| 179 | +
|
| 180 | + Args: |
| 181 | + dictionary (dict): The dictionary to prettify. |
| 182 | + color (Optional[str]): The color to use for pretty printing. |
| 183 | +
|
| 184 | + Returns: |
| 185 | + str |
| 186 | + """ |
| 187 | + length = sum(len(str(v)) for v in [*dictionary.keys(), *dictionary.values()]) |
| 188 | + do_wrap = length > wrap_threshold |
| 189 | + |
| 190 | + index = 0 |
| 191 | + end_index = len(dictionary) - 1 |
| 192 | + kv_str = "(\n" if do_wrap else "(" |
| 193 | + |
| 194 | + for key, value in dictionary.items(): |
| 195 | + if do_wrap: |
| 196 | + kv_str += indent * " " |
| 197 | + |
| 198 | + if isinstance(value, (list, tuple)): |
| 199 | + value = prettify_list(value, 1 if do_wrap else 0) |
| 200 | + |
| 201 | + value_str = f"[{color}]{value}[/]" if color is not None else str(value) |
| 202 | + kv_str += f"{key}={value_str}" if key and not key.isnumeric() else value_str |
| 203 | + if index < end_index: |
| 204 | + kv_str += ", " |
| 205 | + |
| 206 | + if do_wrap: |
| 207 | + kv_str += "\n" |
| 208 | + |
| 209 | + index += 1 |
| 210 | + |
| 211 | + return f"{kv_str})" |
| 212 | + |
| 213 | + |
| 214 | +def _list_to_multiline_str(value: Union[list, tuple], depth: int = 0, indent: int = 2) -> str: |
| 215 | + spacing = indent * " " |
| 216 | + ls_spacing = spacing * (depth + 1) |
| 217 | + joined = ",\n".join([f"{ls_spacing}{v}" for v in value]) |
| 218 | + new_val = f"[\n{joined}\n{spacing * depth}]" |
| 219 | + return new_val |
16 | 220 |
|
17 | 221 |
|
18 | 222 | class TraceStyles:
|
|
0 commit comments