faster-eth-abi 5.2.8__cp314-cp314t-win_amd64.whl → 5.2.19__cp314-cp314t-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of faster-eth-abi might be problematic. Click here for more details.

Files changed (41) hide show
  1. faster_eth_abi/_codec.cp314t-win_amd64.pyd +0 -0
  2. faster_eth_abi/_codec.py +7 -5
  3. faster_eth_abi/_decoding.cp314t-win_amd64.pyd +0 -0
  4. faster_eth_abi/_decoding.py +212 -30
  5. faster_eth_abi/_encoding.cp314t-win_amd64.pyd +0 -0
  6. faster_eth_abi/_encoding.py +159 -11
  7. faster_eth_abi/_grammar.cp314t-win_amd64.pyd +0 -0
  8. faster_eth_abi/_grammar.py +375 -0
  9. faster_eth_abi/abi.cp314t-win_amd64.pyd +0 -0
  10. faster_eth_abi/base.py +5 -1
  11. faster_eth_abi/codec.py +2675 -9
  12. faster_eth_abi/constants.cp314t-win_amd64.pyd +0 -0
  13. faster_eth_abi/decoding.py +214 -176
  14. faster_eth_abi/encoding.py +112 -38
  15. faster_eth_abi/exceptions.py +26 -14
  16. faster_eth_abi/from_type_str.cp314t-win_amd64.pyd +0 -0
  17. faster_eth_abi/from_type_str.py +7 -1
  18. faster_eth_abi/grammar.py +30 -326
  19. faster_eth_abi/io.py +5 -1
  20. faster_eth_abi/packed.cp314t-win_amd64.pyd +0 -0
  21. faster_eth_abi/packed.py +4 -0
  22. faster_eth_abi/registry.py +186 -91
  23. faster_eth_abi/tools/__init__.cp314t-win_amd64.pyd +0 -0
  24. faster_eth_abi/tools/_strategies.cp314t-win_amd64.pyd +0 -0
  25. faster_eth_abi/tools/_strategies.py +12 -6
  26. faster_eth_abi/typing.py +4627 -0
  27. faster_eth_abi/utils/__init__.cp314t-win_amd64.pyd +0 -0
  28. faster_eth_abi/utils/numeric.cp314t-win_amd64.pyd +0 -0
  29. faster_eth_abi/utils/numeric.py +51 -20
  30. faster_eth_abi/utils/padding.cp314t-win_amd64.pyd +0 -0
  31. faster_eth_abi/utils/string.cp314t-win_amd64.pyd +0 -0
  32. faster_eth_abi/utils/validation.cp314t-win_amd64.pyd +0 -0
  33. {faster_eth_abi-5.2.8.dist-info → faster_eth_abi-5.2.19.dist-info}/METADATA +38 -14
  34. faster_eth_abi-5.2.19.dist-info/RECORD +46 -0
  35. faster_eth_abi-5.2.19.dist-info/top_level.txt +2 -0
  36. faster_eth_abi__mypyc.cp314t-win_amd64.pyd +0 -0
  37. 76f9a3652d4d2667c55c__mypyc.cp314t-win_amd64.pyd +0 -0
  38. faster_eth_abi-5.2.8.dist-info/RECORD +0 -44
  39. faster_eth_abi-5.2.8.dist-info/licenses/LICENSE +0 -21
  40. faster_eth_abi-5.2.8.dist-info/top_level.txt +0 -2
  41. {faster_eth_abi-5.2.8.dist-info → faster_eth_abi-5.2.19.dist-info}/WHEEL +0 -0
@@ -1,14 +1,32 @@
1
+ """Classes for ABI decoding logic.
2
+
3
+ Implements classes and functions for deserializing binary data into Python values
4
+ according to ABI type specifications.
5
+ """
1
6
  import abc
2
7
  import decimal
8
+ from functools import (
9
+ cached_property,
10
+ lru_cache,
11
+ )
12
+ from types import (
13
+ MethodType,
14
+ )
3
15
  from typing import (
4
16
  Any,
5
17
  Callable,
6
18
  Final,
19
+ Generic,
7
20
  Optional,
8
21
  Tuple,
22
+ TypeVar,
9
23
  Union,
24
+ final,
10
25
  )
