Skip to content

Commit edef04c

Browse files
committed
monkey patched bad revert error formatters. Pull request to fix this in original web3.py: ethereum/web3.py#3499
1 parent 8e4b683 commit edef04c

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

IceCreamSwapWeb3/Web3Advanced.py

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from .EthAdvanced import EthAdvanced
99
from .Multicall import MultiCall
10+
from .Web3ErrorHandlerPatch import patch_error_formatters
1011

1112

1213
class Web3Advanced(Web3):
@@ -34,6 +35,7 @@ def __init__(
3435
node_url: str,
3536
should_retry=True,
3637
):
38+
patch_error_formatters()
3739
self.node_url = node_url
3840
self.should_retry = should_retry
3941

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import warnings
2+
3+
from eth_abi import abi
4+
from eth_utils import to_bytes
5+
from web3._utils.error_formatters_utils import (
6+
SOLIDITY_ERROR_FUNC_SELECTOR,
7+
ContractLogicError, OFFCHAIN_LOOKUP_FUNC_SELECTOR, OFFCHAIN_LOOKUP_FIELDS, PANIC_ERROR_FUNC_SELECTOR,
8+
PANIC_ERROR_CODES, MISSING_DATA
9+
)
10+
from web3.exceptions import OffchainLookup, ContractPanicError, ContractCustomError
11+
from web3.types import RPCResponse
12+
13+
14+
def _parse_error_with_reverted_prefix(data: str) -> str:
15+
"""
16+
Parse errors from the data string which begin with the "Reverted" prefix or the Reverted function selector.
17+
"Reverted", function selector and offset are always the same for revert errors
18+
"""
19+
if data.startswith("Reverted "):
20+
data = data.replace("Reverted ", "")
21+
22+
data_offset = ("00" * 31) + "20" # 0x0000...0020 (32 bytes)
23+
revert_pattern = SOLIDITY_ERROR_FUNC_SELECTOR + data_offset
24+
25+
error = data
26+
if data.startswith(revert_pattern):
27+
# if common revert pattern
28+
string_length = int(data[len(revert_pattern) : len(revert_pattern) + 64], 16)
29+
error = data[
30+
len(revert_pattern) + 64 : len(revert_pattern) + 64 + string_length * 2
31+
]
32+
elif data.startswith("0x"):
33+
# Special case for this form: '0x...'
34+
error = data.split(" ")[0][2:]
35+
36+
try:
37+
error = bytes.fromhex(error).decode("utf8")
38+
except UnicodeDecodeError:
39+
warnings.warn(
40+
"Could not decode revert reason as UTF-8", RuntimeWarning, stacklevel=2
41+
)
42+
raise ContractLogicError("execution reverted", data=data)
43+
44+
return error
45+
46+
47+
def _raise_contract_error(response_error_data: str) -> None:
48+
"""
49+
Decode response error from data string and raise appropriate exception.
50+
51+
"Reverted " (prefix may be present in `data`)
52+
Function selector for Error(string): 08c379a (4 bytes)
53+
Data offset: 32 (32 bytes)
54+
String length (32 bytes)
55+
Reason string (padded, use string length from above to get meaningful part)
56+
"""
57+
if response_error_data.startswith("Reverted ") or response_error_data[:10] == SOLIDITY_ERROR_FUNC_SELECTOR:
58+
reason_string = _parse_error_with_reverted_prefix(response_error_data)
59+
raise ContractLogicError(
60+
f"execution reverted: {reason_string}", data=response_error_data
61+
)
62+
63+
elif response_error_data[:10] == OFFCHAIN_LOOKUP_FUNC_SELECTOR:
64+
# --- EIP-3668 | CCIP read error --- #
65+
parsed_data_as_bytes = to_bytes(hexstr=response_error_data[10:])
66+
abi_decoded_data = abi.decode(
67+
list(OFFCHAIN_LOOKUP_FIELDS.values()), parsed_data_as_bytes
68+
)
69+
offchain_lookup_payload = dict(
70+
zip(OFFCHAIN_LOOKUP_FIELDS.keys(), abi_decoded_data)
71+
)
72+
raise OffchainLookup(offchain_lookup_payload, data=response_error_data)
73+
74+
elif response_error_data[:10] == PANIC_ERROR_FUNC_SELECTOR:
75+
# --- Solidity Panic Error --- #
76+
panic_error_code = response_error_data[-2:]
77+
raise ContractPanicError(
78+
PANIC_ERROR_CODES[panic_error_code], data=response_error_data
79+
)
80+
81+
# Solidity 0.8.4 introduced custom error messages that allow args to
82+
# be passed in (or not). See:
83+
# https://blog.soliditylang.org/2021/04/21/custom-errors/
84+
elif (len(response_error_data) >= 10):
85+
# Raise with data as both the message and the data for backwards
86+
# compatibility and so that data can be accessed via 'data' attribute
87+
# on the ContractCustomError exception
88+
raise ContractCustomError(response_error_data, data=response_error_data)
89+
90+
91+
def raise_contract_logic_error_on_revert(response: RPCResponse) -> RPCResponse:
92+
"""
93+
Revert responses contain an error with the following optional attributes:
94+
`code` - in this context, used for an unknown edge case when code = '3'
95+
`message` - error message is passed to the raised exception
96+
`data` - response error details (str, dict, None)
97+
98+
See also https://solidity.readthedocs.io/en/v0.6.3/control-structures.html#revert
99+
"""
100+
error = response.get("error")
101+
if error is None or isinstance(error, str):
102+
raise ValueError(error)
103+
104+
message = error.get("message")
105+
message_present = message is not None and message != ""
106+
data = error.get("data")
107+
108+
if data is None:
109+
if message_present:
110+
raise ContractLogicError(message, data=MISSING_DATA)
111+
elif not message_present:
112+
raise ContractLogicError("execution reverted", data=MISSING_DATA)
113+
elif isinstance(data, dict) and message_present:
114+
raise ContractLogicError(f"execution reverted: {message}", data=data)
115+
elif isinstance(data, str):
116+
_raise_contract_error(data)
117+
118+
if message_present:
119+
# Geth Revert with error message and code 3 case:
120+
if error.get("code") == 3:
121+
raise ContractLogicError(message, data=data)
122+
# Geth Revert without error message case:
123+
elif "execution reverted" in message:
124+
raise ContractLogicError("execution reverted", data=data)
125+
126+
return response
127+
128+
129+
def patch_error_formatters():
130+
import web3._utils.error_formatters_utils as old_error_formatters_utils
131+
from web3._utils.method_formatters import ERROR_FORMATTERS
132+
from web3._utils.rpc_abi import RPC
133+
134+
old_error_formatters_utils.raise_contract_logic_error_on_revert = raise_contract_logic_error_on_revert
135+
ERROR_FORMATTERS[RPC.eth_estimateGas] = raise_contract_logic_error_on_revert
136+
ERROR_FORMATTERS[RPC.eth_call] = raise_contract_logic_error_on_revert

0 commit comments

Comments
 (0)