diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2de45d1..9cc7305 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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 @@ -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] diff --git a/ape_bsc/__init__.py b/ape_bsc/__init__.py index e2a40d3..cc5dd79 100644 --- a/ape_bsc/__init__.py +++ b/ape_bsc/__init__.py @@ -21,11 +21,10 @@ def ecosystems(): def networks(): for network_name, network_params in NETWORKS.items(): yield "bsc", network_name, create_network_type(*network_params) + yield "bsc", f"{network_name}-fork", NetworkAPI # NOTE: This works for development providers, as they get chain_id from themselves yield "bsc", LOCAL_NETWORK_NAME, NetworkAPI - yield "bsc", "mainnet-fork", NetworkAPI - yield "bsc", "testnet-fork", NetworkAPI @plugins.register(plugins.ProviderPlugin) diff --git a/ape_bsc/ecosystem.py b/ape_bsc/ecosystem.py index a534fe9..1a7f149 100644 --- a/ape_bsc/ecosystem.py +++ b/ape_bsc/ecosystem.py @@ -1,14 +1,12 @@ -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_utils.hexadecimal import decode_hex NETWORKS = { # chain_id, network_id @@ -21,23 +19,29 @@ def _create_network_config( required_confirmations: int = 1, block_time: int = 3, **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, **kwargs) -> NetworkConfig: return _create_network_config( - required_confirmations=0, + block_time=0, default_provider=default_provider, - transaction_acceptance_timeout=DEFAULT_LOCAL_TRANSACTION_ACCEPTANCE_TIMEOUT, gas_limit="max", + required_confirmations=0, + transaction_acceptance_timeout=DEFAULT_LOCAL_TRANSACTION_ACCEPTANCE_TIMEOUT, **kwargs, ) class BSCConfig(PluginConfig): mainnet: NetworkConfig = _create_network_config() + mainnet_fork: NetworkConfig = _create_local_config() testnet: NetworkConfig = _create_network_config() + testnet_fork: NetworkConfig = _create_local_config() local: NetworkConfig = _create_local_config(default_provider="test") default_network: str = LOCAL_NETWORK_NAME @@ -58,9 +62,27 @@ def create_transaction(self, **kwargs) -> TransactionAPI: :class:`~ape.api.transactions.TransactionAPI` """ - transaction_type = self.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, + } + + 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`. @@ -74,8 +96,11 @@ def create_transaction(self, **kwargs) -> TransactionAPI: if isinstance(kwargs.get("chainId"), str): kwargs["chainId"] = int(kwargs["chainId"], 16) + 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"] = decode_hex(kwargs.pop("input").hex()) + kwargs["data"] = kwargs.pop("input") if all(field in kwargs for field in ("v", "r", "s")): kwargs["signature"] = TransactionSignature( @@ -84,30 +109,14 @@ def create_transaction(self, **kwargs) -> TransactionAPI: s=bytes(kwargs["s"]), ) - return txn_class.parse_obj(kwargs) - - def get_transaction_type(self, _type: Optional[Union[int, str, bytes]]) -> TransactionType: - if _type is None: - version = TransactionType.STATIC - elif not isinstance(_type, int): - version = TransactionType(self.conversion_manager.convert(_type, int)) - else: - version = TransactionType(_type) - return version - - -class ApeBSCError(ApeException): - """ - Raised in the ape-bsc plugin. - """ + 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 ApeBSCError(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) diff --git a/setup.py b/setup.py index cb06266..82ec052 100644 --- a/setup.py +++ b/setup.py @@ -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 - "flake8>=6.0.0,<7", # Style linter + "black>=23.9.1,<24", # auto-formatter and linter + "mypy>=1.5.1,<2", # Static type analyzer + "flake8>=6.1.0,<7", # Style linter "isort>=5.10.1,<6", # Import sorting linter "types-setuptools", # Needed due to mypy typeshed - "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 ], @@ -68,7 +68,7 @@ packages=find_packages(exclude=["tests", "tests.*"]), package_data={"ape_bsc": ["py.typed"]}, classifiers=[ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", diff --git a/tests/conftest.py b/tests/conftest.py index f3966e3..94a6b7a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,5 @@ import ape import pytest -from ape._cli import cli as ape_cli -from click.testing import CliRunner @pytest.fixture @@ -15,21 +13,24 @@ def accounts(): @pytest.fixture -def runner(): - return CliRunner() +def account(accounts): + return accounts.test_accounts[0] @pytest.fixture -def cli(): - return ape_cli +def second_account(accounts): + return accounts.test_accounts[1] @pytest.fixture -def provider(networks): - with networks.bsc.local.use_provider("test"): - yield +def bsc(networks): + return networks.bsc @pytest.fixture -def account(accounts): - return accounts.test_accounts[0] +def eth_tester_provider(): + if not ape.networks.active_provider or ape.networks.provider.name != "test": + with ape.networks.bsc.local.use_provider("test") as provider: + yield provider + else: + yield ape.networks.provider diff --git a/tests/test_ecosystem.py b/tests/test_ecosystem.py index 5e976b8..5f1f21f 100644 --- a/tests/test_ecosystem.py +++ b/tests/test_ecosystem.py @@ -1,14 +1,33 @@ import pytest from ape_ethereum.transactions import TransactionType +from ethpm_types import MethodABI -def test_gas_limit(networks): - bsc = networks.bsc +def test_gas_limit(bsc): assert bsc.config.local.gas_limit == "max" -@pytest.mark.parametrize("type", (0, "0x0")) -def test_create_transaction(networks, type): - bsc = networks.bsc - txn = bsc.create_transaction(type=type) - assert txn.type == TransactionType.STATIC.value +@pytest.mark.parametrize("tx_type", (None, 0, "0x0")) +def test_create_transaction(bsc, tx_type, eth_tester_provider): + tx = bsc.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), +) +def test_encode_transaction(tx_type, bsc, eth_tester_provider): + abi = MethodABI.parse_obj( + { + "type": "function", + "name": "fooAndBar", + "stateMutability": "nonpayable", + "inputs": [], + "outputs": [], + } + ) + address = "0x274b028b03A250cA03644E6c578D81f019eE1323" + actual = bsc.encode_transaction(address, abi, sender=address, type=tx_type) + assert actual.gas_limit == eth_tester_provider.max_gas diff --git a/tests/test_integration.py b/tests/test_integration.py index fc3f15e..bae6577 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,3 +1,7 @@ +import pytest +from ape._cli import cli as ape_cli +from click.testing import CliRunner + EXPECTED_OUTPUT = """ bsc ├── mainnet @@ -9,6 +13,16 @@ """.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 @@ -30,6 +44,10 @@ def assert_rich_text(actual: str, expected: str): assert expected_line in actual_lines -def test_networks(runner, cli): +def test_networks(runner, cli, bsc): + # Do this in case local env changed it. + bsc.mainnet.set_default_provider("geth") + bsc.testnet.set_default_provider("geth") + result = runner.invoke(cli, ["networks", "list"]) assert_rich_text(result.output, EXPECTED_OUTPUT) diff --git a/tests/test_provider.py b/tests/test_provider.py index e5efa51..33005c9 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -1,6 +1,5 @@ -def test_use_provider(account, provider): - receipt = account.transfer(account, 100) +def test_basic(account, second_account, networks, eth_tester_provider): + receipt = account.transfer(second_account, 100) + assert not receipt.failed assert receipt.value == 100 - # Ensure uses type 0 by default. - assert receipt.transaction.type == 0