11
26
 
27
+ from eth_typing import (
28
+ HexAddress,
29
+ )
12
30
  from faster_eth_utils import (
13
31
  big_endian_to_int,
14
32
  to_normalized_address,
@@ -19,14 +37,19 @@ from faster_eth_abi._decoding import (
19
37
  decode_head_tail,
20
38
  decode_sized_array,
21
39
  decode_tuple,
40
+ decoder_fn_boolean,
41
+ get_value_byte_size,
22
42
  read_fixed_byte_size_data_from_stream,
43
+ split_data_and_padding_fixed_byte_size,
44
+ validate_padding_bytes_fixed_byte_size,
45
+ validate_padding_bytes_signed_integer,
46
+ validate_pointers_array,
23
47
  )
24
48
  from faster_eth_abi.base import (
25
49
  BaseCoder,
26
50
  )
27
51
  from faster_eth_abi.exceptions import (
28
52
  InsufficientDataBytes,
29
- InvalidPointer,
30
53
  NonEmptyPaddingBytes,
31
54
  )
32
55
  from faster_eth_abi.from_type_str import (
@@ -36,18 +59,19 @@ from faster_eth_abi.from_type_str import (
36
59
  from faster_eth_abi.io import (
37
60
  ContextFramesBytesIO,
38
61
  )
62
+ from faster_eth_abi.typing import (
63
+ T,
64
+ )
39
65
  from faster_eth_abi.utils.numeric import (
40
66
  TEN,
41
67
  abi_decimal_context,
42
68
  ceil32,
43
69
  )
44
70
 
45
- DynamicDecoder = Union[
46
- "HeadTailDecoder", "SizedArrayDecoder", "DynamicArrayDecoder", "ByteStringDecoder"
47
- ]
71
+ TByteStr = TypeVar("TByteStr", bytes, str)
48
72
 
49
73
 
50
- class BaseDecoder(BaseCoder, metaclass=abc.ABCMeta):
74
+ class BaseDecoder(BaseCoder, Generic[T], metaclass=abc.ABCMeta):
51
75
  """
52
76
  Base class for all decoder classes. Subclass this if you want to define a
53
77
  custom decoder class. Subclasses must also implement
@@ -57,18 +81,18 @@ class BaseDecoder(BaseCoder, metaclass=abc.ABCMeta):
57
81
  strict = True
58
82
 
59
83
  @abc.abstractmethod
60
- def decode(self, stream: ContextFramesBytesIO) -> Any: # pragma: no cover
84
+ def decode(self, stream: ContextFramesBytesIO) -> T: # pragma: no cover
61
85
  """
62
86
  Decodes the given stream of bytes into a python value. Should raise
63
87
  :any:`exceptions.DecodingError` if a python value cannot be decoded
64
88
  from the given byte stream.
65
89
  """
66
90
 
67
- def __call__(self, stream: ContextFramesBytesIO) -> Any:
91
+ def __call__(self, stream: ContextFramesBytesIO) -> T:
68
92
  return self.decode(stream)
69
93
 
70
94
 
71
- class HeadTailDecoder(BaseDecoder):
95
+ class HeadTailDecoder(BaseDecoder[T]):
72
96
  """
73
97
  Decoder for a dynamic element of a dynamic container (a dynamic array, or a sized
74
98
  array or tuple that contains dynamic elements). A dynamic element consists of a
@@ -78,35 +102,47 @@ class HeadTailDecoder(BaseDecoder):
78
102
 
79
103
  is_dynamic = True
80
104
 
81
- tail_decoder: Optional[DynamicDecoder] = None
82
-
83
- def validate(self) -> None:
84
- super().validate()
105
+ def __init__(
106
+ self,
107
+ tail_decoder: Union[ # type: ignore [type-var]
108
+ "HeadTailDecoder[T]",
109
+ "SizedArrayDecoder[T]",
110
+ "DynamicArrayDecoder[T]",
111
+ "ByteStringDecoder[T]",
112
+ ],
113
+ ) -> None:
114
+ super().__init__()
85
115
 
86
- if self.tail_decoder is None:
116
+ if tail_decoder is None:
87
117
  raise ValueError("No `tail_decoder` set")
88
118
 
89
- def decode(self, stream: ContextFramesBytesIO) -> Any:
119
+ self.tail_decoder: Final = tail_decoder
120
+
121
+ def decode(self, stream: ContextFramesBytesIO) -> T:
90
122
  return decode_head_tail(self, stream)
91
123
 
92
124
  __call__ = decode
93
125
 
94
126
 
95
- class TupleDecoder(BaseDecoder):
96
- decoders: Tuple[BaseDecoder, ...] = ()
127
+ class TupleDecoder(BaseDecoder[Tuple[T, ...]]):
128
+ decoders: Tuple[BaseDecoder[T], ...] = ()
97
129
 
98
- def __init__(self, decoders: Tuple[BaseDecoder, ...], **kwargs: Any) -> None:
99
- super().__init__(**kwargs)
130
+ def __init__(self, decoders: Tuple[BaseDecoder[T], ...]) -> None:
131
+ super().__init__()
100
132
 
101
- self.decoders = tuple(
133
+ self.decoders = decoders = tuple(
102
134
  HeadTailDecoder(tail_decoder=d) if getattr(d, "is_dynamic", False) else d
103
135
  for d in decoders
104
136
  )
105
137
 
106
- self.is_dynamic = any(getattr(d, "is_dynamic", False) for d in self.decoders)
138
+ self.is_dynamic = any(getattr(d, "is_dynamic", False) for d in decoders)
107
139
  self.len_of_head = sum(
108
- getattr(decoder, "array_size", 1) for decoder in self.decoders
140
+ getattr(decoder, "array_size", 1) for decoder in decoders
141
+ )
142
+ self._is_head_tail = tuple(
143
+ isinstance(decoder, HeadTailDecoder) for decoder in decoders
109
144
  )
145
+ self._no_head_tail = not any(self._is_head_tail)
110
146
 
111
147
  def validate(self) -> None:
112
148
  super().validate()
@@ -114,37 +150,11 @@ class TupleDecoder(BaseDecoder):
114
150
  if self.decoders is None:
115
151
  raise ValueError("No `decoders` set")
116
152
 
153
+ @final
117
154
  def validate_pointers(self, stream: ContextFramesBytesIO) -> None:
118
- """
119
- Verify that all pointers point to a valid location in the stream.
120
- """
121
- current_location = stream.tell()
122
- end_of_offsets = current_location + 32 * self.len_of_head
123
- total_stream_length = len(stream.getbuffer())
124
- for decoder in self.decoders:
125
- if isinstance(decoder, HeadTailDecoder):
126
- # the next 32 bytes are a pointer
127
- offset = decode_uint_256(stream)
128
- indicated_idx = current_location + offset
129
- if (
130
- indicated_idx < end_of_offsets
131
- or indicated_idx >= total_stream_length
132
- ):
133
- # the pointer is indicating its data is located either within the
134
- # offsets section of the stream or beyond the end of the stream,
135
- # both of which are invalid
136
- raise InvalidPointer(
137
- "Invalid pointer in tuple at location "
138
- f"{stream.tell() - 32} in payload"
139
- )
140
- else:
141
- # the next 32 bytes are not a pointer, so progress the stream per
142
- # the decoder
143
- decoder(stream)
144
- # return the stream to its original location for actual decoding
145
- stream.seek(current_location)
146
-
147
- def decode(self, stream: ContextFramesBytesIO) -> Tuple[Any, ...]:
155
+ raise NotImplementedError("didnt call __init__")
156
+
157
+ def decode(self, stream: ContextFramesBytesIO) -> Tuple[T, ...]:
148
158
  return decode_tuple(self, stream)
149
159
 
150
160
  __call__ = decode
@@ -158,7 +168,7 @@ class TupleDecoder(BaseDecoder):
158
168
  return cls(decoders=decoders)
159
169
 
160
170
 
161
- class SingleDecoder(BaseDecoder):
171
+ class SingleDecoder(BaseDecoder[T]):
162
172
  decoder_fn = None
163
173
 
164
174
  def validate(self) -> None:
@@ -167,15 +177,13 @@ class SingleDecoder(BaseDecoder):
167
177
  if self.decoder_fn is None:
168
178
  raise ValueError("No `decoder_fn` set")
169
179
 
170
- def validate_padding_bytes(self, value, padding_bytes):
180
+ def validate_padding_bytes(self, value: Any, padding_bytes: bytes) -> None:
171
181
  raise NotImplementedError("Must be implemented by subclasses")
172
182
 
173
- def decode(self, stream):
183
+ def decode(self, stream: ContextFramesBytesIO) -> T:
174
184
  raw_data = self.read_data_from_stream(stream)
175
185
  data, padding_bytes = self.split_data_and_padding(raw_data)
176
- if self.decoder_fn is None:
177
- raise AssertionError("`decoder_fn` is None")
178
- value = self.decoder_fn(data)
186
+ value = self.decoder_fn(data) # type: ignore [misc]
179
187
  self.validate_padding_bytes(value, padding_bytes)
180
188
 
181
189
  return value
@@ -189,17 +197,26 @@ class SingleDecoder(BaseDecoder):
189
197
  return raw_data, b""
190
198
 
191
199
 
192
- class BaseArrayDecoder(BaseDecoder):
193
- item_decoder = None
200
+ class BaseArrayDecoder(BaseDecoder[Tuple[T, ...]]):
201
+ item_decoder: BaseDecoder = None
194
202
 
195
203
  def __init__(self, **kwargs: Any) -> None:
196
204
  super().__init__(**kwargs)
197
205
 
198
206
  # Use a head-tail decoder to decode dynamic elements
199
- if self.item_decoder.is_dynamic:
200
- self.item_decoder = HeadTailDecoder(
201
- tail_decoder=self.item_decoder,
202
- )
207
+ item_decoder = self.item_decoder
208
+ if item_decoder.is_dynamic:
209
+ self.item_decoder = HeadTailDecoder(tail_decoder=item_decoder)
210
+ self.validate_pointers = MethodType(validate_pointers_array, self)
211
+ else:
212
+
213
+ def noop(stream: ContextFramesBytesIO, array_size: int) -> None:
214
+ ...
215
+
216
+ self.validate_pointers = noop
217
+
218
+ def decode(self, stream: ContextFramesBytesIO) -> Tuple[T, ...]:
219
+ raise NotImplementedError # this is a type stub
203
220
 
204
221
  def validate(self) -> None:
205
222
  super().validate()
@@ -226,132 +243,105 @@ class BaseArrayDecoder(BaseDecoder):
226
243
  """
227
244
  Verify that all pointers point to a valid location in the stream.
228
245
  """
229
- if isinstance(self.item_decoder, HeadTailDecoder):
230
- current_location = stream.tell()
231
- end_of_offsets = current_location + 32 * array_size
232
- total_stream_length = len(stream.getbuffer())
233
- for _ in range(array_size):
234
- offset = decode_uint_256(stream)
235
- indicated_idx = current_location + offset
236
- if (
237
- indicated_idx < end_of_offsets
238
- or indicated_idx >= total_stream_length
239
- ):
240
- # the pointer is indicating its data is located either within the
241
- # offsets section of the stream or beyond the end of the stream,
242
- # both of which are invalid
243
- raise InvalidPointer(
244
- "Invalid pointer in array at location "
245
- f"{stream.tell() - 32} in payload"
246
- )
247
- stream.seek(current_location)
248
-
249
-
250
- class SizedArrayDecoder(BaseArrayDecoder):
246
+ validate_pointers_array(self, stream, array_size)
247
+
248
+
249
+ class SizedArrayDecoder(BaseArrayDecoder[T]):
251
250
  array_size: int = None
252
251
 
253
- def __init__(self, **kwargs):
252
+ def __init__(self, **kwargs: Any) -> None:
254
253
  super().__init__(**kwargs)
255
254
 
256
255
  self.is_dynamic = self.item_decoder.is_dynamic
257
256
 
258
- def decode(self, stream):
257
+ def decode(self, stream: ContextFramesBytesIO) -> Tuple[T, ...]:
259
258
  return decode_sized_array(self, stream)
260
259
 
261
260
  __call__ = decode
262
261
 
263
262
 
264
- class DynamicArrayDecoder(BaseArrayDecoder):
263
+ class DynamicArrayDecoder(BaseArrayDecoder[T]):
265
264
  # Dynamic arrays are always dynamic, regardless of their elements
266
265
  is_dynamic = True
267
266
 
268
- def decode(self, stream: ContextFramesBytesIO) -> Tuple[Any, ...]:
267
+ def decode(self, stream: ContextFramesBytesIO) -> Tuple[T, ...]:
269
268
  return decode_dynamic_array(self, stream)
270
269
 
271
270
  __call__ = decode
272
271
 
273
272
 
274
- class FixedByteSizeDecoder(SingleDecoder):
275
- decoder_fn: Callable[[bytes], Any] = None
273
+ class FixedByteSizeDecoder(SingleDecoder[T]):
274
+ decoder_fn: Callable[[bytes], T] = None
276
275
  value_bit_size: int = None
277
276
  data_byte_size: int = None
278
277
  is_big_endian: bool = None
279
278
 
279
+ def __init__(self, **kwargs: Any) -> None:
280
+ super().__init__(**kwargs)
281
+
282
+ self.read_data_from_stream = MethodType(
283
+ read_fixed_byte_size_data_from_stream, self
284
+ )
285
+ self.split_data_and_padding = MethodType(
286
+ split_data_and_padding_fixed_byte_size, self
287
+ )
288
+ self._get_value_byte_size = MethodType(get_value_byte_size, self)
289
+
290
+ # Only assign validate_padding_bytes if not overridden in subclass
291
+ if type(self).validate_padding_bytes is SingleDecoder.validate_padding_bytes:
292
+ self.validate_padding_bytes = MethodType(
293
+ validate_padding_bytes_fixed_byte_size, self
294
+ )
295
+
280
296
  def validate(self) -> None:
281
297
  super().validate()
282
298
 
283
- if self.value_bit_size is None:
299
+ value_bit_size = self.value_bit_size
300
+ if value_bit_size is None:
284
301
  raise ValueError("`value_bit_size` may not be None")
285
- if self.data_byte_size is None:
302
+ data_byte_size = self.data_byte_size
303
+ if data_byte_size is None:
286
304
  raise ValueError("`data_byte_size` may not be None")
287
305
  if self.decoder_fn is None:
288
306
  raise ValueError("`decoder_fn` may not be None")
289
307
  if self.is_big_endian is None:
290
308
  raise ValueError("`is_big_endian` may not be None")
291
309
 
292
- if self.value_bit_size % 8 != 0:
310
+ if value_bit_size % 8 != 0:
293
311
  raise ValueError(
294
- "Invalid value bit size: {self.value_bit_size}. Must be a multiple of 8"
312
+ f"Invalid value bit size: {value_bit_size}. Must be a multiple of 8"
295
313
  )
296
314
 
297
- if self.value_bit_size > self.data_byte_size * 8:
315
+ if value_bit_size > data_byte_size * 8:
298
316
  raise ValueError("Value byte size exceeds data size")
299
317
 
300
318
  def read_data_from_stream(self, stream: ContextFramesBytesIO) -> bytes:
301
- return read_fixed_byte_size_data_from_stream(self, stream)
319
+ raise NotImplementedError("didnt call __init__")
302
320
 
303
321
  def split_data_and_padding(self, raw_data: bytes) -> Tuple[bytes, bytes]:
304
- value_byte_size = self._get_value_byte_size()
305
- padding_size = self.data_byte_size - value_byte_size
306
-
307
- if self.is_big_endian:
308
- padding_bytes = raw_data[:padding_size]
309
- data = raw_data[padding_size:]
310
- else:
311
- data = raw_data[:value_byte_size]
312
- padding_bytes = raw_data[value_byte_size:]
313
-
314
- return data, padding_bytes
315
-
316
- def validate_padding_bytes(self, value: Any, padding_bytes: bytes) -> None:
317
- value_byte_size = self._get_value_byte_size()
318
- padding_size = self.data_byte_size - value_byte_size
319
-
320
- if padding_bytes != b"\x00" * padding_size:
321
- raise NonEmptyPaddingBytes(
322
- f"Padding bytes were not empty: {padding_bytes!r}"
323
- )
322
+ raise NotImplementedError("didnt call __init__")
324
323
 
325
- def _get_value_byte_size(self):
326
- value_byte_size = self.value_bit_size // 8
327
- return value_byte_size
324
+ # This is unused, but it is kept in to preserve the eth-abi api
325
+ def _get_value_byte_size(self) -> int:
326
+ raise NotImplementedError("didnt call __init__")
328
327
 
329
328
 
330
- class Fixed32ByteSizeDecoder(FixedByteSizeDecoder):
329
+ class Fixed32ByteSizeDecoder(FixedByteSizeDecoder[T]):
331
330
  data_byte_size = 32
332
331
 
333
332
 
334
- class BooleanDecoder(Fixed32ByteSizeDecoder):
333
+ class BooleanDecoder(Fixed32ByteSizeDecoder[bool]):
335
334
  value_bit_size = 8
336
335
  is_big_endian = True
337
336
 
338
- @staticmethod
339
- def decoder_fn(data):
340
- if data == b"\x00":
341
- return False
342
- elif data == b"\x01":
343
- return True
344
- else:
345
- raise NonEmptyPaddingBytes(
346
- f"Boolean must be either 0x0 or 0x1. Got: {data!r}"
347
- )
337
+ decoder_fn = staticmethod(decoder_fn_boolean)
348
338
 
349
339
  @parse_type_str("bool")
350
340
  def from_type_str(cls, abi_type, registry):
351
341
  return cls()
352
342
 
353
343
 
354
- class AddressDecoder(Fixed32ByteSizeDecoder):
344
+ class AddressDecoder(Fixed32ByteSizeDecoder[HexAddress]):
355
345
  value_bit_size = 20 * 8
356
346
  is_big_endian = True
357
347
  decoder_fn = staticmethod(to_normalized_address)
@@ -364,8 +354,8 @@ class AddressDecoder(Fixed32ByteSizeDecoder):
364
354
  #
365
355
  # Unsigned Integer Decoders
366
356
  #
367
- class UnsignedIntegerDecoder(Fixed32ByteSizeDecoder):
368
- decoder_fn = staticmethod(big_endian_to_int)
357
+ class UnsignedIntegerDecoder(Fixed32ByteSizeDecoder[int]):
358
+ decoder_fn: "staticmethod[[bytes], int]" = staticmethod(big_endian_to_int)
369
359
  is_big_endian = True
370
360
 
371
361
  @parse_type_str("uint")
@@ -376,46 +366,74 @@ class UnsignedIntegerDecoder(Fixed32ByteSizeDecoder):
376
366
  decode_uint_256 = UnsignedIntegerDecoder(value_bit_size=256)
377
367
 
378
368
 
369
+ class UnsignedIntegerDecoderCached(UnsignedIntegerDecoder):
370
+ decoder_fn: Callable[[bytes], int]
371
+ maxsize: Final[Optional[int]]
372
+
373
+ def __init__(self, maxsize: Optional[int] = None, **kwargs: Any) -> None:
374
+ super().__init__(**kwargs)
375
+ self.maxsize = maxsize
376
+ self.decoder_fn = lru_cache(maxsize=maxsize)(self.decoder_fn)
377
+
378
+
379
379
  #
380
380
  # Signed Integer Decoders
381
381
  #
382
- class SignedIntegerDecoder(Fixed32ByteSizeDecoder):
382
+ class SignedIntegerDecoder(Fixed32ByteSizeDecoder[int]):
383
383
  is_big_endian = True
384
384
 
385
- def decoder_fn(self, data):
386
- value = big_endian_to_int(data)
387
- if value >= 2 ** (self.value_bit_size - 1):
388
- return value - 2**self.value_bit_size
389
- else:
390
- return value
385
+ def __init__(self, **kwargs: Any) -> None:
386
+ super().__init__(**kwargs)
391
387
 
392
- def validate_padding_bytes(self, value: Any, padding_bytes: bytes) -> None:
393
- value_byte_size = self._get_value_byte_size()
394
- padding_size = self.data_byte_size - value_byte_size
388
+ # Only assign validate_padding_bytes if not overridden in subclass
389
+ if (
390
+ type(self).validate_padding_bytes
391
+ is SignedIntegerDecoder.validate_padding_bytes
392
+ ):
393
+ self.validate_padding_bytes = MethodType(
394
+ validate_padding_bytes_signed_integer, self
395
+ )
395
396
 
396
- if value >= 0:
397
- expected_padding_bytes = b"\x00" * padding_size
398
- else:
399
- expected_padding_bytes = b"\xff" * padding_size
397
+ @cached_property
398
+ def neg_threshold(self) -> int:
399
+ return int(2 ** (self.value_bit_size - 1))
400
400
 
401
- if padding_bytes != expected_padding_bytes:
402
- raise NonEmptyPaddingBytes(
403
- f"Padding bytes were not empty: {padding_bytes!r}"
404
- )
401
+ @cached_property
402
+ def neg_offset(self) -> int:
403
+ return int(2**self.value_bit_size)
404
+
405
+ def decoder_fn(self, data: bytes) -> int:
406
+ value = big_endian_to_int(data)
407
+ if value >= self.neg_threshold:
408
+ value -= self.neg_offset
409
+ return value
410
+
411
+ def validate_padding_bytes(self, value: Any, padding_bytes: bytes) -> None:
412
+ return validate_padding_bytes_signed_integer(self, value, padding_bytes)
405
413
 
406
414
  @parse_type_str("int")
407
415
  def from_type_str(cls, abi_type, registry):
408
416
  return cls(value_bit_size=abi_type.sub)
409
417
 
410
418
 
419
+ class SignedIntegerDecoderCached(SignedIntegerDecoder):
420
+ decoder_fn: Callable[[bytes], int]
421
+ maxsize: Final[Optional[int]]
422
+
423
+ def __init__(self, maxsize: Optional[int] = None, **kwargs: Any) -> None:
424
+ super().__init__(**kwargs)
425
+ self.maxsize = maxsize
426
+ self.decoder_fn = lru_cache(maxsize=maxsize)(self.decoder_fn)
427
+
428
+
411
429
  #
412
430
  # Bytes1..32
413
431
  #
414
- class BytesDecoder(Fixed32ByteSizeDecoder):
432
+ class BytesDecoder(Fixed32ByteSizeDecoder[bytes]):
415
433
  is_big_endian = False
416
434
 
417
435
  @staticmethod
418
- def decoder_fn(data):
436
+ def decoder_fn(data: bytes) -> bytes:
419
437
  return data
420
438
 
421
439
  @parse_type_str("bytes")
@@ -423,26 +441,31 @@ class BytesDecoder(Fixed32ByteSizeDecoder):
423
441
  return cls(value_bit_size=abi_type.sub * 8)
424
442
 
425
443
 
426
- class BaseFixedDecoder(Fixed32ByteSizeDecoder):
427
- frac_places = None
444
+ class BaseFixedDecoder(Fixed32ByteSizeDecoder[decimal.Decimal]):
445
+ frac_places: int = None
428
446
  is_big_endian = True
429
447
 
448
+ @cached_property
449
+ def denominator(self) -> decimal.Decimal:
450
+ return TEN**self.frac_places
451
+
430
452
  def validate(self) -> None:
431
453
  super().validate()
432
454
 
433
- if self.frac_places is None:
455
+ frac_places = self.frac_places
456
+ if frac_places is None:
434
457
  raise ValueError("must specify `frac_places`")
435
458
 
436
- if self.frac_places <= 0 or self.frac_places > 80:
437
- raise ValueError("`frac_places` must be in range (0, 80]")
459
+ if frac_places <= 0 or frac_places > 80:
460
+ raise ValueError("`frac_places` must be in range (0, 80)")
438
461
 
439
462
 
440
463
  class UnsignedFixedDecoder(BaseFixedDecoder):
441
- def decoder_fn(self, data):
464
+ def decoder_fn(self, data: bytes) -> decimal.Decimal:
442
465
  value = big_endian_to_int(data)
443
466
 
444
467
  with decimal.localcontext(abi_decimal_context):
445
- decimal_value = decimal.Decimal(value) / TEN**self.frac_places
468
+ decimal_value = decimal.Decimal(value) / self.denominator
446
469
 
447
470
  return decimal_value
448
471
 
@@ -454,26 +477,41 @@ class UnsignedFixedDecoder(BaseFixedDecoder):
454
477
 
455
478
 
456
479
  class SignedFixedDecoder(BaseFixedDecoder):
457
- def decoder_fn(self, data):
480
+ @cached_property
481
+ def neg_threshold(self) -> int:
482
+ return int(2 ** (self.value_bit_size - 1))
483
+
484
+ @cached_property
485
+ def neg_offset(self) -> int:
486
+ return int(2**self.value_bit_size)
487
+
488
+ @cached_property
489
+ def expected_padding_pos(self) -> bytes:
490
+ value_byte_size = get_value_byte_size(self)
491
+ padding_size = self.data_byte_size - value_byte_size
492
+ return b"\x00" * padding_size
493
+
494
+ @cached_property
495
+ def expected_padding_neg(self) -> bytes:
496
+ value_byte_size = get_value_byte_size(self)
497
+ padding_size = self.data_byte_size - value_byte_size
498
+ return b"\xff" * padding_size
499
+
500
+ def decoder_fn(self, data: bytes) -> decimal.Decimal:
458
501
  value = big_endian_to_int(data)
459
- if value >= 2 ** (self.value_bit_size - 1):
460
- signed_value = value - 2**self.value_bit_size
461
- else:
462
- signed_value = value
502
+ if value >= self.neg_threshold:
503
+ value -= self.neg_offset
463
504
 
464
505
  with decimal.localcontext(abi_decimal_context):
465
- decimal_value = decimal.Decimal(signed_value) / TEN**self.frac_places
506
+ decimal_value = decimal.Decimal(value) / self.denominator
466
507
 
467
508
  return decimal_value
468
509
 
469
510
  def validate_padding_bytes(self, value: Any, padding_bytes: bytes) -> None:
470
- value_byte_size = self._get_value_byte_size()
471
- padding_size = self.data_byte_size - value_byte_size
472
-
473
511
  if value >= 0:
474
- expected_padding_bytes = b"\x00" * padding_size
512
+ expected_padding_bytes = self.expected_padding_pos
475
513
  else:
476
- expected_padding_bytes = b"\xff" * padding_size
514
+ expected_padding_bytes = self.expected_padding_neg
477
515
 
478
516
  if padding_bytes != expected_padding_bytes:
479
517
  raise NonEmptyPaddingBytes(
@@ -490,11 +528,11 @@ class SignedFixedDecoder(BaseFixedDecoder):
490
528
  #
491
529
  # String and Bytes
492
530
  #
493
- class ByteStringDecoder(SingleDecoder):
531
+ class ByteStringDecoder(SingleDecoder[TByteStr]):
494
532
  is_dynamic = True
495
533
 
496
534
  @staticmethod
497
- def decoder_fn(data):
535
+ def decoder_fn(data: bytes) -> bytes:
498
536
  return data
499
537
 
500
538
  def read_data_from_stream(self, stream: ContextFramesBytesIO) -> bytes:
@@ -525,7 +563,7 @@ class ByteStringDecoder(SingleDecoder):
525
563
  return cls()
526
564
 
527
565
 
528
- class StringDecoder(ByteStringDecoder):
566
+ class StringDecoder(ByteStringDecoder[str]):
529
567
  def __init__(self, handle_string_errors: str = "strict") -> None:
530
568
  self.bytes_errors: Final = handle_string_errors
531
569
  super().__init__()