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

fix: all network related fixes [APE-1436] #4

Merged
merged 1 commit into from
Oct 4, 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
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.2.0
rev: v4.4.0
hooks:
- id: check-yaml

Expand All @@ -10,24 +10,24 @@ repos:
- id: isort

- repo: https://github.com/psf/black
rev: 23.3.0
rev: 23.9.1
hooks:
- id: black
name: black

- repo: https://github.com/pycqa/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991
rev: v1.5.1
hooks:
- id: mypy
additional_dependencies: [types-setuptools, pydantic]

- repo: https://github.com/executablebooks/mdformat
rev: 0.7.14
rev: 0.7.17
hooks:
- id: mdformat
additional_dependencies: [mdformat-gfm, mdformat-frontmatter]
Expand Down
95 changes: 51 additions & 44 deletions ape_base/ecosystem.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
from typing import Optional, Type, Union, cast
from typing import Dict, Optional, Type, cast

from ape.api import TransactionAPI
from ape.api.config import PluginConfig
from ape.api.networks import LOCAL_NETWORK_NAME
from ape.exceptions import ApeException
from ape.types import TransactionSignature
from ape.utils import DEFAULT_LOCAL_TRANSACTION_ACCEPTANCE_TIMEOUT
from ape_ethereum.ecosystem import Ethereum, NetworkConfig
from ape_ethereum.transactions import DynamicFeeTransaction, StaticFeeTransaction, TransactionType
from eth_typing import HexStr
from eth_utils import add_0x_prefix

NETWORKS = {
# chain_id, network_id
"mainnet": (8453, 8453),
"goerli": (84531, 84531),
}


class ApeBaseError(ApeException):
"""
Raised in the ape-base plugin.
"""
_SECOND_STATIC_TYPE = 126
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bilbeyt What is this? Im trying to find more info



def _create_network_config(
required_confirmations: int = 1, block_time: int = 2, **kwargs
) -> NetworkConfig:
return NetworkConfig(
required_confirmations=required_confirmations, block_time=block_time, **kwargs
block_time=block_time,
default_transaction_type=TransactionType.STATIC,
required_confirmations=required_confirmations,
**kwargs,
)


def _create_local_config(default_provider: Optional[str] = None) -> NetworkConfig:
def _create_local_config(default_provider: Optional[str] = None, **kwargs) -> NetworkConfig:
return _create_network_config(
required_confirmations=0, block_time=0, default_provider=default_provider
block_time=0,
default_provider=default_provider,
gas_limit="max",
required_confirmations=0,
transaction_acceptance_timeout=DEFAULT_LOCAL_TRANSACTION_ACCEPTANCE_TIMEOUT,
**kwargs,
)


Expand All @@ -55,14 +56,35 @@ def create_transaction(self, **kwargs) -> TransactionAPI:
"""
Returns a transaction using the given constructor kwargs.
Overridden because does not support

**kwargs: Kwargs for the transaction class.

Returns:
:class:`~ape.api.transactions.TransactionAPI`
"""

transaction_type = _get_transaction_type(kwargs.get("type"))
kwargs["type"] = transaction_type.value
txn_class = _get_transaction_cls(transaction_type)
transaction_types: Dict[int, Type[TransactionAPI]] = {
TransactionType.STATIC.value: StaticFeeTransaction,
TransactionType.DYNAMIC.value: DynamicFeeTransaction,
_SECOND_STATIC_TYPE: StaticFeeTransaction,
}

if "type" in kwargs:
if kwargs["type"] is None:
# The Default is pre-EIP-1559.
version = self.default_transaction_type.value
elif not isinstance(kwargs["type"], int):
version = self.conversion_manager.convert(kwargs["type"], int)
else:
version = kwargs["type"]

elif "gas_price" in kwargs:
version = TransactionType.STATIC.value
else:
version = self.default_transaction_type.value

kwargs["type"] = version
txn_class = transaction_types[version]

if "required_confirmations" not in kwargs or kwargs["required_confirmations"] is None:
# Attempt to use default required-confirmations from `ape-config.yaml`.
Expand All @@ -76,8 +98,11 @@ def create_transaction(self, **kwargs) -> TransactionAPI:
if isinstance(kwargs.get("chainId"), str):
kwargs["chainId"] = int(kwargs["chainId"], 16)

if "hash" in kwargs:
kwargs["data"] = kwargs.pop("hash")
elif "chainId" not in kwargs and self.network_manager.active_provider is not None:
kwargs["chainId"] = self.provider.chain_id

if "input" in kwargs:
kwargs["data"] = kwargs.pop("input")

if all(field in kwargs for field in ("v", "r", "s")):
kwargs["signature"] = TransactionSignature(
Expand All @@ -86,32 +111,14 @@ def create_transaction(self, **kwargs) -> TransactionAPI:
s=bytes(kwargs["s"]),
)

return txn_class.parse_obj(kwargs)


def _get_transaction_type(_type: Optional[Union[int, str, bytes]]) -> TransactionType:
if not _type or _type == 126:
return TransactionType.STATIC

if _type is None:
_type = TransactionType.STATIC.value
elif isinstance(_type, int):
_type = f"0{_type}"
elif isinstance(_type, bytes):
_type = _type.hex()

