Repository: kigawas/web3-input-decoder (language: python)

Issue:
OverflowError: Python int too large to convert to C ssize_t
I don't have a way to reproduce it, but here is a stack frame:

```
Traceback (most recent call last):
  File "Python\Python39\lib\threading.py", line 954, in _bootstrap_inner
    self.run()
  File "Python\Python39\lib\threading.py", line 892, in run
    self._target(*self._args, **self._kwargs)
  File " pyevm\lib\EVMBlockchain.py", line 109, in __new_blocks_thread_run
    self.__trigger_new_block(new_block)
  File " pyevm\lib\EVMBlockchain.py", line 63, in __trigger_new_block
    decoded_fn_data = decode_function_v2(UNISWAP_V2_ABI, tx.input)
  File " pyevm\lib\decoder.py", line 54, in decode_function_v2
    values = decode_abi(types, args)
  File "C:\global_python_env\lib\site-packages\eth_abi\codec.py", line 181, in decode_abi
    return decoder(stream)
  File "C:\global_python_env\lib\site-packages\eth_abi\decoding.py", line 127, in __call__
    return self.decode(stream)
  File "C:\global_python_env\lib\site-packages\eth_utils\functional.py", line 45, in inner
    return callback(fn(*args, **kwargs))
  File "C:\global_python_env\lib\site-packages\eth_abi\decoding.py", line 173, in decode
    yield decoder(stream)
  File "C:\global_python_env\lib\site-packages\eth_abi\decoding.py", line 127, in __call__
    return self.decode(stream)
  File "C:\global_python_env\lib\site-packages\eth_abi\decoding.py", line 144, in decode
    stream.push_frame(start_pos)
  File "C:\global_python_env\lib\site-packages\eth_abi\decoding.py", line 95, in push_frame
    self.seek_in_frame(0)
  File "C:\global_python_env\lib\site-packages\eth_abi\decoding.py", line 84, in seek_in_frame
    self.seek(self._total_offset + pos, *args, **kwargs)
OverflowError: Python int too large to convert to C ssize_t
```


Discussion / hints:
Reproducible trying to decode this tx: https://bscscan.com/tx/0xc3bea1a0ae512cd3e0bf3afc0c3aee49dbd03b4ecde05d3e465a3b18fa0bf299

It may happen only with failed txs... Here is quick fix for decode_function in order to avoid raising ugly system Overflow exception to the user:

  ```
  try:
        values = decode_abi(types, args)
    except OverflowError:
        raise InputDataError("decoding failed: OverflowError (did the tx being parsed fail?)")
```

It seems that failed transaction and overflow error share the same cause: overflowed numbers in input data. Need some investigations on the exceptions raised by eth_abi 🤔 

