From 262e94d9242f2d9b510e2bd1b9e0902be6b321fc Mon Sep 17 00:00:00 2001 From: antazoey Date: Tue, 26 Nov 2024 22:28:59 +0700 Subject: [PATCH 1/6] chore: delete GH templates (#46) * chore: update lint deps * chore: del templates * chore: update deps and add flake8 plugins --------- Co-authored-by: slush --- .github/ISSUE_TEMPLATE/bug.md | 32 ------------------- .github/ISSUE_TEMPLATE/feature.md | 22 ------------- .github/ISSUE_TEMPLATE/work-item.md | 48 ----------------------------- .github/PULL_REQUEST_TEMPLATE.md | 16 ---------- .pre-commit-config.yaml | 11 ++++--- setup.py | 12 +++++--- 6 files changed, 13 insertions(+), 128 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug.md delete mode 100644 .github/ISSUE_TEMPLATE/feature.md delete mode 100644 .github/ISSUE_TEMPLATE/work-item.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md deleted file mode 100644 index 91fc385..0000000 --- a/.github/ISSUE_TEMPLATE/bug.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Bug report -about: Report an error that you've encountered. -labels: bug ---- - -### Environment information - -- `ape` and plugin versions: - -``` -$ ape --version -# ...copy and paste result of above command here... - -$ ape plugins list -# ...copy and paste result of above command here... -``` - -- Python Version: x.x.x -- OS: osx/linux/win - -### What went wrong? - -Please include information like: - -- what command you ran -- the code that caused the failure (see [this link](https://help.github.com/articles/basic-writing-and-formatting-syntax/) for help with formatting code) -- full output of the error you received - -### How can it be fixed? - -Fill this in if you have ideas on how the bug could be fixed. diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md deleted file mode 100644 index 1b56056..0000000 --- a/.github/ISSUE_TEMPLATE/feature.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Feature request -about: Request a new feature, or an improvement to existing functionality. -labels: enhancement ---- - -### Overview - -Provide a simple overview of what you wish to see added. Please include: - -- What you are trying to do -- Why Ape's current functionality is inadequate to address your goal - -### Specification - -Describe the syntax and semantics of how you would like to see this feature implemented. The more detailed the better! - -Remember, your feature is much more likely to be included if it does not involve any breaking changes. - -### Dependencies - -Include links to any open issues that must be resolved before this feature can be implemented. diff --git a/.github/ISSUE_TEMPLATE/work-item.md b/.github/ISSUE_TEMPLATE/work-item.md deleted file mode 100644 index 1932055..0000000 --- a/.github/ISSUE_TEMPLATE/work-item.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: Work item -about: New work item for Ape team -labels: backlog ---- - -### Elevator pitch: - - - -### Value: - - - -### Dependencies: - - - -### Design approach: - - - -### Task list: - - - -- [ ] Tasks go here - -### Estimated completion date: - -### Design review: - - - -Do not signoff unless: - -- 1. agreed the tasks and design approach will achieve acceptance, and -- 2. the work can be completed by one person within the SLA. - Design reviewers should consider simpler approaches to achieve goals. - -(Please leave a comment to sign off) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 544cbc6..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,16 +0,0 @@ -### What I did - - - -fixes: # - -### How I did it - -### How to verify it - -### Checklist - -- [ ] Passes all linting checks (pre-commit and CI jobs) -- [ ] New test cases have been added and are passing -- [ ] Documentation has been updated -- [ ] PR title follows [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) standard (will be automatically included in the changelog) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a35de4c..25f459b 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.6.0 + rev: v5.0.0 hooks: - id: check-yaml @@ -10,24 +10,25 @@ repos: - id: isort - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 24.10.0 hooks: - id: black name: black - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.1.1 hooks: - id: flake8 + additional_dependencies: [flake8-breakpoint, flake8-print, flake8-pydantic, flake8-type-checking] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.0 + rev: v1.13.0 hooks: - id: mypy additional_dependencies: [types-setuptools, pydantic] - repo: https://github.com/executablebooks/mdformat - rev: 0.7.17 + rev: 0.7.19 hooks: - id: mdformat additional_dependencies: [mdformat-gfm, mdformat-frontmatter, mdformat-pyproject] diff --git a/setup.py b/setup.py index 6898941..b607d69 100644 --- a/setup.py +++ b/setup.py @@ -9,18 +9,20 @@ "hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer ], "lint": [ - "black>=24.4.2,<25", # Auto-formatter and linter - "mypy>=1.10.0,<2", # Static type analyzer + "black>=24.10.0,<25", # Auto-formatter and linter + "mypy>=1.13.0,<2", # Static type analyzer "types-setuptools", # Needed for mypy type shed "types-requests", # Needed for mypy type shed - "flake8>=7.0.0,<8", # Style linter + "flake8>=7.1.1,<8", # 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 + "flake8-pydantic", # For detecting issues with Pydantic models + "flake8-type-checking", # Detect imports to move in/out of type-checking blocks "isort>=5.13.2,<6", # Import sorting linter - "mdformat>=0.7.17", # Auto-formatter for markdown + "mdformat>=0.7.19", # 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 + "mdformat-pyproject>=0.0.2", # Allows configuring in pyproject.toml ], "release": [ # `release` GitHub Action job uses this "setuptools", # Installation tool From da2fd2e39cb80c3da837b7afc58936d99d7e16ad Mon Sep 17 00:00:00 2001 From: slush Date: Mon, 16 Dec 2024 09:55:27 -0600 Subject: [PATCH 2/6] feat: pep-625 compliance and enabling python 3.13 (#47) --- .github/workflows/test.yaml | 2 +- pyproject.toml | 4 ++-- setup.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7b81c86..cc05c4f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -63,7 +63,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] # eventually add `windows-latest` - python-version: [3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index edb7d64..5bd02ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=51.1.1", "wheel", "setuptools_scm[toml]>=5.0"] +requires = ["setuptools>=75.6.0", "wheel", "setuptools_scm[toml]>=5.0"] [tool.mypy] exclude = "build/" @@ -15,7 +15,7 @@ write_to = "/version.py" [tool.black] line-length = 100 -target-version = ['py39', 'py310', 'py311', 'py312'] +target-version = ['py39', 'py310', 'py311', 'py312', 'py313'] include = '\.pyi?$' [tool.pytest.ini_options] diff --git a/setup.py b/setup.py index b607d69..6450cb8 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ "mdformat-pyproject>=0.0.2", # Allows configuring in pyproject.toml ], "release": [ # `release` GitHub Action job uses this - "setuptools", # Installation tool + "setuptools>=75.6.0", # Installation tool "wheel", # Packaging tool "twine", # Package upload tool ], @@ -82,5 +82,6 @@ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ], ) From 52bf2926bd3ab219431eae3c21e61bac91ea5b09 Mon Sep 17 00:00:00 2001 From: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com> Date: Fri, 25 Apr 2025 11:58:38 -0400 Subject: [PATCH 3/6] fix(commitlint): only check last commit (#48) --- .github/workflows/commitlint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/commitlint.yaml b/.github/workflows/commitlint.yaml index f5ce092..54cf654 100644 --- a/.github/workflows/commitlint.yaml +++ b/.github/workflows/commitlint.yaml @@ -22,4 +22,4 @@ jobs: run: pip install commitizen - name: Check commit history - run: cz check --rev-range $(git rev-list --all --reverse | head -1)..HEAD + run: cz check --rev $(git rev-list --all --reverse | head -1) From e2edb9fca46199145bb777c88a5db01611d53aac Mon Sep 17 00:00:00 2001 From: Jongseung Lim Date: Wed, 21 May 2025 12:25:09 -0400 Subject: [PATCH 4/6] fix: correct imports and missing test dependencies --- ape_quicknode/provider.py | 7 +++++-- setup.py | 1 + tests/test_provider.py | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ape_quicknode/provider.py b/ape_quicknode/provider.py index dfa354f..e5eea1b 100644 --- a/ape_quicknode/provider.py +++ b/ape_quicknode/provider.py @@ -17,7 +17,10 @@ from web3 import HTTPProvider, Web3 from web3.exceptions import ContractLogicError as Web3ContractLogicError from web3.gas_strategies.rpc import rpc_gas_price_strategy -from web3.middleware import geth_poa_middleware +try: + from web3.middleware import ExtraDataToPOAMiddleware # type: ignore +except ImportError: + from web3.middleware import geth_poa_middleware as ExtraDataToPOAMiddleware # type: ignore from web3.types import RPCEndpoint from .constants import QUICKNODE_NETWORKS @@ -88,7 +91,7 @@ def connect(self): self._web3 = Web3(HTTPProvider(self.uri)) try: if self.network.ecosystem.name in ["optimism", "base", "polygon"]: - self._web3.middleware_onion.inject(geth_poa_middleware, layer=0) + self._web3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0) self._web3.eth.set_gas_price_strategy(rpc_gas_price_strategy) except Exception as err: diff --git a/setup.py b/setup.py index 063b7b2..3c92b85 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ "pytest-xdist", # Multi-process runner "pytest-cov", # Coverage analyzer plugin "hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer + "pytest-mock>=3.14.0", # Mocking library ], "lint": [ "black>=24.10.0,<25", # Auto-formatter and linter diff --git a/tests/test_provider.py b/tests/test_provider.py index 39edc5b..87fd303 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -125,6 +125,7 @@ def test_estimate_gas_would_revert_no_message(token, quicknode_provider, mock_we quicknode_provider.estimate_gas_cost(transaction) def test_get_contract_logs(networks, quicknode_provider, mock_web3, block, log_filter): + _ = quicknode_provider.chain_id # Make sure this has been called _before_ setting mock. mock_web3.eth.get_block.return_value = block quicknode_provider._web3 = mock_web3 networks.active_provider = quicknode_provider From 4d9ceb653f5ec7a14590890a94497ce7c3650243 Mon Sep 17 00:00:00 2001 From: Jongseung Lim Date: Wed, 21 May 2025 12:37:10 -0400 Subject: [PATCH 5/6] chore: linting --- ape_quicknode/__init__.py | 6 ++++-- ape_quicknode/constants.py | 2 +- ape_quicknode/exceptions.py | 5 ++++- ape_quicknode/provider.py | 38 ++++++++++++++++++++++++------------- ape_quicknode/trace.py | 4 +++- tests/conftest.py | 27 ++++++++++++++++++++------ tests/test_provider.py | 28 ++++++++++++++++++--------- 7 files changed, 77 insertions(+), 33 deletions(-) diff --git a/ape_quicknode/__init__.py b/ape_quicknode/__init__.py index 374eb78..8bdf31d 100644 --- a/ape_quicknode/__init__.py +++ b/ape_quicknode/__init__.py @@ -1,9 +1,11 @@ from ape import plugins -from .provider import QuickNode + from .constants import QUICKNODE_NETWORKS +from .provider import QuickNode + @plugins.register(plugins.ProviderPlugin) def providers(): for ecosystem_name in QUICKNODE_NETWORKS: for network_name in QUICKNODE_NETWORKS[ecosystem_name]: - yield ecosystem_name, network_name, QuickNode \ No newline at end of file + yield ecosystem_name, network_name, QuickNode diff --git a/ape_quicknode/constants.py b/ape_quicknode/constants.py index 879974a..2941d50 100644 --- a/ape_quicknode/constants.py +++ b/ape_quicknode/constants.py @@ -28,4 +28,4 @@ "mainnet": "https://{subdomain}.avalanche-mainnet.quiknode.pro/{auth_token}/", "fuji": "https://{subdomain}.avalanche-fuji.quiknode.pro/{auth_token}/", }, -} \ No newline at end of file +} diff --git a/ape_quicknode/exceptions.py b/ape_quicknode/exceptions.py index 7fa1a00..7bcf0e8 100644 --- a/ape_quicknode/exceptions.py +++ b/ape_quicknode/exceptions.py @@ -1,15 +1,18 @@ from ape.exceptions import ApeException + class QuickNodeProviderError(ApeException): """ Raised when there's an error with the QuickNode provider. """ + class QuickNodeFeatureNotAvailable(QuickNodeProviderError): """ Raised when a requested feature is not available in the current QuickNode plan. """ + class MissingAuthTokenError(QuickNodeProviderError): def __init__(self, missing_vars): - super().__init__(f"Missing environment variables: {', '.join(missing_vars)}") \ No newline at end of file + super().__init__(f"Missing environment variables: {', '.join(missing_vars)}") diff --git a/ape_quicknode/provider.py b/ape_quicknode/provider.py index e5eea1b..1f4bb7e 100644 --- a/ape_quicknode/provider.py +++ b/ape_quicknode/provider.py @@ -1,7 +1,6 @@ import os -from collections.abc import Iterable -from typing import Any, Optional -from pydantic import BaseModel, Field +from typing import TYPE_CHECKING, Any, Optional + from ape.api import ReceiptAPI, TraceAPI, TransactionAPI, UpstreamProvider from ape.exceptions import ( APINotImplementedError, @@ -9,40 +8,48 @@ ProviderError, VirtualMachineError, ) -from ape.types import BlockID from ape_ethereum.provider import Web3Provider -from ape_ethereum.transactions import AccessList from eth_typing import HexStr +from pydantic import BaseModel from requests import HTTPError from web3 import HTTPProvider, Web3 from web3.exceptions import ContractLogicError as Web3ContractLogicError from web3.gas_strategies.rpc import rpc_gas_price_strategy + try: from web3.middleware import ExtraDataToPOAMiddleware # type: ignore except ImportError: from web3.middleware import geth_poa_middleware as ExtraDataToPOAMiddleware # type: ignore + from web3.types import RPCEndpoint from .constants import QUICKNODE_NETWORKS -from .exceptions import QuickNodeFeatureNotAvailable, QuickNodeProviderError, MissingAuthTokenError +from .exceptions import MissingAuthTokenError, QuickNodeFeatureNotAvailable, QuickNodeProviderError from .trace import QuickNodeTransactionTrace +if TYPE_CHECKING: + from collections.abc import Iterable # TC003 + + from ape.types import BlockID # TC002 + from ape_ethereum.transactions import AccessList # TC002 + DEFAULT_ENVIRONMENT_VARIABLE_NAMES = ("QUICKNODE_SUBDOMAIN", "QUICKNODE_AUTH_TOKEN") NETWORKS_SUPPORTING_WEBSOCKETS = ("ethereum", "arbitrum", "base", "optimism", "polygon") + class QuickNode(Web3Provider, UpstreamProvider, BaseModel): - name: str = Field(default="QuickNode") + name: str = "QuickNode" def __init__(self, network: Any, name: str = "QuickNode", **data): super().__init__(network=network, name=name, **data) self._web3 = None self.network_uris = {} - + @property def provider_name(self) -> str: return self.name - + network_uris: dict[tuple, str] = {} @property @@ -61,7 +68,10 @@ def uri(self): if not subdomain or not auth_token: raise MissingAuthTokenError(DEFAULT_ENVIRONMENT_VARIABLE_NAMES) - if ecosystem_name not in QUICKNODE_NETWORKS or network_name not in QUICKNODE_NETWORKS[ecosystem_name]: + if ( + ecosystem_name not in QUICKNODE_NETWORKS + or network_name not in QUICKNODE_NETWORKS[ecosystem_name] + ): raise ProviderError(f"Unsupported network: {ecosystem_name} - {network_name}") uri_template = QUICKNODE_NETWORKS[ecosystem_name][network_name] @@ -107,8 +117,10 @@ def _get_prestate_trace(self, transaction_hash: str) -> dict: def get_transaction_trace(self, transaction_hash: str, **kwargs) -> TraceAPI: if not transaction_hash.startswith("0x"): - raise QuickNodeProviderError("Transaction hash must be a hexadecimal string starting with '0x'") - + raise QuickNodeProviderError( + "Transaction hash must be a hexadecimal string starting with '0x'" + ) + return QuickNodeTransactionTrace(transaction_hash=transaction_hash, provider=self, **kwargs) def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMachineError: @@ -195,4 +207,4 @@ def get_receipt( ) return super().get_receipt( txn_hash, required_confirmations=required_confirmations, timeout=timeout, **kwargs - ) \ No newline at end of file + ) diff --git a/ape_quicknode/trace.py b/ape_quicknode/trace.py index 58d25cc..f56fd87 100644 --- a/ape_quicknode/trace.py +++ b/ape_quicknode/trace.py @@ -1,8 +1,10 @@ from functools import cached_property from typing import Any, Optional + from ape_ethereum.trace import TraceApproach, TransactionTrace from hexbytes import HexBytes + class QuickNodeTransactionTrace(TransactionTrace): call_trace_approach: TraceApproach = TraceApproach.PARITY @@ -31,4 +33,4 @@ def _top_level_call(self) -> dict: self.transaction_hash, {"tracer": "callTracer", "tracerConfig": {"onlyTopLevelCall": True}}, ], - ) \ No newline at end of file + ) diff --git a/tests/conftest.py b/tests/conftest.py index 9160c42..a18f1c0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,24 +1,32 @@ import os +from typing import TYPE_CHECKING + import pytest from ape import accounts as ape_accounts from ape import networks as ape_networks -from ape_quicknode.provider import QuickNode from requests import HTTPError, Response from web3 import Web3 +if TYPE_CHECKING: + from ape_quicknode.provider import QuickNode + + @pytest.fixture def accounts(): return ape_accounts + @pytest.fixture def networks(): return ape_networks + @pytest.fixture def missing_token(monkeypatch): monkeypatch.delenv("QUICKNODE_SUBDOMAIN", raising=False) monkeypatch.delenv("QUICKNODE_AUTH_TOKEN", raising=False) + @pytest.fixture def token(mocker): env = os.environ.copy() @@ -35,6 +43,7 @@ def side_effect(key, *args, **kwargs): mock.side_effect = side_effect return mock + @pytest.fixture def mock_web3(mocker): mock = mocker.MagicMock(spec=Web3) @@ -42,6 +51,7 @@ def mock_web3(mocker): mock.middleware_onion = mocker.MagicMock() return mock + @pytest.fixture def transaction(accounts, networks): with networks.ethereum.local.use_provider("test"): @@ -50,14 +60,18 @@ def transaction(accounts, networks): receipt = sender.transfer(receiver, "1 gwei") return receipt.transaction + @pytest.fixture def txn_hash(): return "0x55d07ce5e3f4f5742f3318cf328d700e43ee8cdb46000f2ac731a9379fca8ea7" -@pytest.fixture(params=[ - "This feature is not available on your current plan. Please upgrade to access this functionality.", - "This feature is not supported on the current network." -]) + +@pytest.fixture( + params=[ + "This feature is not available on your current plan. Please upgrade to access this functionality.", + "This feature is not supported on the current network.", + ] +) def feature_not_available_http_error(mocker, request): response = mocker.MagicMock(spec=Response) response.fixture_param = request.param @@ -65,6 +79,7 @@ def feature_not_available_http_error(mocker, request): error = HTTPError(response=response) return error + @pytest.fixture -def quicknode_provider(networks) -> QuickNode: +def quicknode_provider(networks) -> "QuickNode": return networks.ethereum.mainnet.get_provider("quicknode") diff --git a/tests/test_provider.py b/tests/test_provider.py index 87fd303..e789956 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -1,16 +1,16 @@ -import re import pytest +from ape.api import TraceAPI from ape.exceptions import ContractLogicError, ProviderError from ape.types import LogFilter from hexbytes import HexBytes from web3.exceptions import ContractLogicError as Web3ContractLogicError -from ape.api import TraceAPI -from ape_quicknode.trace import QuickNodeTransactionTrace -from ape_quicknode.exceptions import QuickNodeProviderError + from ape_quicknode.exceptions import MissingAuthTokenError, QuickNodeProviderError +from ape_quicknode.trace import QuickNodeTransactionTrace TXN_HASH = "0x3cef4aaa52b97b6b61aa32b3afcecb0d14f7862ca80fdc76504c37a9374645c4" + @pytest.fixture def log_filter(): return LogFilter( @@ -26,6 +26,7 @@ def log_filter(): ], ) + @pytest.fixture def block(): return { @@ -44,6 +45,7 @@ def block(): "totalDifficulty": 131072, } + @pytest.fixture def receipt(): return { @@ -83,12 +85,13 @@ def receipt(): "value": 0, } + def test_when_no_auth_token_raises_error(missing_token, quicknode_provider): with pytest.raises(MissingAuthTokenError) as excinfo: quicknode_provider.connect() assert "QUICKNODE_SUBDOMAIN" in str(excinfo.value) assert "QUICKNODE_AUTH_TOKEN" in str(excinfo.value) - + def test_send_transaction_reverts(token, quicknode_provider, mock_web3, transaction): expected_revert_message = "EXPECTED REVERT MESSAGE" @@ -100,6 +103,7 @@ def test_send_transaction_reverts(token, quicknode_provider, mock_web3, transact with pytest.raises(ContractLogicError, match=expected_revert_message): quicknode_provider.send_transaction(transaction) + def test_send_transaction_reverts_no_message(token, quicknode_provider, mock_web3, transaction): mock_web3.eth.send_raw_transaction.side_effect = Web3ContractLogicError("execution reverted") quicknode_provider._web3 = mock_web3 @@ -107,6 +111,7 @@ def test_send_transaction_reverts_no_message(token, quicknode_provider, mock_web with pytest.raises(ContractLogicError, match="Transaction failed."): quicknode_provider.send_transaction(transaction) + def test_estimate_gas_would_revert(token, quicknode_provider, mock_web3, transaction): expected_revert_message = "EXPECTED REVERT MESSAGE" mock_web3.eth.estimate_gas.side_effect = Web3ContractLogicError( @@ -117,6 +122,7 @@ def test_estimate_gas_would_revert(token, quicknode_provider, mock_web3, transac with pytest.raises(ContractLogicError, match=expected_revert_message): quicknode_provider.estimate_gas_cost(transaction) + def test_estimate_gas_would_revert_no_message(token, quicknode_provider, mock_web3, transaction): mock_web3.eth.estimate_gas.side_effect = Web3ContractLogicError("execution reverted") quicknode_provider._web3 = mock_web3 @@ -124,6 +130,7 @@ def test_estimate_gas_would_revert_no_message(token, quicknode_provider, mock_we with pytest.raises(ContractLogicError, match="Transaction failed."): quicknode_provider.estimate_gas_cost(transaction) + def test_get_contract_logs(networks, quicknode_provider, mock_web3, block, log_filter): _ = quicknode_provider.chain_id # Make sure this has been called _before_ setting mock. mock_web3.eth.get_block.return_value = block @@ -133,6 +140,7 @@ def test_get_contract_logs(networks, quicknode_provider, mock_web3, block, log_f assert actual == [] + def test_unsupported_network(quicknode_provider, monkeypatch): monkeypatch.setenv("QUICKNODE_SUBDOMAIN", "test_subdomain") monkeypatch.setenv("QUICKNODE_AUTH_TOKEN", "test_token") @@ -142,6 +150,7 @@ def test_unsupported_network(quicknode_provider, monkeypatch): with pytest.raises(ProviderError, match="Unsupported network:"): quicknode_provider.uri + def test_quicknode_provider_error(quicknode_provider, mock_web3): error_message = "QuickNode API error" mock_web3.provider.make_request.side_effect = QuickNodeProviderError(error_message) @@ -149,7 +158,8 @@ def test_quicknode_provider_error(quicknode_provider, mock_web3): with pytest.raises(QuickNodeProviderError, match=error_message): quicknode_provider.make_request("eth_blockNumber", []) - + + def test_get_transaction_trace(networks, quicknode_provider, mock_web3, receipt): tx_hash = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" mock_trace_data = { @@ -161,9 +171,9 @@ def test_get_transaction_trace(networks, quicknode_provider, mock_web3, receipt) mock_web3.eth.wait_for_transaction_receipt.return_value = receipt quicknode_provider._web3 = mock_web3 networks.active_provider = quicknode_provider - + trace = quicknode_provider.get_transaction_trace(tx_hash) - + assert isinstance(trace, QuickNodeTransactionTrace) assert isinstance(trace, TraceAPI) - assert trace.transaction_hash == tx_hash \ No newline at end of file + assert trace.transaction_hash == tx_hash From b14f49c51c0004375a3f50628e100a754bc7e571 Mon Sep 17 00:00:00 2001 From: Jongseung Lim Date: Wed, 21 May 2025 13:20:09 -0400 Subject: [PATCH 6/6] chore: fix types --- ape_quicknode/__init__.py | 20 +++++++++++++++++--- ape_quicknode/provider.py | 15 +++++++++------ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/ape_quicknode/__init__.py b/ape_quicknode/__init__.py index 8bdf31d..1e25b1d 100644 --- a/ape_quicknode/__init__.py +++ b/ape_quicknode/__init__.py @@ -1,11 +1,25 @@ from ape import plugins -from .constants import QUICKNODE_NETWORKS -from .provider import QuickNode - @plugins.register(plugins.ProviderPlugin) def providers(): + from .constants import QUICKNODE_NETWORKS + from .provider import QuickNode + for ecosystem_name in QUICKNODE_NETWORKS: for network_name in QUICKNODE_NETWORKS[ecosystem_name]: yield ecosystem_name, network_name, QuickNode + + +def __getattr__(name: str): + if name == "NETWORKS": + from .constants import QUICKNODE_NETWORKS + + return QUICKNODE_NETWORKS + + if name == "QuickNode": + from .provider import QuickNode + + return QuickNode + + raise AttributeError(name) diff --git a/ape_quicknode/provider.py b/ape_quicknode/provider.py index 1f4bb7e..029174c 100644 --- a/ape_quicknode/provider.py +++ b/ape_quicknode/provider.py @@ -28,15 +28,18 @@ from .trace import QuickNodeTransactionTrace if TYPE_CHECKING: - from collections.abc import Iterable # TC003 + from collections.abc import Iterable - from ape.types import BlockID # TC002 - from ape_ethereum.transactions import AccessList # TC002 + from ape.types import BlockID + from ape_ethereum.transactions import AccessList DEFAULT_ENVIRONMENT_VARIABLE_NAMES = ("QUICKNODE_SUBDOMAIN", "QUICKNODE_AUTH_TOKEN") NETWORKS_SUPPORTING_WEBSOCKETS = ("ethereum", "arbitrum", "base", "optimism", "polygon") +# Flashbots will try to publish private transactions for 25 blocks. +PRIVATE_TX_BLOCK_WAIT = 25 + class QuickNode(Web3Provider, UpstreamProvider, BaseModel): name: str = "QuickNode" @@ -153,14 +156,14 @@ def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMa return VirtualMachineError(message=message, txn=txn) def create_access_list( - self, transaction: TransactionAPI, block_id: Optional[BlockID] = None - ) -> list[AccessList]: + self, transaction: TransactionAPI, block_id: Optional["BlockID"] = None + ) -> list["AccessList"]: if self.network.ecosystem.name == "polygon-zkevm": raise APINotImplementedError() return super().create_access_list(transaction, block_id=block_id) - def make_request(self, rpc: str, parameters: Optional[Iterable] = None) -> Any: + def make_request(self, rpc: str, parameters: Optional["Iterable"] = None) -> Any: parameters = parameters or [] try: result = self.web3.provider.make_request(RPCEndpoint(rpc), parameters)