Skip to content

Commit

Permalink
WIP: parse revert reason
Browse files Browse the repository at this point in the history
Solidity allows messages in `require` statements which can help a lot
when debugging. This commit parses the revert reasons from failing
`call`s and raises them as `SolidityError` exceptions.

Closes #941.
  • Loading branch information
karlb committed Feb 24, 2020
1 parent fc997a3 commit 0a74c2c
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 0 deletions.
8 changes: 8 additions & 0 deletions web3/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,11 @@ class InvalidEventABI(ValueError):
Raised when the event ABI is invalid.
"""
pass


class SolidityError(ValueError):
# Inherits from ValueError for backwards compatibility
"""
Raised on a solidity require/revert
"""
pass
30 changes: 30 additions & 0 deletions web3/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
from web3.datastructures import (
NamedElementOnion,
)
from web3.exceptions import (
SolidityError,
)
from web3.middleware import (
abi_middleware,
attrdict_middleware,
Expand Down Expand Up @@ -63,6 +66,30 @@ def apply_error_formatters(
return response


def get_revert_reason(response: RPCResponse) -> str:
"""
Parse revert reason from response, return None if no revert happened.
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 strong (padded, use string length from above to get meaningful part)
"""
assert 'error' in response
if not isinstance(response['error'], dict):
return None
data = response['error'].get('data', '')
prefix = 'Reverted 0x08c379a00000000000000000000000000000000000000000000000000000000000000020'
if not data.startswith(prefix):
return None

reason_length = int(data[len(prefix):len(prefix) + 64], 16)
reason = data[len(prefix) + 64:len(prefix) + 64 + reason_length]
return reason


class RequestManager:
logger = logging.getLogger("web3.RequestManager")

Expand Down Expand Up @@ -150,6 +177,9 @@ def request_blocking(

if "error" in response:
apply_error_formatters(error_formatters, response)
revert_reason = get_revert_reason(response)
if revert_reason:
raise SolidityError(revert_reason)
raise ValueError(response["error"])

return response['result']
Expand Down
1 change: 1 addition & 0 deletions web3/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class EventData(TypedDict):
class RPCError(TypedDict):
code: int
message: str
data: Optional[str]


class RPCResponse(TypedDict, total=False):
Expand Down

0 comments on commit 0a74c2c

Please sign in to comment.