Fix patch:
diff --git a/README.md b/README.md
index b836f14..0550cee 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ If you have lots of inputs in the same contract to decode, consider using [`Inpu
 
 ## API
 
-- [`decode_constructor`](web3_input_decoder/__init__.py#L11)
+- [`decode_constructor`](web3_input_decoder/__init__.py#L12)
 
   ```python
   def decode_constructor(
@@ -79,7 +79,7 @@ If you have lots of inputs in the same contract to decode, consider using [`Inpu
 
   - `List[Tuple[str, str, Any]]`: Decoded type-name-value tuples
 
-- [`decode_function`](web3_input_decoder/__init__.py#L36)
+- [`decode_function`](web3_input_decoder/__init__.py#L37)
 
   ```python
   def decode_function(
diff --git a/poetry.lock b/poetry.lock
index ab23241..8cdffe8 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -193,7 +193,7 @@ test = ["hypothesis (>=4.43.0,<5.0.0)", "pytest (>=6.2.5,<7)", "pytest-xdist", "
 
 [[package]]
 name = "exceptiongroup"
-version = "1.0.1"
+version = "1.0.2"
 description = "Backport of PEP 654 (exception groups)"
 category = "dev"
 optional = false
@@ -384,7 +384,7 @@ testing = ["docopt", "pytest (<6.0.0)"]
 
 [[package]]
 name = "pathspec"
-version = "0.10.1"
+version = "0.10.2"
 description = "Utility library for gitignore style pattern matching of file paths."
 category = "dev"
 optional = false
@@ -411,7 +411,7 @@ python-versions = "*"
 
 [[package]]
 name = "platformdirs"
-version = "2.5.3"
+version = "2.5.4"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 category = "dev"
 optional = false
@@ -569,7 +569,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "stack-data"
-version = "0.6.0"
+version = "0.6.1"
 description = "Extract data from python stack frames and tracebacks for informative displays"
 category = "dev"
 optional = false
@@ -859,8 +859,8 @@ eth-utils = [
     {file = "eth_utils-2.0.0-py3-none-any.whl", hash = "sha256:209dc12255398f2a88f12de78f338b974861f9aefa981af5b68a5d102c9b2043"},
 ]
 exceptiongroup = [
-    {file = "exceptiongroup-1.0.1-py3-none-any.whl", hash = "sha256:4d6c0aa6dd825810941c792f53d7b8d71da26f5e5f84f20f9508e8f2d33b140a"},
-    {file = "exceptiongroup-1.0.1.tar.gz", hash = "sha256:73866f7f842ede6cb1daa42c4af078e2035e5f7607f0e2c762cc51bb31bbe7b2"},
+    {file = "exceptiongroup-1.0.2-py3-none-any.whl", hash = "sha256:c22f11ec6a10d2b453871c5c5fe887436c4d1961324ce9090f2ca6ddc4180c27"},
+    {file = "exceptiongroup-1.0.2.tar.gz", hash = "sha256:a31cd183c3dea02e617aab5153588d5f7258a77b51f0ef41b3815ae8a0d0f695"},
 ]
 executing = [
     {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"},
@@ -942,8 +942,8 @@ parso = [
     {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
 ]
 pathspec = [
-    {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"},
-    {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"},
+    {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"},
+    {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"},
 ]
 pexpect = [
     {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
@@ -954,8 +954,8 @@ pickleshare = [
     {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
 ]
 platformdirs = [
-    {file = "platformdirs-2.5.3-py3-none-any.whl", hash = "sha256:0cb405749187a194f444c25c82ef7225232f11564721eabffc6ec70df83b11cb"},
-    {file = "platformdirs-2.5.3.tar.gz", hash = "sha256:6e52c21afff35cb659c6e52d8b4d61b9bd544557180440538f255d9382c8cbe0"},
+    {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"},
+    {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"},
 ]
 pluggy = [
     {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
@@ -1086,8 +1086,8 @@ six = [
     {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
 ]
 stack-data = [
-    {file = "stack_data-0.6.0-py3-none-any.whl", hash = "sha256:b92d206ef355a367d14316b786ab41cb99eb453a21f2cb216a4204625ff7bc07"},
-    {file = "stack_data-0.6.0.tar.gz", hash = "sha256:8e515439f818efaa251036af72d89e4026e2b03993f3453c000b200fb4f2d6aa"},
+    {file = "stack_data-0.6.1-py3-none-any.whl", hash = "sha256:960cb054d6a1b2fdd9cbd529e365b3c163e8dabf1272e02cfe36b58403cff5c6"},
+    {file = "stack_data-0.6.1.tar.gz", hash = "sha256:6c9a10eb5f342415fe085db551d673955611afb821551f554d91772415464315"},
 ]
 tomli = [
     {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
diff --git a/web3_input_decoder/decoder.py b/web3_input_decoder/decoder.py
index c34dfc9..587dc6a 100644
--- a/web3_input_decoder/decoder.py
+++ b/web3_input_decoder/decoder.py
@@ -3,7 +3,7 @@
 
 from eth_abi.abi import decode
 
-from .exceptions import InputDataError
+from .exceptions import CONSTRUCTOR_NOT_FOUND, INVALID_INPUT, METHOD_NOT_FOUND
 from .utils import (
     detect_constructor_arguments,
     get_constructor_type,
@@ -29,10 +29,14 @@ def decode_function(self, tx_input: Union[str, bytes]):
         selector, args = tx_input[:4], tx_input[4:]
         type_def = self._selector_to_func_type.get(selector, None)
         if not type_def:
-            raise InputDataError("Specified method is not found in ABI")
+            raise METHOD_NOT_FOUND
 
         types, names = get_types_names(type_def["inputs"])
-        values = decode(types, args)
+
+        try:
+            values = decode(types, args)
+        except OverflowError:
+            raise INVALID_INPUT
 
         return ContractCall(type_def["name"], list(zip(types, names, values)))
 
@@ -44,7 +48,7 @@ def decode_constructor(
         tx_input = hex_to_bytes(tx_input)
 
         if not self._constructor_type:
-            raise InputDataError("Constructor is not found in ABI")
+            raise CONSTRUCTOR_NOT_FOUND
 
         if bytecode is not None:
             bytecode_len = len(hex_to_bytes(bytecode))
diff --git a/web3_input_decoder/exceptions.py b/web3_input_decoder/exceptions.py
index 28fcd6b..c3ace96 100644
--- a/web3_input_decoder/exceptions.py
+++ b/web3_input_decoder/exceptions.py
@@ -3,3 +3,11 @@
 
 class InputDataError(ValueError):
     pass
+
+
+METHOD_NOT_FOUND = InputDataError("Specified method is not found in ABI")
+CONSTRUCTOR_NOT_FOUND = InputDataError("Constructor is not found in ABI")
+INVALID_INPUT = InputDataError("Invalid input")
+UNABLE_TO_DETECT_CONSTRUCTOR_ARGUMENTS = InputDataError(
+    "Unable to detect arguments including array. Please provide the bytecode."
+)
diff --git a/web3_input_decoder/utils.py b/web3_input_decoder/utils.py
index 195a533..6b11a1c 100644
--- a/web3_input_decoder/utils.py
+++ b/web3_input_decoder/utils.py
@@ -3,7 +3,7 @@
 from eth_abi.abi import encode
 from eth_utils.abi import function_abi_to_4byte_selector
 
-from .exceptions import InputDataError
+from .exceptions import UNABLE_TO_DETECT_CONSTRUCTOR_ARGUMENTS
 
 __all__ = (
     "get_constructor_type",
@@ -74,10 +74,7 @@ def detect_constructor_arguments(
         if "int" in t:
             default_values.append(0)
         elif "[]" in t:
-            raise InputDataError(
-                "Unable to detect arguments including array. "
-                "Please provide the bytecode."
-            )
+            raise UNABLE_TO_DETECT_CONSTRUCTOR_ARGUMENTS
         elif "string" == t:
             default_values.append("")
         elif "bool" == t:


Test patch:
diff --git a/tests/data/defi.py b/tests/data/defi.py
index 6d24863..53f7ab7 100644
--- a/tests/data/defi.py
+++ b/tests/data/defi.py
@@ -69,3 +69,22 @@
     ("address", "to", "0x7c77b132a0cd0ad1c694ab8645affa26c2787d66"),
     ("uint256", "deadline", 1667934767),
 ]
+
+ROUTER_V2_ABI = [
+    {
+        "inputs": [
+            {"internalType": "uint256", "name": "amountIn", "type": "uint256"},
+            {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"},
+            {"internalType": "address[]", "name": "path", "type": "address[]"},
+            {"internalType": "address", "name": "to", "type": "address"},
+            {"internalType": "uint256", "name": "deadline", "type": "uint256"},
+        ],
+        "name": "swapExactTokensForTokens",
+        "outputs": [
+            {"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}
+        ],
+        "stateMutability": "nonpayable",
+        "type": "function",
+    }
+]
+ROUTER_V2_TOKENS_SWAP_CALL_ERROR_INPUT = "0x38ed1739000200000000000007e3e9b4130e0a0000000000000000031c4b60a9d6004e6bbf3a9d39ef65058149a3fcd9e7b74f1d3a9907d000000000001db6bd61ebf584aa5501e4bd310ce3a2fa9b6b93734a9f62f11e39ccc6ad09c400000000000007e705ff73b7e00000bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c"
diff --git a/tests/test_decode_error.py b/tests/test_decode_error.py
index cd6916b..04370ac 100644
--- a/tests/test_decode_error.py
+++ b/tests/test_decode_error.py
@@ -3,6 +3,7 @@
 from web3_input_decoder import decode_constructor, decode_function
 from web3_input_decoder.exceptions import InputDataError
 
+from .data.defi import ROUTER_V2_ABI, ROUTER_V2_TOKENS_SWAP_CALL_ERROR_INPUT
 from .data.example import EXAMPLE_CONSTRUCTOR_CALL_INPUT, EXAMPLE_CONTRACT_ABI
 from .data.tether import TETHER_ABI
 
@@ -10,15 +11,15 @@
 UNABLE_TO_DETECT = "Unable to detect arguments including array"
 CONSTRUCTOR_NOT_FOUND = "Constructor is not found in ABI"
 METHOD_NOT_FOUND = "Specified method is not found in ABI"
+INVALID_INPUT = "Invalid input"
 
 
 @pytest.mark.parametrize(
     "abi,input,match",
-    zip(
-        [EXAMPLE_CONTRACT_ABI, INVALID_ABI],
-        [EXAMPLE_CONSTRUCTOR_CALL_INPUT, "0x00"],
-        [UNABLE_TO_DETECT, CONSTRUCTOR_NOT_FOUND],
-    ),
+    [
+        (EXAMPLE_CONTRACT_ABI, EXAMPLE_CONSTRUCTOR_CALL_INPUT, UNABLE_TO_DETECT),
+        (INVALID_ABI, "0x00", CONSTRUCTOR_NOT_FOUND),
+    ],
 )
 def test_decode_constructor_error(abi, input, match):
     with pytest.raises(InputDataError, match=match):
@@ -27,11 +28,11 @@ def test_decode_constructor_error(abi, input, match):
 
 @pytest.mark.parametrize(
     "abi,input,match",
-    zip(
-        [TETHER_ABI, INVALID_ABI],
-        ["0x00000000", "0x00"],
-        [METHOD_NOT_FOUND, METHOD_NOT_FOUND],
-    ),
+    [
+        (TETHER_ABI, "0x00000000", METHOD_NOT_FOUND),
+        (INVALID_ABI, "0x00", METHOD_NOT_FOUND),
+        (ROUTER_V2_ABI, ROUTER_V2_TOKENS_SWAP_CALL_ERROR_INPUT, INVALID_INPUT),
+    ],
 )
 def test_decode_function_error(abi, input, match):
     with pytest.raises(InputDataError, match=match):