|
1 | 1 | from abc import ABC, abstractmethod |
| 2 | +import re |
| 3 | +from typing import Any, Union |
| 4 | + |
| 5 | +from algosdk import error |
| 6 | + |
2 | 7 |
|
3 | 8 | # Globals |
4 | 9 | ABI_LENGTH_SIZE = 2 # We use 2 bytes to encode the length of a dynamic element |
| 10 | +UFIXED_REGEX = r"^ufixed([1-9][\d]*)x([1-9][\d]*)$" |
| 11 | +STATIC_ARRAY_REGEX = r"^([a-z\d\[\](),]+)\[([1-9][\d]*)]$" |
5 | 12 |
|
6 | 13 |
|
7 | 14 | class ABIType(ABC): |
8 | 15 | """ |
9 | 16 | Represents an ABI Type for encoding. |
10 | 17 | """ |
11 | 18 |
|
12 | | - def __init__( |
13 | | - self, |
14 | | - ) -> None: |
| 19 | + def __init__(self) -> None: |
15 | 20 | pass |
16 | 21 |
|
17 | 22 | @abstractmethod |
18 | | - def __str__(self): |
| 23 | + def __str__(self) -> str: |
19 | 24 | pass |
20 | 25 |
|
21 | 26 | @abstractmethod |
22 | | - def __eq__(self, other) -> bool: |
| 27 | + def __eq__(self, other: object) -> bool: |
23 | 28 | pass |
24 | 29 |
|
25 | 30 | @abstractmethod |
26 | | - def is_dynamic(self): |
| 31 | + def is_dynamic(self) -> bool: |
27 | 32 | """ |
28 | 33 | Return whether the ABI type is dynamic. |
29 | 34 | """ |
30 | 35 | pass |
31 | 36 |
|
32 | 37 | @abstractmethod |
33 | | - def byte_len(self): |
| 38 | + def byte_len(self) -> int: |
34 | 39 | """ |
35 | 40 | Return the length in bytes of the ABI type. |
36 | 41 | """ |
37 | 42 | pass |
38 | 43 |
|
39 | 44 | @abstractmethod |
40 | | - def encode(self, value): |
| 45 | + def encode(self, value: Any) -> bytes: |
41 | 46 | """ |
42 | 47 | Serialize the ABI value into a byte string using ABI encoding rules. |
43 | 48 | """ |
44 | 49 | pass |
45 | 50 |
|
46 | 51 | @abstractmethod |
47 | | - def decode(self, bytestring): |
| 52 | + def decode(self, bytestring: bytes) -> Any: |
48 | 53 | """ |
49 | 54 | Deserialize the ABI type and value from a byte string using ABI encoding rules. |
50 | 55 | """ |
51 | 56 | pass |
| 57 | + |
| 58 | + @staticmethod |
| 59 | + def from_string(s: str) -> "ABIType": |
| 60 | + """ |
| 61 | + Convert a valid ABI string to a corresponding ABI type. |
| 62 | + """ |
| 63 | + # We define the imports here to avoid circular imports |
| 64 | + from algosdk.abi.uint_type import UintType |
| 65 | + from algosdk.abi.ufixed_type import UfixedType |
| 66 | + from algosdk.abi.byte_type import ByteType |
| 67 | + from algosdk.abi.bool_type import BoolType |
| 68 | + from algosdk.abi.address_type import AddressType |
| 69 | + from algosdk.abi.string_type import StringType |
| 70 | + from algosdk.abi.array_dynamic_type import ArrayDynamicType |
| 71 | + from algosdk.abi.array_static_type import ArrayStaticType |
| 72 | + from algosdk.abi.tuple_type import TupleType |
| 73 | + |
| 74 | + if s.endswith("[]"): |
| 75 | + array_arg_type = ABIType.from_string(s[:-2]) |
| 76 | + return ArrayDynamicType(array_arg_type) |
| 77 | + elif s.endswith("]"): |
| 78 | + matches = re.search(STATIC_ARRAY_REGEX, s) |
| 79 | + try: |
| 80 | + static_length = int(matches.group(2)) |
| 81 | + array_type = ABIType.from_string(matches.group(1)) |
| 82 | + return ArrayStaticType(array_type, static_length) |
| 83 | + except Exception as e: |
| 84 | + raise error.ABITypeError( |
| 85 | + "malformed static array string: {}".format(s) |
| 86 | + ) from e |
| 87 | + if s.startswith("uint"): |
| 88 | + try: |
| 89 | + if not s[4:].isdecimal(): |
| 90 | + raise error.ABITypeError( |
| 91 | + "uint string does not contain a valid size: {}".format( |
| 92 | + s |
| 93 | + ) |
| 94 | + ) |
| 95 | + type_size = int(s[4:]) |
| 96 | + return UintType(type_size) |
| 97 | + except Exception as e: |
| 98 | + raise error.ABITypeError( |
| 99 | + "malformed uint string: {}".format(s) |
| 100 | + ) from e |
| 101 | + elif s == "byte": |
| 102 | + return ByteType() |
| 103 | + elif s.startswith("ufixed"): |
| 104 | + matches = re.search(UFIXED_REGEX, s) |
| 105 | + try: |
| 106 | + bit_size = int(matches.group(1)) |
| 107 | + precision = int(matches.group(2)) |
| 108 | + return UfixedType(bit_size, precision) |
| 109 | + except Exception as e: |
| 110 | + raise error.ABITypeError( |
| 111 | + "malformed ufixed string: {}".format(s) |
| 112 | + ) from e |
| 113 | + elif s == "bool": |
| 114 | + return BoolType() |
| 115 | + elif s == "address": |
| 116 | + return AddressType() |
| 117 | + elif s == "string": |
| 118 | + return StringType() |
| 119 | + elif len(s) >= 2 and s[0] == "(" and s[-1] == ")": |
| 120 | + # Recursively parse parentheses from a tuple string |
| 121 | + tuples = TupleType._parse_tuple(s[1:-1]) |
| 122 | + tuple_list = [] |
| 123 | + for tup in tuples: |
| 124 | + if isinstance(tup, str): |
| 125 | + tt = ABIType.from_string(tup) |
| 126 | + tuple_list.append(tt) |
| 127 | + elif isinstance(tup, list): |
| 128 | + tts = [ABIType.from_string(t_) for t_ in tup] |
| 129 | + tuple_list.append(tts) |
| 130 | + else: |
| 131 | + raise error.ABITypeError( |
| 132 | + "cannot convert {} to an ABI type".format(tup) |
| 133 | + ) |
| 134 | + |
| 135 | + return TupleType(tuple_list) |
| 136 | + else: |
| 137 | + raise error.ABITypeError( |
| 138 | + "cannot convert {} to an ABI type".format(s) |
| 139 | + ) |
0 commit comments