suffix = _type.replace("0x", "")
if len(suffix) == 1:
_type = f"{_type.rstrip(suffix)}0{suffix}"
return TransactionType(int(add_0x_prefix(HexStr(_type)), 16))
if "max_priority_fee_per_gas" in kwargs:
kwargs["max_priority_fee"] = kwargs.pop("max_priority_fee_per_gas")
if "max_fee_per_gas" in kwargs:
kwargs["max_fee"] = kwargs.pop("max_fee_per_gas")

kwargs["gas"] = kwargs.pop("gas_limit", kwargs.get("gas"))

def _get_transaction_cls(transaction_type: TransactionType) -> Type[TransactionAPI]:
transaction_types = {
TransactionType.STATIC: StaticFeeTransaction,
TransactionType.DYNAMIC: DynamicFeeTransaction,
}
if transaction_type not in transaction_types:
raise ApeBaseError(f"Transaction type '{transaction_type}' not supported.")
if "value" in kwargs and not isinstance(kwargs["value"], int):
kwargs["value"] = self.conversion_manager.convert(kwargs["value"], int)

return transaction_types[transaction_type]
return txn_class(**kwargs)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=51.1.1", "wheel", "setuptools_scm[toml]>=5.0"]
requires = ["setuptools>=51.1.1", "wheel", "setuptools_scm[toml]>=5.0,<8"]

[tool.mypy]
exclude = "build/"
Expand Down
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
"hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer
],
"lint": [
"black>=23.3.0,<24", # Auto-formatter and linter
"mypy>=0.991,<1", # Static type analyzer
"black>=23.9.1,<24", # Auto-formatter and linter
"mypy>=1.5.1,<2", # Static type analyzer
"types-setuptools", # Needed for mypy type shed
"flake8>=6.0.0,<7", # Style linter
"flake8>=6.1.0,<7", # Style linter
"isort>=5.10.1,<6", # Import sorting linter
"mdformat>=0.7.16", # Auto-formatter for markdown
"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
],
Expand Down
14 changes: 8 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import ape
import pytest
from ape._cli import cli as ape_cli
from click.testing import CliRunner


@pytest.fixture
Expand All @@ -15,10 +13,14 @@ def accounts():


@pytest.fixture
def runner():
return CliRunner()
def base(networks):
return networks.base


@pytest.fixture
def cli():
return ape_cli
def eth_tester_provider():
if not ape.networks.active_provider or ape.networks.provider.name != "test":
with ape.networks.base.local.use_provider("test") as provider:
yield provider
else:
yield ape.networks.provider
36 changes: 36 additions & 0 deletions tests/test_ecosystem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pytest
from ape_ethereum.transactions import TransactionType
from ethpm_types import MethodABI

from ape_base.ecosystem import _SECOND_STATIC_TYPE


def test_gas_limit(base, eth_tester_provider):
assert base.config.local.gas_limit == "max"


# NOTE: None because we want to show the default is STATIC
@pytest.mark.parametrize("tx_type", (None, 0, "0x0"))
def test_create_transaction(base, tx_type, eth_tester_provider):
tx = base.create_transaction(type=tx_type)
assert tx.type == TransactionType.STATIC.value
assert tx.gas_limit == eth_tester_provider.max_gas


@pytest.mark.parametrize(
"tx_type",
(TransactionType.STATIC.value, TransactionType.DYNAMIC.value, _SECOND_STATIC_TYPE),
)
def test_encode_transaction(tx_type, base, eth_tester_provider):
abi = MethodABI.parse_obj(
{
"type": "function",
"name": "fooAndBar",
"stateMutability": "nonpayable",
"inputs": [],
"outputs": [],
}
)
address = "0x274b028b03A250cA03644E6c578D81f019eE1323"
actual = base.encode_transaction(address, abi, sender=address, type=tx_type)
assert actual.gas_limit == eth_tester_provider.max_gas
53 changes: 53 additions & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import pytest
from ape._cli import cli as ape_cli
from click.testing import CliRunner

EXPECTED_OUTPUT = """
base
├── mainnet
│ └── geth (default)
├── goerli
│ └── geth (default)
└── local (default)
└── test (default)
""".strip()


@pytest.fixture
def runner():
return CliRunner()


@pytest.fixture
def cli():
return ape_cli


def assert_rich_text(actual: str, expected: str):
"""
The output from `rich` causes a bunch of extra spaces to
appear at the end of each line. For easier testing, we remove those here.
Also, we ignore whether the expected line is at the end or in the middle
of the output to handle cases when the test-runner has additional plugins
installed.
"""
expected_lines = [
x.replace("└", "").replace("├", "").replace("│", "").strip()
for x in expected.strip().split("\n")
]
actual_lines = [
x.replace("└", "").replace("├", "").replace("│", "").strip()
for x in actual.strip().split("\n")
]

for expected_line in expected_lines:
assert expected_line in actual_lines


def test_networks(runner, cli, base):
# Do this in case local env changed it.
base.mainnet.set_default_provider("geth")
base.goerli.set_default_provider("geth")

result = runner.invoke(cli, ["networks", "list"])
assert_rich_text(result.output, EXPECTED_OUTPUT)