diff --git a/src/ethproto/wrappers.py b/src/ethproto/wrappers.py index 89b9417..97131d3 100644 --- a/src/ethproto/wrappers.py +++ b/src/ethproto/wrappers.py @@ -18,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) @@ -31,6 +33,10 @@ _providers = {} +class EtherscanError(Exception): + ... + + def get_provider(provider_key=None): global DEFAULT_PROVIDER provider_key = provider_key or DEFAULT_PROVIDER @@ -266,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() @@ -283,9 +288,12 @@ 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": + raise EtherscanError(f"Failed to get first block from {etherscan_url}: {resp}") + try: + return int(resp["result"][0]["blockNumber"]) + except (KeyError, TypeError): + raise EtherscanError(f"Failed to parse first block for {address}: {resp}") def get_contract_address(self, eth_wrapper): return eth_wrapper.contract.address 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,