Skip to content

Commit

Permalink
Merge branch 'ApeWorX:main' into feat/ape-remove-option-for-ape-pm
Browse files Browse the repository at this point in the history
  • Loading branch information
Aviksaikat authored Aug 25, 2023
2 parents fb23a3a + e48fece commit 218ca4a
Show file tree
Hide file tree
Showing 18 changed files with 174 additions and 327 deletions.
5 changes: 4 additions & 1 deletion src/ape/api/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,10 @@ def _create_contract_from_call(
if "address" not in data:
return None, calldata

addr = data["address"]
# NOTE: Handling when providers give us odd address values.
raw_addr = HexBytes(data["address"]).hex().replace("0x", "")
zeroes = max(40 - len(raw_addr), 0) * "0"
addr = f"0x{zeroes}{raw_addr}"

try:
address = self.provider.network.ecosystem.decode_address(addr)
Expand Down
64 changes: 51 additions & 13 deletions src/ape/api/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
ContractLog,
LogFilter,
SnapshotID,
SourceTraceback,
TraceFrame,
)
from ape.utils import (
Expand Down Expand Up @@ -169,6 +170,20 @@ def disconnect(self):
Disconnect from a provider, such as tear-down a process or quit an HTTP session.
"""

@property
def http_uri(self) -> Optional[str]:
"""
Return the raw HTTP/HTTPS URI to connect to this provider, if supported.
"""
return None

@property
def ws_uri(self) -> Optional[str]:
"""
Return the raw WS/WSS URI to connect to this provider, if supported.
"""
return None

@abstractmethod
def update_settings(self, new_settings: dict):
"""
Expand Down Expand Up @@ -688,16 +703,9 @@ def _increment_call_func_coverage_hit_count(self, txn: TransactionAPI):
):
return

cov_data = self._test_runner.coverage_tracker.data
if not cov_data:
return

contract_type = self.chain_manager.contracts.get(txn.receiver)
if not contract_type:
return

contract_src = self.project_manager._create_contract_source(contract_type)
if not contract_src:
if not (contract_type := self.chain_manager.contracts.get(txn.receiver)) or not (
contract_src := self.project_manager._create_contract_source(contract_type)
):
return

method_id = txn.data[:4]
Expand Down Expand Up @@ -726,6 +734,32 @@ def web3(self) -> Web3:

return self._web3

@property
def http_uri(self) -> Optional[str]:
if (
hasattr(self.web3.provider, "endpoint_uri")
and isinstance(self.web3.provider.endpoint_uri, str)
and self.web3.provider.endpoint_uri.startswith("http")
):
return self.web3.provider.endpoint_uri

elif hasattr(self, "uri"):
# NOTE: Some providers define this
return self.uri

return None

@property
def ws_uri(self) -> Optional[str]:
if (
hasattr(self.web3.provider, "endpoint_uri")
and isinstance(self.web3.provider.endpoint_uri, str)
and self.web3.provider.endpoint_uri.startswith("ws")
):
return self.web3.provider.endpoint_uri

return None

@property
def client_version(self) -> str:
if not self._web3:
Expand Down Expand Up @@ -1531,8 +1565,7 @@ def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMa
if not isinstance(err_data, dict):
return VirtualMachineError(base_err=exception, **kwargs)

err_msg = err_data.get("message")
if not err_msg:
if not (err_msg := err_data.get("message")):
return VirtualMachineError(base_err=exception, **kwargs)

if txn is not None and "nonce too low" in str(err_msg):
Expand All @@ -1553,9 +1586,14 @@ def _handle_execution_reverted(
txn: Optional[TransactionAPI] = None,
trace: Optional[Iterator[TraceFrame]] = None,
contract_address: Optional[AddressType] = None,
source_traceback: Optional[SourceTraceback] = None,
) -> ContractLogicError:
message = str(exception).split(":")[-1].strip()
params: Dict = {"trace": trace, "contract_address": contract_address}
params: Dict = {
"trace": trace,
"contract_address": contract_address,
"source_traceback": source_traceback,
}
no_reason = message == "execution reverted"

if isinstance(exception, Web3ContractLogicError) and no_reason:
Expand Down
128 changes: 11 additions & 117 deletions src/ape/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
import tempfile
import time
import traceback
from collections import deque
from functools import cached_property
from inspect import getframeinfo, stack
from pathlib import Path
from types import CodeType, TracebackType
from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Union

import click
from eth_utils import humanize_hash
from ethpm_types import ContractType
from ethpm_types.abi import ConstructorABI, ErrorABI, MethodABI
from rich import print as rich_print

