Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Handle 0.7 breaking changes and updates [APE-1559] #76

Merged
merged 10 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .mdformat.toml

This file was deleted.

4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ repos:
- id: isort

- repo: https://github.com/psf/black
rev: 23.10.1
rev: 23.12.0
hooks:
- id: black
name: black
Expand All @@ -21,7 +21,7 @@ repos:
- id: flake8

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.6.1
rev: v1.7.1
hooks:
- id: mypy
additional_dependencies: [types-PyYAML, types-requests, types-setuptools, pydantic]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ This network provider takes additional Foundry-specific configuration options. T

```yaml
foundry:
port: 8555
host: https://127.0.0.1:8555
```

To select a random port, use a value of "auto":

```yaml
foundry:
port: auto
host: auto
```

This is useful for multiprocessing and starting up multiple providers.
Expand Down
81 changes: 32 additions & 49 deletions ape_foundry/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from subprocess import PIPE, call
from typing import Any, Dict, Iterator, List, Literal, Optional, Tuple, Union, cast

from ape._pydantic_compat import root_validator
from ape.api import (
BlockAPI,
ForkedNetworkAPI,
Expand All @@ -17,7 +16,6 @@
SubprocessProvider,
TestProviderAPI,
TransactionAPI,
Web3Provider,
)
from ape.exceptions import (
APINotImplementedError,
Expand All @@ -38,13 +36,16 @@
TraceFrame,
)
from ape.utils import cached_property
from ape_test import Config as TestConfig
from ape_ethereum.provider import Web3Provider
from ape_test import ApeTestConfig
from eth_pydantic_types import HashBytes32, HexBytes
from eth_typing import HexStr
from eth_utils import add_0x_prefix, is_0x_prefixed, is_hex, to_hex
from ethpm_types import HexBytes
from evm_trace import CallType, ParityTraceList
from evm_trace import TraceFrame as EvmTraceFrame
from evm_trace import get_calltree_from_geth_trace, get_calltree_from_parity_trace
from pydantic import model_validator
from pydantic_settings import SettingsConfigDict
from web3 import HTTPProvider, Web3
from web3.exceptions import ContractCustomError
from web3.exceptions import ContractLogicError as Web3ContractLogicError
Expand All @@ -58,7 +59,6 @@
from ape_foundry.constants import EVM_VERSION_BY_NETWORK

from .exceptions import FoundryNotInstalledError, FoundryProviderError, FoundrySubprocessError
from .utils import to_bytes32

EPHEMERAL_PORTS_START = 49152
EPHEMERAL_PORTS_END = 60999
Expand All @@ -73,9 +73,6 @@ class FoundryForkConfig(PluginConfig):


class FoundryNetworkConfig(PluginConfig):
port: Optional[Union[int, Literal["auto"]]] = DEFAULT_PORT
"""Deprecated. Use ``host`` config."""

host: Optional[Union[str, Literal["auto"]]] = None
"""The host address or ``"auto"`` to use localhost with a random port (with attempts)."""

Expand Down Expand Up @@ -109,9 +106,7 @@ class FoundryNetworkConfig(PluginConfig):
Set a block time to allow mining to happen on an interval
rather than only when a new transaction is submitted.
"""

class Config:
extra = "allow"
model_config = SettingsConfigDict(extra="allow")


def _call(*args):
Expand Down Expand Up @@ -240,22 +235,13 @@ def is_connected(self) -> bool:
return self._web3 is not None

@cached_property
def _test_config(self) -> TestConfig:
return cast(TestConfig, self.config_manager.get_config("test"))
def _test_config(self) -> ApeTestConfig:
return cast(ApeTestConfig, self.config_manager.get_config("test"))

@property
def auto_mine(self) -> bool:
return self._make_request("anvil_getAutomine", [])

@property
def gas_price(self) -> int:
# TODO: Remove this once Ape > 0.6.13
result = super().gas_price
if isinstance(result, str) and is_0x_prefixed(result):
return int(result, 16)

return result

@property
def settings(self) -> FoundryNetworkConfig:
return cast(FoundryNetworkConfig, super().settings)
Expand All @@ -274,24 +260,6 @@ def connect(self):
**NOTE**: Must set port before calling 'super().connect()'.
"""

warning = "`port` setting is deprecated. Please use `host` key that includes the port."

if self.settings.port != DEFAULT_PORT and self.settings.host is not None:
raise FoundryProviderError(
"Cannot use deprecated `port` field with `host`. "
"Place `port` at end of `host` instead."
)

