From fef7ee33aad16260808da238c2206818157d8b21 Mon Sep 17 00:00:00 2001 From: GnP Date: Mon, 26 May 2025 18:09:12 -0300 Subject: [PATCH 1/3] Improve etherscan error handling --- src/ethproto/wrappers.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ethproto/wrappers.py b/src/ethproto/wrappers.py index 89b9417..605400a 100644 --- a/src/ethproto/wrappers.py +++ b/src/ethproto/wrappers.py @@ -3,6 +3,7 @@ from abc import ABC, abstractmethod from contextlib import contextmanager from functools import partial +from warnings import warn import requests from environs import Env @@ -283,9 +284,14 @@ def get_first_block(self, eth_wrapper): resp = requests.get(url) resp.raise_for_status() resp = resp.json() - if not resp["result"]: - return -1 - return int(resp["result"][0]["blockNumber"]) + if resp.get("status") != "1": + warn(f"Failed to get first block from {etherscan_url}: {resp}") + return 0 + try: + return int(resp["result"][0]["blockNumber"]) + except (KeyError, TypeError): + warn(f"Failed to parse first block for {address}: {resp}") + return 0 def get_contract_address(self, eth_wrapper): return eth_wrapper.contract.address From 0f2ac6cefda8dba1ed17751c52418e3aaa7f15f3 Mon Sep 17 00:00:00 2001 From: LucasCambon Date: Tue, 27 May 2025 07:28:06 -0300 Subject: [PATCH 2/3] Fix budler test with web3 v7.12 --- tests/test_aa_bundler.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/test_aa_bundler.py b/tests/test_aa_bundler.py index 8cd3845..4e53c61 100644 --- a/tests/test_aa_bundler.py +++ b/tests/test_aa_bundler.py @@ -5,13 +5,21 @@ import pytest from hexbytes import HexBytes -from web3.auto import w3 +from web3 import HTTPProvider, Web3 from web3.constants import HASH_ZERO +from web3.middleware import ExtraDataToPOAMiddleware from ethproto import aa_bundler from ethproto.test_utils import factories +@pytest.fixture +def w3(): + w3 = Web3(HTTPProvider("http://example.org")) + w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0) + return w3 + + def test_pack_two(): assert aa_bundler.pack_two(0, 0) == HASH_ZERO assert aa_bundler.pack_two(1, 2) == "0x0000000000000000000000000000000100000000000000000000000000000002" @@ -166,8 +174,8 @@ def test_get_nonce_with_local_cache(fetch_nonce_mock, randint_mock): fetch_nonce_mock.assert_not_called() -def test_send_transaction(): - w3 = MagicMock() +def test_send_transaction(w3): + w3.eth = MagicMock() w3.eth.chain_id = CHAIN_ID tx = aa_bundler.Tx( @@ -275,7 +283,8 @@ def worker(): @pytest.mark.vcr -def test_build_user_operation(): +def test_build_user_operation(w3): + tx = aa_bundler.Tx( value=0, chain_id=137, From f1ee83acf61851c6e98d5f1be3216f53cd18daf5 Mon Sep 17 00:00:00 2001 From: GnP Date: Tue, 27 May 2025 10:09:07 -0300 Subject: [PATCH 3/3] Raise exception instead of returning zero on get_first_block --- src/ethproto/wrappers.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ethproto/wrappers.py b/src/ethproto/wrappers.py index 605400a..97131d3 100644 --- a/src/ethproto/wrappers.py +++ b/src/ethproto/wrappers.py @@ -3,7 +3,6 @@ from abc import ABC, abstractmethod from contextlib import contextmanager from functools import partial -from warnings import warn import requests from environs import Env @@ -19,6 +18,8 @@ ETHERSCAN_TOKEN = env.str("ETHERSCAN_TOKEN", None) ETHERSCAN_DOMAIN = env.str("ETHERSCAN_DOMAIN", "api.etherscan.io") ETHERSCAN_URL = env.str("ETHERSCAN_URL", "https://{domain}/v2/api?apikey={token}&chainid={chainid}&") +ETHERSCAN_CHAIN_ID = env.int("ETHERSCAN_CHAIN_ID", None) + AMOUNT_DECIMALS = env.int("AMOUNT_DECIMALS", 18) AMOUNT_CLASSNAME = env.str("AMOUNT_CLASSNAME", None) @@ -32,6 +33,10 @@ _providers = {} +class EtherscanError(Exception): + ... + + def get_provider(provider_key=None): global DEFAULT_PROVIDER provider_key = provider_key or DEFAULT_PROVIDER @@ -267,9 +272,8 @@ def get_events(self, eth_wrapper, event_name, filter_kwargs={}): def get_etherscan_url(self): if ETHERSCAN_TOKEN is None: return None - return ETHERSCAN_URL.format( - token=ETHERSCAN_TOKEN, domain=ETHERSCAN_DOMAIN, chainid=self.w3.eth.chain_id - ) + chain_id = ETHERSCAN_CHAIN_ID or self.w3.eth.chain_id + return ETHERSCAN_URL.format(token=ETHERSCAN_TOKEN, domain=ETHERSCAN_DOMAIN, chainid=chain_id) def get_first_block(self, eth_wrapper): etherscan_url = self.get_etherscan_url() @@ -285,13 +289,11 @@ def get_first_block(self, eth_wrapper): resp.raise_for_status() resp = resp.json() if resp.get("status") != "1": - warn(f"Failed to get first block from {etherscan_url}: {resp}") - return 0 + raise EtherscanError(f"Failed to get first block from {etherscan_url}: {resp}") try: return int(resp["result"][0]["blockNumber"]) except (KeyError, TypeError): - warn(f"Failed to parse first block for {address}: {resp}") - return 0 + raise EtherscanError(f"Failed to parse first block for {address}: {resp}") def get_contract_address(self, eth_wrapper): return eth_wrapper.contract.address