Expand Down Expand Up @@ -180,12 +177,10 @@ def _set_tb(self):
if not self.source_traceback and self.txn:
self.source_traceback = _get_ape_traceback(self.txn)

src_tb = self.source_traceback
if src_tb is not None and self.txn is not None:
if (src_tb := self.source_traceback) and self.txn is not None:
# Create a custom Pythonic traceback using lines from the sources
# found from analyzing the trace of the transaction.
py_tb = _get_custom_python_traceback(self, self.txn, src_tb)
if py_tb:
if py_tb := _get_custom_python_traceback(self, self.txn, src_tb):
self.__traceback__ = py_tb


Expand Down Expand Up @@ -213,12 +208,6 @@ def __init__(
self.txn = txn
self.trace = trace
self.contract_address = contract_address
if revert_message is None:
try:
# Attempt to use dev message as main exception message.
revert_message = self.dev_message
except Exception:
pass

super().__init__(
base_err=base_err,
Expand All @@ -229,11 +218,18 @@ def __init__(
txn=txn,
)

if revert_message is None and source_traceback is not None and (dev := self.dev_message):
try:
# Attempt to use dev message as main exception message.
self.message = dev
except Exception:
pass

@property
def revert_message(self):
return self.message

@cached_property
@property
def dev_message(self) -> Optional[str]:
"""
The dev-string message of the exception.
Expand All @@ -242,109 +238,7 @@ def dev_message(self) -> Optional[str]:
``ValueError``: When unable to get dev message.
"""

trace = self._get_trace()
if len(trace) == 0:
raise ValueError("Missing trace.")

if address := self.address:
try:
contract_type = trace[-1].chain_manager.contracts[address]
except Exception as err:
raise ValueError(
f"Could not fetch contract at {address} to check dev message."
) from err

else:
raise ValueError("Could not fetch contract information to check dev message.")

if contract_type.pcmap is None:
raise ValueError("Compiler does not support source code mapping.")

pc = None
pcmap = contract_type.pcmap.parse()

# To find a suitable line for inspecting dev messages, we must start at the revert and work
# our way backwards. If the last frame's PC is in the PC map, the offending line is very
# likely a 'raise' statement.
if trace[-1].pc in pcmap:
pc = trace[-1].pc

# Otherwise we must traverse the trace backwards until we find our first suitable candidate.
else:
last_depth = 1
while len(trace) > 0:
frame = trace.pop()
if frame.depth > last_depth:
# Call was made, get the new PCMap.
contract_type = self._find_next_contract(trace)
if not contract_type.pcmap:
raise ValueError("Compiler does not support source code mapping.")

pcmap = contract_type.pcmap.parse()
last_depth += 1

if frame.pc in pcmap:
pc = frame.pc
break

# We were unable to find a suitable PC that matched the compiler's map.
if pc is None:
return None

offending_source = pcmap[pc]
if offending_source is None:
return None

dev_messages = contract_type.dev_messages or {}
if offending_source.line_start is None:
# Check for a `dev` field in PCMap.
return None if offending_source.dev is None else offending_source.dev

elif offending_source.line_start in dev_messages:
return dev_messages[offending_source.line_start]

elif offending_source.dev is not None:
return offending_source.dev

# Dev message is neither found from the compiler or from a dev-comment.
return None

def _get_trace(self) -> deque:
trace = None
if self.trace is None and self.txn is not None:
try:
trace = deque(self.txn.trace)
except APINotImplementedError as err:
raise ValueError(
"Cannot check dev message; provider must support transaction tracing."
) from err

except (ProviderError, SignatureError) as err:
raise ValueError("Cannot fetch transaction trace.") from err

elif self.trace is not None:
trace = deque(self.trace)

if not trace:
raise ValueError("Cannot fetch transaction trace.")

return trace

def _find_next_contract(self, trace: deque) -> ContractType:
msg = "Could not fetch contract at '{address}' to check dev message."
idx = len(trace) - 1
while idx >= 0:
frame = trace[idx]
if frame.contract_address:
ct = frame.chain_manager.contracts.get(frame.contract_address)
if not ct:
raise ValueError(msg.format(address=frame.contract_address))

return ct

idx -= 1

raise ValueError(msg.format(address=frame.contract_address))
return self.source_traceback.revert_type if self.source_traceback else None

@classmethod
def from_error(cls, err: Exception):
Expand Down
Loading

0 comments on commit 218ca4a

Please sign in to comment.