elif self.settings.port != DEFAULT_PORT:
# We only get here if the user configured a port without a host,
# the old way of doing it. TODO: Can remove after 0.7.
logger.warning(warning)
if self.settings.port not in (None, "auto"):
self._host = f"http://127.0.0.1:{self.settings.port}"
else:
# This will trigger selecting a random port on localhost and trying.
self._host = "auto"

if "APE_FOUNDRY_HOST" in os.environ:
self._host = os.environ["APE_FOUNDRY_HOST"]

Expand Down Expand Up @@ -455,7 +423,7 @@ def build_command(self) -> List[str]:
"--accounts",
f"{self.number_of_accounts}",
"--derivation-path",
"m/44'/60'/0'",
f"{self.test_config.hd_path}",
"--steps-tracing",
"--block-base-fee-per-gas",
f"{self.settings.base_fee}",
Expand Down Expand Up @@ -532,7 +500,7 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
if original_code:
self.set_code(sender, "")

txn_dict = txn.dict()
txn_dict = txn.model_dump(mode="json", by_alias=True)
if isinstance(txn_dict.get("type"), int):
txn_dict["type"] = HexBytes(txn_dict["type"]).hex()

Expand Down Expand Up @@ -576,7 +544,7 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
)

if receipt.failed:
txn_dict = receipt.transaction.dict()
txn_dict = receipt.transaction.model_dump(mode="json", by_alias=True)
if isinstance(txn_dict.get("type"), int):
txn_dict["type"] = HexBytes(txn_dict["type"]).hex()

Expand All @@ -597,7 +565,17 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
self.chain_manager.history.append(receipt)
return receipt

def send_call(self, txn: TransactionAPI, **kwargs: Any) -> bytes:
def send_call(
self,
txn: TransactionAPI,
block_id: Optional[BlockID] = None,
state: Optional[Dict] = None,
**kwargs,
) -> HexBytes:
if block_id is not None:
kwargs["block_identifier"] = block_id
if state is not None:
kwargs["state_override"] = state
skip_trace = kwargs.pop("skip_trace", False)
arguments = self._prepare_call(txn, **kwargs)

