Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey committed Jul 30, 2024
1 parent 42f0e50 commit 4b4b6b6
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 37 deletions.
11 changes: 11 additions & 0 deletions src/ape/api/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._raise_on_revert = raise_on_revert

@field_validator("nonce", mode="before")
@classmethod
def validate_nonce(cls, value):
if value is None or isinstance(value, int):
return value

elif isinstance(value, str) and value.startswith("0x"):
return to_int(hexstr=value)

return to_int(value)

@field_validator("gas_limit", mode="before")
@classmethod
def validate_gas_limit(cls, value):
Expand Down
2 changes: 1 addition & 1 deletion src/ape_ethereum/ecosystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ def create_transaction(self, **kwargs) -> TransactionAPI:
if "gas" not in tx_data:
tx_data["gas"] = None

return txn_class(**tx_data)
return txn_class.model_validate(tx_data)

def decode_logs(self, logs: Sequence[dict], *events: EventABI) -> Iterator["ContractLog"]:
if not logs:
Expand Down
39 changes: 18 additions & 21 deletions src/ape_ethereum/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def client_version(self) -> str:

@property
def base_fee(self) -> int:
latest_block_number = self._get_latest_block().number
latest_block_number = self._get_latest_block_rpc().get("number")
if latest_block_number is None:
# Possibly no blocks yet.
logger.debug("Latest block has no number. Using base fee of '0'.")
Expand Down Expand Up @@ -261,8 +261,7 @@ def _get_fee_history(self, block_number: int) -> FeeHistory:
raise APINotImplementedError(str(err)) from err

def _get_last_base_fee(self) -> int:
block = self._get_latest_block()
base_fee = getattr(block, "base_fee", None)
base_fee = self._get_latest_block_rpc().get("baseFeePerGas", None)
if base_fee is not None:
return base_fee

Expand All @@ -277,7 +276,7 @@ def is_connected(self) -> bool:

@property
def max_gas(self) -> int:
return self.make_request("eth_getBlockByNumber", ["latest"])["gasLimit"]
return int(self._get_latest_block_rpc()["gasLimit"], 16)

@cached_property
def supports_tracing(self) -> bool:
Expand Down Expand Up @@ -395,12 +394,14 @@ def get_block(self, block_id: BlockID) -> BlockAPI:

def _get_latest_block(self) -> BlockAPI:
# perf: By-pass as much as possible since this is a common action.
data = self.make_request("eth_getBlockByNumber", ["latest"])
data = self._get_latest_block_rpc()
return self.network.ecosystem.decode_block(data)

def _get_latest_block_rpc(self) -> dict:
return self.make_request("eth_getBlockByNumber", ["latest", False])

def get_nonce(self, address: AddressType, block_id: Optional[BlockID] = None) -> int:
arguments = [address] if block_id is None else [address, block_id] # type: ignore
return self.make_request("eth_getTransactionCount", arguments)
return self.web3.eth.get_transaction_count(address, block_identifier=block_id)

def get_balance(self, address: AddressType, block_id: Optional[BlockID] = None) -> int:
return self.web3.eth.get_balance(address, block_identifier=block_id)
Expand Down Expand Up @@ -577,20 +578,16 @@ def get_receipt(
)
hex_hash = HexBytes(txn_hash)

# perf: avoid the "wait" method when using dev networks.
if self.network.is_dev:
receipt_data = self.web3.eth.get_transaction_receipt(hex_hash)
else:
try:
receipt_data = self.web3.eth.wait_for_transaction_receipt(hex_hash, timeout=timeout)
except TimeExhausted as err:
msg_str = str(err)
if f"HexBytes('{txn_hash}')" in msg_str:
msg_str = msg_str.replace(f"HexBytes('{txn_hash}')", f"'{txn_hash}'")

raise TransactionNotFoundError(
transaction_hash=txn_hash, error_message=msg_str
) from err
try:
receipt_data = self.web3.eth.wait_for_transaction_receipt(hex_hash, timeout=timeout)
except TimeExhausted as err:
msg_str = str(err)
if f"HexBytes('{txn_hash}')" in msg_str:
msg_str = msg_str.replace(f"HexBytes('{txn_hash}')", f"'{txn_hash}'")

raise TransactionNotFoundError(
transaction_hash=txn_hash, error_message=msg_str
) from err

