diff --git a/eth_portfolio/_loaders/token_transfer.py b/eth_portfolio/_loaders/token_transfer.py index d92d1165..7c8ad371 100644 --- a/eth_portfolio/_loaders/token_transfer.py +++ b/eth_portfolio/_loaders/token_transfer.py @@ -1,3 +1,7 @@ +""" +This module orchestrates the process of loading and processing token transfers within the eth_portfolio ecosystem. +""" + import decimal import logging from typing import Optional @@ -25,12 +29,35 @@ token_transfer_semaphore = dank_mids.BlockSemaphore( 10_000, name="eth_portfolio.token_transfers" ) # Some arbitrary number +"""A semaphore that regulates the concurrent processing of token transfers by processing lower blocks first.""" @stuck_coro_debugger async def load_token_transfer( transfer_log: "Log", load_prices: bool ) -> Optional[TokenTransfer]: # sourcery skip: simplify-boolean-comparison + """ + Processes and loads a token transfer from a log entry, with comprehensive error handling and optional price fetching. + + This function employs a multi-step process: + 1. Validates the transfer against a known set of 'shitcoins', skipping processing if matched. + 2. Checks for existing database entries, potentially deleting and reprocessing if price data is requested but missing. + 3. Decodes the transfer log and retrieves associated metadata (e.g., token scale, symbol, transaction index). + 4. Optionally fetches the token price at the time of the transaction. + 5. Constructs and persists a :class:`~eth_portfolio.structs.TokenTransfer` object in the database. + + The function handles various exceptions, including :class:`~y.exceptions.NonStandardERC20` for non-compliant tokens and :class:`decimal.InvalidOperation` for extreme :class:`~Decimal` values. + + Args: + transfer_log: A dictionary containing the raw log entry of the token transfer. + load_prices: A flag indicating whether to fetch and include price data for the token at the time of transfer. + + Returns: + A processed TokenTransfer object if successful, or None if the transfer cannot be processed due to various constraints or errors. + + Note: + This function employs caching mechanisms and database operations to optimize performance. + """ if transfer_log.removed: if transfer := await db.get_token_transfer(transfer_log): await db.delete_token_transfer(transfer) @@ -105,6 +132,19 @@ async def _insert_to_db(transfer: TokenTransfer, load_prices: bool) -> None: @stuck_coro_debugger async def get_symbol(token: ERC20) -> Optional[str]: + """ + Retrieves the symbol of a given ERC20 token, with error handling for non-standard implementations. + + This function attempts to access the token's symbol through the standard ERC20 symbol method. If the token contract + does not adhere to the standard ERC20 interface, indicated by a :class:`~y.exceptions.NonStandardERC20` exception, the function + returns `None` instead of propagating the error. + + Args: + token: An ERC20 token object representing the token whose symbol is to be retrieved. + + Returns: + The token's symbol as a string if successfully retrieved, or None if the token does not implement a standard symbol method. + """ try: return await token.__symbol__ except NonStandardERC20: @@ -114,6 +154,19 @@ async def get_symbol(token: ERC20) -> Optional[str]: @stuck_coro_debugger @cache_to_disk async def get_transaction_index(hash: str) -> int: + """ + Retrieves the transaction index for a given transaction hash, with results cached to disk. + + This function fetches the transaction receipt corresponding to the provided hash and extracts the transaction index, + which represents the position of the transaction within its containing block. The result is cached to disk to + optimize performance for future runs. + + Args: + hash: The hexadecimal string representation of the transaction hash. + + Returns: + The zero-based index of the transaction within its block. + """ receipt_bytes = await get_transaction_receipt(hash) return msgspec.json.decode( receipt_bytes, type=HasTxIndex, dec_hook=TransactionIndex._decode_hook