Expand Down Expand Up @@ -684,7 +662,7 @@ def _trace_call(self, arguments: List[Any]) -> Tuple[Dict, Iterator[EvmTraceFram
trace_data = result.get("structLogs", [])
return result, (EvmTraceFrame(**f) for f in trace_data)

def get_balance(self, address: str) -> int:
def get_balance(self, address: AddressType, block_id: Optional[BlockID] = None) -> int:
if hasattr(address, "address"):
address = address.address

Expand All @@ -708,7 +686,7 @@ def _get_transaction_trace(self, txn_hash: str) -> Iterator[EvmTraceFrame]:

def get_call_tree(self, txn_hash: str) -> CallTreeNode:
raw_trace_list = self._make_request("trace_transaction", [txn_hash])
trace_list = ParityTraceList.parse_obj(raw_trace_list)
trace_list = ParityTraceList.model_validate(raw_trace_list)

if not trace_list:
raise FoundryProviderError(f"No trace found for transaction '{txn_hash}'")
Expand Down Expand Up @@ -805,10 +783,14 @@ def set_code(self, address: AddressType, code: ContractCode) -> bool:
def set_storage(self, address: AddressType, slot: int, value: HexBytes):
self._make_request(
"anvil_setStorageAt",
[address, to_bytes32(slot).hex(), to_bytes32(value).hex()],
[
address,
HashBytes32.__eth_pydantic_validate__(slot).hex(),
HashBytes32.__eth_pydantic_validate__(value).hex(),
],
)

def _eth_call(self, arguments: List) -> bytes:
def _eth_call(self, arguments: List) -> HexBytes:
# Override from Web3Provider because foundry is pickier.

txn_dict = copy(arguments[0])
Expand Down Expand Up @@ -859,7 +841,8 @@ class FoundryForkProvider(FoundryProvider):
to use as your archive node.
"""

@root_validator()
@model_validator(mode="before")
@classmethod
def set_upstream_provider(cls, value):
network = value["network"]
adhoc_settings = value.get("provider_settings", {}).get("fork", {})
Expand Down
24 changes: 0 additions & 24 deletions ape_foundry/utils.py

This file was deleted.

3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ force_grid_wrap = 0
include_trailing_comma = true
multi_line_output = 3
use_parentheses = true

[tool.mdformat]
number = true
23 changes: 14 additions & 9 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,26 @@
extras_require = {
"test": [ # `test` GitHub Action jobs uses this
"pytest>=6.0", # Core testing package
"pytest-xdist", # multi-process runner
"pytest-xdist", # Multi-process runner
"pytest-cov", # Coverage analyzer plugin
"hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer
"ape-alchemy", # For running fork tests
"ape-polygon", # For running polygon fork tests
],
"lint": [
"black>=23.10.1,<24", # auto-formatter and linter
"mypy>=1.6.1,<2", # Static type analyzer
"types-requests", # Needed due to mypy typeshed
"types-setuptools", # Needed due to mypy typeshed
"types-PyYAML", # Needed due to mypy typeshed
"flake8>=6.0.1,<7", # Style linter
"black>=23.12.0,<24", # Auto-formatter and linter
"mypy>=1.7.1,<2", # Static type analyzer
"types-setuptools", # Needed for mypy type shed
"types-requests", # Needed for mypy type shed
"types-PyYAML", # Needed for mypy type shed
"flake8>=6.1.0,<7", # Style linter
"flake8-breakpoint>=1.1.0,<2", # Detect breakpoints left in code
"flake8-print>=5.0.0,<6", # Detect print statements left in code
"isort>=5.10.1,<6", # Import sorting linter
"mdformat>=0.7.17", # Auto-formatter for markdown
"mdformat-gfm>=0.3.5", # Needed for formatting GitHub-flavored markdown
"mdformat-frontmatter>=0.4.1", # Needed for frontmatters-style headers in issue templates
"mdformat-pyproject>=0.0.1", # Allows configuring in pyproject.toml
],
"doc": [
"Sphinx>=6.1.3,<7", # Documentation generator
Expand Down Expand Up @@ -69,11 +72,13 @@
url="https://github.com/ApeWorX/ape-foundry",
include_package_data=True,
install_requires=[
"eth-ape>=0.6.24,<0.7",
"eth-ape>=0.7.0,<0.8",
"ethpm-types", # Use same version as eth-ape
"eth-pydantic-types", # Use same version as eth-ape
"evm-trace", # Use same version as ape
"hexbytes", # Use same version as ape
"web3", # Use same version as ape
"yarl", # Use same version as ape
"hexbytes", # Use same version as ape
],
python_requires=">=3.8,<4",
extras_require=extras_require,
Expand Down
3 changes: 2 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ def networks():
@pytest.fixture
def get_contract_type():
def fn(name: str) -> ContractType:
return ContractType.parse_file(LOCAL_CONTRACTS_PATH / f"{name}.json")
json_path = LOCAL_CONTRACTS_PATH / f"{name}.json"
return ContractType.model_validate_json(json_path.read_text())

return fn

Expand Down
2 changes: 1 addition & 1 deletion tests/expected_traces.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
LOCAL_TRACE = r"""
Call trace for '0x([A-Fa-f0-9]{64})'
tx\.origin=0x[a-fA-F0-9]{40}
ContractA\.methodWithoutArguments\(\) -> 0x00\.\.5174 \[\d+ gas\]
ContractA\.methodWithoutArguments\(\) -> 0x00\.\.93bc \[\d+ gas\]
├── SYMBOL\.supercluster\(x=234444\) -> \[
│ \[23523523235235, 11111111111, 234444\],
│ \[
Expand Down
6 changes: 3 additions & 3 deletions tests/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def test_get_call_tree(connected_provider, sender, receiver):
call_tree = connected_provider.get_call_tree(transfer.txn_hash)
assert isinstance(call_tree, CallTreeNode)
assert call_tree.call_type == CallType.CALL.value
assert repr(call_tree) == "0xc89D42189f0450C2b2c3c61f58Ec5d628176A1E7.0x()"
assert repr(call_tree) == "0x70997970C51812dc3A010C7d01b50e0d17dc79C8.0x()"


def test_request_timeout(connected_provider, config):
Expand Down Expand Up @@ -190,9 +190,9 @@ def test_set_code(connected_provider, contract_container, owner):

def test_set_storage(connected_provider, contract_container, owner):
contract = contract_container.deploy(sender=owner)
assert to_int(connected_provider.get_storage_at(contract.address, "0x2b5e3af16b1880000")) == 0
assert to_int(connected_provider.get_storage(contract.address, "0x2b5e3af16b1880000")) == 0
connected_provider.set_storage(contract.address, "0x2b5e3af16b1880000", "0x1")
assert to_int(connected_provider.get_storage_at(contract.address, "0x2b5e3af16b1880000")) == 1
assert to_int(connected_provider.get_storage(contract.address, "0x2b5e3af16b1880000")) == 1


def test_return_value(connected_provider, contract_instance, owner):
Expand Down
Loading