We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
raise_contract_logic_error_on_revert fails with UnicodeDecodeError
* Version: 6.4.0
* Python: 3.10
* OS: linux
* `pip freeze` output

```
aiohttp==3.8.4
aiosignal==1.3.1
async-timeout==4.0.2
attrs==23.1.0
bitarray==2.7.3
cached-property==1.5.2
certifi==2023.5.7
charset-normalizer==3.1.0
coverage==7.2.6
cytoolz==0.12.1
eth-abi==4.0.0
eth-account==0.8.0
eth-bloom==2.0.0
eth-hash==0.5.1
eth-keyfile==0.6.1
eth-keys==0.4.0
eth-rlp==0.3.0
eth-typing==3.3.0
eth-utils==2.1.0
frozenlist==1.3.3
hexbytes==0.3.0
idna==3.4
iniconfig==2.0.0
jsonschema==4.17.3
lru-dict==1.2.0
multidict==6.0.4
mypy-extensions==0.4.4
packaging==23.1
parsimonious==0.9.0
pluggy==1.0.0
protobuf==4.23.2
py-ecc==6.0.0
py-evm==0.7.0a2
pycryptodome==3.18.0
pyethash==0.1.27
pyrsistent==0.19.3
pytest==7.3.1
pytest-cov==4.1.0
regex==2023.5.5
requests==2.31.0
rlp==3.0.0
safe-eth-py==5.4.3
safe-pysha3==1.0.4
sortedcontainers==2.4.0
toolz==0.12.0
trie==2.1.0
urllib3==2.0.2
web3==6.4.0
websockets==11.0.3
yarl==1.9.2
```


### What was wrong?

Function `raise_contract_logic_error_on_revert` raises `UnicodeDecodeError` when parsing receipt for the following response
`response = {'error': {'code': -32015, 'data': 'Reverted 0xd27b44a9', 'message': 'VM execution error.'}, 'id': 3, 'jsonrpc': '2.0'}`