ecosystem_config = self.network.ecosystem_config.model_dump(by_alias=True)
network_config: dict = ecosystem_config.get(self.network.name, {})
Expand Down
26 changes: 19 additions & 7 deletions src/ape_test/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from ape.api import PluginConfig, ReceiptAPI, TestProviderAPI, TransactionAPI
from ape.exceptions import (
APINotImplementedError,
ContractLogicError,
ProviderError,
ProviderNotConnectedError,
Expand Down Expand Up @@ -90,6 +91,10 @@ def auto_mine(self, value: Any) -> None:
else:
raise TypeError("Expecting bool-value for auto_mine setter.")

@property
def max_gas(self) -> int:
return self.evm_backend.get_block_by_number("latest")["gas_limit"]

def connect(self):
if "tester" in self.__dict__:
del self.__dict__["tester"]
Expand Down Expand Up @@ -120,9 +125,7 @@ def estimate_gas_cost(
estimate_gas = self.web3.eth.estimate_gas

# NOTE: Using JSON mode since used as request data.
txn_dict = txn.model_dump(mode="json")

txn_dict.pop("gas", None)
txn_dict = txn.model_dump(by_alias=True, mode="json", exclude=("gas_limit", "chain_id"))
txn_data = cast(TxParams, txn_dict)

try:
Expand Down Expand Up @@ -235,7 +238,9 @@ def send_call(
def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
vm_err = None
try:
txn_hash = self.web3.eth.send_raw_transaction(txn.serialize_transaction()).hex()
txn_hash = self.tester.ethereum_tester.send_raw_transaction(
txn.serialize_transaction().hex()
)
except (ValidationError, TransactionFailed, Web3ContractLogicError) as err:
vm_err = self.get_virtual_machine_error(err, txn=txn)
if txn.raise_on_revert:
Expand All @@ -249,7 +254,7 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
required_confirmations=required_confirmations, error=vm_err, txn_hash=txn_hash
)
else:
receipt_data = self.make_request("eth_getTransactionReceipt", [txn_hash])
receipt_data = self.tester.ethereum_tester.get_transaction_receipt(txn_hash)
receipt = self._create_receipt(
error=vm_err,
txn_hash=txn_hash,
Expand Down Expand Up @@ -293,7 +298,7 @@ def snapshot(self) -> SnapshotID:

def restore(self, snapshot_id: SnapshotID):
if snapshot_id:
current_hash = self._get_latest_block().hash
current_hash = self._get_latest_block_rpc().get("hash")
if current_hash != snapshot_id:
try:
return self.evm_backend.revert_to_snapshot(snapshot_id)
Expand Down Expand Up @@ -337,7 +342,7 @@ def get_contract_logs(self, log_filter: LogFilter) -> Iterator[ContractLog]:
if log_filter.stop_block is None:
to_block = None
else:
latest_block = self._get_latest_block().number
latest_block = self._get_latest_block_rpc().get("number")
to_block = (
min(latest_block, log_filter.stop_block)
if latest_block is not None
Expand Down Expand Up @@ -369,6 +374,13 @@ def get_test_account(self, index: int) -> "TestAccountAPI":
def add_account(self, private_key: str):
self.evm_backend.add_account(private_key)

def _get_last_base_fee(self) -> int:
base_fee = self._get_latest_block_rpc().get("base_fee_per_gas", None)
if base_fee is not None:
return base_fee

raise APINotImplementedError("No base fee found in block.")

def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMachineError:
if isinstance(exception, ValidationError):
match = self._CANNOT_AFFORD_GAS_PATTERN.match(str(exception))
Expand Down
16 changes: 8 additions & 8 deletions tests/functional/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from eth_tester.exceptions import TransactionFailed # type: ignore
from eth_typing import HexStr
from eth_utils import ValidationError
from hexbytes import HexBytes
from requests import HTTPError
from web3.exceptions import ContractPanicError

Expand Down Expand Up @@ -298,17 +297,18 @@ def test_no_comma_in_rpc_url():


def test_send_transaction_when_no_error_and_receipt_fails(
mock_transaction, mock_web3, eth_tester_provider, owner, vyper_contract_instance
mocker, mock_transaction, eth_tester_provider, owner, vyper_contract_instance
):
start_web3 = eth_tester_provider._web3
eth_tester_provider._web3 = mock_web3
mock_eth_tester = mocker.MagicMock()
original_tester = eth_tester_provider.tester
eth_tester_provider.__dict__["tester"] = mock_eth_tester

try:
# NOTE: Value is meaningless.
tx_hash = HashBytes32.__eth_pydantic_validate__(123**36)

# Sending tx "works" meaning no vm error.
mock_web3.eth.send_raw_transaction.return_value = tx_hash
mock_eth_tester.ethereum_tester.send_raw_transaction.return_value = tx_hash

# Getting a receipt "works", but you get a failed one.
receipt_data = {
Expand All @@ -322,17 +322,17 @@ def test_send_transaction_when_no_error_and_receipt_fails(
"gasUsed": 123,
"gasLimit": 100,
}
mock_web3.eth.wait_for_transaction_receipt.return_value = receipt_data
mock_eth_tester.ethereum_tester.get_transaction_receipt.return_value = receipt_data

# Attempting to replay the tx does not produce any error.
mock_web3.eth.call.return_value = HexBytes("")
# mock_web3.eth.call.return_value = HexBytes("")

# Execute test.
with pytest.raises(TransactionError):
eth_tester_provider.send_transaction(mock_transaction)

finally:
eth_tester_provider._web3 = start_web3
eth_tester_provider.__dict__["tester"] = original_tester


def test_network_choice(eth_tester_provider):
Expand Down

0 comments on commit 4b4b6b6

Please sign in to comment.