Skip to content

Commit

Permalink
Parse revert reason when call fails
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 5135957
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 0 deletions.
1 change: 1 addition & 0 deletions newsfragments/941.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Raise `SolidityError` exceptions that contain the revert reason when a `call` fails.
39 changes: 39 additions & 0 deletions tests/core/manager/test_revert_reason.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from web3.manager import (
get_revert_reason,
)
from web3.types import (
RPCResponse,
)


def test_get_revert_reason() -> None:
response = RPCResponse({
'jsonrpc': '2.0',
'error': {
'code': -32015,
'message': 'VM execution error.',
'data': (
'Reverted '
'0x08c379a'
'00000000000000000000000000000000000000000000000000000000000000020'
'0000000000000000000000000000000000000000000000000000000000000016'
'6e6f7420616c6c6f77656420746f206d6f6e69746f7200000000000000000000'
),
},
'id': 2987
})
assert get_revert_reason(response) == 'not allowed to monitor'


def test_get_revert_reason_without_reason() -> None:
""" In this case, no reason string is given in the solidity code """
response = RPCResponse({
'jsonrpc': '2.0',
'error': {
'code': -32015,
'message': 'VM execution error.',
'data': 'Reverted 0x',
},
'id': 2987
})
assert get_revert_reason(response) is None
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
32 changes: 32 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,32 @@ 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', '')
# "Reverted", function selector and offset are always the same for revert errors
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 * 2]
return bytes.fromhex(reason).decode('utf8')


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

Expand Down Expand Up @@ -150,6 +179,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 5135957

Please sign in to comment.