* Node: GnosisChain (issue happens for most nodes, https://rpc.gnosischain.com/, https://rpc.ankr.com/gnosis, etc)

```
/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/contract/contract.py:461: in call
    return call_contract_function(
/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/contract/utils.py:96: in call_contract_function
    return_data = w3.eth.call(
/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/eth/eth.py:255: in call
    return self._durin_call(transaction, block_identifier, state_override)
/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/eth/eth.py:274: in _durin_call
    return self._call(transaction, block_identifier, state_override)
/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/module.py:68: in caller
    result = w3.manager.request_blocking(
/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/manager.py:232: in request_blocking
    return self.formatted_response(
/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/manager.py:197: in formatted_response
    apply_error_formatters(error_formatters, response)
/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/manager.py:73: in apply_error_formatters
    formatted_resp = pipe(response, error_formatters)
cytoolz/functoolz.pyx:666: in cytoolz.functoolz.pipe
    ???
cytoolz/functoolz.pyx:641: in cytoolz.functoolz.c_pipe
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

response = {'error': {'code': -32015, 'data': 'Reverted 0xd27b44a9', 'message': 'VM execution error.'}, 'id': 3, 'jsonrpc': '2.0'}

    def raise_contract_logic_error_on_revert(response: RPCResponse) -> RPCResponse:
        """
        Reverts contain a `data` attribute with the following layout:
            "Reverted "
            Function selector for Error(string): 08c379a (4 bytes)
            Data offset: 32 (32 bytes)
            String length (32 bytes)
            Reason string (padded, use string length from above to get meaningful part)
    
        See also https://solidity.readthedocs.io/en/v0.6.3/control-structures.html#revert
        """
        if not isinstance(response["error"], dict):
            raise ValueError("Error expected to be a dict")
    
        data = response["error"].get("data", "")
    
        # Ganache case:
        if isinstance(data, dict) and response["error"].get("message"):
            raise ContractLogicError(
                f'execution reverted: {response["error"]["message"]}', data=data
            )
    
        # Parity/OpenEthereum case:
        if data.startswith("Reverted "):
            # "Reverted", function selector and offset are always the same for revert errors
            prefix = "Reverted 0x08c379a00000000000000000000000000000000000000000000000000000000000000020"  # noqa: 501
            if not data.startswith(prefix):
                if data.startswith("Reverted 0x"):
                    # Special case for this form: 'Reverted 0x...'
                    receipt = data.split(" ")[1][2:]
>                   revert_reason = bytes.fromhex(receipt).decode("utf-8")
E                   UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd2 in position 0: invalid continuation byte

/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/_utils/method_formatters.py:743: UnicodeDecodeError
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `web3/_utils/contract_error_handling.py`
Content:
```
1 from eth_abi import (
2     abi,
3 )
4 from eth_utils import (
5     to_bytes,
6 )
7 
8 from web3.exceptions import (
9     ContractCustomError,
10     ContractLogicError,
11     ContractPanicError,
12     OffchainLookup,
13 )
14 from web3.types import (
15     RPCResponse,
16 )
17 
18 # func selector for "Error(string)"
19 SOLIDITY_ERROR_FUNC_SELECTOR = "0x08c379a0"
20 
21 # --- CCIP Read - EIP-3668 --- #
22 # the first 4 bytes of keccak hash (func selector) for:
23 # "OffchainLookup(address,string[],bytes,bytes4,bytes)"
24 OFFCHAIN_LOOKUP_FUNC_SELECTOR = "0x556f1830"
25 OFFCHAIN_LOOKUP_FIELDS = {
26     "sender": "address",
27     "urls": "string[]",
28     "callData": "bytes",
29     "callbackFunction": "bytes4",
30     "extraData": "bytes",
31 }
32 
33 
34 # --- Solidity Panic Error, as of Solidity 0.8.0 --- #
35 PANIC_ERROR_FUNC_SELECTOR = "0x4e487b71"
36 PANIC_ERROR_CODES = {
37     "00": "Panic error 0x00: Generic compiler inserted panics.",
38     "01": "Panic error 0x01: Assert evaluates to false.",
39     "11": "Panic error 0x11: Arithmetic operation results in underflow or overflow.",
40     "12": "Panic error 0x12: Division by zero.",
41     "21": "Panic error 0x21: Cannot convert value into an enum type.",
42     "22": "Panic error 0x12: Storage byte array is incorrectly encoded.",
43     "31": "Panic error 0x31: Call to 'pop()' on an empty array.",
44     "32": "Panic error 0x32: Array index is out of bounds.",
45     "41": "Panic error 0x41: Allocation of too much memory or array too large.",
46     "51": "Panic error 0x51: Call to a zero-initialized variable of internal "
47     "function type.",
48 }
49 
50 
51 def raise_contract_logic_error_on_revert(response: RPCResponse) -> RPCResponse:
52     """
53     Reverts contain a `data` attribute with the following layout:
54         "Reverted "
55         Function selector for Error(string): 08c379a (4 bytes)
56         Data offset: 32 (32 bytes)
57         String length (32 bytes)
58         Reason string (padded, use string length from above to get meaningful part)
59 
60     See also https://solidity.readthedocs.io/en/v0.6.3/control-structures.html#revert
61     """
62     if not isinstance(response["error"], dict):
63         raise ValueError("Error expected to be a dict")
64 
65     data = response["error"].get("data", "")
66 
67     # Ganache case:
68     if isinstance(data, dict) and response["error"].get("message"):
69         raise ContractLogicError(
70             f'execution reverted: {response["error"]["message"]}', data=data
71         )
72 
73     # Parity/OpenEthereum case:
74     if data.startswith("Reverted "):
75         # "Reverted", function selector and offset are always the same for revert errors
76         prefix = f"Reverted {SOLIDITY_ERROR_FUNC_SELECTOR}"
77         data_offset = ("00" * 31) + "20"  # 0x0000...0020 (32 bytes)
78         revert_pattern = prefix + data_offset
79 
80         if data.startswith(revert_pattern):
81             # if common revert pattern
82             string_length = int(
83                 data[len(revert_pattern) : len(revert_pattern) + 64], 16
84             )
85             reason_as_hex = data[
86                 len(revert_pattern) + 64 : len(revert_pattern) + 64 + string_length * 2
87             ]
88         elif data.startswith("Reverted 0x"):
89             # Special case for this form: 'Reverted 0x...'
90             reason_as_hex = data.split(" ")[1][2:]
91         else:
92             raise ContractLogicError("execution reverted", data=data)
93 
94         reason_string = bytes.fromhex(reason_as_hex).decode("utf8")
95         raise ContractLogicError(f"execution reverted: {reason_string}", data=data)
96 
97     # --- EIP-3668 | CCIP Read --- #
98     if data[:10] == OFFCHAIN_LOOKUP_FUNC_SELECTOR:
99         parsed_data_as_bytes = to_bytes(hexstr=data[10:])
100         abi_decoded_data = abi.decode(
101             list(OFFCHAIN_LOOKUP_FIELDS.values()), parsed_data_as_bytes
102         )
103         offchain_lookup_payload = dict(
104             zip(OFFCHAIN_LOOKUP_FIELDS.keys(), abi_decoded_data)
105         )
106         raise OffchainLookup(offchain_lookup_payload, data=data)
107 
108     # --- Solidity Panic Error --- #
109     if data[:10] == PANIC_ERROR_FUNC_SELECTOR:
110         panic_error_code = data[-2:]
111         raise ContractPanicError(PANIC_ERROR_CODES[panic_error_code], data=data)
112 
113     # Solidity 0.8.4 introduced custom error messages that allow args to
114     # be passed in (or not). See:
115     # https://blog.soliditylang.org/2021/04/21/custom-errors/
116     if len(data) >= 10 and not data[:10] == SOLIDITY_ERROR_FUNC_SELECTOR:
117         # Raise with data as both the message and the data for backwards
118         # compatibility and so that data can be accessed via 'data' attribute
119         # on the ContractCustomError exception
120         raise ContractCustomError(data, data=data)
121 
122     # Geth case:
123     if "message" in response["error"] and response["error"].get("code", "") == 3:
124         message = response["error"]["message"]
125         raise ContractLogicError(message, data=data)
126 
127     # Geth Revert without error message case:
128     if "execution reverted" in response["error"].get("message"):
129         raise ContractLogicError("execution reverted", data=data)
130 
131     return response
132
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/web3/_utils/contract_error_handling.py b/web3/_utils/contract_error_handling.py
--- a/web3/_utils/contract_error_handling.py
+++ b/web3/_utils/contract_error_handling.py
@@ -1,3 +1,5 @@
+import warnings
+
 from eth_abi import (
     abi,
 )
@@ -91,8 +93,12 @@
         else:
             raise ContractLogicError("execution reverted", data=data)
 
-        reason_string = bytes.fromhex(reason_as_hex).decode("utf8")
-        raise ContractLogicError(f"execution reverted: {reason_string}", data=data)
+        try:
+            reason_string = bytes.fromhex(reason_as_hex).decode("utf8")
+            raise ContractLogicError(f"execution reverted: {reason_string}", data=data)
+        except UnicodeDecodeError:
+            warnings.warn("Could not decode revert reason as UTF-8", RuntimeWarning)
+            raise ContractLogicError("execution reverted", data=data)
 
     # --- EIP-3668 | CCIP Read --- #
     if data[:10] == OFFCHAIN_LOOKUP_FUNC_SELECTOR:

{"golden_diff": "diff --git a/web3/_utils/contract_error_handling.py b/web3/_utils/contract_error_handling.py\n--- a/web3/_utils/contract_error_handling.py\n+++ b/web3/_utils/contract_error_handling.py\n@@ -1,3 +1,5 @@\n+import warnings\n+\n from eth_abi import (\n     abi,\n )\n@@ -91,8 +93,12 @@\n         else:\n             raise ContractLogicError(\"execution reverted\", data=data)\n \n-        reason_string = bytes.fromhex(reason_as_hex).decode(\"utf8\")\n-        raise ContractLogicError(f\"execution reverted: {reason_string}\", data=data)\n+        try:\n+            reason_string = bytes.fromhex(reason_as_hex).decode(\"utf8\")\n+            raise ContractLogicError(f\"execution reverted: {reason_string}\", data=data)\n+        except UnicodeDecodeError:\n+            warnings.warn(\"Could not decode revert reason as UTF-8\", RuntimeWarning)\n+            raise ContractLogicError(\"execution reverted\", data=data)\n \n     # --- EIP-3668 | CCIP Read --- #\n     if data[:10] == OFFCHAIN_LOOKUP_FUNC_SELECTOR:\n", "issue": "raise_contract_logic_error_on_revert fails with UnicodeDecodeError\n* Version: 6.4.0\r\n* Python: 3.10\r\n* OS: linux\r\n* `pip freeze` output\r\n\r\n```\r\naiohttp==3.8.4\r\naiosignal==1.3.1\r\nasync-timeout==4.0.2\r\nattrs==23.1.0\r\nbitarray==2.7.3\r\ncached-property==1.5.2\r\ncertifi==2023.5.7\r\ncharset-normalizer==3.1.0\r\ncoverage==7.2.6\r\ncytoolz==0.12.1\r\neth-abi==4.0.0\r\neth-account==0.8.0\r\neth-bloom==2.0.0\r\neth-hash==0.5.1\r\neth-keyfile==0.6.1\r\neth-keys==0.4.0\r\neth-rlp==0.3.0\r\neth-typing==3.3.0\r\neth-utils==2.1.0\r\nfrozenlist==1.3.3\r\nhexbytes==0.3.0\r\nidna==3.4\r\niniconfig==2.0.0\r\njsonschema==4.17.3\r\nlru-dict==1.2.0\r\nmultidict==6.0.4\r\nmypy-extensions==0.4.4\r\npackaging==23.1\r\nparsimonious==0.9.0\r\npluggy==1.0.0\r\nprotobuf==4.23.2\r\npy-ecc==6.0.0\r\npy-evm==0.7.0a2\r\npycryptodome==3.18.0\r\npyethash==0.1.27\r\npyrsistent==0.19.3\r\npytest==7.3.1\r\npytest-cov==4.1.0\r\nregex==2023.5.5\r\nrequests==2.31.0\r\nrlp==3.0.0\r\nsafe-eth-py==5.4.3\r\nsafe-pysha3==1.0.4\r\nsortedcontainers==2.4.0\r\ntoolz==0.12.0\r\ntrie==2.1.0\r\nurllib3==2.0.2\r\nweb3==6.4.0\r\nwebsockets==11.0.3\r\nyarl==1.9.2\r\n```\r\n\r\n\r\n### What was wrong?\r\n\r\nFunction `raise_contract_logic_error_on_revert` raises `UnicodeDecodeError` when parsing receipt for the following response\r\n`response = {'error': {'code': -32015, 'data': 'Reverted 0xd27b44a9', 'message': 'VM execution error.'}, 'id': 3, 'jsonrpc': '2.0'}`\r\n\r\n\r\n* Node: GnosisChain (issue happens for most nodes, https://rpc.gnosischain.com/, https://rpc.ankr.com/gnosis, etc)\r\n\r\n```\r\n/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/contract/contract.py:461: in call\r\n    return call_contract_function(\r\n/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/contract/utils.py:96: in call_contract_function\r\n    return_data = w3.eth.call(\r\n/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/eth/eth.py:255: in call\r\n    return self._durin_call(transaction, block_identifier, state_override)\r\n/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/eth/eth.py:274: in _durin_call\r\n    return self._call(transaction, block_identifier, state_override)\r\n/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/module.py:68: in caller\r\n    result = w3.manager.request_blocking(\r\n/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/manager.py:232: in request_blocking\r\n    return self.formatted_response(\r\n/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/manager.py:197: in formatted_response\r\n    apply_error_formatters(error_formatters, response)\r\n/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/manager.py:73: in apply_error_formatters\r\n    formatted_resp = pipe(response, error_formatters)\r\ncytoolz/functoolz.pyx:666: in cytoolz.functoolz.pipe\r\n    ???\r\ncytoolz/functoolz.pyx:641: in cytoolz.functoolz.c_pipe\r\n    ???\r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\n\r\nresponse = {'error': {'code': -32015, 'data': 'Reverted 0xd27b44a9', 'message': 'VM execution error.'}, 'id': 3, 'jsonrpc': '2.0'}\r\n\r\n    def raise_contract_logic_error_on_revert(response: RPCResponse) -> RPCResponse:\r\n        \"\"\"\r\n        Reverts contain a `data` attribute with the following layout:\r\n            \"Reverted \"\r\n            Function selector for Error(string): 08c379a (4 bytes)\r\n            Data offset: 32 (32 bytes)\r\n            String length (32 bytes)\r\n            Reason string (padded, use string length from above to get meaningful part)\r\n    \r\n        See also https://solidity.readthedocs.io/en/v0.6.3/control-structures.html#revert\r\n        \"\"\"\r\n        if not isinstance(response[\"error\"], dict):\r\n            raise ValueError(\"Error expected to be a dict\")\r\n    \r\n        data = response[\"error\"].get(\"data\", \"\")\r\n    \r\n        # Ganache case:\r\n        if isinstance(data, dict) and response[\"error\"].get(\"message\"):\r\n            raise ContractLogicError(\r\n                f'execution reverted: {response[\"error\"][\"message\"]}', data=data\r\n            )\r\n    \r\n        # Parity/OpenEthereum case:\r\n        if data.startswith(\"Reverted \"):\r\n            # \"Reverted\", function selector and offset are always the same for revert errors\r\n            prefix = \"Reverted 0x08c379a00000000000000000000000000000000000000000000000000000000000000020\"  # noqa: 501\r\n            if not data.startswith(prefix):\r\n                if data.startswith(\"Reverted 0x\"):\r\n                    # Special case for this form: 'Reverted 0x...'\r\n                    receipt = data.split(\" \")[1][2:]\r\n>                   revert_reason = bytes.fromhex(receipt).decode(\"utf-8\")\r\nE                   UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd2 in position 0: invalid continuation byte\r\n\r\n/opt/hostedtoolcache/Python/3.10.11/x64/lib/python3.10/site-packages/web3/_utils/method_formatters.py:743: UnicodeDecodeError\r\n```\r\n\n", "before_files": [{"content": "from eth_abi import (\n    abi,\n)\nfrom eth_utils import (\n    to_bytes,\n)\n\nfrom web3.exceptions import (\n    ContractCustomError,\n    ContractLogicError,\n    ContractPanicError,\n    OffchainLookup,\n)\nfrom web3.types import (\n    RPCResponse,\n)\n\n# func selector for \"Error(string)\"\nSOLIDITY_ERROR_FUNC_SELECTOR = \"0x08c379a0\"\n\n# --- CCIP Read - EIP-3668 --- #\n# the first 4 bytes of keccak hash (func selector) for:\n# \"OffchainLookup(address,string[],bytes,bytes4,bytes)\"\nOFFCHAIN_LOOKUP_FUNC_SELECTOR = \"0x556f1830\"\nOFFCHAIN_LOOKUP_FIELDS = {\n    \"sender\": \"address\",\n    \"urls\": \"string[]\",\n    \"callData\": \"bytes\",\n    \"callbackFunction\": \"bytes4\",\n    \"extraData\": \"bytes\",\n}\n\n\n# --- Solidity Panic Error, as of Solidity 0.8.0 --- #\nPANIC_ERROR_FUNC_SELECTOR = \"0x4e487b71\"\nPANIC_ERROR_CODES = {\n    \"00\": \"Panic error 0x00: Generic compiler inserted panics.\",\n    \"01\": \"Panic error 0x01: Assert evaluates to false.\",\n    \"11\": \"Panic error 0x11: Arithmetic operation results in underflow or overflow.\",\n    \"12\": \"Panic error 0x12: Division by zero.\",\n    \"21\": \"Panic error 0x21: Cannot convert value into an enum type.\",\n    \"22\": \"Panic error 0x12: Storage byte array is incorrectly encoded.\",\n    \"31\": \"Panic error 0x31: Call to 'pop()' on an empty array.\",\n    \"32\": \"Panic error 0x32: Array index is out of bounds.\",\n    \"41\": \"Panic error 0x41: Allocation of too much memory or array too large.\",\n    \"51\": \"Panic error 0x51: Call to a zero-initialized variable of internal \"\n    \"function type.\",\n}\n\n\ndef raise_contract_logic_error_on_revert(response: RPCResponse) -> RPCResponse:\n    \"\"\"\n    Reverts contain a `data` attribute with the following layout:\n        \"Reverted \"\n        Function selector for Error(string): 08c379a (4 bytes)\n        Data offset: 32 (32 bytes)\n        String length (32 bytes)\n        Reason string (padded, use string length from above to get meaningful part)\n\n    See also https://solidity.readthedocs.io/en/v0.6.3/control-structures.html#revert\n    \"\"\"\n    if not isinstance(response[\"error\"], dict):\n        raise ValueError(\"Error expected to be a dict\")\n\n    data = response[\"error\"].get(\"data\", \"\")\n\n    # Ganache case:\n    if isinstance(data, dict) and response[\"error\"].get(\"message\"):\n        raise ContractLogicError(\n            f'execution reverted: {response[\"error\"][\"message\"]}', data=data\n        )\n\n    # Parity/OpenEthereum case:\n    if data.startswith(\"Reverted \"):\n        # \"Reverted\", function selector and offset are always the same for revert errors\n        prefix = f\"Reverted {SOLIDITY_ERROR_FUNC_SELECTOR}\"\n        data_offset = (\"00\" * 31) + \"20\"  # 0x0000...0020 (32 bytes)\n        revert_pattern = prefix + data_offset\n\n        if data.startswith(revert_pattern):\n            # if common revert pattern\n            string_length = int(\n                data[len(revert_pattern) : len(revert_pattern) + 64], 16\n            )\n            reason_as_hex = data[\n                len(revert_pattern) + 64 : len(revert_pattern) + 64 + string_length * 2\n            ]\n        elif data.startswith(\"Reverted 0x\"):\n            # Special case for this form: 'Reverted 0x...'\n            reason_as_hex = data.split(\" \")[1][2:]\n        else:\n            raise ContractLogicError(\"execution reverted\", data=data)\n\n        reason_string = bytes.fromhex(reason_as_hex).decode(\"utf8\")\n        raise ContractLogicError(f\"execution reverted: {reason_string}\", data=data)\n\n    # --- EIP-3668 | CCIP Read --- #\n    if data[:10] == OFFCHAIN_LOOKUP_FUNC_SELECTOR:\n        parsed_data_as_bytes = to_bytes(hexstr=data[10:])\n        abi_decoded_data = abi.decode(\n            list(OFFCHAIN_LOOKUP_FIELDS.values()), parsed_data_as_bytes\n        )\n        offchain_lookup_payload = dict(\n            zip(OFFCHAIN_LOOKUP_FIELDS.keys(), abi_decoded_data)\n        )\n        raise OffchainLookup(offchain_lookup_payload, data=data)\n\n    # --- Solidity Panic Error --- #\n    if data[:10] == PANIC_ERROR_FUNC_SELECTOR:\n        panic_error_code = data[-2:]\n        raise ContractPanicError(PANIC_ERROR_CODES[panic_error_code], data=data)\n\n    # Solidity 0.8.4 introduced custom error messages that allow args to\n    # be passed in (or not). See:\n    # https://blog.soliditylang.org/2021/04/21/custom-errors/\n    if len(data) >= 10 and not data[:10] == SOLIDITY_ERROR_FUNC_SELECTOR:\n        # Raise with data as both the message and the data for backwards\n        # compatibility and so that data can be accessed via 'data' attribute\n        # on the ContractCustomError exception\n        raise ContractCustomError(data, data=data)\n\n    # Geth case:\n    if \"message\" in response[\"error\"] and response[\"error\"].get(\"code\", \"\") == 3:\n        message = response[\"error\"][\"message\"]\n        raise ContractLogicError(message, data=data)\n\n    # Geth Revert without error message case:\n    if \"execution reverted\" in response[\"error\"].get(\"message\"):\n        raise ContractLogicError(\"execution reverted\", data=data)\n\n    return response\n", "path": "web3/_utils/contract_error_handling.py"}], "after_files": [{"content": "import warnings\n\nfrom eth_abi import (\n    abi,\n)\nfrom eth_utils import (\n    to_bytes,\n)\n\nfrom web3.exceptions import (\n    ContractCustomError,\n    ContractLogicError,\n    ContractPanicError,\n    OffchainLookup,\n)\nfrom web3.types import (\n    RPCResponse,\n)\n\n# func selector for \"Error(string)\"\nSOLIDITY_ERROR_FUNC_SELECTOR = \"0x08c379a0\"\n\n# --- CCIP Read - EIP-3668 --- #\n# the first 4 bytes of keccak hash (func selector) for:\n# \"OffchainLookup(address,string[],bytes,bytes4,bytes)\"\nOFFCHAIN_LOOKUP_FUNC_SELECTOR = \"0x556f1830\"\nOFFCHAIN_LOOKUP_FIELDS = {\n    \"sender\": \"address\",\n    \"urls\": \"string[]\",\n    \"callData\": \"bytes\",\n    \"callbackFunction\": \"bytes4\",\n    \"extraData\": \"bytes\",\n}\n\n\n# --- Solidity Panic Error, as of Solidity 0.8.0 --- #\nPANIC_ERROR_FUNC_SELECTOR = \"0x4e487b71\"\nPANIC_ERROR_CODES = {\n    \"00\": \"Panic error 0x00: Generic compiler inserted panics.\",\n    \"01\": \"Panic error 0x01: Assert evaluates to false.\",\n    \"11\": \"Panic error 0x11: Arithmetic operation results in underflow or overflow.\",\n    \"12\": \"Panic error 0x12: Division by zero.\",\n    \"21\": \"Panic error 0x21: Cannot convert value into an enum type.\",\n    \"22\": \"Panic error 0x12: Storage byte array is incorrectly encoded.\",\n    \"31\": \"Panic error 0x31: Call to 'pop()' on an empty array.\",\n    \"32\": \"Panic error 0x32: Array index is out of bounds.\",\n    \"41\": \"Panic error 0x41: Allocation of too much memory or array too large.\",\n    \"51\": \"Panic error 0x51: Call to a zero-initialized variable of internal \"\n    \"function type.\",\n}\n\n\ndef raise_contract_logic_error_on_revert(response: RPCResponse) -> RPCResponse:\n    \"\"\"\n    Reverts contain a `data` attribute with the following layout:\n        \"Reverted \"\n        Function selector for Error(string): 08c379a (4 bytes)\n        Data offset: 32 (32 bytes)\n        String length (32 bytes)\n        Reason string (padded, use string length from above to get meaningful part)\n\n    See also https://solidity.readthedocs.io/en/v0.6.3/control-structures.html#revert\n    \"\"\"\n    if not isinstance(response[\"error\"], dict):\n        raise ValueError(\"Error expected to be a dict\")\n\n    data = response[\"error\"].get(\"data\", \"\")\n\n    # Ganache case:\n    if isinstance(data, dict) and response[\"error\"].get(\"message\"):\n        raise ContractLogicError(\n            f'execution reverted: {response[\"error\"][\"message\"]}', data=data\n        )\n\n    # Parity/OpenEthereum case:\n    if data.startswith(\"Reverted \"):\n        # \"Reverted\", function selector and offset are always the same for revert errors\n        prefix = f\"Reverted {SOLIDITY_ERROR_FUNC_SELECTOR}\"\n        data_offset = (\"00\" * 31) + \"20\"  # 0x0000...0020 (32 bytes)\n        revert_pattern = prefix + data_offset\n\n        if data.startswith(revert_pattern):\n            # if common revert pattern\n            string_length = int(\n                data[len(revert_pattern) : len(revert_pattern) + 64], 16\n            )\n            reason_as_hex = data[\n                len(revert_pattern) + 64 : len(revert_pattern) + 64 + string_length * 2\n            ]\n        elif data.startswith(\"Reverted 0x\"):\n            # Special case for this form: 'Reverted 0x...'\n            reason_as_hex = data.split(\" \")[1][2:]\n        else:\n            raise ContractLogicError(\"execution reverted\", data=data)\n\n        try:\n            reason_string = bytes.fromhex(reason_as_hex).decode(\"utf8\")\n            raise ContractLogicError(f\"execution reverted: {reason_string}\", data=data)\n        except UnicodeDecodeError:\n            warnings.warn(\"Could not decode revert reason as UTF-8\", RuntimeWarning)\n            raise ContractLogicError(\"execution reverted\", data=data)\n\n    # --- EIP-3668 | CCIP Read --- #\n    if data[:10] == OFFCHAIN_LOOKUP_FUNC_SELECTOR:\n        parsed_data_as_bytes = to_bytes(hexstr=data[10:])\n        abi_decoded_data = abi.decode(\n            list(OFFCHAIN_LOOKUP_FIELDS.values()), parsed_data_as_bytes\n        )\n        offchain_lookup_payload = dict(\n            zip(OFFCHAIN_LOOKUP_FIELDS.keys(), abi_decoded_data)\n        )\n        raise OffchainLookup(offchain_lookup_payload, data=data)\n\n    # --- Solidity Panic Error --- #\n    if data[:10] == PANIC_ERROR_FUNC_SELECTOR:\n        panic_error_code = data[-2:]\n        raise ContractPanicError(PANIC_ERROR_CODES[panic_error_code], data=data)\n\n    # Solidity 0.8.4 introduced custom error messages that allow args to\n    # be passed in (or not). See:\n    # https://blog.soliditylang.org/2021/04/21/custom-errors/\n    if len(data) >= 10 and not data[:10] == SOLIDITY_ERROR_FUNC_SELECTOR:\n        # Raise with data as both the message and the data for backwards\n        # compatibility and so that data can be accessed via 'data' attribute\n        # on the ContractCustomError exception\n        raise ContractCustomError(data, data=data)\n\n    # Geth case:\n    if \"message\" in response[\"error\"] and response[\"error\"].get(\"code\", \"\") == 3:\n        message = response[\"error\"][\"message\"]\n        raise ContractLogicError(message, data=data)\n\n    # Geth Revert without error message case:\n    if \"execution reverted\" in response[\"error\"].get(\"message\"):\n        raise ContractLogicError(\"execution reverted\", data=data)\n\n    return response\n", "path": "web3/_utils/contract_error_handling.py"}]}