diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 00000000..d1c27b7d --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,48 @@ +name: Pylint Check + +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + + - name: Debug Information + run: | + echo "Current directory:" + pwd + echo "\nDirectory structure:" + ls -R + echo "\nGit root directory:" + git rev-parse --show-toplevel + echo "\nSearching for Python files:" + find . -name "*.py" + echo "\nGit ls-files output:" + git ls-files + echo "\nSpecific path search:" + git ls-files 'data_handler/*.py' + git ls-files './data_handler/*.py' + git ls-files 'apps/data_handler/*.py' + git ls-files './apps/data_handler/*.py' + + - name: Run Pylint + run: | + PYTHON_FILES=$(find ./apps/data_handler -name "*.py") + if [ -n "$PYTHON_FILES" ]; then + echo "Files to lint: $PYTHON_FILES" + python -m pylint $PYTHON_FILES --disable=all --enable=C0114,C0115,C0116,C0301 --max-line-length=150 + else + echo "No Python files found in apps/data_handler/" + exit 1 + fi \ No newline at end of file diff --git a/apps/data_handler/__init__.py b/apps/data_handler/__init__.py index 8b137891..b70cc3df 100644 --- a/apps/data_handler/__init__.py +++ b/apps/data_handler/__init__.py @@ -1 +1,2 @@ +"""Module docstring placeholder.""" diff --git a/apps/data_handler/celery_app/__init__.py b/apps/data_handler/celery_app/__init__.py index e69de29b..232f5405 100644 --- a/apps/data_handler/celery_app/__init__.py +++ b/apps/data_handler/celery_app/__init__.py @@ -0,0 +1,7 @@ +""" +This module initializes the Celery application for the data handler. + +It sets up the necessary configurations and integrates with the broader +application to manage asynchronous tasks efficiently. +""" + diff --git a/apps/data_handler/celery_app/celery_conf.py b/apps/data_handler/celery_app/celery_conf.py index 83a9d405..4ef2481f 100644 --- a/apps/data_handler/celery_app/celery_conf.py +++ b/apps/data_handler/celery_app/celery_conf.py @@ -1,3 +1,18 @@ +""" +Celery configuration for scheduling periodic tasks. +""" + +# run_loan_states_computation_for_hashtack_v0,; +# run_loan_states_computation_for_hashtack_v1,; run_loan_states_computation_for_nostra_alpha,; +# run_loan_states_computation_for_nostra_mainnet,; run_loan_states_computation_for_zklend,; +# run_liquidable_debt_computation_for_nostra_alpha,; +# run_liquidable_debt_computation_for_nostra_mainnet,; +# run_liquidable_debt_computation_for_hashstack_v0,; +# run_liquidable_debt_computation_for_hashstack_v1,; uniswap_v2_order_book, + +from data_handler.celery_app.tasks import ( + run_liquidable_debt_computation_for_zklend, ) +from data_handler.celery_app.order_books_tasks import ekubo_order_book import os from celery import Celery @@ -10,10 +25,8 @@ REDIS_HOST = os.environ.get("REDIS_HOST", "") REDIS_PORT = os.environ.get("REDIS_PORT", 6379) - ORDER_BOOK_TIME_INTERVAL = int(os.environ.get("ORDER_BOOK_TIME_INTERVAL", 5)) - app = Celery( main="DataHandler", broker=f"redis://{REDIS_HOST}:{REDIS_PORT}/0", @@ -59,7 +72,13 @@ from data_handler.celery_app.order_books_tasks import ekubo_order_book from data_handler.celery_app.tasks import ( - run_liquidable_debt_computation_for_zklend, -) # run_loan_states_computation_for_hashtack_v0,; run_loan_states_computation_for_hashtack_v1,; run_loan_states_computation_for_nostra_alpha,; run_loan_states_computation_for_nostra_mainnet,; run_loan_states_computation_for_zklend,; run_liquidable_debt_computation_for_nostra_alpha,; run_liquidable_debt_computation_for_nostra_mainnet,; run_liquidable_debt_computation_for_hashstack_v0,; run_liquidable_debt_computation_for_hashstack_v1,; uniswap_v2_order_book, + run_liquidable_debt_computation_for_zklend, ) + +# run_loan_states_computation_for_hashtack_v0,; run_loan_states_computation_for_hashtack_v1,; +# run_loan_states_computation_for_nostra_alpha,; run_loan_states_computation_for_nostra_mainnet,; +# run_loan_states_computation_for_zklend,; run_liquidable_debt_computation_for_nostra_alpha,; +# run_liquidable_debt_computation_for_nostra_mainnet,; +# run_liquidable_debt_computation_for_hashstack_v0,; +# run_liquidable_debt_computation_for_hashstack_v1,; uniswap_v2_order_book, app.autodiscover_tasks(["celery_app.tasks", "celery_app.order_books_tasks"]) diff --git a/apps/data_handler/celery_app/order_books_tasks.py b/apps/data_handler/celery_app/order_books_tasks.py index 04c5c18d..46053b40 100644 --- a/apps/data_handler/celery_app/order_books_tasks.py +++ b/apps/data_handler/celery_app/order_books_tasks.py @@ -1,3 +1,6 @@ +"""Tasks for fetching and storing order book data from Ekubo and Haiko APIs. +""" + import logging from data_handler.celery_app.celery_conf import app @@ -19,27 +22,20 @@ def ekubo_order_book(): """ pool_states = EkuboAPIConnector().get_pools() filtered_pool_states = [ - pool_state - for pool_state in pool_states - if isinstance(pool_state, dict) - and pool_state["token0"] in TOKEN_MAPPING - and pool_state["token1"] in TOKEN_MAPPING + pool_state for pool_state in pool_states if isinstance(pool_state, dict) + and pool_state["token0"] in TOKEN_MAPPING and pool_state["token1"] in TOKEN_MAPPING ] for pool_state in filtered_pool_states: token_a = pool_state["token0"] token_b = pool_state["token1"] - logging.getLogger().info( - f"Fetching data for token pair: {token_a} and {token_b}" - ) + logging.getLogger().info(f"Fetching data for token pair: {token_a} and {token_b}") try: order_book = EkuboOrderBook(token_a, token_b) order_book.fetch_price_and_liquidity() serialized_data = order_book.serialize() connector.write_to_db(OrderBookModel(**serialized_data.model_dump())) except Exception as exc: - logger.info( - f"With token pair: {token_a} and {token_b} something happened: {exc}" - ) + logger.info(f"With token pair: {token_a} and {token_b} something happened: {exc}") continue diff --git a/apps/data_handler/celery_app/tasks.py b/apps/data_handler/celery_app/tasks.py index 68f9b712..71f35c79 100644 --- a/apps/data_handler/celery_app/tasks.py +++ b/apps/data_handler/celery_app/tasks.py @@ -1,3 +1,6 @@ +"""Celery tasks for running loan state and liquidable debt computations, +and fetching Uniswap V2 order book data.""" + import logging from time import monotonic @@ -27,7 +30,6 @@ connector = DBConnector() - # @app.task(name="run_loan_states_computation_for_hashtack_v0") # def run_loan_states_computation_for_hashtack_v0(): # start = monotonic() @@ -42,7 +44,6 @@ # monotonic() - start, # ) - # @app.task(name="run_loan_states_computation_for_hashtack_v1") # def run_loan_states_computation_for_hashtack_v1(): # start = monotonic() @@ -57,7 +58,6 @@ # monotonic() - start, # ) - # @app.task(name="run_loan_states_computation_for_zklend") # def run_loan_states_computation_for_zklend(): # start = monotonic() @@ -75,6 +75,7 @@ @app.task(name="run_loan_states_computation_for_nostra_alpha") def run_loan_states_computation_for_nostra_alpha(): + """fn docstring""" start = monotonic() logging.basicConfig(level=logging.INFO) @@ -126,6 +127,7 @@ def uniswap_v2_order_book(): @app.task(name="run_liquidable_debt_computation_for_zklend") def run_liquidable_debt_computation_for_zklend(): + """fn docstring""" logging.info("Starting zkLend liquidable debt computation") zklend.run() logging.info("zkLend liquidable debt computation finished") @@ -133,6 +135,7 @@ def run_liquidable_debt_computation_for_zklend(): @app.task(name="run_liquidable_debt_computation_for_nostra_alpha") def run_liquidable_debt_computation_for_nostra_alpha(): + """fn docstring""" logging.info("Starting nostra alpha liquidable debt computation") nostra_alpha.run() logging.info("Nostra alpha liquidable debt computation finished") @@ -140,6 +143,7 @@ def run_liquidable_debt_computation_for_nostra_alpha(): @app.task(name="run_liquidable_debt_computation_for_hashstack_v0") def run_liquidable_debt_computation_for_hashstack_v0(): + """fn docstring""" logging.info("Starting hashstack v0 liquidable debt computation") hashstack_v0.run() logging.info("Hashstack v0 liquidable debt computation finished") @@ -147,6 +151,7 @@ def run_liquidable_debt_computation_for_hashstack_v0(): @app.task(name="run_liquidable_debt_computation_for_nostra_mainnet") def run_liquidable_debt_computation_for_nostra_mainnet(): + """fn docstring""" logging.info("Starting nostra mainnet liquidable debt computation") nostra_mainnet.run() logging.info("Nostra mainnet liquidable debt computation finished") @@ -154,6 +159,7 @@ def run_liquidable_debt_computation_for_nostra_mainnet(): @app.task(name="run_liquidable_debt_computation_for_hashstack_v1") def run_liquidable_debt_computation_for_hashstack_v1(): + """fn docstring""" logging.info("Starting hashstack v1 liquidable debt computation") hashstack_v1.run() logging.info("Hashstack v1 liquidable debt computation finished") diff --git a/apps/data_handler/db/__init__.py b/apps/data_handler/db/__init__.py index 7b060f1c..a80eb454 100644 --- a/apps/data_handler/db/__init__.py +++ b/apps/data_handler/db/__init__.py @@ -1 +1,9 @@ +""" +This module initializes the database connection and imports necessary components. + +It imports: +- `SQLALCHEMY_DATABASE_URL`: The URL for the SQLAlchemy database connection. +- `Base`: The declarative base class for SQLAlchemy models. +""" + from data_handler.db.database import SQLALCHEMY_DATABASE_URL, Base diff --git a/apps/data_handler/db/crud.py b/apps/data_handler/db/crud.py index bf8d71d4..6277ccef 100644 --- a/apps/data_handler/db/crud.py +++ b/apps/data_handler/db/crud.py @@ -1,3 +1,8 @@ +"""Classes: +- DBConnector: Manages database connections and CRUD operations. +- InitializerDBConnector: Handles ZkLendCollateralDebt-specific operations. +- ZkLendEventDBConnector: Manages ZkLend event-specific operations.""" + import logging import uuid from typing import List, Optional, Type, TypeVar @@ -9,7 +14,6 @@ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Query, Session, aliased, scoped_session, sessionmaker - from data_handler.db.models import ( Base, HashtackCollateralDebt, @@ -24,7 +28,6 @@ LiquidationEventData, ) - logger = logging.getLogger(__name__) ModelType = TypeVar("ModelType", bound=Base) @@ -69,9 +72,7 @@ def write_to_db(self, obj: Base = None) -> None: finally: db.close() - def get_object( - self, model: Type[ModelType] = None, obj_id: uuid = None - ) -> ModelType | None: + def get_object(self, model: Type[ModelType] = None, obj_id: uuid = None) -> ModelType | None: """ Retrieves an object by its ID from the database. :param: model: type[Base] = None @@ -116,11 +117,9 @@ def _get_subquery(self) -> Subquery: """ session = self.Session() return ( - session.query( - LoanState.user, func.max(LoanState.block).label("latest_block") - ) - .group_by(LoanState.user) - .subquery() + session.query(LoanState.user, + func.max(LoanState.block).label("latest_block")).group_by(LoanState.user + ).subquery() ) def get_latest_block_loans(self) -> Query: @@ -132,15 +131,13 @@ def get_latest_block_loans(self) -> Query: subquery = self._get_subquery() result = ( - session.query(LoanState) - .join( + session.query(LoanState).join( subquery, and_( LoanState.user == subquery.c.user, LoanState.block == subquery.c.latest_block, ), - ) - .all() + ).all() ) return result @@ -187,10 +184,10 @@ def get_last_hashstack_loan_state(self, user_id: str) -> HashtackCollateralDebt: db = self.Session() try: return ( - db.query(HashtackCollateralDebt) - .filter(HashtackCollateralDebt.user_id == user_id) - .order_by(HashtackCollateralDebt.loan_id.desc()) - .first() + db.query(HashtackCollateralDebt).filter(HashtackCollateralDebt.user_id == user_id + ).order_by( + HashtackCollateralDebt.loan_id.desc() + ).first() ) finally: db.close() @@ -205,9 +202,8 @@ def get_last_block(self, protocol_id: ProtocolIDs) -> int: db = self.Session() try: max_block = ( - db.query(func.max(LoanState.block)) - .filter(LoanState.protocol_id == protocol_id) - .scalar() + db.query(func.max(LoanState.block)).filter(LoanState.protocol_id == protocol_id + ).scalar() ) return max_block or 0 finally: @@ -247,8 +243,7 @@ def write_loan_states_to_db(self, objects: List[LoanState]) -> None: "timestamp": obj.timestamp, "block": obj.block, "deposit": obj.deposit, - } - for obj in objects + } for obj in objects ] try: # Use PostgreSQL's insert with on_conflict_do_update for upserting records @@ -264,9 +259,7 @@ def write_loan_states_to_db(self, objects: List[LoanState]) -> None: # Execute the upsert statement db.execute(stmt) - logger.info( - f"Updating or adding {len(objects)} loan states to the database." - ) + logger.info(f"Updating or adding {len(objects)} loan states to the database.") # Commit the changes db.commit() @@ -279,9 +272,7 @@ def write_loan_states_to_db(self, objects: List[LoanState]) -> None: db.close() logging.info("Loan states have been written to the database.") - def get_latest_order_book( - self, dex: str, token_a: str, token_b: str - ) -> OrderBookModel | None: + def get_latest_order_book(self, dex: str, token_a: str, token_b: str) -> OrderBookModel | None: """ Retrieves the latest order book for a given pair of tokens and DEX. :param dex: str - The DEX name. @@ -297,21 +288,17 @@ def get_latest_order_book( OrderBookModel.token_b == token_b, ) max_timestamp = ( - select(func.max(OrderBookModel.timestamp)) - .where(order_book_condition) - .scalar_subquery() + select(func.max(OrderBookModel.timestamp) + ).where(order_book_condition).scalar_subquery() ) return db.execute( - select(OrderBookModel).where( - OrderBookModel.timestamp == max_timestamp, order_book_condition - ) + select(OrderBookModel + ).where(OrderBookModel.timestamp == max_timestamp, order_book_condition) ).scalar() finally: db.close() - def get_unique_users_last_block_objects( - self, protocol_id: ProtocolIDs - ) -> LoanState: + def get_unique_users_last_block_objects(self, protocol_id: ProtocolIDs) -> LoanState: """ Retrieves the latest loan states for unique users. """ @@ -319,10 +306,11 @@ def get_unique_users_last_block_objects( try: # Create a subquery to get the max block for each user subquery = ( - db.query(LoanState.user, func.max(LoanState.block).label("max_block")) - .filter(LoanState.protocol_id == protocol_id) - .group_by(LoanState.user) - .subquery() + db.query(LoanState.user, + func.max( + LoanState.block + ).label("max_block")).filter(LoanState.protocol_id == protocol_id + ).group_by(LoanState.user).subquery() ) # Alias the subquery for clarity @@ -330,16 +318,13 @@ def get_unique_users_last_block_objects( # Join the main LoanState table with the subquery return ( - db.query(LoanState) - .join( + db.query(LoanState).join( alias_subquery, and_( LoanState.user == alias_subquery.c.user, LoanState.block == alias_subquery.c.max_block, ), - ) - .filter(LoanState.protocol_id == protocol_id) - .all() + ).filter(LoanState.protocol_id == protocol_id).all() ) finally: db.close() @@ -355,19 +340,16 @@ def get_last_interest_rate_record_by_protocol_id( db = self.Session() try: return ( - db.query(InterestRate) - .filter(InterestRate.protocol_id == protocol_id) - .order_by(InterestRate.block.desc()) - .first() + db.query(InterestRate).filter(InterestRate.protocol_id == protocol_id + ).order_by(InterestRate.block.desc()).first() ) finally: db.close() - def get_interest_rate_by_block( - self, block_number: int, protocol_id: str - ) -> InterestRate: + def get_interest_rate_by_block(self, block_number: int, protocol_id: str) -> InterestRate: """ - Fetch the closest InterestRate instance by block number that is less than or equal to the given block number. + Fetch the closest InterestRate instance by block number that is less than or equal + to the given block number. :param protocol_id: The protocol ID to search for. :param block_number: The block number to search for. @@ -376,11 +358,9 @@ def get_interest_rate_by_block( db = self.Session() try: return ( - db.query(InterestRate) - .filter(InterestRate.protocol_id == protocol_id) - .filter(InterestRate.block <= block_number) - .order_by(desc(InterestRate.block)) - .first() + db.query(InterestRate).filter(InterestRate.protocol_id == protocol_id + ).filter(InterestRate.block <= block_number + ).order_by(desc(InterestRate.block)).first() ) finally: db.close() @@ -430,16 +410,15 @@ def get_zklend_by_user_ids(self, user_ids: List[str]) -> List[ZkLendCollateralDe session = self.Session() try: return ( - session.query(ZkLendCollateralDebt) - .filter(ZkLendCollateralDebt.user_id.in_(user_ids)) - .all() + session.query(ZkLendCollateralDebt).filter( + ZkLendCollateralDebt.user_id.in_(user_ids) + ).all() ) finally: session.close() - def get_hashtack_by_loan_ids( - self, loan_ids: List[str], version: int - ) -> List[HashtackCollateralDebt]: + def get_hashtack_by_loan_ids(self, loan_ids: List[str], + version: int) -> List[HashtackCollateralDebt]: """ Retrieve HashtackCollateralDebt records by loan_ids. :param loan_ids: A list of user IDs to filter by. @@ -449,10 +428,9 @@ def get_hashtack_by_loan_ids( session = self.Session() try: return ( - session.query(HashtackCollateralDebt) - .filter(HashtackCollateralDebt.loan_id.in_(loan_ids)) - .filter(HashtackCollateralDebt.version == version) - .all() + session.query(HashtackCollateralDebt).filter( + HashtackCollateralDebt.loan_id.in_(loan_ids) + ).filter(HashtackCollateralDebt.version == version).all() ) finally: session.close() @@ -489,9 +467,7 @@ def save_collateral_enabled_by_user( collateral = self._convert_decimal_to_float(collateral) debt = self._convert_decimal_to_float(debt) try: - record = ( - session.query(ZkLendCollateralDebt).filter_by(user_id=user_id).first() - ) + record = (session.query(ZkLendCollateralDebt).filter_by(user_id=user_id).first()) if record: # Update existing record if collateral is not None: @@ -544,9 +520,7 @@ def save_debt_category( borrowed_collateral = self._convert_decimal_to_float(borrowed_collateral) try: - record = ( - session.query(HashtackCollateralDebt).filter_by(loan_id=loan_id).first() - ) + record = (session.query(HashtackCollateralDebt).filter_by(loan_id=loan_id).first()) logger.info(f"Going to save loan_id {loan_id}") # if debt category is the same, update the record if record and record.debt_category == debt_category: diff --git a/apps/data_handler/db/database.py b/apps/data_handler/db/database.py index 33137a17..f3adc2a4 100644 --- a/apps/data_handler/db/database.py +++ b/apps/data_handler/db/database.py @@ -1,3 +1,4 @@ +""" Database configuration """ import os from dotenv import load_dotenv @@ -12,9 +13,7 @@ DB_PORT = os.environ.get("DB_PORT", 5432) DB_NAME = os.environ.get("DB_NAME", "") -SQLALCHEMY_DATABASE_URL = ( - f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_NAME}" -) +SQLALCHEMY_DATABASE_URL = (f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_NAME}") engine = create_engine(SQLALCHEMY_DATABASE_URL) @@ -22,7 +21,6 @@ Base = declarative_base() - def get_database() -> SessionLocal: """ Creates the database session diff --git a/apps/data_handler/db/models/__init__.py b/apps/data_handler/db/models/__init__.py index 01e8cae0..fc3ebdd3 100644 --- a/apps/data_handler/db/models/__init__.py +++ b/apps/data_handler/db/models/__init__.py @@ -1,3 +1,4 @@ +""" This module contains all the models used in the database. """ from .base import Base from .liquidable_debt import HealthRatioLevel, LiquidableDebt from .loan_states import ( diff --git a/apps/data_handler/db/models/base.py b/apps/data_handler/db/models/base.py index 871a6d0f..cd06cc48 100644 --- a/apps/data_handler/db/models/base.py +++ b/apps/data_handler/db/models/base.py @@ -1,3 +1,4 @@ +""" Base classes for ORM models. """ from uuid import uuid4 from sqlalchemy import UUID, BigInteger, Column, MetaData, String diff --git a/apps/data_handler/db/models/event.py b/apps/data_handler/db/models/event.py index 047cf92b..04d27dd6 100644 --- a/apps/data_handler/db/models/event.py +++ b/apps/data_handler/db/models/event.py @@ -1,10 +1,10 @@ """ Module: event.py -Defines the `EventBaseModel` class for storing event data in the database. -This model includes attributes +Defines the `EventBaseModel` class for storing event data in the database. +This model includes attributes like `event_name`, `block_number`, and `protocol_id`, using the `ProtocolIDs` e -num to enforce valid protocol IDs. +num to enforce valid protocol IDs. Fields are indexed for efficient querying. """ diff --git a/apps/data_handler/db/models/liquidable_debt.py b/apps/data_handler/db/models/liquidable_debt.py index 9025bf94..b750fe5b 100644 --- a/apps/data_handler/db/models/liquidable_debt.py +++ b/apps/data_handler/db/models/liquidable_debt.py @@ -1,3 +1,4 @@ +""" SQLAlchemy models for the liquidable debt and health ratio level tables. """ from data_handler.handlers.liquidable_debt.values import LendingProtocolNames from sqlalchemy import DECIMAL, BigInteger, Column, String from sqlalchemy_utils.types.choice import ChoiceType @@ -14,9 +15,7 @@ class LiquidableDebt(Base): __tablename__ = "liquidable_debt" liquidable_debt = Column(DECIMAL, nullable=False) - protocol_name = Column( - ChoiceType(LendingProtocolNames, impl=String()), nullable=False - ) + protocol_name = Column(ChoiceType(LendingProtocolNames, impl=String()), nullable=False) collateral_token_price = Column(DECIMAL, nullable=False) collateral_token = Column(String, nullable=False) debt_token = Column(String, nullable=False) diff --git a/apps/data_handler/db/models/loan_states.py b/apps/data_handler/db/models/loan_states.py index c2dbcec5..11376b05 100644 --- a/apps/data_handler/db/models/loan_states.py +++ b/apps/data_handler/db/models/loan_states.py @@ -1,3 +1,4 @@ +""" SQLAlchemy models for the loan_states table. """ from decimal import Decimal from sqlalchemy import Column, Integer, String, UniqueConstraint @@ -29,9 +30,7 @@ class InterestRate(BaseState): def get_json_deserialized(self) -> tuple[dict[str, Decimal], dict[str, Decimal]]: """Deserialize the JSON fields of the model from str to the Decimal type.""" - collateral = { - token_name: Decimal(value) for token_name, value in self.collateral.items() - } + collateral = {token_name: Decimal(value) for token_name, value in self.collateral.items()} debt = {token_name: Decimal(value) for token_name, value in self.debt.items()} return collateral, debt diff --git a/apps/data_handler/db/models/order_book.py b/apps/data_handler/db/models/order_book.py index 46115360..38912031 100644 --- a/apps/data_handler/db/models/order_book.py +++ b/apps/data_handler/db/models/order_book.py @@ -1,3 +1,5 @@ +""" This module contains the OrderBookModel class representing +an order book entry in the database. """ from sqlalchemy import DECIMAL, BigInteger, Column, String from sqlalchemy.types import JSON diff --git a/apps/data_handler/db/models/zklend_events.py b/apps/data_handler/db/models/zklend_events.py index 66d8c058..996dfcf0 100644 --- a/apps/data_handler/db/models/zklend_events.py +++ b/apps/data_handler/db/models/zklend_events.py @@ -1,3 +1,4 @@ +""" This module contains the SQLAlchemy models for the zkLend events. """ from decimal import Decimal from data_handler.db.models.event import EventBaseModel @@ -16,9 +17,7 @@ class AccumulatorsSyncEventData(EventBaseModel): __tablename__ = "accumulators_sync_event_data" token: Mapped[str] = mapped_column(String, nullable=False) - lending_accumulator: Mapped[Decimal] = mapped_column( - Numeric(38, 18), nullable=False - ) + lending_accumulator: Mapped[Decimal] = mapped_column(Numeric(38, 18), nullable=False) debt_accumulator: Mapped[Decimal] = mapped_column(Numeric(38, 18), nullable=False) diff --git a/apps/data_handler/db/schemas.py b/apps/data_handler/db/schemas.py index 25173a2e..45bcede3 100644 --- a/apps/data_handler/db/schemas.py +++ b/apps/data_handler/db/schemas.py @@ -1,3 +1,5 @@ +""" This module contains the data models for the database schema """ + import decimal from decimal import Decimal from typing import Dict, List, Optional @@ -6,6 +8,7 @@ class LoanStateBase(BaseModel): + """ Base class for LoanStateResponse """ protocol_id: str block: int timestamp: int @@ -15,10 +18,12 @@ class LoanStateBase(BaseModel): deposit: Optional[Dict] class Config: + """ Pydantic configuration """ from_attributes = True class LoanStateResponse(LoanStateBase): + """ Pydantic model for LoanStateResponse """ pass diff --git a/apps/data_handler/handler_tools/__init__.py b/apps/data_handler/handler_tools/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handler_tools/__init__.py +++ b/apps/data_handler/handler_tools/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handler_tools/api_connector.py b/apps/data_handler/handler_tools/api_connector.py index b4aa207d..28969e3f 100644 --- a/apps/data_handler/handler_tools/api_connector.py +++ b/apps/data_handler/handler_tools/api_connector.py @@ -1,3 +1,4 @@ +""" Module for making HTTP GET requests to the DeRisk API using the `requests` library. """ import os import requests @@ -21,9 +22,7 @@ def __init__(self): raise ValueError("DERISK_API_URL environment variable is not set") @staticmethod - def _validate_data( - from_address: str, min_block_number: int, max_block_number: int - ) -> None: + def _validate_data(from_address: str, min_block_number: int, max_block_number: int) -> None: """ Check the data for the DeRisk API. :param from_address: From address. @@ -38,9 +37,7 @@ def _validate_data( if not isinstance(max_block_number, int): raise TypeError("max_block_number must be an integer") - def get_data( - self, from_address: str, min_block_number: int, max_block_number: int - ) -> dict: + def get_data(self, from_address: str, min_block_number: int, max_block_number: int) -> dict: """ Retrieves data from the DeRisk API for a given address and block number range. diff --git a/apps/data_handler/handler_tools/constants.py b/apps/data_handler/handler_tools/constants.py index 81cbd3bf..898dc847 100644 --- a/apps/data_handler/handler_tools/constants.py +++ b/apps/data_handler/handler_tools/constants.py @@ -1,3 +1,4 @@ +""" This module contains constants for the data handler """ from dataclasses import dataclass, field from enum import Enum from typing import Set @@ -6,6 +7,7 @@ # Temporary solution. # TODO remove it when all protocols with interest rate models will be available class AvailableProtocolID(Enum): + """class docstring""" # nostra protocols NOSTRA_ALPHA: str = "Nostra_alpha" NOSTRA_MAINNET: str = "Nostra_mainnet" @@ -23,9 +25,8 @@ class ProtocolAddresses: default_factory=lambda: "0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05" ) HASHSTACK_V0_ADDRESSES: Set[str] = field( - default_factory=lambda: { - "0x03dcf5c72ba60eb7b2fe151032769d49dd3df6b04fa3141dffd6e2aa162b7a6e" - } + default_factory=lambda: + {"0x03dcf5c72ba60eb7b2fe151032769d49dd3df6b04fa3141dffd6e2aa162b7a6e"} ) HASHSTACK_V1_R_TOKENS: Set[str] = field( default_factory=lambda: { @@ -110,10 +111,14 @@ class ProtocolAddresses: NOSTRA_EVENTS_MAPPING = { - "Mint": "process_debt_mint_event", - "Burn": "process_debt_burn_event", - "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Burn": "process_debt_burn_event", - "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Mint": "process_debt_mint_event", + "Mint": + "process_debt_mint_event", + "Burn": + "process_debt_burn_event", + "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Burn": + "process_debt_burn_event", + "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Mint": + "process_debt_mint_event", } FIRST_RUNNING_MAPPING = { diff --git a/apps/data_handler/handler_tools/data_parser/__init__.py b/apps/data_handler/handler_tools/data_parser/__init__.py index e69de29b..bc68a398 100644 --- a/apps/data_handler/handler_tools/data_parser/__init__.py +++ b/apps/data_handler/handler_tools/data_parser/__init__.py @@ -0,0 +1 @@ +""" """ diff --git a/apps/data_handler/handler_tools/data_parser/serializers.py b/apps/data_handler/handler_tools/data_parser/serializers.py index 3cbe339b..6181009d 100644 --- a/apps/data_handler/handler_tools/data_parser/serializers.py +++ b/apps/data_handler/handler_tools/data_parser/serializers.py @@ -1,3 +1,4 @@ +""" This module contains the data models and parser class for zkLend data events. """ from decimal import Decimal from pydantic import BaseModel, ValidationInfo, field_validator from shared.helpers import add_leading_zeros @@ -129,7 +130,8 @@ class AccumulatorsSyncEventData(BaseModel): @field_validator("token") def validate_address(cls, value: str, info: ValidationInfo) -> str: """ - Validates if the value is a valid address and formats it to have leading zeros. + Validates if the value is a valid address and formats it to + have leading zeros. Raises: ValueError: If the provided address is invalid. @@ -227,7 +229,8 @@ class BorrowingEventData(BaseModel): @field_validator("user", "token") def validate_address(cls, value: str, info: ValidationInfo) -> str: """ - Validates if the value is a valid address and formats it to have leading zeros. + Validates if the value is a valid address and formats + it to have leading zeros. Raises: ValueError: If the provided address is invalid. @@ -275,7 +278,8 @@ class WithdrawalEventData(BaseModel): @field_validator("user", "token") def validate_addresses(cls, value: str) -> str: """ - Validates that the provided address starts with '0x' and formats it with leading zeros. + Validates that the provided address starts with '0x' and + formats it with leading zeros. Args: value (str): The address string to validate. @@ -310,14 +314,7 @@ def validate_amount(cls, value: str) -> Decimal: class CollateralEnabledDisabledEventData(BaseModel): - """ - A data model representing essential collateral enabled/disabled event data. - - Attributes: - user (str): The user address associated with the collateral enabled/disabled event, represented as a string. - token (str): The token address represented as a string. - """ - + """ Data model representing a collateral enabled/disabled event in the system. """ user: str token: str @@ -332,4 +329,4 @@ def validate_valid_addresses(cls, value: str, info: ValidationInfo) -> str: """ if not value.startswith("0x"): raise ValueError("Invalid address provided for %s" % info.field_name) - return add_leading_zeros(value) + return add_leading_zeros(value) \ No newline at end of file diff --git a/apps/data_handler/handler_tools/data_parser/zklend.py b/apps/data_handler/handler_tools/data_parser/zklend.py index 86bf8090..1a95c821 100644 --- a/apps/data_handler/handler_tools/data_parser/zklend.py +++ b/apps/data_handler/handler_tools/data_parser/zklend.py @@ -151,4 +151,4 @@ def parse_collateral_enabled_disabled_event( return CollateralEnabledDisabledEventData( user=event_data["data"][0], token=event_data["data"][1], - ) + ) \ No newline at end of file diff --git a/apps/data_handler/handler_tools/nostra_alpha_settings.py b/apps/data_handler/handler_tools/nostra_alpha_settings.py index 8db1fb78..04a3938c 100644 --- a/apps/data_handler/handler_tools/nostra_alpha_settings.py +++ b/apps/data_handler/handler_tools/nostra_alpha_settings.py @@ -1,3 +1,4 @@ +""" Settings for the Nostra Alpha data handler. """ # Keys are values of the "key_name" column in the database, values are the respective method names. NOSTRA_ALPHA_EVENTS_TO_METHODS: dict[tuple[str, str], str] = { ("collateral", "Transfer"): "process_collateral_transfer_event", @@ -16,7 +17,6 @@ ("debt", "Burn"): "process_debt_burn_event", } - NOSTRA_ALPHA_EVENTS_TO_ORDER: dict[str, str] = { "InterestStateUpdated": 0, "Transfer": 1, @@ -63,23 +63,34 @@ "0x06d272e18e66289eeb874d0206a23afba148ef35f250accfdfdca085a478aec0" ) -# This seems to be a magical address, it's first event is a withdrawal. Ignore it's loan state changes. +# This seems to be a magical address, it's first event is a withdrawal. +# Ignore it's loan state changes. NOSTRA_ALPHA_DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: str = ( "0x05a0042fa9bb87ed72fbee4d5a2da416528ebc84a569081ad02e9ad60b0af7d7" ) # Source: https://docs.nostra.finance/lend/deployed-contracts/lend-alpha#asset-contracts. NOSTRA_ALPHA_ADDRESSES_TO_EVENTS: dict[str, str] = { - "0x0553cea5d1dc0e0157ffcd36a51a0ced717efdadd5ef1b4644352bb45bd35453": "non_interest_bearing_collateral", - "0x047e794d7c49c49fd2104a724cfa69a92c5a4b50a5753163802617394e973833": "non_interest_bearing_collateral", - "0x003cd2066f3c8b4677741b39db13acebba843bbbaa73d657412102ab4fd98601": "non_interest_bearing_collateral", - "0x04403e420521e7a4ca0dc5192af81ca0bb36de343564a9495e11c8d9ba6e9d17": "non_interest_bearing_collateral", - "0x06b59e2a746e141f90ec8b6e88e695265567ab3bdcf27059b4a15c89b0b7bd53": "non_interest_bearing_collateral", - "0x070f8a4fcd75190661ca09a7300b7c93fab93971b67ea712c664d7948a8a54c6": "interest_bearing_collateral", - "0x029959a546dda754dc823a7b8aa65862c5825faeaaf7938741d8ca6bfdc69e4e": "interest_bearing_collateral", - "0x055ba2baf189b98c59f6951a584a3a7d7d6ff2c4ef88639794e739557e1876f0": "interest_bearing_collateral", - "0x01ac55cabf2b79cf39b17ba0b43540a64205781c4b7850e881014aea6f89be58": "interest_bearing_collateral", - "0x00687b5d9e591844169bc6ad7d7256c4867a10cee6599625b9d78ea17a7caef9": "interest_bearing_collateral", + "0x0553cea5d1dc0e0157ffcd36a51a0ced717efdadd5ef1b4644352bb45bd35453": + "non_interest_bearing_collateral", + "0x047e794d7c49c49fd2104a724cfa69a92c5a4b50a5753163802617394e973833": + "non_interest_bearing_collateral", + "0x003cd2066f3c8b4677741b39db13acebba843bbbaa73d657412102ab4fd98601": + "non_interest_bearing_collateral", + "0x04403e420521e7a4ca0dc5192af81ca0bb36de343564a9495e11c8d9ba6e9d17": + "non_interest_bearing_collateral", + "0x06b59e2a746e141f90ec8b6e88e695265567ab3bdcf27059b4a15c89b0b7bd53": + "non_interest_bearing_collateral", + "0x070f8a4fcd75190661ca09a7300b7c93fab93971b67ea712c664d7948a8a54c6": + "interest_bearing_collateral", + "0x029959a546dda754dc823a7b8aa65862c5825faeaaf7938741d8ca6bfdc69e4e": + "interest_bearing_collateral", + "0x055ba2baf189b98c59f6951a584a3a7d7d6ff2c4ef88639794e739557e1876f0": + "interest_bearing_collateral", + "0x01ac55cabf2b79cf39b17ba0b43540a64205781c4b7850e881014aea6f89be58": + "interest_bearing_collateral", + "0x00687b5d9e591844169bc6ad7d7256c4867a10cee6599625b9d78ea17a7caef9": + "interest_bearing_collateral", "0x040b091cb020d91f4a4b34396946b4d4e2a450dbd9410432ebdbfe10e55ee5e5": "debt", "0x03b6058a9f6029b519bc72b2cc31bcb93ca704d0ab79fec2ae5d43f79ac07f7a": "debt", "0x065c6c7119b738247583286021ea05acc6417aa86d391dcdda21843c1fc6e9c6": "debt", diff --git a/apps/data_handler/handler_tools/nostra_mainnet_settings.py b/apps/data_handler/handler_tools/nostra_mainnet_settings.py index ecc4b275..a02b8b9e 100644 --- a/apps/data_handler/handler_tools/nostra_mainnet_settings.py +++ b/apps/data_handler/handler_tools/nostra_mainnet_settings.py @@ -1,3 +1,4 @@ +"""Settings for the Nostra mainnet.""" # Source: https://docs.nostra.finance/lend/deployed-contracts/lend-mainnet#core-contracts. NOSTRA_MAINNET_INTEREST_RATE_MODEL_ADDRESS: str = ( "0x059a943ca214c10234b9a3b61c558ac20c005127d183b86a99a8f3c60a08b4ff" @@ -71,95 +72,141 @@ "0x073f6addc9339de9822cab4dac8c9431779c09077f02ba7bc36904ea342dd9eb" ) -# This seems to be a magical address, it's first event is a withdrawal. Ignore it's loan state changes. +# This seems to be a magical address, it's first event is a withdrawal. +# Ignore it's loan state changes. NOSTRA_MAINNET_DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: str = ( "0x05fc7053cca20fcb38550d7554c84fa6870e2b9e7ebd66398a67697ba440f12b" ) - -# Keys are tuples of event types and names, values are names of the respective methods that process the given event. +# Keys are tuples of event types and names, values are names of the +# respective methods that process the given event. NOSTRA_MAINNET_EVENTS_TO_METHODS = { - ("collateral", "Transfer"): "process_collateral_transfer_event", + ("collateral", "Transfer"): + "process_collateral_transfer_event", ( "collateral", "openzeppelin::token::erc20_v070::erc20::ERC20::Transfer", - ): "process_collateral_transfer_event", + ): + "process_collateral_transfer_event", ( "collateral", "openzeppelin::token::erc20::erc20::ERC20Component::Transfer", - ): "process_collateral_transfer_event", + ): + "process_collateral_transfer_event", ( "collateral", "nstr::openzeppelin::token::erc20_v070::erc20::ERC20Starkgate::Transfer", - ): "process_collateral_transfer_event", - ("collateral", "Mint"): "process_collateral_mint_event", + ): + "process_collateral_transfer_event", + ("collateral", "Mint"): + "process_collateral_mint_event", ( "collateral", "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Mint", - ): "process_collateral_mint_event", - ("collateral", "Burn"): "process_collateral_burn_event", + ): + "process_collateral_mint_event", + ("collateral", "Burn"): + "process_collateral_burn_event", ( "collateral", "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Burn", - ): "process_collateral_burn_event", - ("debt", "Transfer"): "process_debt_transfer_event", + ): + "process_collateral_burn_event", + ("debt", "Transfer"): + "process_debt_transfer_event", ( "debt", "openzeppelin::token::erc20_v070::erc20::ERC20::Transfer", - ): "process_debt_transfer_event", + ): + "process_debt_transfer_event", ( "debt", "openzeppelin::token::erc20::erc20::ERC20Component::Transfer", - ): "process_debt_transfer_event", + ): + "process_debt_transfer_event", ( "debt", "nstr::openzeppelin::token::erc20_v070::erc20::ERC20Starkgate::Transfer", - ): "process_debt_transfer_event", - ("debt", "Mint"): "process_debt_mint_event", + ): + "process_debt_transfer_event", + ("debt", "Mint"): + "process_debt_mint_event", ( "debt", "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Mint", - ): "process_debt_mint_event", - ("debt", "Burn"): "process_debt_burn_event", + ): + "process_debt_mint_event", + ("debt", "Burn"): + "process_debt_burn_event", ( "debt", "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Burn", - ): "process_debt_burn_event", + ): + "process_debt_burn_event", } # Keys are event names, values denote the order in which the given events should be processed. NOSTRA_MAINNET_EVENTS_TO_ORDER: dict[str, str] = { - "InterestStateUpdated": 0, - "nostra::lending::interest_rate_model::interest_rate_model::InterestRateModel::InterestStateUpdated": 1, - "Transfer": 2, - "openzeppelin::token::erc20_v070::erc20::ERC20::Transfer": 3, - "openzeppelin::token::erc20::erc20::ERC20Component::Transfer": 4, - "nstr::openzeppelin::token::erc20_v070::erc20::ERC20Starkgate::Transfer": 5, - "Burn": 6, - "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Burn": 7, - "Mint": 8, - "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Mint": 9, + "InterestStateUpdated": + 0, + "nostra::lending::interest_rate_model::interest_rate_model::" + "InterestRateModel::InterestStateUpdated": + 1, + "Transfer": + 2, + "openzeppelin::token::erc20_v070::erc20::ERC20::Transfer": + 3, + "openzeppelin::token::erc20::erc20::ERC20Component::Transfer": + 4, + "nstr::openzeppelin::token::erc20_v070::erc20::ERC20Starkgate::Transfer": + 5, + "Burn": + 6, + "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Burn": + 7, + "Mint": + 8, + "nostra::core::tokenization::lib::nostra_token::NostraTokenComponent::Mint": + 9, } NOSTRA_MAINNET_ADDRESSES_TO_EVENTS: dict[str, str] = { - "0x044debfe17e4d9a5a1e226dabaf286e72c9cc36abbe71c5b847e669da4503893": "non_interest_bearing_collateral", - "0x05f296e1b9f4cf1ab452c218e72e02a8713cee98921dad2d3b5706235e128ee4": "non_interest_bearing_collateral", - "0x0514bd7ee8c97d4286bd481c54aa0793e43edbfb7e1ab9784c4b30469dcf9313": "non_interest_bearing_collateral", - "0x005c4676bcb21454659479b3cd0129884d914df9c9b922c1c649696d2e058d70": "non_interest_bearing_collateral", - "0x036b68238f3a90639d062669fdec08c4d0bdd09826b1b6d24ef49de6d8141eaa": "non_interest_bearing_collateral", - "0x05eb6de9c7461b3270d029f00046c8a10d27d4f4a4c931a4ea9769c72ef4edbb": "non_interest_bearing_collateral", - "0x02530a305dd3d92aad5cf97e373a3d07577f6c859337fb0444b9e851ee4a2dd4": "non_interest_bearing_collateral", - "0x040f5a6b7a6d3c472c12ca31ae6250b462c6d35bbdae17bd52f6c6ca065e30cf": "non_interest_bearing_collateral", - "0x0142af5b6c97f02cac9c91be1ea9895d855c5842825cb2180673796e54d73dc5": "non_interest_bearing_collateral", - "0x057146f6409deb4c9fa12866915dd952aa07c1eb2752e451d7f3b042086bdeb8": "interest_bearing_collateral", - "0x05dcd26c25d9d8fd9fc860038dcb6e4d835e524eb8a85213a8cda5b7fff845f6": "interest_bearing_collateral", - "0x0453c4c996f1047d9370f824d68145bd5e7ce12d00437140ad02181e1d11dc83": "interest_bearing_collateral", - "0x04f18ffc850cdfa223a530d7246d3c6fc12a5969e0aa5d4a88f470f5fe6c46e9": "interest_bearing_collateral", - "0x05b7d301fa769274f20e89222169c0fad4d846c366440afc160aafadd6f88f0c": "interest_bearing_collateral", - "0x009377fdde350e01e0397820ea83ed3b4f05df30bfb8cf8055d62cafa1b2106a": "interest_bearing_collateral", - "0x0739760bce37f89b6c1e6b1198bb8dc7166b8cf21509032894f912c9d5de9cbd": "interest_bearing_collateral", - "0x07c2e1e733f28daa23e78be3a4f6c724c0ab06af65f6a95b5e0545215f1abc1b": "interest_bearing_collateral", - "0x067a34ff63ec38d0ccb2817c6d3f01e8b0c4792c77845feb43571092dcf5ebb5": "interest_bearing_collateral", + "0x044debfe17e4d9a5a1e226dabaf286e72c9cc36abbe71c5b847e669da4503893": + "non_interest_bearing_collateral", + "0x05f296e1b9f4cf1ab452c218e72e02a8713cee98921dad2d3b5706235e128ee4": + "non_interest_bearing_collateral", + "0x0514bd7ee8c97d4286bd481c54aa0793e43edbfb7e1ab9784c4b30469dcf9313": + "non_interest_bearing_collateral", + "0x005c4676bcb21454659479b3cd0129884d914df9c9b922c1c649696d2e058d70": + "non_interest_bearing_collateral", + "0x036b68238f3a90639d062669fdec08c4d0bdd09826b1b6d24ef49de6d8141eaa": + "non_interest_bearing_collateral", + "0x05eb6de9c7461b3270d029f00046c8a10d27d4f4a4c931a4ea9769c72ef4edbb": + "non_interest_bearing_collateral", + "0x02530a305dd3d92aad5cf97e373a3d07577f6c859337fb0444b9e851ee4a2dd4": + "non_interest_bearing_collateral", + "0x040f5a6b7a6d3c472c12ca31ae6250b462c6d35bbdae17bd52f6c6ca065e30cf": + "non_interest_bearing_collateral", + "0x0142af5b6c97f02cac9c91be1ea9895d855c5842825cb2180673796e54d73dc5": + "non_interest_bearing_collateral", + "0x057146f6409deb4c9fa12866915dd952aa07c1eb2752e451d7f3b042086bdeb8": + "interest_bearing_collateral", + "0x05dcd26c25d9d8fd9fc860038dcb6e4d835e524eb8a85213a8cda5b7fff845f6": + "interest_bearing_collateral", + "0x0453c4c996f1047d9370f824d68145bd5e7ce12d00437140ad02181e1d11dc83": + "interest_bearing_collateral", + "0x04f18ffc850cdfa223a530d7246d3c6fc12a5969e0aa5d4a88f470f5fe6c46e9": + "interest_bearing_collateral", + "0x05b7d301fa769274f20e89222169c0fad4d846c366440afc160aafadd6f88f0c": + "interest_bearing_collateral", + "0x009377fdde350e01e0397820ea83ed3b4f05df30bfb8cf8055d62cafa1b2106a": + "interest_bearing_collateral", + "0x0739760bce37f89b6c1e6b1198bb8dc7166b8cf21509032894f912c9d5de9cbd": + "interest_bearing_collateral", + "0x07c2e1e733f28daa23e78be3a4f6c724c0ab06af65f6a95b5e0545215f1abc1b": + "interest_bearing_collateral", + "0x067a34ff63ec38d0ccb2817c6d3f01e8b0c4792c77845feb43571092dcf5ebb5": + "interest_bearing_collateral", "0x00ba3037d968790ac486f70acaa9a1cab10cf5843bb85c986624b4d0e5a82e74": "debt", "0x063d69ae657bd2f40337c39bf35a870ac27ddf91e6623c2f52529db4c1619a51": "debt", "0x024e9b0d6bc79e111e6872bb1ada2a874c25712cf08dfc5bcf0de008a7cca55f": "debt", diff --git a/apps/data_handler/handlers/__init__.py b/apps/data_handler/handlers/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/__init__.py +++ b/apps/data_handler/handlers/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/blockchain_call.py b/apps/data_handler/handlers/blockchain_call.py index 9d5d6055..0027ebb0 100644 --- a/apps/data_handler/handlers/blockchain_call.py +++ b/apps/data_handler/handlers/blockchain_call.py @@ -1,3 +1,6 @@ +""" +Provides utility functions for making blockchain calls on StarkNet. +""" import time import starknet_py.cairo.felt @@ -10,6 +13,9 @@ async def func_call(addr, selector, calldata): + """ + Executes a contract call with retry on StarkNet. + """ call = starknet_py.net.client_models.Call( to_addr=addr, selector=starknet_py.hash.selector.get_selector_from_name(selector), @@ -17,20 +23,25 @@ async def func_call(addr, selector, calldata): ) try: res = await NET.call_contract(call) - except: + except BaseException: time.sleep(10) res = await NET.call_contract(call) return res async def balance_of(token_addr, holder_addr): - res = await func_call( - int(token_addr, base=16), "balanceOf", [int(holder_addr, base=16)] - ) + """ + Retrieves the token balance of a specified holder. + """ + res = await func_call(int(token_addr, base=16), "balanceOf", [int(holder_addr, base=16)]) return res[0] async def get_myswap_pool(id): + """ + Fetches details of a MySwap pool by ID. + """ + res = await func_call( 467359278613506166151492726487752216059557962335532790304583050955123345960, "get_pool", diff --git a/apps/data_handler/handlers/events/__init__.py b/apps/data_handler/handlers/events/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/events/__init__.py +++ b/apps/data_handler/handlers/events/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/events/zklend/__init__.py b/apps/data_handler/handlers/events/zklend/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/events/zklend/__init__.py +++ b/apps/data_handler/handlers/events/zklend/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/health_ratio_level/__init__.py b/apps/data_handler/handlers/health_ratio_level/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/health_ratio_level/__init__.py +++ b/apps/data_handler/handlers/health_ratio_level/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/health_ratio_level/health_ratio_handlers.py b/apps/data_handler/handlers/health_ratio_level/health_ratio_handlers.py index 6ac24ab4..1c6b64f8 100644 --- a/apps/data_handler/handlers/health_ratio_level/health_ratio_handlers.py +++ b/apps/data_handler/handlers/health_ratio_level/health_ratio_handlers.py @@ -1,3 +1,4 @@ +""" This module contains the health ratio level handlers for different protocols. """ import asyncio from datetime import datetime from decimal import Decimal @@ -67,11 +68,9 @@ def initialize_loan_entities(self, state: State, data: dict = None) -> State: loan_entity.debt = TokenValues(values=instance.debt) loan_entity.collateral = TokenValues(values=instance.collateral) - state.loan_entities.update( - { - instance.user: loan_entity, - } - ) + state.loan_entities.update({ + instance.user: loan_entity, + }) return state @@ -82,9 +81,7 @@ def health_ratio_is_valid(health_ratio_level: Decimal) -> bool: :param health_ratio_level: Health ratio level :return: bool """ - return health_ratio_level > Decimal("0") and health_ratio_level != Decimal( - "Infinity" - ) + return health_ratio_level > Decimal("0") and health_ratio_level != Decimal("Infinity") class ZkLendHealthRatioHandler(BaseHealthRatioHandler): @@ -103,16 +100,12 @@ def calculate_health_ratio(self) -> list[dict]: Calculates health ratio based on provided data. :return: A list of the ready health ratio data. """ - data, interest_rate_models = self.fetch_data( - protocol_name=ProtocolIDs.ZKLEND.value - ) + data, interest_rate_models = self.fetch_data(protocol_name=ProtocolIDs.ZKLEND.value) state = self.state_class() state = self.initialize_loan_entities(state=state, data=data) # Set up collateral and debt interest rate models - state.collateral_interest_rate_models = TokenValues( - values=interest_rate_models.collateral - ) + state.collateral_interest_rate_models = TokenValues(values=interest_rate_models.collateral) state.debt_interest_rate_models = TokenValues(values=interest_rate_models.debt) current_prices = Prices() @@ -157,25 +150,19 @@ class NostrAlphaHealthRatioHandler(BaseHealthRatioHandler): """ def __init__(self): - super().__init__( - state_class=NostraAlphaState, loan_entity_class=NostraAlphaLoanEntity - ) + super().__init__(state_class=NostraAlphaState, loan_entity_class=NostraAlphaLoanEntity) def calculate_health_ratio(self) -> list[dict]: """ Calculates health ratio based on provided data. :return: A list of the ready health ratio data. """ - data, interest_rate_models = self.fetch_data( - protocol_name=ProtocolIDs.NOSTRA_ALPHA.value - ) + data, interest_rate_models = self.fetch_data(protocol_name=ProtocolIDs.NOSTRA_ALPHA.value) state = self.state_class() state = self.initialize_loan_entities(state=state, data=data) # Set up collateral and debt interest rate models - state.collateral_interest_rate_models = TokenValues( - values=interest_rate_models.collateral - ) + state.collateral_interest_rate_models = TokenValues(values=interest_rate_models.collateral) state.debt_interest_rate_models = TokenValues(values=interest_rate_models.debt) current_prices = Prices() @@ -220,25 +207,19 @@ class NostrMainnetHealthRatioHandler(BaseHealthRatioHandler): """ def __init__(self): - super().__init__( - state_class=NostraMainnetState, loan_entity_class=NostraMainnetLoanEntity - ) + super().__init__(state_class=NostraMainnetState, loan_entity_class=NostraMainnetLoanEntity) def calculate_health_ratio(self) -> list[dict]: """ Calculates health ratio based on provided data. :return: A list of the ready health ratio data. """ - data, interest_rate_models = self.fetch_data( - protocol_name=ProtocolIDs.NOSTRA_MAINNET.value - ) + data, interest_rate_models = self.fetch_data(protocol_name=ProtocolIDs.NOSTRA_MAINNET.value) state = self.state_class() state = self.initialize_loan_entities(state=state, data=data) # Set up collateral and debt interest rate models - state.collateral_interest_rate_models = TokenValues( - values=interest_rate_models.collateral - ) + state.collateral_interest_rate_models = TokenValues(values=interest_rate_models.collateral) state.debt_interest_rate_models = TokenValues(values=interest_rate_models.debt) current_prices = Prices() diff --git a/apps/data_handler/handlers/health_ratio_level/nostra_alpha.py b/apps/data_handler/handlers/health_ratio_level/nostra_alpha.py index ed8feb8e..3f65c23b 100644 --- a/apps/data_handler/handlers/health_ratio_level/nostra_alpha.py +++ b/apps/data_handler/handlers/health_ratio_level/nostra_alpha.py @@ -1,3 +1,4 @@ +""" Module for NostrAlpha health ratio level handler """ from data_handler.handlers.liquidable_debt.values import ( HEALTH_FACTOR_FIELD_NAME, TIMESTAMP_FIELD_NAME, @@ -10,6 +11,7 @@ def run(): + """fn docstring""" handler = NostrAlphaHealthRatioHandler() data = handler.calculate_health_ratio() diff --git a/apps/data_handler/handlers/health_ratio_level/nostra_mainnet.py b/apps/data_handler/handlers/health_ratio_level/nostra_mainnet.py index 9fc2e286..5acd0a3d 100644 --- a/apps/data_handler/handlers/health_ratio_level/nostra_mainnet.py +++ b/apps/data_handler/handlers/health_ratio_level/nostra_mainnet.py @@ -1,3 +1,4 @@ +""" Module for NostrAlpha health ratio level handler """ from data_handler.handlers.liquidable_debt.values import ( HEALTH_FACTOR_FIELD_NAME, TIMESTAMP_FIELD_NAME, @@ -10,6 +11,7 @@ def run(): + """fn docstring""" handler = NostrMainnetHealthRatioHandler() data = handler.calculate_health_ratio() diff --git a/apps/data_handler/handlers/health_ratio_level/zklend.py b/apps/data_handler/handlers/health_ratio_level/zklend.py index 3bede77f..225dbabb 100644 --- a/apps/data_handler/handlers/health_ratio_level/zklend.py +++ b/apps/data_handler/handlers/health_ratio_level/zklend.py @@ -1,3 +1,4 @@ +""" Module for handling health ratio level data for zkLend protocol """ from data_handler.handlers.liquidable_debt.values import ( HEALTH_FACTOR_FIELD_NAME, TIMESTAMP_FIELD_NAME, @@ -10,6 +11,7 @@ def run(): + """fn docstring""" handler = ZkLendHealthRatioHandler() data = handler.calculate_health_ratio() diff --git a/apps/data_handler/handlers/helpers.py b/apps/data_handler/handlers/helpers.py index 8652e9e3..bd23a17a 100644 --- a/apps/data_handler/handlers/helpers.py +++ b/apps/data_handler/handlers/helpers.py @@ -1,3 +1,6 @@ +""" +Helper functions for data handling, blockchain interactions, and Google Cloud Storage operations. +""" import asyncio import decimal import logging @@ -45,7 +48,8 @@ def __init__(self, current_block: int, last_block_data: InterestRate | None): """ Initialize the InterestRateState object. :param current_block: int - The current block number. - :param last_block_data: InterestRate | None - The last block data from storage or None if is not present. + :param last_block_data: InterestRate | None - + The last block data from storage or None if is not present. """ self.last_block_data = last_block_data self.current_block = current_block @@ -62,9 +66,7 @@ def get_seconds_passed(self, token_name: str) -> Decimal: :param token_name: str - The name of the token, for example `STRK`. :return: Decimal - The number of seconds passed. """ - return Decimal( - self.current_timestamp - self.previous_token_timestamps[token_name] - ) + return Decimal(self.current_timestamp - self.previous_token_timestamps[token_name]) def update_state_cumulative_data( self, @@ -77,15 +79,14 @@ def update_state_cumulative_data( Update the state of interest rate calculation with the new data. :param token_name: str - The name of the token, for example `STRK`. :param current_block: int - The current block number. - :param cumulative_collateral_interest_rate_increase: Decimal - The change in collateral(supply) interest rate. - :param cumulative_debt_interest_rate_increase: Decimal - The change in debt(borrow) interest rate. + :param cumulative_collateral_interest_rate_increase: + Decimal - The change in collateral(supply) interest rate. + :param cumulative_debt_interest_rate_increase: + Decimal - The change in debt(borrow) interest rate. """ - self.cumulative_collateral_interest_rates[ - token_name - ] += cumulative_collateral_interest_rate_increase - self.cumulative_debt_interest_rate[ - token_name - ] += cumulative_debt_interest_rate_increase + self.cumulative_collateral_interest_rates[token_name + ] += cumulative_collateral_interest_rate_increase + self.cumulative_debt_interest_rate[token_name] += cumulative_debt_interest_rate_increase self.previous_token_timestamps[token_name] = self.current_timestamp self.current_block = current_block @@ -95,7 +96,8 @@ def _fill_state_data(self) -> None: self._fill_timestamps() def _fill_cumulative_data(self) -> None: - """Fill the cumulative collateral and debt data with latest block data or default values. Default value is 1.""" + """Fill the cumulative collateral and debt data with latest block data or default values. + Default value is 1.""" if self.last_block_data: ( self.cumulative_collateral_interest_rates, @@ -103,14 +105,14 @@ def _fill_cumulative_data(self) -> None: ) = self.last_block_data.get_json_deserialized() else: self.cumulative_collateral_interest_rates = { - token_name: Decimal("1") for token_name in TOKEN_MAPPING.values() + token_name: Decimal("1") + for token_name in TOKEN_MAPPING.values() } - self.cumulative_debt_interest_rate = ( - self.cumulative_collateral_interest_rates.copy() - ) + self.cumulative_debt_interest_rate = (self.cumulative_collateral_interest_rates.copy()) def _fill_timestamps(self) -> None: - """Fill the token timestamps with latest block timestamp or default value. Default value is 0""" + """Fill the token timestamps with latest block timestamp or default value. + Default value is 0""" if self.last_block_data: self.previous_token_timestamps = { token_name: self.last_block_data.timestamp @@ -120,7 +122,8 @@ def _fill_timestamps(self) -> None: # First token event occurrence will update the timestamp, # so after first interest rate for token will be 1. self.previous_token_timestamps = { - token_name: 0 for token_name in TOKEN_MAPPING.values() + token_name: 0 + for token_name in TOKEN_MAPPING.values() } def build_interest_rate_model(self, protocol_id: ProtocolIDs) -> InterestRate: @@ -149,17 +152,21 @@ def _serialize_cumulative_data(self) -> dict[str, dict[str, str]]: return {"collateral": collateral, "debt": debt} -def decimal_range( - start: decimal.Decimal, stop: decimal.Decimal, step: decimal.Decimal -) -> Iterator[decimal.Decimal]: +def decimal_range(start: decimal.Decimal, stop: decimal.Decimal, + step: decimal.Decimal) -> Iterator[decimal.Decimal]: + """ + Generates a range of decimals from start to stop with a given step. + """ while start < stop: yield start start += step -def get_range( - start: decimal.Decimal, stop: decimal.Decimal, step: decimal.Decimal -) -> list[decimal.Decimal]: +def get_range(start: decimal.Decimal, stop: decimal.Decimal, + step: decimal.Decimal) -> list[decimal.Decimal]: + """ + Returns a list of decimal values from start to stop with a specified step. + """ return [x for x in decimal_range(start=start, stop=stop, step=step)] @@ -167,6 +174,9 @@ def get_collateral_token_range( collateral_token: str, collateral_token_price: decimal.Decimal, ) -> list[decimal.Decimal]: + """ + Returns a range of collateral token values based on price and token type. + """ assert collateral_token in {"ETH", "wBTC", "STRK"} TOKEN_STEP = { "ETH": decimal.Decimal("50"), @@ -183,15 +193,16 @@ def get_collateral_token_range( def load_data( protocol: str, ) -> tuple[dict[str, pandas.DataFrame], pandas.DataFrame, pandas.DataFrame]: + """ + Loads data for a specified protocol from Google Cloud Storage as DataFrames. + """ directory = f"{protocol.lower().replace(' ', '_')}_data" main_chart_data = {} for pair in PAIRS: main_chart_data[pair] = pandas.read_parquet( f"gs://{GS_BUCKET_NAME}/{directory}/{pair}.parquet" ) - histogram_data = pandas.read_parquet( - f"gs://{GS_BUCKET_NAME}/{directory}/histogram.parquet" - ) + histogram_data = pandas.read_parquet(f"gs://{GS_BUCKET_NAME}/{directory}/histogram.parquet") loans_data = pandas.read_parquet(f"gs://{GS_BUCKET_NAME}/{directory}/loans.parquet") return ( main_chart_data, @@ -205,15 +216,18 @@ def get_symbol(address: str, protocol: str | None = None) -> str: """ Get the symbol of the token by its address. - This function takes an address and an optional protocol as input, and returns the symbol of the token. + This function takes an address and an optional protocol as input, + and returns the symbol of the token. If the address is not found in the symbol table, it raises a KeyError. - If a protocol is provided and the address is not found, it also sends an error message to a Telegram bot. + If a protocol is provided and the address is not found, + it also sends an error message to a Telegram bot. :param address: str - The address of the token. :param protocol: str | None - The name of the protocol. :return: str - The symbol of the token. :raises KeyError: If the address is not found in the symbol table. - :note: If the address is not found and a protocol is provided, an error message will be sent to a Telegram bot. + :note: If the address is not found and a protocol is provided, + an error message will be sent to a Telegram bot. """ # A tuple of that always has this order: `address`, `protocol`. @@ -232,9 +246,7 @@ def get_symbol(address: str, protocol: str | None = None) -> str: ERROR_LOGS.update({error_info}) asyncio.run( BOT.send_message( - MessageTemplates.NEW_TOKEN_MESSAGE.format( - protocol_name=protocol, address=address - ) + MessageTemplates.NEW_TOKEN_MESSAGE.format(protocol_name=protocol, address=address) ) ) @@ -242,11 +254,11 @@ def get_symbol(address: str, protocol: str | None = None) -> str: async def get_async_symbol(token_address: str) -> str: + """ + Retrieves the symbol of a token asynchronously by its address. + """ # DAI V2's symbol is `DAI` but we don't want to mix it with DAI = DAI V1. - if ( - token_address - == "0x05574eb6b8789a91466f902c380d978e472db68170ff82a5b650b95a58ddf4ad" - ): + if (token_address == "0x05574eb6b8789a91466f902c380d978e472db68170ff82a5b650b95a58ddf4ad"): return "DAI V2" symbol = await blockchain_call.func_call( addr=token_address, @@ -274,9 +286,7 @@ def upload_file_to_bucket(source_path: str, target_path: str) -> None: os.getenv("CREDENTIALS_PATH", "") ) except FileNotFoundError as e: - logger.info( - f"Failed to initialize the Google Cloud Storage client due to an error: {e}" - ) + logger.info(f"Failed to initialize the Google Cloud Storage client due to an error: {e}") raise FileNotFoundError # Get the target bucket. @@ -285,12 +295,14 @@ def upload_file_to_bucket(source_path: str, target_path: str) -> None: # Upload the file to the bucket. blob = bucket.blob(target_path) blob.upload_from_filename(source_path) - logger.info( - f"File = {source_path} uploaded to = gs://{GS_BUCKET_NAME}/{target_path}" - ) + logger.info(f"File = {source_path} uploaded to = gs://{GS_BUCKET_NAME}/{target_path}") def save_dataframe(data: pandas.DataFrame, path: str) -> None: + """ + Saves a DataFrame to a local file, uploads it to Google Cloud Storage, and + deletes the local file. + """ directory = path.rstrip(path.split("/")[-1]) if not directory == "": os.makedirs(directory, exist_ok=True) @@ -314,15 +326,12 @@ def get_addresses( # Up to 2 addresses can match the given `underlying_address` or `underlying_symbol`. if underlying_address: addresses = [ - x.address - for x in token_parameters.values() + x.address for x in token_parameters.values() if x.underlying_address == underlying_address ] elif underlying_symbol: addresses = [ - x.address - for x in token_parameters.values() - if x.underlying_symbol == underlying_symbol + x.address for x in token_parameters.values() if x.underlying_symbol == underlying_symbol ] else: raise ValueError( diff --git a/apps/data_handler/handlers/liquidable_debt/__init__.py b/apps/data_handler/handlers/liquidable_debt/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/liquidable_debt/__init__.py +++ b/apps/data_handler/handlers/liquidable_debt/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/liquidable_debt/bases.py b/apps/data_handler/handlers/liquidable_debt/bases.py index 1cbef8ed..3e1b2478 100644 --- a/apps/data_handler/handlers/liquidable_debt/bases.py +++ b/apps/data_handler/handlers/liquidable_debt/bases.py @@ -1,3 +1,4 @@ +""" Base class for collectors. """ from abc import ABC, abstractmethod @@ -10,4 +11,5 @@ class Collector(ABC): @abstractmethod def collect_data(self, *args, **kwargs): + """fn docstring""" pass diff --git a/apps/data_handler/handlers/liquidable_debt/collectors.py b/apps/data_handler/handlers/liquidable_debt/collectors.py index fb8f7be5..539bfdf7 100644 --- a/apps/data_handler/handlers/liquidable_debt/collectors.py +++ b/apps/data_handler/handlers/liquidable_debt/collectors.py @@ -1,3 +1,4 @@ +""" This module contains the data collectors for the liquidable debt data handler. """ from typing import Iterable import dask.dataframe as dd @@ -7,6 +8,7 @@ class GoogleCloudDataCollector(Collector): + """class docstring""" LS_MANAGER = LocalStorageManager @classmethod @@ -54,9 +56,7 @@ def _download_file( :return: None """ - data = dd.read_parquet( - url.format(bucket_name=bucket_name, protocol_name=protocol_name) - ) + data = dd.read_parquet(url.format(bucket_name=bucket_name, protocol_name=protocol_name)) dd.to_parquet(df=data, path=path.format(protocol_name=protocol_name)) @classmethod @@ -79,8 +79,8 @@ def _check_protocol_existence( :return: None """ if not cls._protocol_exists( - protocol_name=protocol_name, - available_protocols=available_protocols, + protocol_name=protocol_name, + available_protocols=available_protocols, ): raise ProtocolExistenceError(protocol=protocol_name) @@ -99,6 +99,7 @@ def _protocol_exists( class DBDataCollector(Collector): + """class docstring""" # TODO write logic when it will be needed def collect_data(self): pass diff --git a/apps/data_handler/handlers/liquidable_debt/debt_handlers.py b/apps/data_handler/handlers/liquidable_debt/debt_handlers.py index 4beb2d72..a0a2e02e 100644 --- a/apps/data_handler/handlers/liquidable_debt/debt_handlers.py +++ b/apps/data_handler/handlers/liquidable_debt/debt_handlers.py @@ -1,3 +1,4 @@ +""" This module contains the classes that handle the liquidable debt data. """ import asyncio from decimal import Decimal from typing import Iterable, Type @@ -34,9 +35,7 @@ def __init__(self, *args, **kwargs): self.db_connector = DBConnector() @staticmethod - def get_prices_range( - collateral_token_name: str, current_price: Decimal - ) -> Iterable[Decimal]: + def get_prices_range(collateral_token_name: str, current_price: Decimal) -> Iterable[Decimal]: """ Get prices range based on the current price. :param current_price: Decimal - The current pair price. @@ -48,9 +47,7 @@ def get_prices_range( if collateral_token_name in collateral_tokens: return get_collateral_token_range(collateral_token_name, current_price) - return get_range( - Decimal(0), current_price * Decimal("1.3"), Decimal(current_price / 100) - ) + return get_range(Decimal(0), current_price * Decimal("1.3"), Decimal(current_price / 100)) def initialize_loan_entities(self, state: State, data: dict = None) -> State: """ @@ -65,11 +62,9 @@ def initialize_loan_entities(self, state: State, data: dict = None) -> State: loan_entity.debt = TokenValues(values=instance.debt) loan_entity.collateral = TokenValues(values=instance.collateral) - state.loan_entities.update( - { - instance.user: loan_entity, - } - ) + state.loan_entities.update({ + instance.user: loan_entity, + }) return state @@ -118,9 +113,7 @@ def calculate_liquidable_debt(self, protocol_name: str = None) -> list: state = self.initialize_loan_entities(state=state, data=data) # Set up collateral and debt interest rate models - state.collateral_interest_rate_models = TokenValues( - values=interest_rate_models.collateral - ) + state.collateral_interest_rate_models = TokenValues(values=interest_rate_models.collateral) state.debt_interest_rate_models = TokenValues(values=interest_rate_models.debt) current_prices = Prices() @@ -183,9 +176,7 @@ def calculate_liquidable_debt(self, protocol_name: str = None) -> list: state = self.initialize_loan_entities(state=state, data=data) # Set up collateral and debt interest rate models - state.collateral_interest_rate_models = TokenValues( - values=interest_rate_models.collateral - ) + state.collateral_interest_rate_models = TokenValues(values=interest_rate_models.collateral) state.debt_interest_rate_models = TokenValues(values=interest_rate_models.debt) current_prices = Prices() @@ -248,9 +239,7 @@ def calculate_liquidable_debt(self, protocol_name: str = None) -> list: state = self.initialize_loan_entities(state=state, data=data) # Set up collateral and debt interest rate models - state.collateral_interest_rate_models = TokenValues( - values=interest_rate_models.collateral - ) + state.collateral_interest_rate_models = TokenValues(values=interest_rate_models.collateral) state.debt_interest_rate_models = TokenValues(values=interest_rate_models.debt) current_prices = Prices() @@ -310,9 +299,7 @@ def initialize_loan_entities(self, state: State, data: dict = None): """ for instance in data: - hashstack_loan_state = self.db_connector.get_last_hashstack_loan_state( - instance.user - ) + hashstack_loan_state = self.db_connector.get_last_hashstack_loan_state(instance.user) if debt_category := hashstack_loan_state.debt_category: loan_entity = self.loan_entity_class( @@ -322,11 +309,9 @@ def initialize_loan_entities(self, state: State, data: dict = None): loan_entity.debt = TokenValues(values=instance.debt) loan_entity.collateral = TokenValues(values=instance.collateral) - state.loan_entities.update( - { - instance.user: loan_entity, - } - ) + state.loan_entities.update({ + instance.user: loan_entity, + }) def calculate_liquidable_debt(self, protocol_name: str = None) -> list: """ @@ -340,9 +325,7 @@ def calculate_liquidable_debt(self, protocol_name: str = None) -> list: self.initialize_loan_entities(state=state, data=data) # Set up collateral and debt interest rate models - state.collateral_interest_rate_models = TokenValues( - values=interest_rate_models.collateral - ) + state.collateral_interest_rate_models = TokenValues(values=interest_rate_models.collateral) state.debt_interest_rate_models = TokenValues(values=interest_rate_models.debt) current_prices = Prices() diff --git a/apps/data_handler/handlers/liquidable_debt/exceptions.py b/apps/data_handler/handlers/liquidable_debt/exceptions.py index b7c21ad2..d71fc905 100644 --- a/apps/data_handler/handlers/liquidable_debt/exceptions.py +++ b/apps/data_handler/handlers/liquidable_debt/exceptions.py @@ -1,3 +1,5 @@ +""" This module contains the exceptions that can be raised by the liquidable_debt handler.""" + class ProtocolExistenceError(Exception): """ An exception that should be raised when a given protocol doesn't exist. diff --git a/apps/data_handler/handlers/liquidable_debt/managers.py b/apps/data_handler/handlers/liquidable_debt/managers.py index e593c06c..64159928 100644 --- a/apps/data_handler/handlers/liquidable_debt/managers.py +++ b/apps/data_handler/handlers/liquidable_debt/managers.py @@ -1,3 +1,4 @@ +""" This module contains the manager for liquidable debts. """ import os import shutil diff --git a/apps/data_handler/handlers/liquidable_debt/protocols/__init__.py b/apps/data_handler/handlers/liquidable_debt/protocols/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/liquidable_debt/protocols/__init__.py +++ b/apps/data_handler/handlers/liquidable_debt/protocols/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/liquidable_debt/protocols/hashstack_v0.py b/apps/data_handler/handlers/liquidable_debt/protocols/hashstack_v0.py index 6b2fde1c..be8be8d4 100644 --- a/apps/data_handler/handlers/liquidable_debt/protocols/hashstack_v0.py +++ b/apps/data_handler/handlers/liquidable_debt/protocols/hashstack_v0.py @@ -1,3 +1,4 @@ +""" This module contains the script to compute liquidable debt for Hashstack V0 protocol. """ from data_handler.handlers.liquidable_debt.debt_handlers import ( HashstackV0DBLiquidableDebtDataHandler, ) @@ -25,9 +26,7 @@ def run() -> None: loan_state_class=HashstackV0State, loan_entity_class=HashstackV0LoanEntity ) - data = handler.calculate_liquidable_debt( - protocol_name=ProtocolIDs.HASHSTACK_V0.value - ) + data = handler.calculate_liquidable_debt(protocol_name=ProtocolIDs.HASHSTACK_V0.value) for liquidable_debt_info in data: db_row = LiquidableDebt( diff --git a/apps/data_handler/handlers/liquidable_debt/protocols/hashstack_v1.py b/apps/data_handler/handlers/liquidable_debt/protocols/hashstack_v1.py index be1b6b4b..7b39ae28 100644 --- a/apps/data_handler/handlers/liquidable_debt/protocols/hashstack_v1.py +++ b/apps/data_handler/handlers/liquidable_debt/protocols/hashstack_v1.py @@ -1,3 +1,4 @@ +""" This module contains the script for computing liquidable debt for Hashstack v1 protocol. """ from data_handler.handlers.liquidable_debt.values import ( COLLATERAL_FIELD_NAME, GS_BUCKET_NAME, @@ -22,9 +23,7 @@ def run() -> None: bucket_name=GS_BUCKET_NAME, ) - data = handler.prepare_data( - protocol_name=LendingProtocolNames.HASHSTACK_V1.value, - ) + data = handler.prepare_data(protocol_name=LendingProtocolNames.HASHSTACK_V1.value, ) for debt_token, liquidable_debt_info in data.items(): db_row = LiquidableDebt( diff --git a/apps/data_handler/handlers/liquidable_debt/protocols/nostra_alpha.py b/apps/data_handler/handlers/liquidable_debt/protocols/nostra_alpha.py index ab21d9f2..c5838490 100644 --- a/apps/data_handler/handlers/liquidable_debt/protocols/nostra_alpha.py +++ b/apps/data_handler/handlers/liquidable_debt/protocols/nostra_alpha.py @@ -1,3 +1,4 @@ +""" This script is used to calculate the liquidable debt for Nostra Alpha protocol. """ from data_handler.handlers.liquidable_debt.debt_handlers import ( NostraAlphaDBLiquidableDebtDataHandler, ) @@ -25,9 +26,7 @@ def run() -> None: loan_state_class=NostraAlphaState, loan_entity_class=NostraAlphaLoanEntity ) - data = handler.calculate_liquidable_debt( - protocol_name=ProtocolIDs.NOSTRA_ALPHA.value - ) + data = handler.calculate_liquidable_debt(protocol_name=ProtocolIDs.NOSTRA_ALPHA.value) for liquidable_debt_info in data: db_row = LiquidableDebt( diff --git a/apps/data_handler/handlers/liquidable_debt/protocols/nostra_mainnet.py b/apps/data_handler/handlers/liquidable_debt/protocols/nostra_mainnet.py index ab4bc367..be52d34c 100644 --- a/apps/data_handler/handlers/liquidable_debt/protocols/nostra_mainnet.py +++ b/apps/data_handler/handlers/liquidable_debt/protocols/nostra_mainnet.py @@ -1,3 +1,4 @@ +""" This script is used to compute the liquidable debt for the Nostra Mainnet protocol. """ from data_handler.handlers.liquidable_debt.debt_handlers import ( NostraMainnetDBLiquidableDebtDataHandler, ) @@ -25,9 +26,7 @@ def run() -> None: loan_state_class=NostraMainnetState, loan_entity_class=NostraMainnetLoanEntity ) - data = handler.calculate_liquidable_debt( - protocol_name=ProtocolIDs.NOSTRA_MAINNET.value - ) + data = handler.calculate_liquidable_debt(protocol_name=ProtocolIDs.NOSTRA_MAINNET.value) for liquidable_debt_info in data: db_row = LiquidableDebt( diff --git a/apps/data_handler/handlers/liquidable_debt/protocols/zklend.py b/apps/data_handler/handlers/liquidable_debt/protocols/zklend.py index 6c4ccc5f..fb805790 100644 --- a/apps/data_handler/handlers/liquidable_debt/protocols/zklend.py +++ b/apps/data_handler/handlers/liquidable_debt/protocols/zklend.py @@ -1,3 +1,4 @@ +""" This script is used to compute the liquidable debt for the zKlend protocol. """ import logging from data_handler.handlers.liquidable_debt.debt_handlers import ( diff --git a/apps/data_handler/handlers/liquidable_debt/utils.py b/apps/data_handler/handlers/liquidable_debt/utils.py index 9c370edf..ad85adc2 100644 --- a/apps/data_handler/handlers/liquidable_debt/utils.py +++ b/apps/data_handler/handlers/liquidable_debt/utils.py @@ -1,3 +1,4 @@ +""" This module contains classes to fetch token prices and LP token prices. """ import time from decimal import Decimal @@ -28,9 +29,7 @@ async def balance_of(token_addr: str, holder_addr: str) -> int: :param holder_addr: address of holder :return: balance of `token_addr` and `holder_addr` """ - res = await func_call( - int(token_addr, base=16), "balanceOf", [int(holder_addr, base=16)] - ) + res = await func_call(int(token_addr, base=16), "balanceOf", [int(holder_addr, base=16)]) return res[0] @@ -42,12 +41,10 @@ async def func_call(addr: int, selector: int, calldata: list[int] = None) -> lis :param calldata: The data to call the contract function with. :return: A list of the results of the contract function call. """ - call = Call( - to_addr=addr, selector=get_selector_from_name(selector), calldata=calldata - ) + call = Call(to_addr=addr, selector=get_selector_from_name(selector), calldata=calldata) try: res = await NET.call_contract(call) - except: + except BaseException: time.sleep(10) res = await NET.call_contract(call) return res @@ -55,8 +52,10 @@ async def func_call(addr: int, selector: int, calldata: list[int] = None) -> lis class JediSwapPool: """ - This class implements JediSwap pools where Hashstack V1 users can spend their debt. To properly account for their - token holdings, we collect the total supply of LP tokens and the amounts of both tokens in the pool. + This class implements JediSwap pools where Hashstack V1 users can + spend their debt. To properly account for their + token holdings, we collect the total supply of LP tokens and the amounts of both + tokens in the pool. """ def __init__(self, settings: JediSwapPoolSettings) -> None: @@ -71,13 +70,11 @@ async def get_data(self) -> None: :return: None """ self.total_lp_supply = Decimal( - ( - await func_call( - addr=self.settings.address, - selector="totalSupply", - calldata=[], - ) - )[0] + (await func_call( + addr=self.settings.address, + selector="totalSupply", + calldata=[], + ))[0] ) self.token_amounts = TokenValues() for token in [self.settings.token_1, self.settings.token_2]: @@ -91,8 +88,10 @@ async def get_data(self) -> None: class MySwapPool: """ - This class implements MySwap pools where Hashstack V1 users can spend their debt. To properly account for their - token holdings, we collect the total supply of LP tokens and the amounts of both tokens in the pool. + This class implements MySwap pools where Hashstack V1 users can spend their debt. + To properly account for their + token holdings, we collect the total supply of LP tokens and the amounts of both tokens + in the pool. """ def __init__(self, settings: MySwapPoolSettings) -> None: @@ -116,7 +115,8 @@ async def get_data(self) -> None: )[0] ) self.token_amounts = TokenValues() - # The order of the values returned is: `name`, `token_a_address`, `token_a_reserves`, ``, `token_b_address`, + # The order of the values returned is: `name`, `token_a_address`, + # `token_a_reserves`, ``, `token_b_address`, # `token_b_reserves`, ``, `fee_percentage`, `cfmm_type`, `liq_token`. pool = await func_call( addr=self.settings.address, @@ -132,7 +132,8 @@ async def get_data(self) -> None: class LPTokenPools: """ This class initializes all JediSwap and MySwap pools configured in `JEDISWAP_POOL_SETTINGS` and - `MYSWAP_POOL_SETTINGS` and collects the total supply of LP tokens and the amounts of both tokens in each pool. + `MYSWAP_POOL_SETTINGS` and collects the total supply of LP + tokens and the amounts of both tokens in each pool. """ def __init__(self) -> None: @@ -149,7 +150,8 @@ def __init__(self) -> None: async def get_data(self) -> None: """ - This method collects the total supply of LP tokens and the amounts of both tokens in the pool. + This method collects the total supply of LP tokens + and the amounts of both tokens in the pool. :return: None """ for pool in self.pools.values(): @@ -157,7 +159,9 @@ async def get_data(self) -> None: class Prices: - URL = "https://api.coingecko.com/api/v3/simple/price?ids={token_ids}&vs_currencies={vs_currency}" + """ This class fetches token prices from Coingecko API. """ + URL = "https://api.coingecko.com/api/v3/simple/price"\ + "?ids={token_ids}&vs_currencies={vs_currency}" def __init__(self): self.tokens = [ @@ -190,9 +194,7 @@ def get_prices(self) -> None: (id, symbol) = token self.prices.values[symbol] = Decimal(data[id][self.vs_currency]) else: - raise Exception( - f"Failed getting prices, status code = {response.status_code}." - ) + raise Exception(f"Failed getting prices, status code = {response.status_code}.") async def get_lp_token_prices(self) -> None: """ @@ -220,18 +222,14 @@ def _get_lp_token_price( token_1 = get_symbol(pool.settings.token_1) token_2 = get_symbol(pool.settings.token_2) token_1_value = ( - pool.token_amounts.values[token_1] - / TOKEN_SETTINGS[token_1].decimal_factor - * prices.values[token_1] + pool.token_amounts.values[token_1] / TOKEN_SETTINGS[token_1].decimal_factor * + prices.values[token_1] ) token_2_value = ( - pool.token_amounts.values[token_2] - / TOKEN_SETTINGS[token_2].decimal_factor - * prices.values[token_2] + pool.token_amounts.values[token_2] / TOKEN_SETTINGS[token_2].decimal_factor * + prices.values[token_2] ) return (token_1_value + token_2_value) / ( - pool.total_lp_supply - / HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS[ - pool.settings.symbol - ].decimal_factor + pool.total_lp_supply / + HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS[pool.settings.symbol].decimal_factor ) diff --git a/apps/data_handler/handlers/liquidable_debt/values.py b/apps/data_handler/handlers/liquidable_debt/values.py index de1d819c..510b6361 100644 --- a/apps/data_handler/handlers/liquidable_debt/values.py +++ b/apps/data_handler/handlers/liquidable_debt/values.py @@ -1,3 +1,4 @@ +""" Module for storing constants and values used in the liquidable_debt module. """ import os from enum import Enum @@ -41,6 +42,7 @@ class LendingProtocolNames(Enum): + """class docstring""" HASHSTACK_V0: str = "Hashstack_v0" HASHSTACK_V1: str = "Hashstack_v1" NOSTRA_ALPHA: str = "Nostra_alpha" diff --git a/apps/data_handler/handlers/loan_states/__init__.py b/apps/data_handler/handlers/loan_states/__init__.py index 819f15ee..c7093471 100644 --- a/apps/data_handler/handlers/loan_states/__init__.py +++ b/apps/data_handler/handlers/loan_states/__init__.py @@ -1,3 +1,5 @@ +"""Module docstring placeholder.""" + from data_handler.handlers.loan_states.hashtack_v0.events import HashstackV0State from data_handler.handlers.loan_states.hashtack_v1.events import HashstackV1State from data_handler.handlers.loan_states.nostra_alpha.events import NostraAlphaState diff --git a/apps/data_handler/handlers/loan_states/abstractions.py b/apps/data_handler/handlers/loan_states/abstractions.py index 0f9c305c..a3b2b2b7 100644 --- a/apps/data_handler/handlers/loan_states/abstractions.py +++ b/apps/data_handler/handlers/loan_states/abstractions.py @@ -1,3 +1,5 @@ +""" This module contains the base class for computing loan states +based on data from a DeRisk API. """ import logging from abc import ABC, abstractmethod from typing import Dict, Optional @@ -37,9 +39,7 @@ def __init__(self): self.last_block = self.db_connector.get_last_block(self.PROTOCOL_TYPE) self.interest_rate_result: list = [] - def process_interest_rate_event( - self, instance_state: State, event: pd.Series - ) -> None: + def process_interest_rate_event(self, instance_state: State, event: pd.Series) -> None: """ Processes an interest rate event. @@ -62,9 +62,7 @@ def process_data(self, data: list[dict]) -> pd.DataFrame: """ pass - def process_event( - self, instance_state: State, method_name: str, event: pd.Series - ) -> None: + def process_event(self, instance_state: State, method_name: str, event: pd.Series) -> None: """ Processes an event based on the method name and the event data. @@ -86,9 +84,7 @@ def process_event( if method: method(event) else: - logger.debug( - f"No method named {method_name} found for processing event." - ) + logger.debug(f"No method named {method_name} found for processing event.") except Exception as e: logger.exception(f"Failed to process event due to an error: {e}") @@ -111,9 +107,7 @@ def get_data(self, from_address: str, min_block: int) -> list: max_block_number=min_block + self.PAGINATION_SIZE, ) - def get_addresses_data( - self, from_addresses: list[str], min_block: int - ) -> list[dict]: + def get_addresses_data(self, from_addresses: list[str], min_block: int) -> list[dict]: """ Fetches data from the DeRisk API endpoint using the defined protocol address. This method must be implemented by subclasses to specify how data is retrieved from the API. @@ -194,16 +188,15 @@ def get_result_df(self, loan_entities: dict) -> pd.DataFrame: { token: float(amount) for token, amount in loan.collateral.values.items() - } - for loan in loan_entities_values + } for loan in loan_entities_values ], "block": [entity.extra_info.block for entity in loan_entities_values], - "timestamp": [ - entity.extra_info.timestamp for entity in loan_entities_values - ], + "timestamp": [entity.extra_info.timestamp for entity in loan_entities_values], "debt": [ - {token: float(amount) for token, amount in loan.debt.values.items()} - for loan in loan_entities_values + { + token: float(amount) + for token, amount in loan.debt.values.items() + } for loan in loan_entities_values ], } result_df = pd.DataFrame(result_dict) @@ -230,9 +223,7 @@ def add_interest_rate_data(self, state_instance: State, event: pd.Series) -> Non } ) - def set_interest_rate( - self, instance_state: State, block: int, protocol_type: str - ) -> None: + def set_interest_rate(self, instance_state: State, block: int, protocol_type: str) -> None: """ Sets the interest rate for the zkLend protocol. @@ -249,11 +240,8 @@ def set_interest_rate( interest_rate_data = self.db_connector.get_interest_rate_by_block( block_number=block, protocol_id=protocol_type ) - if ( - interest_rate_data - and instance_state.last_interest_rate_block_number - != interest_rate_data.block - ): + if (interest_rate_data + and instance_state.last_interest_rate_block_number != interest_rate_data.block): ( collateral_interest_rate, debt_interest_rate, @@ -261,9 +249,7 @@ def set_interest_rate( instance_state.interest_rate_models.collateral = InterestRateModels( collateral_interest_rate ) - instance_state.interest_rate_models.debt = InterestRateModels( - debt_interest_rate - ) + instance_state.interest_rate_models.debt = InterestRateModels(debt_interest_rate) instance_state.last_interest_rate_block_number = block def run(self) -> None: @@ -323,26 +309,21 @@ def get_result_df(self, loan_entities: dict) -> pd.DataFrame: result_dict = { "protocol": [self.PROTOCOL_TYPE for _ in filtered_loan_entities.keys()], - "user": [ - loan_entity.user for loan_entity in filtered_loan_entities.values() - ], + "user": [loan_entity.user for loan_entity in filtered_loan_entities.values()], "collateral": [ { token: float(amount) for token, amount in loan.collateral.values.items() - } - for loan in filtered_loan_entities.values() - ], - "block": [ - entity.extra_info.block for entity in filtered_loan_entities.values() - ], - "timestamp": [ - entity.extra_info.timestamp - for entity in filtered_loan_entities.values() + } for loan in filtered_loan_entities.values() ], + "block": [entity.extra_info.block for entity in filtered_loan_entities.values()], + "timestamp": + [entity.extra_info.timestamp for entity in filtered_loan_entities.values()], "debt": [ - {token: float(amount) for token, amount in loan.debt.values.items()} - for loan in filtered_loan_entities.values() + { + token: float(amount) + for token, amount in loan.debt.values.items() + } for loan in filtered_loan_entities.values() ], } diff --git a/apps/data_handler/handlers/loan_states/hashtack_v0/__init__.py b/apps/data_handler/handlers/loan_states/hashtack_v0/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/loan_states/hashtack_v0/__init__.py +++ b/apps/data_handler/handlers/loan_states/hashtack_v0/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/loan_states/hashtack_v0/events.py b/apps/data_handler/handlers/loan_states/hashtack_v0/events.py index 26ae6f32..c1c493d9 100644 --- a/apps/data_handler/handlers/loan_states/hashtack_v0/events.py +++ b/apps/data_handler/handlers/loan_states/hashtack_v0/events.py @@ -1,3 +1,9 @@ +""" +Defines classes and methods for handling events +and loan entities in Hashstack V0, tracking user loans, debt, +and collateral changes through events. +""" + import copy import dataclasses import decimal @@ -18,6 +24,8 @@ @dataclasses.dataclass class HashstackV0SpecificTokenSettings: + """Defines collateral and debt factors specific to Hashstack V0 tokens.""" + # These are set to neutral values because Hashstack V0 doesn't use collateral factors. collateral_factor: decimal.Decimal # These are set to neutral values because Hashstack V0 doesn't use debt factors. @@ -26,6 +34,8 @@ class HashstackV0SpecificTokenSettings: @dataclasses.dataclass class TokenSettings(HashstackV0SpecificTokenSettings, TokenSettings): + """Custom settings for Hashstack V0 tokens, inheriting collateral and debt factors.""" + pass @@ -72,7 +82,6 @@ class TokenSettings(HashstackV0SpecificTokenSettings, TokenSettings): for token in TOKEN_SETTINGS } - # Keys are values of the "key_name" column in the database, values are the respective method names. EVENTS_METHODS_MAPPING: dict[str, str] = { "new_loan": "process_new_loan_event", @@ -88,13 +97,20 @@ class TokenSettings(HashstackV0SpecificTokenSettings, TokenSettings): class HashstackV0LoanEntity(LoanEntity): """ - A class that describes the Hashstack V0 loan entity. On top of the abstract `LoanEntity`, it implements the `user`, - `debt_category`, `original_collateral` and `borrowed_collateral` attributes in order to help with accounting for - the changes in collateral. This is because under Hashstack V0, each user can have multiple loans which are treated - completely separately (including liquidations). The `debt_category` attribute determines liquidation conditions. - Also, because Hashstack V0 provides leverage to its users, we split `collateral` into `original_collateral` - (collateral deposited by the user directly) and `borrowed_collateral` (the current state, i.e. token and amount of - the borrowed funds). We also use face amounts (no need to convert amounts using interest rates) because Hashstack + A class that describes the Hashstack V0 loan entity. On top of the abstract `LoanEntity`, + it implements the `user`, + `debt_category`, `original_collateral` and `borrowed_collateral` attributes in order to help + with accounting for + the changes in collateral. This is because under Hashstack V0, each user can have multiple + loans which are treated + completely separately (including liquidations). The `debt_category` attribute determines + liquidation conditions. + Also, because Hashstack V0 provides leverage to its users, we split `collateral` into + `original_collateral` + (collateral deposited by the user directly) and `borrowed_collateral` + (the current state, i.e. token and amount of + the borrowed funds). We also use face amounts + (no need to convert amounts using interest rates) because Hashstack V0 doesn't publish interest rate events. """ @@ -129,14 +145,13 @@ def compute_health_factor( prices=prices, ) if standardized: - # Denominator is the value of (risk-adjusted) collateral at which the loan entity can be liquidated. + # Denominator is the value of (risk-adjusted) collateral at which the loan + # entity can be liquidated. health_factor_liquidation_threshold = ( decimal.Decimal("1.06") if self.debt_category == 1 else ( - decimal.Decimal("1.05") - if self.debt_category == 2 - else decimal.Decimal("1.04") + decimal.Decimal("1.05") if self.debt_category == 2 else decimal.Decimal("1.04") ) ) denominator = health_factor_liquidation_threshold * debt_usd @@ -185,45 +200,35 @@ def __init__( # `rewrite_debt`? def process_new_loan_event(self, event: pandas.Series) -> None: + """Initialize a new loan based on event data.""" # The order of the values in the `data` column is: [`loan_record`] `id`, `owner`, `market`, `commitment`, # `amount`, ``, `current_market`, `current_amount`, ``, `is_loan_withdrawn`, `debt_category`, `state`, # `l3_integration`, `created_at`, [`collateral`] `market`, `amount`, ``, `current_amount`, ``, `commitment`, # `timelock_validity`, `is_timelock_activated`, `activation_time`, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x04ff9acb9154603f1fc14df328a3ea53a6c58087aaac0bfbe9cc7f2565777db8_2. + # Example: + # https://starkscan.co/event/0x04ff9acb9154603f1fc14df328a3ea53a6c58087aaac0bfbe9cc7f2565777db8_2. loan_id = int(event["data"][0], base=16) user = event["data"][1] debt_token = get_symbol(event["data"][2]) debt_face_amount = decimal.Decimal(str(int(event["data"][4], base=16))) borrowed_collateral_token = get_symbol(event["data"][6]) - borrowed_collateral_face_amount = decimal.Decimal( - str(int(event["data"][7], base=16)) - ) + borrowed_collateral_face_amount = decimal.Decimal(str(int(event["data"][7], base=16))) debt_category = int(event["data"][10], base=16) # Several initial loans have different structure of 'data'. try: original_collateral_token = get_symbol(event["data"][14]) - original_collateral_face_amount = decimal.Decimal( - str(int(event["data"][17], base=16)) - ) + original_collateral_face_amount = decimal.Decimal(str(int(event["data"][17], base=16))) except KeyError: original_collateral_token = get_symbol(event["data"][13]) - original_collateral_face_amount = decimal.Decimal( - str(int(event["data"][16], base=16)) - ) + original_collateral_face_amount = decimal.Decimal(str(int(event["data"][16], base=16))) - self.loan_entities[loan_id] = HashstackV0LoanEntity( - user=user, debt_category=debt_category - ) + self.loan_entities[loan_id] = HashstackV0LoanEntity(user=user, debt_category=debt_category) # TODO: Make it possible to initialize Portfolio with some token amount directly. original_collateral = Portfolio() - original_collateral.values[original_collateral_token] = ( - original_collateral_face_amount - ) + original_collateral.values[original_collateral_token] = original_collateral_face_amount self.loan_entities[loan_id].original_collateral = original_collateral borrowed_collateral = Portfolio() - borrowed_collateral.values[borrowed_collateral_token] = ( - borrowed_collateral_face_amount - ) + borrowed_collateral.values[borrowed_collateral_token] = borrowed_collateral_face_amount self.loan_entities[loan_id].borrowed_collateral = borrowed_collateral # TODO: Make it easier to sum 2 Portfolio instances. self.loan_entities[loan_id].collateral.values = { @@ -267,19 +272,17 @@ def process_new_loan_event(self, event: pandas.Series) -> None: ) def process_collateral_added_event(self, event: pandas.Series) -> None: + """Handle event where collateral is added to a loan.""" # The order of the values in the `data` column is: [`collateral_record`] `market`, `amount`, ``, # `current_amount`, ``, `commitment`, `timelock_validity`, `is_timelock_activated`, `activation_time`, # [`loan_id`] `loan_id`, [`amount_added`] `amount_added`, ``, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x02df71b02fce15f2770533328d1e645b957ac347d96bd730466a2e087f24ee07_2. + # Example: + # https://starkscan.co/event/0x02df71b02fce15f2770533328d1e645b957ac347d96bd730466a2e087f24ee07_2. loan_id = int(event["data"][9], base=16) original_collateral_token = get_symbol(event["data"][0]) - original_collateral_face_amount = decimal.Decimal( - str(int(event["data"][3], base=16)) - ) + original_collateral_face_amount = decimal.Decimal(str(int(event["data"][3], base=16))) original_collateral = Portfolio() - original_collateral.values[original_collateral_token] = ( - original_collateral_face_amount - ) + original_collateral.values[original_collateral_token] = original_collateral_face_amount self.loan_entities[loan_id].original_collateral = original_collateral self.loan_entities[loan_id].collateral.values = { token: ( @@ -302,19 +305,17 @@ def process_collateral_added_event(self, event: pandas.Series) -> None: ) def process_collateral_withdrawal_event(self, event: pandas.Series) -> None: + """Process collateral withdrawal event, updating loan collateral.""" # The order of the values in the `data` column is: [`collateral_record`] `market`, `amount`, ``, # `current_amount`, ``, `commitment`, `timelock_validity`, `is_timelock_activated`, `activation_time`, # [`loan_id`] `loan_id`, [`amount_withdrawn`] `amount_withdrawn`, ``, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x03809ebcaad1647f2c6d5294706e0dc619317c240b5554848c454683a18b75ba_5. + # Example: + # https://starkscan.co/event/0x03809ebcaad1647f2c6d5294706e0dc619317c240b5554848c454683a18b75ba_5. loan_id = int(event["data"][9], base=16) original_collateral_token = get_symbol(event["data"][0]) - original_collateral_face_amount = decimal.Decimal( - str(int(event["data"][3], base=16)) - ) + original_collateral_face_amount = decimal.Decimal(str(int(event["data"][3], base=16))) original_collateral = Portfolio() - original_collateral.values[original_collateral_token] = ( - original_collateral_face_amount - ) + original_collateral.values[original_collateral_token] = original_collateral_face_amount # add additional info block and timestamp self.loan_entities[loan_id].extra_info.block = event["block_number"] self.loan_entities[loan_id].extra_info.timestamp = event["timestamp"] @@ -337,18 +338,17 @@ def process_collateral_withdrawal_event(self, event: pandas.Series) -> None: ) def process_loan_withdrawal_event(self, event: pandas.Series) -> None: + """Handle event where a loan is withdrawn, updating loan state.""" # The order of the values in the `data` column is: [`loan_record`] `id`, `owner`, `market`, `commitment`, # `amount`, ``, `current_market`, `current_amount`, ``, `is_loan_withdrawn`, `debt_category`, `state`, # `l3_integration`, `created_at`, [`amount_withdrawn`] `amount_withdrawn`, ``, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x05bb8614095fac1ac9b405c27e7ce870804e85aa5924ef2494fec46792b6b8dc_2. + # Example: + # https://starkscan.co/event/0x05bb8614095fac1ac9b405c27e7ce870804e85aa5924ef2494fec46792b6b8dc_2. loan_id = int(event["data"][0], base=16) user = event["data"][1] # TODO: Is this assert needed? try: - if ( - self.loan_entities.get(loan_id) - and self.loan_entities[loan_id].user != user - ): + if self.loan_entities.get(loan_id) and self.loan_entities[loan_id].user != user: logging.error( "In block number = {}, loan was withdrawn, but the user is different from the one in the loan entity.".format( event["block_number"] @@ -364,15 +364,11 @@ def process_loan_withdrawal_event(self, event: pandas.Series) -> None: debt_token = get_symbol(event["data"][2]) debt_face_amount = decimal.Decimal(str(int(event["data"][4], base=16))) borrowed_collateral_token = get_symbol(event["data"][6]) - borrowed_collateral_face_amount = decimal.Decimal( - str(int(event["data"][7], base=16)) - ) + borrowed_collateral_face_amount = decimal.Decimal(str(int(event["data"][7], base=16))) debt_category = int(event["data"][10], base=16) borrowed_collateral = Portfolio() - borrowed_collateral.values[borrowed_collateral_token] = ( - borrowed_collateral_face_amount - ) + borrowed_collateral.values[borrowed_collateral_token] = borrowed_collateral_face_amount self.loan_entities[loan_id].borrowed_collateral = borrowed_collateral self.loan_entities[loan_id].collateral.values = { token: ( @@ -398,10 +394,12 @@ def process_loan_withdrawal_event(self, event: pandas.Series) -> None: ) def process_loan_repaid_event(self, event: pandas.Series) -> None: + """Process loan repayment event, updating debt and collateral balances.""" # The order of the values in the `data` column is: [`loan_record`] `id`, `owner`, `market`, `commitment`, # `amount`, ``, `current_market`, `current_amount`, ``, `is_loan_withdrawn`, `debt_category`, `state`, # `l3_integration`, `created_at`, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x07731e48d33f6b916f4e4e81e9cee1d282e20e970717e11ad440f73cc1a73484_1. + # Example: + # https://starkscan.co/event/0x07731e48d33f6b916f4e4e81e9cee1d282e20e970717e11ad440f73cc1a73484_1. loan_id = int(event["data"][0], base=16) user = event["data"][1] assert self.loan_entities[loan_id].user == user @@ -409,17 +407,13 @@ def process_loan_repaid_event(self, event: pandas.Series) -> None: # This prevents repaid loans to appear as not repaid. debt_face_amount = decimal.Decimal("0") borrowed_collateral_token = get_symbol(event["data"][6]) - borrowed_collateral_face_amount = decimal.Decimal( - str(int(event["data"][7], base=16)) - ) + borrowed_collateral_face_amount = decimal.Decimal(str(int(event["data"][7], base=16))) # Based on the documentation, it seems that it's only possible to repay the whole amount. assert borrowed_collateral_face_amount == decimal.Decimal("0") debt_category = int(event["data"][10], base=16) borrowed_collateral = Portfolio() - borrowed_collateral.values[borrowed_collateral_token] = ( - borrowed_collateral_face_amount - ) + borrowed_collateral.values[borrowed_collateral_token] = borrowed_collateral_face_amount # add additional info block and timestamp self.loan_entities[loan_id].extra_info.block = event["block_number"] self.loan_entities[loan_id].extra_info.timestamp = event["timestamp"] @@ -449,18 +443,17 @@ def process_loan_repaid_event(self, event: pandas.Series) -> None: ) def process_loan_swap_event(self, event: pandas.Series) -> None: + """Process event to handle loan swaps, updating debt and collateral.""" # The order of the values in the `data` column is: [`old_loan_record`] `id`, `owner`, `market`, `commitment`, # `amount`, ``, `current_market`, `current_amount`, ``, `is_loan_withdrawn`, `debt_category`, `state`, # `l3_integration`, `created_at`, [`new_loan_record`] `id`, `owner`, `market`, `commitment`, `amount`, ``, # `current_market`, `current_amount`, ``, `is_loan_withdrawn`, `debt_category`, `state`, `l3_integration`, # `created_at`, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x00ad0b6b00ce68a1d7f5b79cd550d7f4a15b1708b632b88985a4f6faeb42d5b1_7. + # Example: + # https://starkscan.co/event/0x00ad0b6b00ce68a1d7f5b79cd550d7f4a15b1708b632b88985a4f6faeb42d5b1_7. old_loan_id = int(event["data"][0], base=16) old_user = event["data"][1] - if ( - self.loan_entities.get(old_loan_id) - and self.loan_entities[old_loan_id].user != old_user - ): + if self.loan_entities.get(old_loan_id) and self.loan_entities[old_loan_id].user != old_user: logging.error( "In block number = {}, loan was swapped, but the user is different from the one in the loan entity.".format( event["block_number"] @@ -477,9 +470,7 @@ def process_loan_swap_event(self, event: pandas.Series) -> None: new_debt_token = get_symbol(event["data"][16]) new_debt_face_amount = decimal.Decimal(str(int(event["data"][18], base=16))) new_borrowed_collateral_token = get_symbol(event["data"][20]) - new_borrowed_collateral_face_amount = decimal.Decimal( - str(int(event["data"][21], base=16)) - ) + new_borrowed_collateral_face_amount = decimal.Decimal(str(int(event["data"][21], base=16))) new_debt_category = int(event["data"][24], base=16) new_borrowed_collateral = Portfolio() @@ -523,20 +514,18 @@ def process_loan_swap_event(self, event: pandas.Series) -> None: ) def process_loan_interest_deducted_event(self, event: pandas.Series) -> None: + """Process event to handle deducted loan interest.""" # The order of the values in the `data` column is: [`collateral_record`] `market`, `amount`, ``, # `current_amount`, ``, `commitment`, `timelock_validity`, `is_timelock_activated`, `activation_time`, # [`accrued_interest`] `accrued_interest`, ``, [`loan_id`] `loan_id`, [`amount_withdrawn`] `amount_withdrawn`, # ``, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x050db0ed93d7abbfb152e16608d4cf4dbe0b686b134f890dd0ad8418b203c580_2. + # Example: + # https://starkscan.co/event/0x050db0ed93d7abbfb152e16608d4cf4dbe0b686b134f890dd0ad8418b203c580_2. loan_id = int(event["data"][11], base=16) original_collateral_token = get_symbol(event["data"][0]) - original_collateral_face_amount = decimal.Decimal( - str(int(event["data"][3], base=16)) - ) + original_collateral_face_amount = decimal.Decimal(str(int(event["data"][3], base=16))) original_collateral = Portfolio() - original_collateral.values[original_collateral_token] = ( - original_collateral_face_amount - ) + original_collateral.values[original_collateral_token] = original_collateral_face_amount try: self.loan_entities[loan_id].original_collateral = original_collateral self.loan_entities[loan_id].collateral.values = { @@ -547,9 +536,7 @@ def process_loan_interest_deducted_event(self, event: pandas.Series) -> None: for token in TOKEN_SETTINGS } except TypeError as exc: - logging.getLogger("ErrorHandler").info( - f"TypeErrorHandler: {exc}: {loan_id=}" - ) + logging.getLogger("ErrorHandler").info(f"TypeErrorHandler: {exc}: {loan_id=}") return # add additional info block and timestamp self.loan_entities[loan_id].extra_info.block = event["block_number"] @@ -565,10 +552,12 @@ def process_loan_interest_deducted_event(self, event: pandas.Series) -> None: ) def process_liquidated_event(self, event: pandas.Series) -> None: + """Compute maximum debt liquidatable at a specified collateral price.""" # The order of the values in the `data` column is: [`loan_record`] `id`, `owner`, `market`, `commitment`, # `amount`, ``, `current_market`, `current_amount`, ``, `is_loan_withdrawn`, `debt_category`, `state`, # `l3_integration`, `created_at`, [`liquidator`] `liquidator`, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x0774bebd15505d3f950c362d813dc81c6320ae92cb396b6469fd1ac5d8ff62dc_8. + # Example: + # https://starkscan.co/event/0x0774bebd15505d3f950c362d813dc81c6320ae92cb396b6469fd1ac5d8ff62dc_8. loan_id = int(event["data"][0], base=16) user = event["data"][1] assert self.loan_entities[loan_id].user == user @@ -576,17 +565,14 @@ def process_liquidated_event(self, event: pandas.Series) -> None: # This prevents liquidated loans to appear as not repaid. debt_face_amount = decimal.Decimal("0") borrowed_collateral_token = get_symbol(event["data"][6]) - borrowed_collateral_face_amount = decimal.Decimal( - str(int(event["data"][7], base=16)) - ) - # Based on the documentation, it seems that it's only possible to liquidate the whole amount. + borrowed_collateral_face_amount = decimal.Decimal(str(int(event["data"][7], base=16))) + # Based on the documentation, it seems that it's only possible to + # liquidate the whole amount. assert borrowed_collateral_face_amount == decimal.Decimal("0") debt_category = int(event["data"][10], base=16) borrowed_collateral = Portfolio() - borrowed_collateral.values[borrowed_collateral_token] = ( - borrowed_collateral_face_amount - ) + borrowed_collateral.values[borrowed_collateral_token] = borrowed_collateral_face_amount # add additional info block and timestamp self.loan_entities[loan_id].extra_info.block = event["block_number"] self.loan_entities[loan_id].extra_info.timestamp = event["timestamp"] @@ -635,7 +621,7 @@ def compute_liquidable_debt_at_price( for token, token_amount in loan_entity.debt.values.items() if token_amount > decimal.Decimal("0") } - if not debt_token in debt_tokens: + if debt_token not in debt_tokens: continue # Filter out users with health factor below 1. @@ -663,12 +649,11 @@ def compute_liquidable_debt_at_price( continue # Find out how much of the `debt_token` will be liquidated. - max_liquidated_amount += loan_entity.compute_debt_to_be_liquidated( - debt_usd=debt_usd - ) + max_liquidated_amount += loan_entity.compute_debt_to_be_liquidated(debt_usd=debt_usd) return max_liquidated_amount def compute_number_of_active_users(self) -> int: + """Calculate the number of users with active collateral or debt.""" unique_active_users = { loan_entity.user for loan_entity in self.loan_entities.values() @@ -677,6 +662,7 @@ def compute_number_of_active_users(self) -> int: return len(unique_active_users) def compute_number_of_active_borrowers(self) -> int: + """Calculate the number of users with active debt.""" unique_active_borrowers = { loan_entity.user for loan_entity in self.loan_entities.values() diff --git a/apps/data_handler/handlers/loan_states/hashtack_v0/interest_rate.py b/apps/data_handler/handlers/loan_states/hashtack_v0/interest_rate.py index 59a0772b..40101541 100644 --- a/apps/data_handler/handlers/loan_states/hashtack_v0/interest_rate.py +++ b/apps/data_handler/handlers/loan_states/hashtack_v0/interest_rate.py @@ -1,3 +1,4 @@ +""" Module for calculating interest rates on the Hashstack V0 protocol. """ import asyncio import logging from decimal import Decimal @@ -54,9 +55,7 @@ def _set_events(self, start_block: int, end_block: int) -> None: end_block, ) if isinstance(result, dict): - logging.info( - f"Error while fetching events: {result.get('error', 'Unknown error')}" - ) + logging.info(f"Error while fetching events: {result.get('error', 'Unknown error')}") self.events = [] self._events_over = True return @@ -97,13 +96,9 @@ def calculate_interest_rates(self) -> None: continue # Set initial timestamp values for the first token event - if ( - not self.last_block_data - or interest_rate_state.previous_token_timestamps[token_name] == 0 - ): - interest_rate_state.previous_token_timestamps[token_name] = event[ - "timestamp" - ] + if (not self.last_block_data + or interest_rate_state.previous_token_timestamps[token_name] == 0): + interest_rate_state.previous_token_timestamps[token_name] = event["timestamp"] continue # Get needed variables @@ -127,17 +122,13 @@ def calculate_interest_rates(self) -> None: ) # Write last block data - self._add_interest_rate_entry( - interest_rate_state.build_interest_rate_model(HASHSTACK_ID) - ) + self._add_interest_rate_entry(interest_rate_state.build_interest_rate_model(HASHSTACK_ID)) async def _run_async(self) -> None: """Asynchronous function for running the interest rate calculation process and fetching data from on-chain.""" latest_block = await NET.get_block_number() previous_block = ( - self.last_block_data.block - if self.last_block_data - else self.DEFAULT_START_BLOCK + self.last_block_data.block if self.last_block_data else self.DEFAULT_START_BLOCK ) if previous_block == latest_block: return diff --git a/apps/data_handler/handlers/loan_states/hashtack_v0/run.py b/apps/data_handler/handlers/loan_states/hashtack_v0/run.py index 5e6865bb..d788af14 100644 --- a/apps/data_handler/handlers/loan_states/hashtack_v0/run.py +++ b/apps/data_handler/handlers/loan_states/hashtack_v0/run.py @@ -1,3 +1,6 @@ +""" This module contains the HashtackV0StateComputation +class that computes the loan states for the HashstackV0 protocol. """ + import logging from time import monotonic @@ -40,9 +43,7 @@ def process_data(self, data: list[dict]) -> pd.DataFrame: # init HashtackInitializer hashtack_initializer = HashtackInitializer(hashtack_v0_state) loan_ids = hashtack_initializer.get_loan_ids(df) - hashtack_initializer.set_last_loan_states_per_loan_ids( - list(set(loan_ids)), version=0 - ) + hashtack_initializer.set_last_loan_states_per_loan_ids(list(set(loan_ids)), version=0) # Filter out events that are not in the mapping df_filtered = df[df["key_name"].isin(events_mapping.keys())] @@ -54,9 +55,7 @@ def process_data(self, data: list[dict]) -> pd.DataFrame: result_df = self.get_result_df(hashtack_v0_state.loan_entities) return result_df - def process_event( - self, instance_state: "State", method_name: str, event: pd.Series - ) -> None: + def process_event(self, instance_state: "State", method_name: str, event: pd.Series) -> None: """ Processes an event based on the method name and the event data. @@ -78,9 +77,7 @@ def process_event( if method: method(event) else: - logger.debug( - f"No method named {method_name} found for processing event." - ) + logger.debug(f"No method named {method_name} found for processing event.") else: logger.debug(f"No InterestRate found for block number {block_number}") except Exception as e: diff --git a/apps/data_handler/handlers/loan_states/hashtack_v0/utils.py b/apps/data_handler/handlers/loan_states/hashtack_v0/utils.py index f21f0427..619563ef 100644 --- a/apps/data_handler/handlers/loan_states/hashtack_v0/utils.py +++ b/apps/data_handler/handlers/loan_states/hashtack_v0/utils.py @@ -1,3 +1,4 @@ +""" This module contains the HashtackInitializer class that initializes the HashtackV0 loan states. """ import logging from decimal import Decimal from typing import Union @@ -16,24 +17,18 @@ class HashtackInitializer: A class that initializes the HashstackV0 or HashtackV1 loan states. """ - def __init__( - self, hashtack_state: Union["HashstackV0State", "HashstackV1State"] - ) -> None: + def __init__(self, hashtack_state: Union["HashstackV0State", "HashstackV1State"]) -> None: self.db_connector = InitializerDBConnector() self.hashtack_state = hashtack_state - def set_last_loan_states_per_loan_ids( - self, users_ids: list[str], version: int - ) -> None: + def set_last_loan_states_per_loan_ids(self, users_ids: list[str], version: int) -> None: """ Sets the last loan states for the given users. :param version: Version of Hashtack or V0 or V1 :param users_ids: The list of user ids to set the loan states for. """ - loan_states = self.db_connector.get_hashtack_by_loan_ids( - users_ids, version=version - ) + loan_states = self.db_connector.get_hashtack_by_loan_ids(users_ids, version=version) for loan_state in loan_states: self._set_loan_state_per_loan_id(loan_state) @@ -88,9 +83,7 @@ def _set_loan_state_per_loan_id(self, loan_state: HashtackCollateralDebt) -> Non user_loan_state.borrowed_collateral.values = self._convert_float_to_decimal( loan_state.borrowed_collateral ) - user_loan_state.collateral.values = self._convert_float_to_decimal( - loan_state.collateral - ) + user_loan_state.collateral.values = self._convert_float_to_decimal(loan_state.collateral) user_loan_state.debt.values = self._convert_float_to_decimal(loan_state.debt) # extra field to avoid saving to db diff --git a/apps/data_handler/handlers/loan_states/hashtack_v1/__init__.py b/apps/data_handler/handlers/loan_states/hashtack_v1/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/loan_states/hashtack_v1/__init__.py +++ b/apps/data_handler/handlers/loan_states/hashtack_v1/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/loan_states/hashtack_v1/events.py b/apps/data_handler/handlers/loan_states/hashtack_v1/events.py index 4fbeffac..6edb5ec6 100644 --- a/apps/data_handler/handlers/loan_states/hashtack_v1/events.py +++ b/apps/data_handler/handlers/loan_states/hashtack_v1/events.py @@ -1,3 +1,6 @@ +""" +Event handling and state management for Hashstack V1, including token settings, portfolios, and loan entity updates. +""" import copy import dataclasses import decimal @@ -17,7 +20,6 @@ logger = logging.getLogger(__name__) - R_TOKENS: dict[str, str] = { "ETH": "0x00436d8d078de345c11493bd91512eae60cd2713e05bcaa0bb9f0cba90358c6e", "USDC": "0x03bcecd40212e9b91d92bbe25bb3643ad93f0d230d93237c675f46fac5187e8c", @@ -26,7 +28,6 @@ "wBTC": "0x01320a9910e78afc18be65e4080b51ecc0ee5c0a8b6cc7ef4e685e02b50e57ef", } - ADDRESSES_TO_TOKENS: dict[str, str] = { "0x00436d8d078de345c11493bd91512eae60cd2713e05bcaa0bb9f0cba90358c6e": "ETH", "0x03bcecd40212e9b91d92bbe25bb3643ad93f0d230d93237c675f46fac5187e8c": "USDC", @@ -44,10 +45,13 @@ "0x00f0f5b3eed258344152e1f17baf84a2e1b621cd754b625bec169e8595aea767": "JediSwap: DAI/USDT Pool", "0x04d0390b777b424e43839cd1e744799f3de6c176c7e32c1812a41dbd9c19db6a": "JediSwap: ETH/USDC Pool", "0x045e7131d776dddc137e30bdd490b431c7144677e97bf9369f629ed8d3fb7dd6": "JediSwap: ETH/USDT Pool", - "0x05801bdad32f343035fb242e98d1e9371ae85bc1543962fedea16c59b35bd19b": "JediSwap: USDC/USDT Pool", + "0x05801bdad32f343035fb242e98d1e9371ae85bc1543962fedea16c59b35bd19b": + "JediSwap: USDC/USDT Pool", "0x0260e98362e0949fefff8b4de85367c035e44f734c9f8069b6ce2075ae86b45c": "JediSwap: WBTC/ETH Pool", - "0x005a8054e5ca0b277b295a830e53bd71a6a6943b42d0dbb22329437522bc80c8": "JediSwap: WBTC/USDC Pool", - "0x044d13ad98a46fd2322ef2637e5e4c292ce8822f47b7cb9a1d581176a801c1a0": "JediSwap: WBTC/USDT Pool", + "0x005a8054e5ca0b277b295a830e53bd71a6a6943b42d0dbb22329437522bc80c8": + "JediSwap: WBTC/USDC Pool", + "0x044d13ad98a46fd2322ef2637e5e4c292ce8822f47b7cb9a1d581176a801c1a0": + "JediSwap: WBTC/USDT Pool", # MySwap pools. "0x07c662b10f409d7a0a69c8da79b397fd91187ca5f6230ed30effef2dceddc5b3": "mySwap: DAI/ETH Pool", "0x0611e8f4f3badf1737b9e8f0ca77dd2f6b46a1d33ce4eed951c6b18ac497d505": "mySwap: DAI/USDC Pool", @@ -55,7 +59,8 @@ "0x041f9a1e9a4d924273f5a5c0c138d52d66d2e6a8bee17412c6b0f48fe059ae04": "mySwap: ETH/USDT Pool", "0x01ea237607b7d9d2e9997aa373795929807552503683e35d8739f4dc46652de1": "mySwap: USDC/USDT Pool", "0x025b392609604c75d62dde3d6ae98e124a31b49123b8366d7ce0066ccb94f696": "mySwap: WBTC/USDC Pool", - # TODO: Non-Hashstack specific tokens. This mapping duplicates information from `TOKEN_SETTINGS`. + # TODO: Non-Hashstack specific tokens. This mapping duplicates information + # from `TOKEN_SETTINGS`. "0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3": "DAI", "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7": "ETH", "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8": "USDC", @@ -66,6 +71,9 @@ @dataclasses.dataclass class HashstackV1SpecificTokenSettings: + """ + Token settings specific to Hashstack V1, with neutral collateral and debt factors. + """ # These are set to neutral values because Hashstack V1 doesn't use collateral factors. collateral_factor: decimal.Decimal # These are set to neutral values because Hashstack V1 doesn't use debt factors. @@ -74,192 +82,231 @@ class HashstackV1SpecificTokenSettings: @dataclasses.dataclass class CustomTokenSettings(HashstackV1SpecificTokenSettings, TokenSettings): + """ + Custom token settings for Hashstack V1, extending specific and general token settings. + """ pass HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS: dict[str, TokenSettings] = { - "JediSwap: DAI/ETH Pool": TokenSettings( + "JediSwap: DAI/ETH Pool": + TokenSettings( symbol="JediSwap: DAI/ETH Pool", decimal_factor=decimal.Decimal("1e18"), address="0x07e2a13b40fc1119ec55e0bcf9428eedaa581ab3c924561ad4e955f95da63138", ), - "JediSwap: DAI/USDC Pool": TokenSettings( + "JediSwap: DAI/USDC Pool": + TokenSettings( symbol="JediSwap: DAI/USDC Pool", decimal_factor=decimal.Decimal("1e18"), address="0x00cfd39f5244f7b617418c018204a8a9f9a7f72e71f0ef38f968eeb2a9ca302b", ), - "JediSwap: DAI/USDT Pool": TokenSettings( + "JediSwap: DAI/USDT Pool": + TokenSettings( symbol="JediSwap: DAI/USDT Pool", decimal_factor=decimal.Decimal("1e18"), address="0x00f0f5b3eed258344152e1f17baf84a2e1b621cd754b625bec169e8595aea767", ), - "JediSwap: ETH/USDC Pool": TokenSettings( + "JediSwap: ETH/USDC Pool": + TokenSettings( symbol="JediSwap: ETH/USDC Pool", decimal_factor=decimal.Decimal("1e18"), address="0x04d0390b777b424e43839cd1e744799f3de6c176c7e32c1812a41dbd9c19db6a", ), - "JediSwap: ETH/USDT Pool": TokenSettings( + "JediSwap: ETH/USDT Pool": + TokenSettings( symbol="JediSwap: ETH/USDT Pool", decimal_factor=decimal.Decimal("1e18"), address="0x045e7131d776dddc137e30bdd490b431c7144677e97bf9369f629ed8d3fb7dd6", ), - "JediSwap: USDC/USDT Pool": TokenSettings( + "JediSwap: USDC/USDT Pool": + TokenSettings( symbol="JediSwap: USDC/USDT Pool", decimal_factor=decimal.Decimal("1e18"), address="0x05801bdad32f343035fb242e98d1e9371ae85bc1543962fedea16c59b35bd19b", ), - "JediSwap: WBTC/ETH Pool": TokenSettings( + "JediSwap: WBTC/ETH Pool": + TokenSettings( symbol="JediSwap: WBTC/ETH Pool", decimal_factor=decimal.Decimal("1e18"), address="0x0260e98362e0949fefff8b4de85367c035e44f734c9f8069b6ce2075ae86b45c", ), - "JediSwap: WBTC/USDC Pool": TokenSettings( + "JediSwap: WBTC/USDC Pool": + TokenSettings( symbol="JediSwap: WBTC/USDC Pool", decimal_factor=decimal.Decimal("1e18"), address="0x005a8054e5ca0b277b295a830e53bd71a6a6943b42d0dbb22329437522bc80c8", ), - "JediSwap: WBTC/USDT Pool": TokenSettings( + "JediSwap: WBTC/USDT Pool": + TokenSettings( symbol="JediSwap: WBTC/USDT Pool", decimal_factor=decimal.Decimal("1e18"), address="0x044d13ad98a46fd2322ef2637e5e4c292ce8822f47b7cb9a1d581176a801c1a0", ), - "mySwap: DAI/ETH Pool": TokenSettings( + "mySwap: DAI/ETH Pool": + TokenSettings( symbol="mySwap: DAI/ETH Pool", decimal_factor=decimal.Decimal("1e18"), address="0x07c662b10f409d7a0a69c8da79b397fd91187ca5f6230ed30effef2dceddc5b3", ), - "mySwap: DAI/USDC Pool": TokenSettings( + "mySwap: DAI/USDC Pool": + TokenSettings( symbol="mySwap: DAI/USDC Pool", decimal_factor=decimal.Decimal("1e12"), address="0x0611e8f4f3badf1737b9e8f0ca77dd2f6b46a1d33ce4eed951c6b18ac497d505", ), - "mySwap: ETH/USDC Pool": TokenSettings( + "mySwap: ETH/USDC Pool": + TokenSettings( symbol="mySwap: ETH/USDC Pool", decimal_factor=decimal.Decimal("1e12"), address="0x022b05f9396d2c48183f6deaf138a57522bcc8b35b67dee919f76403d1783136", ), - "mySwap: ETH/USDT Pool": TokenSettings( + "mySwap: ETH/USDT Pool": + TokenSettings( symbol="mySwap: ETH/USDT Pool", decimal_factor=decimal.Decimal("1e12"), address="0x041f9a1e9a4d924273f5a5c0c138d52d66d2e6a8bee17412c6b0f48fe059ae04", ), - "mySwap: USDC/USDT Pool": TokenSettings( + "mySwap: USDC/USDT Pool": + TokenSettings( symbol="mySwap: USDC/USDT Pool", decimal_factor=decimal.Decimal("1e6"), address="0x01ea237607b7d9d2e9997aa373795929807552503683e35d8739f4dc46652de1", ), - "mySwap: WBTC/USDC Pool": TokenSettings( + "mySwap: WBTC/USDC Pool": + TokenSettings( symbol="mySwap: WBTC/USDC Pool", decimal_factor=decimal.Decimal("1e7"), address="0x025b392609604c75d62dde3d6ae98e124a31b49123b8366d7ce0066ccb94f696", ), } HASHSTACK_V1_SPECIFIC_TOKEN_SETTINGS: dict[str, TokenSettings] = { - "ETH": HashstackV1SpecificTokenSettings( + "ETH": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") ), - "wBTC": HashstackV1SpecificTokenSettings( + "wBTC": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") ), - "USDC": HashstackV1SpecificTokenSettings( + "USDC": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") ), - "DAI": HashstackV1SpecificTokenSettings( + "DAI": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") ), - "USDT": HashstackV1SpecificTokenSettings( + "USDT": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") ), # TODO: Add wstETH. - "wstETH": HashstackV1SpecificTokenSettings( + "wstETH": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), # TODO: Add LORDS. - "LORDS": HashstackV1SpecificTokenSettings( + "LORDS": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), # TODO: Add STRK. - "STRK": HashstackV1SpecificTokenSettings( + "STRK": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "JediSwap: DAI/ETH Pool": HashstackV1SpecificTokenSettings( + "JediSwap: DAI/ETH Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "JediSwap: DAI/USDC Pool": HashstackV1SpecificTokenSettings( + "JediSwap: DAI/USDC Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "JediSwap: DAI/USDT Pool": HashstackV1SpecificTokenSettings( + "JediSwap: DAI/USDT Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "JediSwap: ETH/USDC Pool": HashstackV1SpecificTokenSettings( + "JediSwap: ETH/USDC Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "JediSwap: ETH/USDT Pool": HashstackV1SpecificTokenSettings( + "JediSwap: ETH/USDT Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "JediSwap: USDC/USDT Pool": HashstackV1SpecificTokenSettings( + "JediSwap: USDC/USDT Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "JediSwap: WBTC/ETH Pool": HashstackV1SpecificTokenSettings( + "JediSwap: WBTC/ETH Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "JediSwap: WBTC/USDC Pool": HashstackV1SpecificTokenSettings( + "JediSwap: WBTC/USDC Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "JediSwap: WBTC/USDT Pool": HashstackV1SpecificTokenSettings( + "JediSwap: WBTC/USDT Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "mySwap: DAI/ETH Pool": HashstackV1SpecificTokenSettings( + "mySwap: DAI/ETH Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "mySwap: DAI/USDC Pool": HashstackV1SpecificTokenSettings( + "mySwap: DAI/USDC Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "mySwap: ETH/USDC Pool": HashstackV1SpecificTokenSettings( + "mySwap: ETH/USDC Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "mySwap: ETH/USDT Pool": HashstackV1SpecificTokenSettings( + "mySwap: ETH/USDT Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "mySwap: USDC/USDT Pool": HashstackV1SpecificTokenSettings( + "mySwap: USDC/USDT Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), - "mySwap: WBTC/USDC Pool": HashstackV1SpecificTokenSettings( + "mySwap: WBTC/USDC Pool": + HashstackV1SpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), ), } TOKEN_SETTINGS: dict[str, CustomTokenSettings] = { - token: CustomTokenSettings( + token: + CustomTokenSettings( symbol=token_settings.symbol, decimal_factor=token_settings.decimal_factor, address=token_settings.address, collateral_factor=HASHSTACK_V1_SPECIFIC_TOKEN_SETTINGS[token].collateral_factor, debt_factor=HASHSTACK_V1_SPECIFIC_TOKEN_SETTINGS[token].debt_factor, ) - for token, token_settings in ( - TOKEN_SETTINGS | HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS - ).items() + for token, token_settings in (TOKEN_SETTINGS | HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS).items() } - # Keys are values of the "key_name" column in the database, values are the respective method names. EVENTS_METHODS_MAPPING: dict[str, str] = { "new_loan": "process_new_loan_event", @@ -271,14 +318,11 @@ class CustomTokenSettings(HashstackV1SpecificTokenSettings, TokenSettings): "updated_debt_token_price": "process_updated_debt_token_price_event", } - MAX_ROUNDING_ERRORS: TokenValues = MAX_ROUNDING_ERRORS # TODO: The additional tokens are not allowed in `TokenValues`, fix this. MAX_ROUNDING_ERRORS.values.update( - { - token: decimal.Decimal("0.5e13") - for token in HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS - } + {token: decimal.Decimal("0.5e13") + for token in HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS} ) @@ -290,10 +334,8 @@ class HashstackV1Portfolio(Portfolio): def __init__(self) -> None: super().__init__() self.values.update( - { - token: decimal.Decimal("0") - for token in HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS - } + {token: decimal.Decimal("0") + for token in HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS} ) @@ -306,10 +348,8 @@ class HashstackV1InterestRateModels(TokenValues): def __init__(self) -> None: super().__init__(init_value=decimal.Decimal("1")) self.values.update( - { - token: decimal.Decimal("1") - for token in HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS - } + {token: decimal.Decimal("1") + for token in HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS} ) @@ -468,10 +508,14 @@ def process_updated_debt_token_price_event(self, event: pd.Series) -> None: self.debt_interest_rate_models.values[token] = cumulative_interest_rate def process_new_loan_event(self, event: pd.Series) -> None: + """ + Process a new loan event and initialize a new loan entity for the user. + """ # The order of the values in the `data` column is: [`loan_record`] `loan_id`, `borrower`, `market`, `amount`, # ``, `current_market`, `current_amount`, ``, `state`, `l3_integration`, `l3_category`, `created_at`, # [`collateral`] `loan_id`, `collateral_token`, `amount`, ``, `created_at`, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x00085b80dbb6c3cb161bb2b73ebc8c3b3b806395fc5c9a0f110ae2a1fa04a578_9. + # Example: + # https://starkscan.co/event/0x00085b80dbb6c3cb161bb2b73ebc8c3b3b806395fc5c9a0f110ae2a1fa04a578_9. loan_id = int(event["data"][0], base=16) collateral_loan_id = int(event["data"][12], base=16) assert loan_id == collateral_loan_id @@ -480,37 +524,28 @@ def process_new_loan_event(self, event: pd.Series) -> None: debt_token = self.ADDRESSES_TO_TOKENS[add_leading_zeros(event["data"][2])] debt_face_amount = decimal.Decimal(str(int(event["data"][3], base=16))) borrowed_collateral_token = get_symbol(add_leading_zeros(event["data"][5])) - borrowed_collateral_face_amount = decimal.Decimal( - str(int(event["data"][6], base=16)) - ) - original_collateral_token = self.ADDRESSES_TO_TOKENS[ - add_leading_zeros(event["data"][13]) - ] - original_collateral_face_amount = decimal.Decimal( - str(int(event["data"][14], base=16)) - ) + borrowed_collateral_face_amount = decimal.Decimal(str(int(event["data"][6], base=16))) + original_collateral_token = self.ADDRESSES_TO_TOKENS[add_leading_zeros(event["data"][13])] + original_collateral_face_amount = decimal.Decimal(str(int(event["data"][14], base=16))) self.loan_entities[loan_id] = HashstackV1LoanEntity(user=user) - # TODO: Make it possible to initialize `HashstackV1Portfolio`` with some token amount directly. + # TODO: Make it possible to initialize `HashstackV1Portfolio`` with some + # token amount directly. original_collateral = HashstackV1Portfolio() - original_collateral.values[original_collateral_token] = ( - original_collateral_face_amount - ) + original_collateral.values[original_collateral_token] = (original_collateral_face_amount) self.loan_entities[loan_id].original_collateral = original_collateral # add additional info block and timestamp self.loan_entities[loan_id].extra_info.block = event["block_number"] self.loan_entities[loan_id].extra_info.timestamp = event["timestamp"] borrowed_collateral = HashstackV1Portfolio() - borrowed_collateral.values[borrowed_collateral_token] = ( - borrowed_collateral_face_amount - ) + borrowed_collateral.values[borrowed_collateral_token] = (borrowed_collateral_face_amount) self.loan_entities[loan_id].borrowed_collateral = borrowed_collateral # TODO: Make it easier to sum 2 `HashstackV1Portfolio` instances. self.loan_entities[loan_id].collateral.values = { token: ( - self.loan_entities[loan_id].original_collateral.values[token] - + self.loan_entities[loan_id].borrowed_collateral.values[token] + self.loan_entities[loan_id].original_collateral.values[token] + + self.loan_entities[loan_id].borrowed_collateral.values[token] ) for token in TOKEN_SETTINGS } @@ -533,7 +568,8 @@ def process_new_loan_event(self, event: pd.Series) -> None: if self.loan_entities[loan_id].user == self.verbose_user: logging.info( "In block number = {}, face amount = {} of token = {} was borrowed against original collateral face " - "amount = {} of token = {} and borrowed collateral face amount = {} of token = {}.".format( + "amount = {} of token = {} and borrowed collateral face amount = {} of token = {}.". + format( event["block_number"], debt_face_amount, debt_token, @@ -546,27 +582,25 @@ def process_new_loan_event(self, event: pd.Series) -> None: ) def process_collateral_added_event(self, event: pd.Series) -> None: + """ + Process the collateral added event, adjusting the loan entity's collateral balances. + """ # The order of the values in the `data` column is: [`collateral_record`] `loan_id`, `collateral_token`, # `amount`, ``, `created_at`, [`amount_added`] `amount_added`, ``, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x027b7e40273848af37e092eaec38311ac1d2e6c3fc2724020736e9f322b6fcf7_0. + # Example: + # https://starkscan.co/event/0x027b7e40273848af37e092eaec38311ac1d2e6c3fc2724020736e9f322b6fcf7_0. loan_id = int(event["data"][0], base=16) - original_collateral_token = self.ADDRESSES_TO_TOKENS[ - add_leading_zeros(event["data"][1]) - ] - original_collateral_face_amount = decimal.Decimal( - str(int(event["data"][2], base=16)) - ) + original_collateral_token = self.ADDRESSES_TO_TOKENS[add_leading_zeros(event["data"][1])] + original_collateral_face_amount = decimal.Decimal(str(int(event["data"][2], base=16))) original_collateral = HashstackV1Portfolio() - original_collateral.values[original_collateral_token] = ( - original_collateral_face_amount - ) + original_collateral.values[original_collateral_token] = (original_collateral_face_amount) self.loan_entities[loan_id].original_collateral = original_collateral self.loan_entities[loan_id].collateral.values = { token: ( - self.loan_entities[loan_id].original_collateral.values[token] - + self.loan_entities[loan_id].borrowed_collateral.values[token] + self.loan_entities[loan_id].original_collateral.values[token] + + self.loan_entities[loan_id].borrowed_collateral.values[token] ) for token in TOKEN_SETTINGS } @@ -581,11 +615,15 @@ def process_collateral_added_event(self, event: pd.Series) -> None: ) def process_loan_spent_event(self, event: pd.Series) -> None: + """ + Process the loan spent event, updating the loan entity with new debt and collateral details. + """ # The order of the values in the `data` column is: [`old_loan_record`] `loan_id`, `borrower`, `market`, # `amount`, ``, `current_market`, `current_amount`, ``, `state`, `l3_integration`, `l3_category`, `created_at`, # [`new_loan_record`] `loan_id`, `borrower`, `market`, `amount`, ``, `current_market`, `current_amount`, ``, # `state`, `l3_integration`, `l3_category`, `created_at`, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x0051f75ef1e08f70d1c8efe7866384d026aa0ca092ded8bd1c903aac0478b990_25. + # Example: + # https://starkscan.co/event/0x0051f75ef1e08f70d1c8efe7866384d026aa0ca092ded8bd1c903aac0478b990_25. old_loan_id = int(event["data"][0], base=16) old_user = event["data"][1] assert self.loan_entities[old_loan_id].user == old_user @@ -598,12 +636,10 @@ def process_loan_spent_event(self, event: pd.Series) -> None: new_debt_token = self.ADDRESSES_TO_TOKENS[add_leading_zeros(event["data"][14])] new_debt_face_amount = decimal.Decimal(str(int(event["data"][15], base=16))) - new_borrowed_collateral_token = self.ADDRESSES_TO_TOKENS[ - add_leading_zeros(event["data"][17]) - ] - new_borrowed_collateral_face_amount = decimal.Decimal( - str(int(event["data"][18], base=16)) - ) + new_borrowed_collateral_token = self.ADDRESSES_TO_TOKENS[add_leading_zeros( + event["data"][17] + )] + new_borrowed_collateral_face_amount = decimal.Decimal(str(int(event["data"][18], base=16))) new_borrowed_collateral = HashstackV1Portfolio() new_borrowed_collateral.values[new_borrowed_collateral_token] = ( @@ -612,8 +648,8 @@ def process_loan_spent_event(self, event: pd.Series) -> None: self.loan_entities[new_loan_id].borrowed_collateral = new_borrowed_collateral self.loan_entities[new_loan_id].collateral.values = { token: ( - self.loan_entities[new_loan_id].original_collateral.values[token] - + self.loan_entities[new_loan_id].borrowed_collateral.values[token] + self.loan_entities[new_loan_id].original_collateral.values[token] + + self.loan_entities[new_loan_id].borrowed_collateral.values[token] ) for token in TOKEN_SETTINGS } @@ -639,9 +675,13 @@ def process_loan_spent_event(self, event: pd.Series) -> None: ) def process_loan_transferred_event(self, event: pd.Series) -> None: + """ + Process the loan transferred event, updating the loan entity's user. + """ # The order of the values in the `data` column is: [`loan_id`] `loan_id`, [`sender`] `sender`, [`reciever`] # `reciever`, [`timestamp`] `timestamp`. - # Example: https://starkscan.co/event/0x028ea2b3cb9759214c7ea18e86a2d1b33a4bf3f87b4b0b4eb75919c9ab87a62e_5. + # Example: + # https://starkscan.co/event/0x028ea2b3cb9759214c7ea18e86a2d1b33a4bf3f87b4b0b4eb75919c9ab87a62e_5. loan_id = int(event["data"][0], base=16) old_user = event["data"][1] assert self.loan_entities[loan_id].user == old_user @@ -661,13 +701,17 @@ def process_loan_transferred_event(self, event: pd.Series) -> None: ) def process_loan_repaid_event(self, event: pd.Series) -> None: + """ + Process the loan repaid event and update the relevant loan entity details. + """ # The order of the values in the `data` column is: [`loan_record`] `loan_id`, `borrower`, `market`, `amount`, # ``, `current_market`, `current_amount`, ``, `state`, `l3_integration`, `l3_category`, `created_at`, # [`new_loan_record`] `loan_id`, `borrower`, `market`, `amount`, ``, `current_market`, `current_amount`, ``, # `state`, `l3_integration`, `l3_category`, `created_at`, [`collateral_record`] `loan_id`, `collateral_token`, # `amount`, ``, `created_at`, [`totalUserDebt`] `totalUserDebt`, [`deficit`] `deficit`, [`timestamp`] # `timestamp`. - # Example: https://starkscan.co/event/0x0069ff177c728aae4248ba8625322f75f0c5df918215f9e5dee10fe22c1fa26c_53. + # Example: + # https://starkscan.co/event/0x0069ff177c728aae4248ba8625322f75f0c5df918215f9e5dee10fe22c1fa26c_53. old_loan_id = int(event["data"][0], base=16) old_user = event["data"][1] assert self.loan_entities[old_loan_id].user == old_user @@ -682,18 +726,14 @@ def process_loan_repaid_event(self, event: pd.Series) -> None: new_debt_token = self.ADDRESSES_TO_TOKENS[add_leading_zeros(event["data"][14])] new_debt_face_amount = decimal.Decimal(str(int(event["data"][15], base=16))) - new_borrowed_collateral_token = self.ADDRESSES_TO_TOKENS[ - add_leading_zeros(event["data"][17]) - ] - new_borrowed_collateral_face_amount = decimal.Decimal( - str(int(event["data"][18], base=16)) - ) - new_original_collateral_token = self.ADDRESSES_TO_TOKENS[ - add_leading_zeros(event["data"][25]) - ] - new_original_collateral_face_amount = decimal.Decimal( - str(int(event["data"][26], base=16)) - ) + new_borrowed_collateral_token = self.ADDRESSES_TO_TOKENS[add_leading_zeros( + event["data"][17] + )] + new_borrowed_collateral_face_amount = decimal.Decimal(str(int(event["data"][18], base=16))) + new_original_collateral_token = self.ADDRESSES_TO_TOKENS[add_leading_zeros( + event["data"][25] + )] + new_original_collateral_face_amount = decimal.Decimal(str(int(event["data"][26], base=16))) # Based on the documentation, it seems that it's only possible to repay the whole amount. assert new_debt_face_amount == decimal.Decimal("0") assert new_borrowed_collateral_face_amount == decimal.Decimal("0") @@ -715,8 +755,8 @@ def process_loan_repaid_event(self, event: pd.Series) -> None: self.loan_entities[new_loan_id].collateral.values = { token: ( - self.loan_entities[new_loan_id].original_collateral.values[token] - + self.loan_entities[new_loan_id].borrowed_collateral.values[token] + self.loan_entities[new_loan_id].original_collateral.values[token] + + self.loan_entities[new_loan_id].borrowed_collateral.values[token] ) for token in TOKEN_SETTINGS } @@ -759,7 +799,7 @@ def compute_liquidable_debt_at_price( for token, token_amount in loan_entity.debt.values.items() if decimal.Decimal(token_amount) > decimal.Decimal("0") } - if not debt_token in debt_tokens: + if debt_token not in debt_tokens: continue # Filter out users with health factor below 1. @@ -779,12 +819,13 @@ def compute_liquidable_debt_at_price( continue # Find out how much of the `debt_token` will be liquidated. - max_liquidated_amount += loan_entity.compute_debt_to_be_liquidated( - debt_usd=debt_usd - ) + max_liquidated_amount += loan_entity.compute_debt_to_be_liquidated(debt_usd=debt_usd) return max_liquidated_amount def compute_number_of_active_users(self) -> int: + """ + Calculate the number of unique users with active collateral or debt. + """ unique_active_users = { loan_entity.user for loan_entity in self.loan_entities.values() @@ -793,9 +834,11 @@ def compute_number_of_active_users(self) -> int: return len(unique_active_users) def compute_number_of_active_borrowers(self) -> int: + """ + Calculate the number of unique users with active debt. + """ unique_active_borrowers = { loan_entity.user - for loan_entity in self.loan_entities.values() - if loan_entity.has_debt() + for loan_entity in self.loan_entities.values() if loan_entity.has_debt() } return len(unique_active_borrowers) diff --git a/apps/data_handler/handlers/loan_states/hashtack_v1/run.py b/apps/data_handler/handlers/loan_states/hashtack_v1/run.py index 5d84a990..c4d4aa2c 100644 --- a/apps/data_handler/handlers/loan_states/hashtack_v1/run.py +++ b/apps/data_handler/handlers/loan_states/hashtack_v1/run.py @@ -1,3 +1,4 @@ +""" This module contains the class that computes the loan states for the HashtackV1 protocol. """ import logging from time import monotonic @@ -44,9 +45,7 @@ def process_data(self, data: list[dict]) -> pd.DataFrame: # init HashtackInitializer hashtack_initializer = HashtackInitializer(hashtack_v1_state) loan_ids = hashtack_initializer.get_loan_ids(df) - hashtack_initializer.set_last_loan_states_per_loan_ids( - list(set(loan_ids)), version=1 - ) + hashtack_initializer.set_last_loan_states_per_loan_ids(list(set(loan_ids)), version=1) # Filter out events that are not in the mapping df_filtered = df[df["key_name"].isin(events_mapping.keys())] diff --git a/apps/data_handler/handlers/loan_states/nostra_alpha/__init__.py b/apps/data_handler/handlers/loan_states/nostra_alpha/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/loan_states/nostra_alpha/__init__.py +++ b/apps/data_handler/handlers/loan_states/nostra_alpha/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/loan_states/nostra_alpha/events.py b/apps/data_handler/handlers/loan_states/nostra_alpha/events.py index f6291aee..69b2e495 100644 --- a/apps/data_handler/handlers/loan_states/nostra_alpha/events.py +++ b/apps/data_handler/handlers/loan_states/nostra_alpha/events.py @@ -1,3 +1,6 @@ +""" +Event processing for Nostra Alpha protocol, including loan and collateral management. +""" import asyncio import copy import decimal @@ -34,9 +37,12 @@ class NostraAlphaLoanEntity(LoanEntity): """ - A class that describes the Nostra Alpha loan entity. On top of the abstract `LoanEntity`, it implements the - `non_interest_bearing_collateral` and `interest_bearing_collateral` attributes in order to help with accounting for - the changes in collateral. This is because Nostra Alpha allows the user to decide the amount of collateral that + A class that describes the Nostra + Alpha loan entity. On top of the abstract `LoanEntity`, it implements the + `non_interest_bearing_collateral` + and `interest_bearing_collateral` attributes in order to help with accounting for + the changes in collateral. + This is because Nostra Alpha allows the user to decide the amount of collateral that earns interest and the amount that doesn't. We keep all balances in raw amounts. """ @@ -125,36 +131,24 @@ def compute_debt_to_be_liquidated( # See an example of a liquidation here: # https://docs.nostra.finance/lend/liquidations/an-example-of-liquidation. liquidator_fee = min( - collateral_token_parameters[ - collateral_token_address - ].liquidator_fee_beta - * (self.LIQUIDATION_HEALTH_FACTOR_THRESHOLD - health_factor), - collateral_token_parameters[ - collateral_token_address - ].liquidator_fee_max, + collateral_token_parameters[collateral_token_address].liquidator_fee_beta * + (self.LIQUIDATION_HEALTH_FACTOR_THRESHOLD - health_factor), + collateral_token_parameters[collateral_token_address].liquidator_fee_max, ) total_fee = ( - liquidator_fee - + collateral_token_parameters[collateral_token_address].protocol_fee + liquidator_fee + collateral_token_parameters[collateral_token_address].protocol_fee ) max_liquidation_percentage = (self.TARGET_HEALTH_FACTOR - health_factor) / ( - self.TARGET_HEALTH_FACTOR - - ( - collateral_token_parameters[ - collateral_token_address - ].collateral_factor - * debt_token_parameters[debt_token_addresses[0]].debt_factor - * (1.0 + total_fee) + self.TARGET_HEALTH_FACTOR - ( + collateral_token_parameters[collateral_token_address].collateral_factor * + debt_token_parameters[debt_token_addresses[0]].debt_factor * (1.0 + total_fee) ) ) max_liquidation_percentage = min(max_liquidation_percentage, 1.0) - max_liquidation_amount = max_liquidation_percentage * float( - debt_token_debt_amount - ) + max_liquidation_amount = max_liquidation_percentage * float(debt_token_debt_amount) max_liquidation_amount_usd = ( - debt_token_price - * max_liquidation_amount - / (10 ** debt_token_parameters[debt_token_addresses[0]].decimals) + debt_token_price * max_liquidation_amount / + (10**debt_token_parameters[debt_token_addresses[0]].decimals) ) max_liquidator_fee_usd = liquidator_fee * max_liquidation_amount_usd if max_liquidator_fee_usd > liquidator_fee_usd: @@ -165,25 +159,23 @@ def compute_debt_to_be_liquidated( class NostraAlphaState(State): """ - A class that describes the state of all Nostra Alpha loan entities. It implements a method for correct processing + A class that describes the state of all + Nostra Alpha loan entities. It implements a method for correct processing of every relevant event. """ PROTOCOL_NAME = ProtocolIDs.NOSTRA_ALPHA.value - IGNORE_USER: str = ( - "0x5a0042fa9bb87ed72fbee4d5a2da416528ebc84a569081ad02e9ad60b0af7d7" - ) + IGNORE_USER: str = ("0x5a0042fa9bb87ed72fbee4d5a2da416528ebc84a569081ad02e9ad60b0af7d7") TOKEN_ADDRESSES: list[str] = NOSTRA_ALPHA_TOKEN_ADDRESSES INTEREST_RATE_MODEL_ADDRESS: str = NOSTRA_ALPHA_INTEREST_RATE_MODEL_ADDRESS CDP_MANAGER_ADDRESS: str = NOSTRA_ALPHA_CDP_MANAGER_ADDRESS - DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: str = ( - NOSTRA_ALPHA_DEFERRED_BATCH_CALL_ADAPTER_ADDRESS - ) + DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: str = (NOSTRA_ALPHA_DEFERRED_BATCH_CALL_ADAPTER_ADDRESS) EVENTS_TO_METHODS: dict[str, str] = NOSTRA_ALPHA_EVENTS_TO_METHODS - # These variables are missing the leading 0's on purpose, because they're missing in the raw event keys, too. + # These variables are missing the leading 0's on purpose, because they're + # missing in the raw event keys, too. INTEREST_STATE_UPDATED_KEY = ( "0x33db1d611576200c90997bde1f948502469d333e65e87045c250e6efd2e42c7" ) @@ -191,7 +183,8 @@ class NostraAlphaState(State): MINT_KEY = "0x34e55c1cd55f1338241b50d352f0e91c7e4ffad0e4271d64eb347589ebdfd16" BURN_KEY = "0x243e1de00e8a6bc1dfa3e950e6ade24c52e4a25de4dee7fb5affe918ad1e744" - # We ignore transfers where the sender or recipient are '0x0' because these are mints and burns covered by other + # We ignore transfers where the + # sender or recipient are '0x0' because these are mints and burns covered by other # events. ZERO_ADDRESS: str = add_leading_zeros("0x0") @@ -206,9 +199,8 @@ def __init__( ) # These dicts' keys are the collateral/debt token addresses. self.token_addresses_to_events: dict[str, str] = {} - self.debt_token_addresses_to_interest_bearing_collateral_token_addresses: dict[ - str, str - ] = {} + self.debt_token_addresses_to_interest_bearing_collateral_token_addresses: dict[str, + str] = {} asyncio.run(self.collect_token_parameters()) @@ -241,9 +233,7 @@ async def collect_token_parameters(self) -> None: decimals = int(decimals[0]) token_symbol = await get_symbol(token_address=token_address) - event, is_interest_bearing = self._infer_token_type( - token_symbol=token_symbol - ) + event, is_interest_bearing = self._infer_token_type(token_symbol=token_symbol) self.token_addresses_to_events[token_address] = event underlying_token_address = await stark_client.func_call( @@ -251,15 +241,12 @@ async def collect_token_parameters(self) -> None: selector="underlyingAsset", calldata=[], ) - underlying_token_address = add_leading_zeros( - hex(underlying_token_address[0]) - ) - underlying_token_symbol = await get_symbol( - token_address=underlying_token_address - ) + underlying_token_address = add_leading_zeros(hex(underlying_token_address[0])) + underlying_token_symbol = await get_symbol(token_address=underlying_token_address) if event == "collateral": - # The order of the arguments is: `id`, `asset`, `collateralFactor`, ``, `priceOracle`. + # The order of the arguments is: `id`, `asset`, `collateralFactor`, ``, + # `priceOracle`. collateral_data = await stark_client.func_call( addr=self.CDP_MANAGER_ADDRESS, selector="getCollateralData", @@ -267,7 +254,8 @@ async def collect_token_parameters(self) -> None: ) collateral_factor = collateral_data[2] / 1e18 - # The order of the arguments is: `protocolFee`, ``, `protocolFeeRecipient`, `liquidatorFeeBeta`, ``, + # The order of the arguments is: + # `protocolFee`, ``, `protocolFeeRecipient`, `liquidatorFeeBeta`, ``, # `liquidatorFeeMax`, ``. liquidation_settings = await stark_client.func_call( addr=self.CDP_MANAGER_ADDRESS, @@ -291,7 +279,8 @@ async def collect_token_parameters(self) -> None: protocol_fee=protocol_fee, ) else: - # The order of the arguments is: `id`, `debtTier`, `debtToken`, `debtFactor`, ``, `priceOracle`. + # The order of the arguments is: `id`, `debtTier`, `debtToken`, + # `debtFactor`, ``, `priceOracle`. debt_data = await stark_client.func_call( addr=self.CDP_MANAGER_ADDRESS, selector="getDebtData", @@ -309,22 +298,20 @@ async def collect_token_parameters(self) -> None: ) getattr(self.token_parameters, event)[token_address] = token_parameters - # Create the mapping between the debt token addresses and the respective interest bearing collateral token + # Create the mapping between the debt + # token addresses and the respective interest bearing collateral token # addresses. for debt_token_parameters in self.token_parameters.debt.values(): interest_bearing_collateral_token_addresses = [ collateral_token_parameters.address - for collateral_token_parameters in self.token_parameters.collateral.values() - if ( - collateral_token_parameters.is_interest_bearing - and collateral_token_parameters.underlying_address - == debt_token_parameters.underlying_address + for collateral_token_parameters in self.token_parameters.collateral.values() if ( + collateral_token_parameters.is_interest_bearing and collateral_token_parameters. + underlying_address == debt_token_parameters.underlying_address ) ] assert len(interest_bearing_collateral_token_addresses) == 1 self.debt_token_addresses_to_interest_bearing_collateral_token_addresses[ - debt_token_parameters.address - ] = interest_bearing_collateral_token_addresses[0] + debt_token_parameters.address] = interest_bearing_collateral_token_addresses[0] def process_event(self, event: pd.Series) -> None: """ @@ -337,25 +324,26 @@ def process_event(self, event: pd.Series) -> None: self.process_interest_rate_model_event(event) return event_type = NOSTRA_ALPHA_ADDRESSES_TO_EVENTS[event["from_address"]] - getattr(self, self.EVENTS_METHODS_MAPPING[(event_type, event["key_name"])])( - event=event - ) + getattr(self, self.EVENTS_METHODS_MAPPING[(event_type, event["key_name"])])(event=event) def process_interest_rate_model_event(self, event: pd.Series) -> None: - # The order of the values in the `data` column is: `debtToken`, `lendingRate`, ``, `borrowRate`, ``, + """Processes an interest rate model event, + updating collateral and debt interest rate indices.""" + # The order of the values in the `data` column is: + # `debtToken`, `lendingRate`, ``, `borrowRate`, ``, # `lendIndex`, ``, `borrowIndex`, ``. - # Example: https://starkscan.co/event/0x05e95588e281d7cab6f89aa266057c4c9bcadf3ff0bb85d4feea40a4faa94b09_4. + # Example: + # https://starkscan.co/event/0x05e95588e281d7cab6f89aa266057c4c9bcadf3ff0bb85d4feea40a4faa94b09_4. if event["keys"] == [self.INTEREST_STATE_UPDATED_KEY]: # The order of the values in the `data` column is: `debtToken`, `lendingRate`, ``, `borrowRate`, ``, # `lendIndex`, ``, `borrowIndex`, ``. - # Example: https://starkscan.co/event/0x05e95588e281d7cab6f89aa266057c4c9bcadf3ff0bb85d4feea40a4faa94b09_4. + # Example: + # https://starkscan.co/event/0x05e95588e281d7cab6f89aa266057c4c9bcadf3ff0bb85d4feea40a4faa94b09_4. debt_token = add_leading_zeros(event["data"][0]) - collateral_interest_rate_index = decimal.Decimal( - str(int(event["data"][5], base=16)) - ) / decimal.Decimal("1e18") - debt_interest_rate_index = decimal.Decimal( - str(int(event["data"][7], base=16)) - ) / decimal.Decimal("1e18") + collateral_interest_rate_index = decimal.Decimal(str(int(event["data"][5], base=16)) + ) / decimal.Decimal("1e18") + debt_interest_rate_index = decimal.Decimal(str(int(event["data"][7], base=16)) + ) / decimal.Decimal("1e18") else: raise ValueError("Event = {} has an unexpected structure.".format(event)) collateral_token = self.debt_token_addresses_to_interest_bearing_collateral_token_addresses.get( @@ -368,15 +356,18 @@ def process_interest_rate_model_event(self, event: pd.Series) -> None: ) self.interest_rate_models.debt[debt_token] = debt_interest_rate_index - def process_non_interest_bearing_collateral_mint_event( - self, event: pd.Series - ) -> None: + def process_non_interest_bearing_collateral_mint_event(self, event: pd.Series) -> None: + """Processes non-interest-bearing collateral mint event, + adjusting collateral values for the sender and recipient.""" # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x015dccf7bc9a434bcc678cf730fa92641a2f6bcbfdb61cbe7a1ef7d0a614d1ac_3. + # Example: + # https://starkscan.co/event/0x015dccf7bc9a434bcc678cf730fa92641a2f6bcbfdb61cbe7a1ef7d0a614d1ac_3. if event["keys"] == [self.TRANSFER_KEY]: - # The order of the values in the `data` column is: `sender`, `recipient`, `value`, ``. Alternatively, + # The order of the values in the `data` column is: `sender`, + # `recipient`, `value`, ``. Alternatively, # `from_`, `to`, `value`, ``. - # Example: https://starkscan.co/event/0x06ddd34767c8cef97d4508bcbb4e3771b1c93e160e02ca942cadbdfa29ef9ba8_2. + # Example: + # https://starkscan.co/event/0x06ddd34767c8cef97d4508bcbb4e3771b1c93e160e02ca942cadbdfa29ef9ba8_2. sender = add_leading_zeros(event["data"][0]) recipient = add_leading_zeros(event["data"][1]) raw_amount = decimal.Decimal(str(int(event["data"][2], base=16))) @@ -387,20 +378,17 @@ def process_non_interest_bearing_collateral_mint_event( token = add_leading_zeros(event["from_address"]) if sender != self.DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: - self.loan_entities[sender].collateral.increase_value( - token=token, value=-raw_amount - ) + self.loan_entities[sender].collateral.increase_value(token=token, value=-raw_amount) self.loan_entities[sender].extra_info.block = event["block_number"] self.loan_entities[sender].extra_info.timestamp = event["timestamp"] if recipient != self.DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: - self.loan_entities[recipient].collateral.increase_value( - token=token, value=raw_amount - ) + self.loan_entities[recipient].collateral.increase_value(token=token, value=raw_amount) self.loan_entities[recipient].extra_info.block = event["block_number"] self.loan_entities[recipient].extra_info.timestamp = event["timestamp"] if self.verbose_user in {sender, recipient}: logging.info( - "In block number = {}, collateral of raw amount = {} of token = {} was transferred from user = {} to user = {}.".format( + "In block number = {}, collateral of raw amount = {} of token = {} was transferred from user = {} to user = {}." + .format( event["block_number"], raw_amount, token, @@ -410,9 +398,11 @@ def process_non_interest_bearing_collateral_mint_event( ) def process_collateral_mint_event(self, event: pd.Series) -> None: + """Process collateral addition event for a loan.""" if event["keys"] == [self.MINT_KEY]: # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x015dccf7bc9a434bcc678cf730fa92641a2f6bcbfdb61cbe7a1ef7d0a614d1ac_3. + # Example: + # https://starkscan.co/event/0x015dccf7bc9a434bcc678cf730fa92641a2f6bcbfdb61cbe7a1ef7d0a614d1ac_3. user = add_leading_zeros(event["data"][0]) face_amount = decimal.Decimal(str(int(event["data"][1], base=16))) else: @@ -424,12 +414,11 @@ def process_collateral_mint_event(self, event: pd.Series) -> None: raw_amount = face_amount / self.interest_rate_models.collateral[token] else: raw_amount = face_amount - self.loan_entities[user].collateral.increase_value( - token=token, value=raw_amount - ) + self.loan_entities[user].collateral.increase_value(token=token, value=raw_amount) if user == self.verbose_user: logging.info( - "In block number = {}, collateral of raw amount = {} of token = {} was added.".format( + "In block number = {}, collateral of raw amount = {} of token = {} was added.". + format( event["block_number"], raw_amount, token, @@ -437,9 +426,12 @@ def process_collateral_mint_event(self, event: pd.Series) -> None: ) def process_collateral_burn_event(self, event: pd.Series) -> None: + """Handles collateral withdrawal event by + decreasing the collateral value for a specified user.""" if event["keys"] == [self.BURN_KEY]: # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x00744177ee88dd3d96dda1784e2dff50f0c989b7fd48755bc42972af2e951dd6_1. + # Example: + # https://starkscan.co/event/0x00744177ee88dd3d96dda1784e2dff50f0c989b7fd48755bc42972af2e951dd6_1. user = add_leading_zeros(event["data"][0]) face_amount = decimal.Decimal(str(int(event["data"][1], base=16))) else: @@ -451,12 +443,11 @@ def process_collateral_burn_event(self, event: pd.Series) -> None: raw_amount = face_amount / self.interest_rate_models.collateral[token] else: raw_amount = face_amount - self.loan_entities[user].collateral.increase_value( - token=token, value=-raw_amount - ) + self.loan_entities[user].collateral.increase_value(token=token, value=-raw_amount) if user == self.verbose_user: logging.info( - "In block number = {}, collateral of raw amount = {} of token = {} was withdrawn.".format( + "In block number = {}, collateral of raw amount = {} of token = {} was withdrawn.". + format( event["block_number"], raw_amount, token, @@ -464,10 +455,13 @@ def process_collateral_burn_event(self, event: pd.Series) -> None: ) def process_debt_transfer_event(self, event: pd.Series) -> None: + """Process debt transfer event, adjusting loan debt for sender and recipient.""" if event["keys"] == [self.TRANSFER_KEY]: - # The order of the values in the `data` column is: `sender`, `recipient`, `value`, ``. Alternatively, + # The order of the values in the `data` column is: + # `sender`, `recipient`, `value`, ``. Alternatively, # `from_`, `to`, `value`, ``. - # Example: https://starkscan.co/event/0x0786a8918c8897db760899ee35b43071bfd723fec76487207882695e4b3014a0_1. + # Example: + # https://starkscan.co/event/0x0786a8918c8897db760899ee35b43071bfd723fec76487207882695e4b3014a0_1. sender = add_leading_zeros(event["data"][0]) recipient = add_leading_zeros(event["data"][1]) raw_amount = decimal.Decimal(str(int(event["data"][2], base=16))) @@ -478,18 +472,15 @@ def process_debt_transfer_event(self, event: pd.Series) -> None: token = add_leading_zeros(event["from_address"]) if sender != self.DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: - self.loan_entities[sender].debt.increase_value( - token=token, value=-raw_amount - ) + self.loan_entities[sender].debt.increase_value(token=token, value=-raw_amount) if recipient != self.DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: - self.loan_entities[recipient].debt.increase_value( - token=token, value=raw_amount - ) + self.loan_entities[recipient].debt.increase_value(token=token, value=raw_amount) if self.verbose_user in {sender, recipient}: logging.info( - "In block number = {}, debt of raw amount = {} of token = {} was transferred from user = {} to user = {}.".format( + "In block number = {}, debt of raw amount = {} of token = {} was transferred from user = {} to user = {}." + .format( event["block_number"], raw_amount, token, @@ -498,15 +489,14 @@ def process_debt_transfer_event(self, event: pd.Series) -> None: ) ) - def process_non_interest_bearing_collateral_burn_event( - self, event: pd.Series - ) -> None: + def process_non_interest_bearing_collateral_burn_event(self, event: pd.Series) -> None: """ Processes the non-interest-bearing collateral burn event. :param event: Event data. """ # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x00744177ee88dd3d96dda1784e2dff50f0c989b7fd48755bc42972af2e951dd6_1. + # Example: + # https://starkscan.co/event/0x00744177ee88dd3d96dda1784e2dff50f0c989b7fd48755bc42972af2e951dd6_1. user = event["data"][0] if user == self.IGNORE_USER: return @@ -522,14 +512,15 @@ def process_non_interest_bearing_collateral_burn_event( ) self.loan_entities[user].collateral.values = { token: ( - self.loan_entities[user].non_interest_bearing_collateral.values[token] - + self.loan_entities[user].interest_bearing_collateral.values[token] + self.loan_entities[user].non_interest_bearing_collateral.values[token] + + self.loan_entities[user].interest_bearing_collateral.values[token] ) for token in NOSTRA_ALPHA_SPECIFIC_TOKEN_SETTINGS } if user == self.verbose_user: logging.info( - "In block number = {}, non-interest-bearing collateral of raw amount = {} of token = {} was withdrawn.".format( + "In block number = {}, non-interest-bearing collateral of raw amount = {} of token = {} was withdrawn." + .format( event["block_number"], raw_amount, token, @@ -537,8 +528,10 @@ def process_non_interest_bearing_collateral_burn_event( ) def process_interest_bearing_collateral_mint_event(self, event: pd.Series) -> None: + """Process event adding interest-bearing collateral to a loan.""" # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x07d222d9a70edbe717001ab4305a7a8cfb05116a35da24a9406209dbb07b6d0b_5. + # Example: + # https://starkscan.co/event/0x07d222d9a70edbe717001ab4305a7a8cfb05116a35da24a9406209dbb07b6d0b_5. user = event["data"][0] if user == self.IGNORE_USER: return @@ -554,14 +547,15 @@ def process_interest_bearing_collateral_mint_event(self, event: pd.Series) -> No ) self.loan_entities[user].collateral.values = { token: ( - self.loan_entities[user].non_interest_bearing_collateral.values[token] - + self.loan_entities[user].interest_bearing_collateral.values[token] + self.loan_entities[user].non_interest_bearing_collateral.values[token] + + self.loan_entities[user].interest_bearing_collateral.values[token] ) for token in NOSTRA_ALPHA_SPECIFIC_TOKEN_SETTINGS } if user == self.verbose_user: logging.info( - "In block number = {}, interest-bearing collateral of raw amount = {} of token = {} was added.".format( + "In block number = {}, interest-bearing collateral of raw amount = {} of token = {} was added." + .format( event["block_number"], raw_amount, token, @@ -569,8 +563,10 @@ def process_interest_bearing_collateral_mint_event(self, event: pd.Series) -> No ) def process_interest_bearing_collateral_burn_event(self, event: pd.Series) -> None: + """Handle withdrawal of interest-bearing collateral from a loan.""" # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x0106494005bbab6f01e7779760891eb9ae20e01b905afdb16111f7cf3a28a53e_1. + # Example: + # https://starkscan.co/event/0x0106494005bbab6f01e7779760891eb9ae20e01b905afdb16111f7cf3a28a53e_1. user = event["data"][0] if user == self.IGNORE_USER: return @@ -586,14 +582,15 @@ def process_interest_bearing_collateral_burn_event(self, event: pd.Series) -> No ) self.loan_entities[user].collateral.values = { token: ( - self.loan_entities[user].non_interest_bearing_collateral.values[token] - + self.loan_entities[user].interest_bearing_collateral.values[token] + self.loan_entities[user].non_interest_bearing_collateral.values[token] + + self.loan_entities[user].interest_bearing_collateral.values[token] ) for token in NOSTRA_ALPHA_SPECIFIC_TOKEN_SETTINGS } if user == self.verbose_user: logging.info( - "In block number = {}, interest-bearing collateral of raw amount = {} of token = {} was withdrawn.".format( + "In block number = {}, interest-bearing collateral of raw amount = {} of token = {} was withdrawn." + .format( event["block_number"], raw_amount, token, @@ -607,7 +604,8 @@ def process_debt_mint_event(self, event: pd.Series) -> None: """ if event["keys"] == [self.MINT_KEY]: # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x030d23c4769917bc673875e107ebdea31711e2bdc45e658125dbc2e988945f69_4. + # Example: + # https://starkscan.co/event/0x030d23c4769917bc673875e107ebdea31711e2bdc45e658125dbc2e988945f69_4. user = add_leading_zeros(event["data"][0]) face_amount = decimal.Decimal(str(int(event["data"][1], base=16))) else: @@ -637,7 +635,8 @@ def process_debt_burn_event(self, event: pd.Series) -> None: """ if event["keys"] == [self.BURN_KEY]: # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x002e4ee376785f687f32715d8bbed787b6d0fa9775dc9329ca2185155a139ca3_5. + # Example: + # https://starkscan.co/event/0x002e4ee376785f687f32715d8bbed787b6d0fa9775dc9329ca2185155a139ca3_5. user = add_leading_zeros(event["data"][0]) face_amount = decimal.Decimal(str(int(event["data"][1], base=16))) else: @@ -675,10 +674,7 @@ def compute_liquidable_debt_at_price( for token, token_amount in loan_entity.collateral.items() if token_amount > decimal.Decimal("0") } - if ( - not collateral_token_underlying_address - in collateral_token_underlying_addresses - ): + if (not collateral_token_underlying_address in collateral_token_underlying_addresses): continue # Filter out entities where the debt token of interest is borowed. @@ -687,7 +683,7 @@ def compute_liquidable_debt_at_price( for token, token_amount in loan_entity.debt.items() if token_amount > decimal.Decimal("0") } - if not debt_token_underlying_address in debt_token_underlying_addresses: + if debt_token_underlying_address not in debt_token_underlying_addresses: continue # Filter out entities with health factor below 1. @@ -711,8 +707,10 @@ def compute_liquidable_debt_at_price( if health_factor >= 1.0: continue - # Find out how much of the `debt_token` will be liquidated. We assume that the liquidator receives the - # collateral token of interest even though it might not be the most optimal choice for the liquidator. + # Find out how much of the `debt_token` will be liquidated. + # We assume that the liquidator receives the + # collateral token of interest even though it might not be the most + # optimal choice for the liquidator. collateral_token_addresses = { self.token_parameters.collateral[token].address for token, token_amount in loan_entity.collateral.items() diff --git a/apps/data_handler/handlers/loan_states/nostra_alpha/run.py b/apps/data_handler/handlers/loan_states/nostra_alpha/run.py index 1d890234..1aafce15 100644 --- a/apps/data_handler/handlers/loan_states/nostra_alpha/run.py +++ b/apps/data_handler/handlers/loan_states/nostra_alpha/run.py @@ -1,3 +1,4 @@ +""" This module contains the logic to compute the loan states for the NOSTRA_ALPHA protocol. """ import logging from time import monotonic @@ -38,9 +39,7 @@ class NostraAlphaStateComputation(LoanStateComputationBase): EVENTS_METHODS_MAPPING = NOSTRA_ALPHA_EVENTS_TO_METHODS ADDRESSES_TO_EVENTS = NOSTRA_ALPHA_ADDRESSES_TO_EVENTS - def process_interest_rate_event( - self, nostra_state: NostraAlphaState, event: pd.Series - ) -> None: + def process_interest_rate_event(self, nostra_state: NostraAlphaState, event: pd.Series) -> None: """ Processes an interest rate event. @@ -64,9 +63,7 @@ def process_data(self, data: list[dict]) -> pd.DataFrame: """ nostra_alpha_state = NostraAlphaState() - events_with_interest_rate = ( - list(self.EVENTS_MAPPING.keys()) + self.INTEREST_RATES_KEYS - ) + events_with_interest_rate = (list(self.EVENTS_MAPPING.keys()) + self.INTEREST_RATES_KEYS) # Init DataFrame df = pd.DataFrame(data) diff --git a/apps/data_handler/handlers/loan_states/nostra_mainnet/__init__.py b/apps/data_handler/handlers/loan_states/nostra_mainnet/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/loan_states/nostra_mainnet/__init__.py +++ b/apps/data_handler/handlers/loan_states/nostra_mainnet/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/loan_states/nostra_mainnet/events.py b/apps/data_handler/handlers/loan_states/nostra_mainnet/events.py index c283610b..55175604 100644 --- a/apps/data_handler/handlers/loan_states/nostra_mainnet/events.py +++ b/apps/data_handler/handlers/loan_states/nostra_mainnet/events.py @@ -1,3 +1,4 @@ +""" This module contains the class that describes the state of all Nostra Mainnet loan entities. """ import copy import logging from decimal import Decimal @@ -31,7 +32,8 @@ class NostraMainnetLoanEntity(NostraAlphaLoanEntity): """ - A class that describes the state of all Nostra Mainnet loan entities. All methods for correct processing of every + A class that describes the state of all Nostra Mainnet loan entities. + All methods for correct processing of every relevant event are implemented in `.nostra_alpha.NostraAlphaState`. """ @@ -55,7 +57,8 @@ def compute_debt_to_be_liquidated( risk_adjusted_debt_usd: float | None = None, ) -> float: """ - Computes the amount of debt that can be liquidated given the current state of the loan entity. + Computes the amount of debt that can be liquidated given the + current state of the loan entity. :param collateral_token_addresses: Collateral token addresses. :param debt_token_addresses: Debt token addresses. :param prices: Prices of all tokens. @@ -86,39 +89,34 @@ def compute_debt_to_be_liquidated( # See an example of a liquidation here: # https://docs.nostra.finance/lend/liquidations/an-example-of-liquidation. numerator = ( - risk_adjusted_collateral_usd - - risk_adjusted_debt_usd * self.TARGET_HEALTH_FACTOR + risk_adjusted_collateral_usd - risk_adjusted_debt_usd * self.TARGET_HEALTH_FACTOR ) # TODO: figure out what to do when there's multiple collateral token addresses collateral_token_address = collateral_token_addresses[0] # TODO: figure out what to do when there's multiple collateral token addresses debt_token_address = debt_token_addresses[0] denominator = ( - collateral_token_parameters[collateral_token_address].collateral_factor - * (1 + self.LIQUIDATION_BONUS) - - (1 / debt_token_parameters[debt_token_address].debt_factor) - * self.TARGET_HEALTH_FACTOR + collateral_token_parameters[collateral_token_address].collateral_factor * + (1 + self.LIQUIDATION_BONUS) - + (1 / debt_token_parameters[debt_token_address].debt_factor) * self.TARGET_HEALTH_FACTOR ) max_debt_to_be_liquidated = numerator / denominator # The liquidator can't liquidate more debt than what is available. - debt_to_be_liquidated = min( - float(self.debt[debt_token_address]), max_debt_to_be_liquidated - ) + debt_to_be_liquidated = min(float(self.debt[debt_token_address]), max_debt_to_be_liquidated) return debt_to_be_liquidated class NostraMainnetState(NostraAlphaState): """ - A class that describes the state of all Nostra Mainnet loan entities. All methods for correct processing of every + A class that describes the state of all Nostra Mainnet + loan entities. All methods for correct processing of every relevant event are implemented in `.nostra_alpha.NostraAlphaState`. """ TOKEN_ADDRESSES: list[str] = NOSTRA_MAINNET_TOKEN_ADDRESSES INTEREST_RATE_MODEL_ADDRESS: str = NOSTRA_MAINNET_INTEREST_RATE_MODEL_ADDRESS CDP_MANAGER_ADDRESS: str = NOSTRA_MAINNET_CDP_MANAGER_ADDRESS - DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: str = ( - NOSTRA_MAINNET_DEFERRED_BATCH_CALL_ADAPTER_ADDRESS - ) + DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: str = (NOSTRA_MAINNET_DEFERRED_BATCH_CALL_ADAPTER_ADDRESS) EVENTS_TO_METHODS: dict[str, str] = NOSTRA_MAINNET_EVENTS_TO_METHODS @@ -145,9 +143,7 @@ async def collect_token_parameters(self) -> None: decimals = int(decimals[0]) token_symbol = await get_symbol(token_address=token_address) - event, is_interest_bearing = self._infer_token_type( - token_symbol=token_symbol - ) + event, is_interest_bearing = self._infer_token_type(token_symbol=token_symbol) self.token_addresses_to_events[token_address] = event underlying_token_address = await stark_client.func_call( @@ -155,16 +151,13 @@ async def collect_token_parameters(self) -> None: selector="underlyingAsset", calldata=[], ) - underlying_token_address = add_leading_zeros( - hex(underlying_token_address[0]) - ) - underlying_token_symbol = await get_symbol( - token_address=underlying_token_address - ) + underlying_token_address = add_leading_zeros(hex(underlying_token_address[0])) + underlying_token_symbol = await get_symbol(token_address=underlying_token_address) if event == "collateral": try: - # The order of the arguments is: `index`, `collateral_factor`, ``, `price_oracle`. + # The order of the arguments is: `index`, `collateral_factor`, ``, + # `price_oracle`. collateral_data = await stark_client.func_call( addr=self.CDP_MANAGER_ADDRESS, selector="collateral_data", @@ -194,7 +187,8 @@ async def collect_token_parameters(self) -> None: protocol_fee=protocol_fee, ) else: - # The order of the arguments is: `index`, `debt_tier`, `debt_factor`, ``, `price_oracle`. + # The order of the arguments is: `index`, `debt_tier`, `debt_factor`, ``, + # `price_oracle`. debt_data = await stark_client.func_call( addr=self.CDP_MANAGER_ADDRESS, selector="debt_data", @@ -212,26 +206,22 @@ async def collect_token_parameters(self) -> None: ) getattr(self.token_parameters, event)[token_address] = token_parameters - # Create the mapping between the debt token addresses and the respective interest bearing collateral token + # Create the mapping between + # the debt token addresses and the respective interest bearing collateral token # addresses. for debt_token_parameters in self.token_parameters.debt.values(): interest_bearing_collateral_token_addresses = [ collateral_token_parameters.address - for collateral_token_parameters in self.token_parameters.collateral.values() - if ( - collateral_token_parameters.is_interest_bearing - and collateral_token_parameters.underlying_address - == debt_token_parameters.underlying_address + for collateral_token_parameters in self.token_parameters.collateral.values() if ( + collateral_token_parameters.is_interest_bearing and collateral_token_parameters. + underlying_address == debt_token_parameters.underlying_address ) ] # TODO: check DAI V2 if interest_bearing_collateral_token_addresses: assert len(interest_bearing_collateral_token_addresses) == 1 self.debt_token_addresses_to_interest_bearing_collateral_token_addresses[ - debt_token_parameters.address - ] = interest_bearing_collateral_token_addresses[ - 0 - ] + debt_token_parameters.address] = interest_bearing_collateral_token_addresses[0] def process_interest_rate_model_event(self, event: pd.Series) -> None: """ @@ -239,30 +229,27 @@ def process_interest_rate_model_event(self, event: pd.Series) -> None: :param event: Event data. """ if event["keys"] == [self.INTEREST_STATE_UPDATED_KEY]: - # The order of the values in the `data` column is: `debtToken`, `lendingRate`, ``, `borrowRate`, ``, + # The order of the values in the `data` column is: + # `debtToken`, `lendingRate`, ``, `borrowRate`, ``, # `lendIndex`, ``, `borrowIndex`, ``. - # Example: https://starkscan.co/event/0x0735fc1d2fdd75ec049af40073a09ffc948c45467752d3123eb2b8c1d3f46edb_7. + # Example: + # https://starkscan.co/event/0x0735fc1d2fdd75ec049af40073a09ffc948c45467752d3123eb2b8c1d3f46edb_7. debt_token = add_leading_zeros(event["data"][0]) - collateral_interest_rate_index = Decimal( - str(int(event["data"][5], base=16)) - ) / Decimal("1e18") - debt_interest_rate_index = Decimal( - str(int(event["data"][7], base=16)) - ) / Decimal("1e18") - elif ( - len(event["keys"]) == 2 - and event["keys"][0] == self.INTEREST_STATE_UPDATED_KEY - ): - # The order of the values in the `data` column is: `lendingRate`, ``, `borrowingRate`, ``, `lendingIndex`, + collateral_interest_rate_index = Decimal(str(int(event["data"][5], base=16)) + ) / Decimal("1e18") + debt_interest_rate_index = Decimal(str(int(event["data"][7], base=16)) + ) / Decimal("1e18") + elif (len(event["keys"]) == 2 and event["keys"][0] == self.INTEREST_STATE_UPDATED_KEY): + # The order of the values in the `data` column is: `lendingRate`, + # ``, `borrowingRate`, ``, `lendingIndex`, # ``, `borrowingIndex`, ``. - # Example: https://starkscan.co/event/0x046d972ab22bd443534b32fdeabb1e4751ae6fa92610e9e2d4833764367d08f8_10. + # Example: + # https://starkscan.co/event/0x046d972ab22bd443534b32fdeabb1e4751ae6fa92610e9e2d4833764367d08f8_10. debt_token = add_leading_zeros(event["keys"][1]) - collateral_interest_rate_index = Decimal( - str(int(event["data"][4], base=16)) - ) / Decimal("1e18") - debt_interest_rate_index = Decimal( - str(int(event["data"][6], base=16)) - ) / Decimal("1e18") + collateral_interest_rate_index = Decimal(str(int(event["data"][4], base=16)) + ) / Decimal("1e18") + debt_interest_rate_index = Decimal(str(int(event["data"][6], base=16)) + ) / Decimal("1e18") else: raise ValueError("Event = {} has an unexpected structure.".format(event)) collateral_token = self.debt_token_addresses_to_interest_bearing_collateral_token_addresses.get( @@ -281,15 +268,18 @@ def process_collateral_transfer_event(self, event: pd.Series) -> None: :param event: Event data. """ if event["keys"] == [self.TRANSFER_KEY]: - # The order of the values in the `data` column is: `sender`, `recipient`, `value`, ``. Alternatively, + # The order of the values in the `data` column is: `sender`, + # `recipient`, `value`, ``. Alternatively, # `from_`, `to`, `value`, ``. - # Example: https://starkscan.co/event/0x00489af46e28392d1c3e4007476328ba4ccf4bd84f4f5565fda0888d5518a70b_3. + # Example: + # https://starkscan.co/event/0x00489af46e28392d1c3e4007476328ba4ccf4bd84f4f5565fda0888d5518a70b_3. sender = add_leading_zeros(event["data"][0]) recipient = add_leading_zeros(event["data"][1]) raw_amount = Decimal(str(int(event["data"][2], base=16))) elif len(event["keys"]) == 3 and event["keys"][0] == self.TRANSFER_KEY: # The order of the values in the `data` column is: `value`, ``. - # Example: https://starkscan.co/event/0x0476bd5d00fa21ad5f4b6c02352770ec869b66659de4e784a77bf293dc1010a5_0. + # Example: + # https://starkscan.co/event/0x0476bd5d00fa21ad5f4b6c02352770ec869b66659de4e784a77bf293dc1010a5_0. sender = add_leading_zeros(event["keys"][1]) recipient = add_leading_zeros(event["keys"][2]) raw_amount = Decimal(str(int(event["data"][0], base=16))) @@ -301,16 +291,13 @@ def process_collateral_transfer_event(self, event: pd.Series) -> None: token = add_leading_zeros(event["from_address"]) if sender != self.DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: - self.loan_entities[sender].collateral.increase_value( - token=token, value=-raw_amount - ) + self.loan_entities[sender].collateral.increase_value(token=token, value=-raw_amount) if recipient != self.DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: - self.loan_entities[recipient].collateral.increase_value( - token=token, value=raw_amount - ) + self.loan_entities[recipient].collateral.increase_value(token=token, value=raw_amount) if self.verbose_user in {sender, recipient}: logger.info( - "In block number = {}, collateral of raw amount = {} of token = {} was transferred from user = {} to user = {}.".format( + "In block number = {}, collateral of raw amount = {} of token = {} was transferred from user = {} to user = {}." + .format( event["block_number"], raw_amount, token, @@ -326,12 +313,14 @@ def process_collateral_mint_event(self, event: pd.Series) -> None: """ if event["keys"] == [self.MINT_KEY]: # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x0477258515240a6d24c7b8b9a5d0c1c387b925186efd250c6f278245b40b442d_9. + # Example: + # https://starkscan.co/event/0x0477258515240a6d24c7b8b9a5d0c1c387b925186efd250c6f278245b40b442d_9. user = add_leading_zeros(event["data"][0]) face_amount = Decimal(str(int(event["data"][1], base=16))) elif len(event["keys"]) == 2 and event["keys"][0] == self.MINT_KEY: # The order of the values in the `data` column is: `amount`, ``. - # Example: https://starkscan.co/event/0x01ebb29750907134804218ef5fc5f1688796a1a283e7d94aea87fa9a118f578a_4. + # Example: + # https://starkscan.co/event/0x01ebb29750907134804218ef5fc5f1688796a1a283e7d94aea87fa9a118f578a_4. user = add_leading_zeros(event["keys"][1]) face_amount = Decimal(str(int(event["data"][0], base=16))) else: @@ -344,12 +333,11 @@ def process_collateral_mint_event(self, event: pd.Series) -> None: raw_amount = face_amount / self.interest_rate_models.collateral[token] else: raw_amount = face_amount - self.loan_entities[user].collateral.increase_value( - token=token, value=raw_amount - ) + self.loan_entities[user].collateral.increase_value(token=token, value=raw_amount) if user == self.verbose_user: logger.info( - "In block number = {}, collateral of raw amount = {} of token = {} was added.".format( + "In block number = {}, collateral of raw amount = {} of token = {} was added.". + format( event["block_number"], raw_amount, token, @@ -363,12 +351,14 @@ def process_collateral_burn_event(self, event: pd.Series) -> None: """ if event["keys"] == [self.BURN_KEY]: # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x02eb8bbac79765f948cb49f91d2ffb85ffcf0e98a9292d9fdfbb426f69fd712f_1. + # Example: + # https://starkscan.co/event/0x02eb8bbac79765f948cb49f91d2ffb85ffcf0e98a9292d9fdfbb426f69fd712f_1. user = add_leading_zeros(event["data"][0]) face_amount = Decimal(str(int(event["data"][1], base=16))) elif len(event["keys"]) == 2 and event["keys"][0] == self.BURN_KEY: # The order of the values in the `data` column is: `amount`, ``. - # Example: https://starkscan.co/event/0x060e74cec0b2af4a0a885dd0c2019ae0af4cbc32fd36443286d03f8e1072028f_6. + # Example: + # https://starkscan.co/event/0x060e74cec0b2af4a0a885dd0c2019ae0af4cbc32fd36443286d03f8e1072028f_6. user = add_leading_zeros(event["keys"][1]) face_amount = Decimal(str(int(event["data"][0], base=16))) else: @@ -382,12 +372,11 @@ def process_collateral_burn_event(self, event: pd.Series) -> None: raw_amount = face_amount / self.interest_rate_models.collateral[token] else: raw_amount = face_amount - self.loan_entities[user].collateral.increase_value( - token=token, value=-raw_amount - ) + self.loan_entities[user].collateral.increase_value(token=token, value=-raw_amount) if user == self.verbose_user: logger.info( - "In block number = {}, collateral of raw amount = {} of token = {} was withdrawn.".format( + "In block number = {}, collateral of raw amount = {} of token = {} was withdrawn.". + format( event["block_number"], raw_amount, token, @@ -400,15 +389,18 @@ def process_debt_transfer_event(self, event: pd.Series) -> None: :param event: Event data. """ if event["keys"] == [self.TRANSFER_KEY]: - # The order of the values in the `data` column is: `sender`, `recipient`, `value`, ``. Alternatively, + # The order of the values in the `data` column is: `sender`, + # `recipient`, `value`, ``. Alternatively, # `from_`, `to`, `value`, ``. - # Example: https://starkscan.co/event/0x070f2c92bda051dc9f4daaef5582c7c2727b1ab07f04484c1f6a6109e1f9a0f6_2. + # Example: + # https://starkscan.co/event/0x070f2c92bda051dc9f4daaef5582c7c2727b1ab07f04484c1f6a6109e1f9a0f6_2. sender = add_leading_zeros(event["data"][0]) recipient = add_leading_zeros(event["data"][1]) raw_amount = Decimal(str(int(event["data"][2], base=16))) elif len(event["keys"]) == 3 and event["keys"][0] == self.TRANSFER_KEY: # The order of the values in the `data` column is: `value`, ``. - # Example: https://starkscan.co/event/0x051aca058e0f4e6193f4bc78eabe870bfc07d477e803f89c8293fc97118d523a_4. + # Example: + # https://starkscan.co/event/0x051aca058e0f4e6193f4bc78eabe870bfc07d477e803f89c8293fc97118d523a_4. sender = add_leading_zeros(event["keys"][1]) recipient = add_leading_zeros(event["keys"][2]) raw_amount = Decimal(str(int(event["data"][0], base=16))) @@ -419,18 +411,15 @@ def process_debt_transfer_event(self, event: pd.Series) -> None: token = add_leading_zeros(event["from_address"]) if sender != self.DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: - self.loan_entities[sender].debt.increase_value( - token=token, value=-raw_amount - ) + self.loan_entities[sender].debt.increase_value(token=token, value=-raw_amount) if recipient != self.DEFERRED_BATCH_CALL_ADAPTER_ADDRESS: - self.loan_entities[recipient].debt.increase_value( - token=token, value=raw_amount - ) + self.loan_entities[recipient].debt.increase_value(token=token, value=raw_amount) if self.verbose_user in {sender, recipient}: logger.info( - "In block number = {}, debt of raw amount = {} of token = {} was transferred from user = {} to user = {}.".format( + "In block number = {}, debt of raw amount = {} of token = {} was transferred from user = {} to user = {}." + .format( event["block_number"], raw_amount, token, @@ -446,12 +435,14 @@ def process_debt_mint_event(self, event: pd.Series) -> None: """ if event["keys"] == [self.MINT_KEY]: # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x018092d8bf2b31834f6cc3dd0e00b6ebb71352c30b1c4549ac445c58cbce05fa_7. + # Example: + # https://starkscan.co/event/0x018092d8bf2b31834f6cc3dd0e00b6ebb71352c30b1c4549ac445c58cbce05fa_7. user = add_leading_zeros(event["data"][0]) face_amount = Decimal(str(int(event["data"][1], base=16))) elif len(event["keys"]) == 2 and event["keys"][0] == self.MINT_KEY: # The order of the values in the `data` column is: `amount`, ``. - # Example: https://starkscan.co/event/0x02a7c2cf0263bebe38bbaace9c789ba600a1bd9ddd8f4341d618c0894048b28e_7. + # Example: + # https://starkscan.co/event/0x02a7c2cf0263bebe38bbaace9c789ba600a1bd9ddd8f4341d618c0894048b28e_7. user = add_leading_zeros(event["keys"][1]) face_amount = Decimal(str(int(event["data"][0], base=16))) else: @@ -481,12 +472,14 @@ def process_debt_burn_event(self, event: pd.Series) -> None: """ if event["keys"] == [self.BURN_KEY]: # The order of the values in the `data` column is: `user`, `amount`, ``. - # Example: https://starkscan.co/event/0x045561da020c693288386c92a4aaafae30ed1ddcdaa02373246d556b806662c1_7. + # Example: + # https://starkscan.co/event/0x045561da020c693288386c92a4aaafae30ed1ddcdaa02373246d556b806662c1_7. user = add_leading_zeros(event["data"][0]) face_amount = Decimal(str(int(event["data"][1], base=16))) elif len(event["keys"]) == 2 and event["keys"][0] == self.BURN_KEY: # The order of the values in the `data` column is: `amount`, ``. - # Example: https://starkscan.co/event/0x0580b701b7501058aa97a6e73670ca5530fdfe77e754a185378d85ebdac33034_5. + # Example: + # https://starkscan.co/event/0x0580b701b7501058aa97a6e73670ca5530fdfe77e754a185378d85ebdac33034_5. user = add_leading_zeros(event["keys"][1]) face_amount = Decimal(str(int(event["data"][0], base=16))) else: @@ -518,7 +511,8 @@ def compute_liquidable_debt_at_price( debt_token_underlying_address: str, ) -> float: """ - Computes the maximum amount of debt that can be liquidated given the current state of the loan entities. + Computes the maximum amount of debt that can be + liquidated given the current state of the loan entities. :param prices: Prices of all tokens. :param collateral_token_underlying_address: Collateral token underlying address. :param collateral_token_price: Collateral token price. @@ -535,19 +529,15 @@ def compute_liquidable_debt_at_price( for token, token_amount in loan_entity.collateral.items() if token_amount > Decimal("0") } - if ( - not collateral_token_underlying_address - in collateral_token_underlying_addresses - ): + if (not collateral_token_underlying_address in collateral_token_underlying_addresses): continue # Filter out entities where the debt token of interest is borowed. debt_token_underlying_addresses = { self.token_parameters.debt[token].underlying_address - for token, token_amount in loan_entity.debt.items() - if token_amount > Decimal("0") + for token, token_amount in loan_entity.debt.items() if token_amount > Decimal("0") } - if not debt_token_underlying_address in debt_token_underlying_addresses: + if debt_token_underlying_address not in debt_token_underlying_addresses: continue # Filter out entities with health factor below 1. @@ -571,8 +561,10 @@ def compute_liquidable_debt_at_price( if health_factor >= 1.0: continue - # Find out how much of the `debt_token` will be liquidated. We assume that the liquidator receives the - # collateral token of interest even though it might not be the most optimal choice for the liquidator. + # Find out how much of the `debt_token` will be liquidated. + # We assume that the liquidator receives the + # collateral token of interest even though it might not be the most + # optimal choice for the liquidator. collateral_token_addresses = { self.token_parameters.collateral[token].address for token, token_amount in loan_entity.collateral.items() diff --git a/apps/data_handler/handlers/loan_states/nostra_mainnet/run.py b/apps/data_handler/handlers/loan_states/nostra_mainnet/run.py index f4c4485e..c31623fc 100644 --- a/apps/data_handler/handlers/loan_states/nostra_mainnet/run.py +++ b/apps/data_handler/handlers/loan_states/nostra_mainnet/run.py @@ -1,3 +1,4 @@ +""" This module contains the Nostra Mainnet loan state computation class. """ import logging from time import monotonic @@ -29,7 +30,7 @@ class NostraMainnetStateComputation(LoanStateComputationBase): PROTOCOL_ADDRESSES = ProtocolAddresses().NOSTRA_MAINNET_ADDRESSES INTEREST_RATES_KEYS = ["InterestStateUpdated"] EVENTS_METHODS_MAPPING = NOSTRA_MAINNET_EVENTS_TO_METHODS - ADDRESSES_TO_EVENTS = (NOSTRA_MAINNET_ADDRESSES_TO_EVENTS,) + ADDRESSES_TO_EVENTS = (NOSTRA_MAINNET_ADDRESSES_TO_EVENTS, ) EVENTS_MAPPING = NOSTRA_EVENTS_MAPPING @@ -89,9 +90,7 @@ def process_data(self, data: list[dict]) -> pd.DataFrame: :return: pd.DataFrame """ nostra_mainnet_state = NostraMainnetState() - events_with_interest_rate = ( - list(self.EVENTS_MAPPING.keys()) + self.INTEREST_RATES_KEYS - ) + events_with_interest_rate = (list(self.EVENTS_MAPPING.keys()) + self.INTEREST_RATES_KEYS) # Init DataFrame df = pd.DataFrame(data) diff --git a/apps/data_handler/handlers/loan_states/zklend/__init__.py b/apps/data_handler/handlers/loan_states/zklend/__init__.py index bf6e082d..bdb0d20f 100644 --- a/apps/data_handler/handlers/loan_states/zklend/__init__.py +++ b/apps/data_handler/handlers/loan_states/zklend/__init__.py @@ -1,4 +1 @@ -from data_handler.handlers.loan_states.zklend.settings import ( - TOKEN_SETTINGS, - TokenSettings, -) +""" This module contains the settings for the ZkLend loan states handler. """ \ No newline at end of file diff --git a/apps/data_handler/handlers/loan_states/zklend/events.py b/apps/data_handler/handlers/loan_states/zklend/events.py index 7618a1f0..efddcfa6 100644 --- a/apps/data_handler/handlers/loan_states/zklend/events.py +++ b/apps/data_handler/handlers/loan_states/zklend/events.py @@ -1,3 +1,17 @@ +""" +zkLend Event Handlers + +This module handles events for zkLend loan entities, tracking deposits, +borrowings, repayments, collateral status, and liquidations. + +Classes: + - ZkLendLoanEntity: Manages deposit and collateral status for loans. + - ZkLendState: Processes events and computes liquidatable debt. + +Functions: + - collect_token_parameters: Fetches token parameters. + - process_*_event: Updates loan states based on events. +""" import asyncio import copy import decimal @@ -9,7 +23,7 @@ from data_handler.db.crud import InitializerDBConnector from data_handler.handler_tools.data_parser.zklend import ZklendDataParser from data_handler.handlers.helpers import get_async_symbol -from data_handler.handlers.loan_states.zklend import TokenSettings +from data_handler.handlers.settings import TokenSettings from shared.helpers import add_leading_zeros from shared.loan_entity import LoanEntity from shared.state import State @@ -30,10 +44,7 @@ ZkLendDebtTokenParameters, ) - -ZKLEND_MARKET: str = ( - "0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05" -) +ZKLEND_MARKET: str = ("0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05") EVENTS_METHODS_MAPPING: dict[str, str] = { "AccumulatorsSync": "process_accumulators_sync_event", "zklend::market::Market::AccumulatorsSync": "process_accumulators_sync_event", @@ -58,11 +69,16 @@ class ZkLendLoanEntity(LoanEntity): """ - A class that describes the zkLend loan entity. On top of the abstract `LoanEntity`, it implements the `deposit` and - `collateral_enabled` attributes in order to help with accounting for the changes in collateral. This is because - under zkLend, collateral is the amount deposited that is specificaly flagged with `collateral_enabled` set to True - for the given token. To properly account for the changes in collateral, we must hold the information about the - given token's deposits being enabled as collateral or not and the amount of the deposits. We keep all balances in raw + A class that describes the zkLend loan entity. On top of the abstract `LoanEntity`, + it implements the `deposit` and + `collateral_enabled` attributes in order to help with accounting for the changes in + collateral. This is because + under zkLend, collateral is the amount deposited that is specificaly flagged with + `collateral_enabled` set to True + for the given token. To properly account for the changes in collateral, we must hold the + information about the + given token's deposits being enabled as collateral or not and the amount of the deposits. + We keep all balances in raw amounts. """ @@ -128,15 +144,10 @@ def compute_debt_to_be_liquidated( # TODO: Commit a PDF with the derivation of the formula? numerator = debt_usd - risk_adjusted_collateral_usd denominator = prices[debt_token_underlying_address] * ( - 1 - - collateral_token_parameters[ - collateral_token_underlying_address - ].collateral_factor - * ( - 1 - + collateral_token_parameters[ - collateral_token_underlying_address - ].liquidation_bonus + 1 - collateral_token_parameters[collateral_token_underlying_address].collateral_factor * + ( + 1 + + collateral_token_parameters[collateral_token_underlying_address].liquidation_bonus ) ) max_debt_to_be_liquidated = numerator / denominator @@ -149,7 +160,8 @@ def compute_debt_to_be_liquidated( class ZkLendState(State): """ - A class that describes the state of all zkLend loan entities. It implements methods for correct processing of every + A class that describes the state + of all zkLend loan entities. It implements methods for correct processing of every relevant event. """ @@ -166,12 +178,14 @@ def __init__( self.db_connector = InitializerDBConnector() def process_accumulators_sync_event(self, event: pd.Series) -> None: - # The order of the values in the `data` column is: `token`, `lending_accumulator`, `debt_accumulator`. - # Example: https://starkscan.co/event/0x029628b89875a98c1c64ae206e7eb65669cb478a24449f3485f5e98aba6204dc_0. + """Processes an accumulators sync event, updating collateral and + debt interest rate models based on the latest data.""" + # The order of the values in the `data` column is: `token`, + # `lending_accumulator`, `debt_accumulator`. + # Example: + # https://starkscan.co/event/0x029628b89875a98c1c64ae206e7eb65669cb478a24449f3485f5e98aba6204dc_0. # TODO: Integrate the ZEND token once it's allowed to be borrowed or used as collateral. - parsed_event_data = ZklendDataParser.parse_accumulators_sync_event( - event["data"] - ) + parsed_event_data = ZklendDataParser.parse_accumulators_sync_event(event["data"]) token = add_leading_zeros(parsed_event_data.token) collateral_interest_rate_index = parsed_event_data.lending_accumulator @@ -181,8 +195,11 @@ def process_accumulators_sync_event(self, event: pd.Series) -> None: self.interest_rate_models.debt[token] = debt_interest_rate_index def process_deposit_event(self, event: pd.Series) -> None: + """Handles a deposit event, increasing the user's + deposit and optionally setting it as collateral.""" # The order of the values in the `data` column is: `user`, `token`, `face_amount`. - # Example: https://starkscan.co/event/0x036185142bb51e2c1f5bfdb1e6cef81f8ea87fd4d777990014249bf5435fd31b_3. + # Example: + # https://starkscan.co/event/0x036185142bb51e2c1f5bfdb1e6cef81f8ea87fd4d777990014249bf5435fd31b_3. data = ZklendDataParser.parse_deposit_event(event["data"]) user, token = data.user, data.token @@ -195,9 +212,7 @@ def process_deposit_event(self, event: pd.Series) -> None: self.loan_entities[user].deposit.increase_value(token=token, value=raw_amount) if self.loan_entities[user].collateral_enabled[token]: - self.loan_entities[user].collateral.increase_value( - token=token, value=raw_amount - ) + self.loan_entities[user].collateral.increase_value(token=token, value=raw_amount) if user == self.verbose_user: logging.info( "In block number = {}, raw amount = {} of token = {} was deposited.".format( @@ -208,11 +223,13 @@ def process_deposit_event(self, event: pd.Series) -> None: ) def process_collateral_enabled_event(self, event: pd.Series) -> None: + """Processes a collateral enablement event, + activating the specified token as collateral for the user.""" # The order of the values in the `data` column is: `user`, `token`. - # Example: https://starkscan.co/event/0x036185142bb51e2c1f5bfdb1e6cef81f8ea87fd4d777990014249bf5435fd31b_6. - data = ZklendDataParser.parse_collateral_enabled_disabled_event(event) - user = data.user - token = data.token + # Example: + # https://starkscan.co/event/0x036185142bb51e2c1f5bfdb1e6cef81f8ea87fd4d777990014249bf5435fd31b_6. + user = add_leading_zeros(event["data"][0]) + token = add_leading_zeros(event["data"][1]) # add additional info block and timestamp self.loan_entities[user].extra_info.block = event["block_number"] @@ -239,20 +256,20 @@ def process_collateral_enabled_event(self, event: pd.Series) -> None: ) def process_collateral_disabled_event(self, event: pd.Series) -> None: + """Processes a collateral disablement event, + setting collateral for the specified token to zero for the user.""" # The order of the values in the `data` column is: `user`, `token`. - # Example: https://starkscan.co/event/0x0049b445bed84e0118795dbd22d76610ccac2ad626f8f04a1fc7e38113c2afe7_0. - data = ZklendDataParser.parse_collateral_enabled_disabled_event(event) - user = data.user - token = data.token + # Example: + # https://starkscan.co/event/0x0049b445bed84e0118795dbd22d76610ccac2ad626f8f04a1fc7e38113c2afe7_0. + user = add_leading_zeros(event["data"][0]) + token = add_leading_zeros(event["data"][1]) # add additional info block and timestamp self.loan_entities[user].extra_info.block = event["block_number"] self.loan_entities[user].extra_info.timestamp = event["timestamp"] self.loan_entities[user].collateral_enabled[token] = False - self.loan_entities[user].collateral.set_value( - token=token, value=decimal.Decimal("0") - ) + self.loan_entities[user].collateral.set_value(token=token, value=decimal.Decimal("0")) if user == self.verbose_user: logging.info( "In block number = {}, collateral was disabled for token = {}.".format( @@ -262,16 +279,18 @@ def process_collateral_disabled_event(self, event: pd.Series) -> None: ) def process_withdrawal_event(self, event: pd.Series) -> None: + """Handles a withdrawal event by reducing + the user's deposit and collateral, adjusting based on the raw amount withdrawn.""" # The order of the values in the `data` column is: `user`, `token`, `face_amount`. - # Example: https://starkscan.co/event/0x03472cf7511687a55bc7247f8765c4bbd2c18b70e09b2a10a77c61f567bfd2cb_4. + # Example: + # https://starkscan.co/event/0x03472cf7511687a55bc7247f8765c4bbd2c18b70e09b2a10a77c61f567bfd2cb_4. data = ZklendDataParser.parse_withdrawal_event(event["data"]) user, token = data.user, data.token # Calculate the raw amount from face amount raw_amount = ( - decimal.Decimal(str(data.face_amount)) - / self.interest_rate_models.collateral[token] + decimal.Decimal(str(data.face_amount)) / self.interest_rate_models.collateral[token] ) # Add additional info: block number and timestamp @@ -280,11 +299,10 @@ def process_withdrawal_event(self, event: pd.Series) -> None: # Update the user's deposit and collateral values self.loan_entities[user].deposit.increase_value(token=token, value=-raw_amount) + self.loan_entities[user].deposit.increase_value(token=token, value=-raw_amount) if self.loan_entities[user].collateral_enabled[token]: - self.loan_entities[user].collateral.increase_value( - token=token, value=-raw_amount - ) + self.loan_entities[user].collateral.increase_value(token=token, value=-raw_amount) # Log the information if the user matches the verbose user if user == self.verbose_user: @@ -297,8 +315,12 @@ def process_withdrawal_event(self, event: pd.Series) -> None: ) def process_borrowing_event(self, event: pd.Series) -> None: - # The order of the values in the `data` column is: `user`, `token`, `raw_amount`, `face_amount`. - # Example: https://starkscan.co/event/0x076b1615750528635cf0b63ca80986b185acbd20fa37f0f2b5368a4f743931f8_3. + """Processes a borrowing event, increasing the user's debt by the + raw amount borrowed for the specified token.""" + # The order of the values in the `data` column is: `user`, + # `token`, `raw_amount`, `face_amount`. + # Example: + # https://starkscan.co/event/0x076b1615750528635cf0b63ca80986b185acbd20fa37f0f2b5368a4f743931f8_3. data = ZklendDataParser.parse_borrowing_event(event["data"]) user, token = data.user, data.token raw_amount = data.raw_amount @@ -317,6 +339,8 @@ def process_borrowing_event(self, event: pd.Series) -> None: ) def process_repayment_event(self, event: pd.Series) -> None: + """Processes a repayment event, updating the user’s debt by + reducing it according to the raw amount repaid.""" data = ZklendDataParser.parse_repayment_event(event["data"]) user = data.beneficiary @@ -338,16 +362,19 @@ def process_repayment_event(self, event: pd.Series) -> None: ) def process_liquidation_event(self, event: pd.Series) -> None: - # The order of the arguments is: `liquidator`, `user`, `debt_token`, `debt_raw_amount`, `debt_face_amount`, + """Processes a liquidation event, adjusting the user's debt and collateral + values based on liquidation amounts.""" + # The order of the arguments is: `liquidator`, `user`, `debt_token`, + # `debt_raw_amount`, `debt_face_amount`, # `collateral_token`, `collateral_amount`. - # Example: https://starkscan.co/event/0x07b8ec709df1066d9334d56b426c45440ca1f1bb841285a5d7b33f9d1008f256_5. + # Example: + # https://starkscan.co/event/0x07b8ec709df1066d9334d56b426c45440ca1f1bb841285a5d7b33f9d1008f256_5. data = ZklendDataParser.parse_liquidation_event(event["data"]) user = data.user collateral_raw_amount = ( - data.collateral_amount - / self.interest_rate_models.collateral[data.collateral_token] + data.collateral_amount / self.interest_rate_models.collateral[data.collateral_token] ) # add additional info block and timestamp self.loan_entities[user].extra_info.block = event["block_number"] @@ -392,10 +419,7 @@ def compute_liquidable_debt_at_price( for token, token_amount in loan_entity.collateral.items() if token_amount > decimal.Decimal("0") } - if ( - not collateral_token_underlying_address - in collateral_token_underlying_addresses - ): + if (not collateral_token_underlying_address in collateral_token_underlying_addresses): continue # Filter out entities where the debt token of interest is borrowed. @@ -404,7 +428,7 @@ def compute_liquidable_debt_at_price( for token, token_amount in loan_entity.debt.items() if token_amount > decimal.Decimal("0") } - if not debt_token_underlying_address in debt_token_underlying_addresses: + if debt_token_underlying_address not in debt_token_underlying_addresses: continue # Filter out entities with health factor below 1. @@ -425,12 +449,15 @@ def compute_liquidable_debt_at_price( risk_adjusted_collateral_usd=risk_adjusted_collateral_usd, debt_usd=debt_usd, ) - # TODO: `health_factor` < 0 should not be possible if the data is right. Should we keep the filter? + # TODO: `health_factor` < 0 should not be possible if the data is right. + # Should we keep the filter? if health_factor >= 1.0 or health_factor <= 0.0: continue - # Find out how much of the `debt_token` will be liquidated. We assume that the liquidator receives the - # collateral token of interest even though it might not be the most optimal choice for the liquidator. + # Find out how much of the `debt_token` will be liquidated. + # We assume that the liquidator receives the + # collateral token of interest even though it might not be the most + # optimal choice for the liquidator. max_liquidated_amount += loan_entity.compute_debt_to_be_liquidated( debt_token_underlying_address=debt_token_underlying_address, collateral_token_underlying_address=collateral_token_underlying_address, @@ -442,21 +469,25 @@ def compute_liquidable_debt_at_price( return max_liquidated_amount async def collect_token_parameters(self) -> None: + """Collects and sets token parameters for collateral and debt + tokens under zkLend, including collateral factors, liquidation bonuses, and debt factors.""" # Get the sets of unique collateral and debt tokens. - collateral_tokens = { - y for x in self.loan_entities.values() for y in x.collateral.keys() - } + collateral_tokens = {y for x in self.loan_entities.values() for y in x.collateral.keys()} debt_tokens = {y for x in self.loan_entities.values() for y in x.debt.keys()} - # Get parameters for each collateral and debt token. Under zkLend, the collateral token in the events data is + # Get parameters for each collateral and debt token. Under zkLend, + # the collateral token in the events data is # the underlying token directly. for underlying_collateral_token_address in collateral_tokens: underlying_collateral_token_symbol = await get_async_symbol( token_address=underlying_collateral_token_address ) - # The order of the arguments is: `enabled`, `decimals`, `z_token_address`, `interest_rate_model`, - # `collateral_factor`, `borrow_factor`, `reserve_factor`, `last_update_timestamp`, `lending_accumulator`, - # `debt_accumulator`, `current_lending_rate`, `current_borrowing_rate`, `raw_total_debt`, `flash_loan_fee`, + # The order of the arguments is: + # `enabled`, `decimals`, `z_token_address`, `interest_rate_model`, + # `collateral_factor`, `borrow_factor`, + # `reserve_factor`, `last_update_timestamp`, `lending_accumulator`, + # `debt_accumulator`, `current_lending_rate`, `current_borrowing_rate`, + # `raw_total_debt`, `flash_loan_fee`, # `liquidation_bonus`, `debt_limit`. reserve_data = await blockchain_call.func_call( addr=ZKLEND_MARKET, @@ -464,9 +495,7 @@ async def collect_token_parameters(self) -> None: calldata=[underlying_collateral_token_address], ) collateral_token_address = add_leading_zeros(hex(reserve_data[2])) - collateral_token_symbol = await get_async_symbol( - token_address=collateral_token_address - ) + collateral_token_symbol = await get_async_symbol(token_address=collateral_token_address) self.token_parameters.collateral[underlying_collateral_token_address] = ( ZkLendCollateralTokenParameters( address=collateral_token_address, @@ -482,9 +511,12 @@ async def collect_token_parameters(self) -> None: underlying_debt_token_symbol = await get_async_symbol( token_address=underlying_debt_token_address ) - # The order of the arguments is: `enabled`, `decimals`, `z_token_address`, `interest_rate_model`, - # `collateral_factor`, `borrow_factor`, `reserve_factor`, `last_update_timestamp`, `lending_accumulator`, - # `debt_accumulator`, `current_lending_rate`, `current_borrowing_rate`, `raw_total_debt`, `flash_loan_fee`, + # The order of the arguments is: `enabled`, `decimals`, + # `z_token_address`, `interest_rate_model`, + # `collateral_factor`, `borrow_factor`, `reserve_factor`, + # `last_update_timestamp`, `lending_accumulator`, + # `debt_accumulator`, `current_lending_rate`, `current_borrowing_rate`, + # `raw_total_debt`, `flash_loan_fee`, # `liquidation_bonus`, `debt_limit`. reserve_data = await blockchain_call.func_call( addr=ZKLEND_MARKET, diff --git a/apps/data_handler/handlers/loan_states/zklend/fetch_zklend_specific_token_settings.py b/apps/data_handler/handlers/loan_states/zklend/fetch_zklend_specific_token_settings.py index 39bd5595..414de2a7 100644 --- a/apps/data_handler/handlers/loan_states/zklend/fetch_zklend_specific_token_settings.py +++ b/apps/data_handler/handlers/loan_states/zklend/fetch_zklend_specific_token_settings.py @@ -1,3 +1,4 @@ +""" Fetch ZkLend specific token settings. """ import asyncio import decimal @@ -70,9 +71,7 @@ async def get_token_reserve_data(token_setting_address: str) -> list: return reserve_data -async def fetch_zklend_specific_token_settings() -> ( - dict[str, ZkLendSpecificTokenSettings] -): +async def fetch_zklend_specific_token_settings() -> (dict[str, ZkLendSpecificTokenSettings]): """ Fetch ZkLend specific token settings. :return: Dict of ZkLend specific token settings. diff --git a/apps/data_handler/handlers/loan_states/zklend/run.py b/apps/data_handler/handlers/loan_states/zklend/run.py index 0c89ef28..e687ee96 100644 --- a/apps/data_handler/handlers/loan_states/zklend/run.py +++ b/apps/data_handler/handlers/loan_states/zklend/run.py @@ -1,3 +1,4 @@ +""" This module contains the zkLend loan state computation class. """ import logging from time import monotonic @@ -25,9 +26,7 @@ class ZkLendLoanStateComputation(LoanStateComputationBase): "zklend::market::Market::AccumulatorsSync", ] - def process_event( - self, instance_state: State, method_name: str, event: pd.Series - ) -> None: + def process_event(self, instance_state: State, method_name: str, event: pd.Series) -> None: """ Processes an event based on the method name and the event data. @@ -43,10 +42,7 @@ def process_event( block_number = event.get("block_number") self.set_interest_rate(instance_state, block_number, self.PROTOCOL_TYPE) # For each block number, process the interest rate event - if ( - self.last_block < block_number - and event["key_name"] in self.INTEREST_RATES_KEYS - ): + if (self.last_block < block_number and event["key_name"] in self.INTEREST_RATES_KEYS): self.process_interest_rate_event(instance_state, event) if block_number and block_number >= self.last_block: @@ -55,15 +51,11 @@ def process_event( if method: method(event) else: - logger.info( - f"No method named {method_name} found for processing event." - ) + logger.info(f"No method named {method_name} found for processing event.") except Exception as e: logger.exception(f"Failed to process event due to an error: {e}") - def process_interest_rate_event( - self, zklend_state: ZkLendState, event: pd.Series - ) -> None: + def process_interest_rate_event(self, zklend_state: ZkLendState, event: pd.Series) -> None: """ Processes an interest rate event. @@ -103,8 +95,10 @@ def process_data(self, data: list[dict]) -> pd.DataFrame: result_df = self.get_result_df(zklend_state.loan_entities) result_df["deposit"] = [ - {token: float(amount) for token, amount in loan.deposit.items()} - for loan in zklend_state.loan_entities.values() + { + token: float(amount) + for token, amount in loan.deposit.items() + } for loan in zklend_state.loan_entities.values() ] logger.info(f"Processed data for block {self.last_block}") return result_df @@ -125,16 +119,18 @@ def get_result_df(self, loan_entities: dict) -> pd.DataFrame: "protocol": [self.PROTOCOL_TYPE for _ in loan_entities_values], "user": [user for user in loan_entities.keys()], "collateral": [ - {token: float(amount) for token, amount in loan.collateral.items()} - for loan in loan_entities_values + { + token: float(amount) + for token, amount in loan.collateral.items() + } for loan in loan_entities_values ], "block": [entity.extra_info.block for entity in loan_entities_values], - "timestamp": [ - entity.extra_info.timestamp for entity in loan_entities_values - ], + "timestamp": [entity.extra_info.timestamp for entity in loan_entities_values], "debt": [ - {token: float(amount) for token, amount in loan.debt.items()} - for loan in loan_entities_values + { + token: float(amount) + for token, amount in loan.debt.items() + } for loan in loan_entities_values ], } diff --git a/apps/data_handler/handlers/loan_states/zklend/settings.py b/apps/data_handler/handlers/loan_states/zklend/settings.py index 991886cf..1d443ec3 100644 --- a/apps/data_handler/handlers/loan_states/zklend/settings.py +++ b/apps/data_handler/handlers/loan_states/zklend/settings.py @@ -1,3 +1,4 @@ +""" Settings for ZkLend. """ import decimal from dataclasses import dataclass @@ -7,6 +8,7 @@ @dataclass class ZkLendSpecificTokenSettings: + """Class for ZkLend specific token settings.""" # Source: https://zklend.gitbook.io/documentation/using-zklend/technical/asset-parameters. collateral_factor: decimal.Decimal # These are set to neutral values because zkLend doesn't use debt factors. @@ -18,55 +20,64 @@ class ZkLendSpecificTokenSettings: @dataclass class TokenSettings(ZkLendSpecificTokenSettings, TokenSettings): + """Class for token settings.""" pass ZKLEND_SPECIFIC_TOKEN_SETTINGS: dict[str, ZkLendSpecificTokenSettings] = { - "ETH": ZkLendSpecificTokenSettings( + "ETH": + ZkLendSpecificTokenSettings( collateral_factor=decimal.Decimal("0.80"), debt_factor=decimal.Decimal("1"), liquidation_bonus=decimal.Decimal("0.10"), protocol_token_address="0x01b5bd713e72fdc5d63ffd83762f81297f6175a5e0a4771cdadbc1dd5fe72cb1", ), - "wBTC": ZkLendSpecificTokenSettings( + "wBTC": + ZkLendSpecificTokenSettings( collateral_factor=decimal.Decimal("0.70"), debt_factor=decimal.Decimal("1"), liquidation_bonus=decimal.Decimal("0.15"), protocol_token_address="0x02b9ea3acdb23da566cee8e8beae3125a1458e720dea68c4a9a7a2d8eb5bbb4a", ), - "USDC": ZkLendSpecificTokenSettings( + "USDC": + ZkLendSpecificTokenSettings( collateral_factor=decimal.Decimal("0.80"), debt_factor=decimal.Decimal("1"), liquidation_bonus=decimal.Decimal("0.10"), protocol_token_address="0x047ad51726d891f972e74e4ad858a261b43869f7126ce7436ee0b2529a98f486", ), - "DAI": ZkLendSpecificTokenSettings( + "DAI": + ZkLendSpecificTokenSettings( collateral_factor=decimal.Decimal("0.70"), debt_factor=decimal.Decimal("1"), liquidation_bonus=decimal.Decimal("0.10"), protocol_token_address="0x062fa7afe1ca2992f8d8015385a279f49fad36299754fb1e9866f4f052289376", ), - "USDT": ZkLendSpecificTokenSettings( + "USDT": + ZkLendSpecificTokenSettings( collateral_factor=decimal.Decimal("0.80"), debt_factor=decimal.Decimal("1"), liquidation_bonus=decimal.Decimal("0.10"), protocol_token_address="0x00811d8da5dc8a2206ea7fd0b28627c2d77280a515126e62baa4d78e22714c4a", ), - "wstETH": ZkLendSpecificTokenSettings( + "wstETH": + ZkLendSpecificTokenSettings( collateral_factor=decimal.Decimal("0.80"), debt_factor=decimal.Decimal("1"), liquidation_bonus=decimal.Decimal("0.10"), protocol_token_address="0x0536aa7e01ecc0235ca3e29da7b5ad5b12cb881e29034d87a4290edbb20b7c28", ), # TODO: Add LORDS. - "LORDS": ZkLendSpecificTokenSettings( + "LORDS": + ZkLendSpecificTokenSettings( collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1"), liquidation_bonus=decimal.Decimal("0"), protocol_token_address="", ), # TODO: Update STRK settings. - "STRK": ZkLendSpecificTokenSettings( + "STRK": + ZkLendSpecificTokenSettings( collateral_factor=decimal.Decimal("0.50"), debt_factor=decimal.Decimal("1"), liquidation_bonus=decimal.Decimal("0.15"), @@ -75,16 +86,15 @@ class TokenSettings(ZkLendSpecificTokenSettings, TokenSettings): } TOKEN_SETTINGS: dict[str, TokenSettings] = { - token: TokenSettings( + token: + TokenSettings( symbol=TOKEN_SETTINGS[token].symbol, decimal_factor=TOKEN_SETTINGS[token].decimal_factor, address=TOKEN_SETTINGS[token].address, collateral_factor=ZKLEND_SPECIFIC_TOKEN_SETTINGS[token].collateral_factor, debt_factor=ZKLEND_SPECIFIC_TOKEN_SETTINGS[token].debt_factor, liquidation_bonus=ZKLEND_SPECIFIC_TOKEN_SETTINGS[token].liquidation_bonus, - protocol_token_address=ZKLEND_SPECIFIC_TOKEN_SETTINGS[ - token - ].protocol_token_address, + protocol_token_address=ZKLEND_SPECIFIC_TOKEN_SETTINGS[token].protocol_token_address, ) for token in TOKEN_SETTINGS } diff --git a/apps/data_handler/handlers/loan_states/zklend/utils.py b/apps/data_handler/handlers/loan_states/zklend/utils.py index f7d3ad10..357a47cf 100644 --- a/apps/data_handler/handlers/loan_states/zklend/utils.py +++ b/apps/data_handler/handlers/loan_states/zklend/utils.py @@ -1,3 +1,4 @@ +""" This module contains the ZkLendInitializer class. """ from decimal import Decimal import pandas as pd @@ -66,9 +67,7 @@ def _set_loan_state_per_user(self, loan_state: ZkLendCollateralDebt) -> None: """ user_loan_state = self.zklend_state.loan_entities[loan_state.user_id] user_loan_state.collateral_enabled.values = loan_state.collateral_enabled - user_loan_state.collateral.values = self._convert_float_to_decimal( - loan_state.collateral - ) + user_loan_state.collateral.values = self._convert_float_to_decimal(loan_state.collateral) user_loan_state.debt.values = self._convert_float_to_decimal(loan_state.debt) @staticmethod diff --git a/apps/data_handler/handlers/order_books/__init__.py b/apps/data_handler/handlers/order_books/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/order_books/__init__.py +++ b/apps/data_handler/handlers/order_books/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/order_books/abstractions.py b/apps/data_handler/handlers/order_books/abstractions.py index 16b0cd31..ff33ea76 100644 --- a/apps/data_handler/handlers/order_books/abstractions.py +++ b/apps/data_handler/handlers/order_books/abstractions.py @@ -1,3 +1,4 @@ +""" Abstract base classes for order book data and API connectors. """ from abc import ABC, abstractmethod from datetime import datetime, timezone from decimal import Decimal @@ -10,6 +11,7 @@ class OrderBookBase(ABC): + """ Base class for order book data """ DEX: str = None MIN_PRICE_RANGE = Decimal("0.0001") MAX_PRICE_RANGE = Decimal("100.0") @@ -84,7 +86,7 @@ def get_sqrt_ratio(tick: Decimal) -> Decimal: :param tick: tick value :return: square root ratio """ - return (Decimal("1.000001").sqrt() ** tick) * (Decimal(2) ** 128) + return (Decimal("1.000001").sqrt()**tick) * (Decimal(2)**128) @abstractmethod def tick_to_price(self, tick: Decimal) -> Decimal: @@ -136,7 +138,8 @@ def send_get_request(cls, endpoint: str, params=None) -> dict: :type endpoint: str :param params: Dictionary of URL parameters to append to the URL. :type params: dict, optional - :return: A JSON response from the API if the request is successful; otherwise, a dictionary with an "error" key + :return: A JSON response from the API if the request is successful; + otherwise, a dictionary with an "error" key containing the error message. :rtype: dict """ @@ -158,7 +161,8 @@ def send_post_request(cls, endpoint: str, data=None, json=None) -> dict: :type data: dict, optional :param json: Dictionary of JSON data to send in the request body. :type json: dict, optional - :return: A JSON response from the API if the request is successful; otherwise, a dictionary with an "error" key + :return: A JSON response from the API if the request is successful; + otherwise, a dictionary with an "error" key containing the error message. :rtype: dict """ diff --git a/apps/data_handler/handlers/order_books/commons.py b/apps/data_handler/handlers/order_books/commons.py index eaafff21..009b1477 100644 --- a/apps/data_handler/handlers/order_books/commons.py +++ b/apps/data_handler/handlers/order_books/commons.py @@ -1,3 +1,7 @@ +""" +Module for configuring and retrieving a logger with file and optional console output. +Enables detailed logging with timestamped log files for order book processing. +""" import logging from datetime import datetime from pathlib import Path @@ -19,9 +23,7 @@ def get_logger(name: str, path: str | Path, echo: bool = False): file_handler = logging.FileHandler(log_path) file_handler.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") file_handler.setFormatter(formatter) logger.addHandler(file_handler) diff --git a/apps/data_handler/handlers/order_books/constants.py b/apps/data_handler/handlers/order_books/constants.py index 85afc4bb..6088513e 100644 --- a/apps/data_handler/handlers/order_books/constants.py +++ b/apps/data_handler/handlers/order_books/constants.py @@ -1,41 +1,33 @@ +""" Constants for order books """ from dataclasses import dataclass @dataclass class TokenConfig: + """tokenconfig class docstring""" name: str decimals: int TOKEN_MAPPING = { - "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7": TokenConfig( - name="ETH", decimals=18 - ), - "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8": TokenConfig( - name="USDC", decimals=6 - ), - "0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8": TokenConfig( - name="USDT", decimals=6 - ), - "0x0da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3": TokenConfig( - name="DAI", decimals=18 - ), - "0x3fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac": TokenConfig( - name="wBTC", decimals=8 - ), - "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d": TokenConfig( - name="STRK", decimals=18 - ), - "0x719b5092403233201aa822ce928bd4b551d0cdb071a724edd7dc5e5f57b7f34": TokenConfig( - name="UNO", decimals=18 - ), - "0x0585c32b625999e6e5e78645ff8df7a9001cf5cf3eb6b80ccdd16cb64bd3a34": TokenConfig( - name="ZEND", decimals=18 - ), - "0x42b8f0484674ca266ac5d08e4ac6a3fe65bd3129795def2dca5c34ecc5f96d2": TokenConfig( - name="wstETH", decimals=18 - ), - "0x124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49": TokenConfig( - name="LORDS", decimals=18 - ), + "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7": + TokenConfig(name="ETH", decimals=18), + "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8": + TokenConfig(name="USDC", decimals=6), + "0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8": + TokenConfig(name="USDT", decimals=6), + "0x0da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3": + TokenConfig(name="DAI", decimals=18), + "0x3fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac": + TokenConfig(name="wBTC", decimals=8), + "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d": + TokenConfig(name="STRK", decimals=18), + "0x719b5092403233201aa822ce928bd4b551d0cdb071a724edd7dc5e5f57b7f34": + TokenConfig(name="UNO", decimals=18), + "0x0585c32b625999e6e5e78645ff8df7a9001cf5cf3eb6b80ccdd16cb64bd3a34": + TokenConfig(name="ZEND", decimals=18), + "0x42b8f0484674ca266ac5d08e4ac6a3fe65bd3129795def2dca5c34ecc5f96d2": + TokenConfig(name="wstETH", decimals=18), + "0x124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49": + TokenConfig(name="LORDS", decimals=18), } diff --git a/apps/data_handler/handlers/order_books/ekubo/__init__.py b/apps/data_handler/handlers/order_books/ekubo/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/order_books/ekubo/__init__.py +++ b/apps/data_handler/handlers/order_books/ekubo/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/order_books/ekubo/api_connector.py b/apps/data_handler/handlers/order_books/ekubo/api_connector.py index cb4b1943..c569380b 100644 --- a/apps/data_handler/handlers/order_books/ekubo/api_connector.py +++ b/apps/data_handler/handlers/order_books/ekubo/api_connector.py @@ -1,9 +1,11 @@ +""" This module contains the EkuboAPIConnector class, which is responsible for""" import time from data_handler.handlers.order_books.abstractions import AbstractionAPIConnector class EkuboAPIConnector(AbstractionAPIConnector): + """ A class that interacts with the Ekubo API to fetch data related to the Ekubo protocol. """ API_URL = "https://mainnet-api.ekubo.org" @classmethod @@ -13,8 +15,9 @@ def get_token_prices(cls, quote_token: str) -> dict: :param quote_token: The address of the quote token on StarkNet. :type quote_token: str - :return: A dictionary containing the timestamp and a list of dictionaries for each token with price details. - Each token's dictionary includes its address, price, and trading volume. + :return: A dictionary containing the timestamp and a list + of dictionaries for each token with price details. + Each token's dictionary includes its address, price, and trading volume. :rtype: dict The response dictionary structure is as follows: @@ -23,8 +26,10 @@ def get_token_prices(cls, quote_token: str) -> dict: 'prices': [ { 'token': str, # The address of the token on the blockchain - 'price': str, # The current price of the token expressed in terms of the quote token - 'k_volume': str # The trading volume for the token, expressed in the smallest unit counts + 'price': str, # The current price of the token expressed + in terms of the quote token + 'k_volume': str # The trading volume for the + token, expressed in the smallest unit counts } ] } @@ -47,7 +52,8 @@ def get_token_prices(cls, quote_token: str) -> dict: @classmethod def get_pool_liquidity(cls, key_hash: str) -> list: """ - Get the liquidity delta for each tick for the given pool key hash. The response includes an array + Get the liquidity delta for each tick + for the given pool key hash. The response includes an array of objects, each containing details about the tick and the net liquidity delta difference. :param key_hash: The pool key hash in hexadecimal or decimal format. @@ -55,7 +61,8 @@ def get_pool_liquidity(cls, key_hash: str) -> list: :return: An array of objects detailing the current liquidity chart for the pool. Each object in the array includes the following: - 'tick': The tick index as an integer. - - 'net_liquidity_delta_diff': The difference in net liquidity for the tick, represented as a string. + - 'net_liquidity_delta_diff': The difference in net + liquidity for the tick, represented as a string. :rtype: list Example of returned data: @@ -81,7 +88,8 @@ def get_list_tokens(self) -> list: - 'symbol' (str): The abbreviated symbol of the token, e.g., "WBTC". - 'decimals' (int): The number of decimal places the token is divided into, e.g., 8. - 'l2_token_address' (str): The address of the token on layer 2, in hexadecimal format. - - 'sort_order' (int): An integer specifying the order in which the token should be displayed + - 'sort_order' (int): + An integer specifying the order in which the token should be displayed relative to others; lower numbers appear first. - 'total_supply' (str): The total supply of the token, if known. This can be 'None' if the total supply is not set or unlimited. @@ -106,18 +114,21 @@ def get_list_tokens(self) -> list: def get_pools(self) -> list: """ - Retrieves a list of detailed information about various pools. Each entry in the list is a dictionary + Retrieves a list of detailed information about various pools. Each entry in the + list is a dictionary that provides comprehensive details about a pool, including the tokens involved, fees, and other relevant metrics. - :return: A list of dictionaries, each containing detailed information about a pool. The structure of + :return: A list of dictionaries, each containing detailed information about a pool. + The structure of each dictionary is as follows: - 'key_hash': The unique identifier of the pool in hexadecimal format. (str) - 'token0': The address of the first token in the pool on the blockchain. (str) - 'token1': The address of the second token in the pool on the blockchain. (str) - 'fee': The fee associated with the pool transactions, in hexadecimal format. (str) - 'tick_spacing': The minimum granularity of price movements in the pool. (int) - - 'extension': Additional information or features related to the pool, in hexadecimal format. (str) + - 'extension': Additional information or + features related to the pool, in hexadecimal format. (str) - 'sqrt_ratio': The square root of the current price ratio between token0 and token1, in hexadecimal format. (str) - 'tick': The current position of the pool in its price range. (int) @@ -207,7 +218,8 @@ def get_pair_states(self, tokenA: str, tokenB: str) -> dict: containing the following keys: - timestamp (int): The timestamp of the data. - - tvlByToken (list): A list of dictionaries containing the total value locked (TVL) by token: + - tvlByToken (list): A list of dictionaries containing + the total value locked (TVL) by token: - token (str): The address of the token. - balance (str): The balance of the token in the pool. - volumeByToken (list): A list of dictionaries containing the volume by token: @@ -217,7 +229,8 @@ def get_pair_states(self, tokenA: str, tokenB: str) -> dict: - revenueByToken (list): A list of dictionaries containing the revenue by token: - token (str): The address of the token. - revenue (str): The revenue generated by the token. - - tvlDeltaByTokenByDate (list): A list of dictionaries containing the TVL delta by token by date: + - tvlDeltaByTokenByDate (list): A list of dictionaries containing the + TVL delta by token by date: - token (str): The address of the token. - date (str): The date of the TVL delta. - delta (str): The change in TVL for the token. @@ -226,7 +239,8 @@ def get_pair_states(self, tokenA: str, tokenB: str) -> dict: - date (str): The date of the volume data. - volume (str): The trading volume of the token. - fees (str): The fees generated by the token. - - revenueByTokenByDate (list): A list of dictionaries containing the revenue by token by date: + - revenueByTokenByDate (list): A list of dictionaries containing the revenue by + token by date: - token (str): The address of the token. - date (str): The date of the revenue data. - revenue (str): The revenue generated by the token. diff --git a/apps/data_handler/handlers/order_books/ekubo/histogram.py b/apps/data_handler/handlers/order_books/ekubo/histogram.py index 939e72dd..3b2d72e6 100644 --- a/apps/data_handler/handlers/order_books/ekubo/histogram.py +++ b/apps/data_handler/handlers/order_books/ekubo/histogram.py @@ -1,3 +1,4 @@ +""" A script to display order book histograms for the Ekubo DEX. """ import argparse from decimal import Decimal from typing import List @@ -10,9 +11,7 @@ TOKEN_B = "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8" # USDC -def fetch_order_book_and_current_price( - token_a: str, token_b: str -) -> tuple[dict, Decimal]: +def fetch_order_book_and_current_price(token_a: str, token_b: str) -> tuple[dict, Decimal]: """ Fetch the order book and current price for the given token pair. :param token_a: Base token contract address @@ -99,19 +98,13 @@ def add_current_price_line(self, max_quantity: float = 0) -> None: def add_asks(self) -> None: """Add ask prices and quantities to the histogram.""" - self.ax.barh( - self.ask_prices, self.ask_quantities, color="red", label="Asks", height=15 - ) + self.ax.barh(self.ask_prices, self.ask_quantities, color="red", label="Asks", height=15) def add_bids(self) -> None: """Add bid prices and quantities to the histogram.""" - self.ax.barh( - self.bid_prices, self.bid_quantities, color="green", label="Bids", height=15 - ) + self.ax.barh(self.bid_prices, self.bid_quantities, color="green", label="Bids", height=15) - def add_total_box_quantity( - self, quantities_name: str, sum_quantities: float - ) -> None: + def add_total_box_quantity(self, quantities_name: str, sum_quantities: float) -> None: """ Add a text box displaying the total quantity. @@ -151,6 +144,7 @@ def show_bids(self) -> None: def main(): + """ Main function to parse command line arguments and display histograms. """ parser = argparse.ArgumentParser(description="Display order book histograms.") parser.add_argument( "--type", diff --git a/apps/data_handler/handlers/order_books/ekubo/main.py b/apps/data_handler/handlers/order_books/ekubo/main.py index c7489484..01df241e 100644 --- a/apps/data_handler/handlers/order_books/ekubo/main.py +++ b/apps/data_handler/handlers/order_books/ekubo/main.py @@ -1,3 +1,4 @@ +""" This module contains the EkuboOrderBook class. """ from decimal import Decimal, getcontext import pandas as pd @@ -8,6 +9,7 @@ class EkuboOrderBook(OrderBookBase): + """ Ekubo Order Book class. """ DEX = "Ekubo" def __init__(self, token_a: str, token_b: str) -> None: @@ -35,9 +37,7 @@ def fetch_price_and_liquidity(self) -> None: pools = self.connector.get_pools() df = pd.DataFrame(pools) # filter pool data by token_a and token_b - pool_df = df.loc[ - (df["token0"] == self.token_a) & (df["token1"] == self.token_b) - ] + pool_df = df.loc[(df["token0"] == self.token_a) & (df["token1"] == self.token_b)] # set current price self.set_current_price() @@ -78,14 +78,10 @@ def _calculate_order_book( # Filter asks and bids by price range self.asks = [ - (price, supply) - for price, supply in self.asks - if min_price < price < max_price + (price, supply) for price, supply in self.asks if min_price < price < max_price ] self.bids = [ - (price, supply) - for price, supply in self.bids - if min_price < price < max_price + (price, supply) for price, supply in self.bids if min_price < price < max_price ] def add_asks(self, liquidity_data: list[dict], row: pd.Series) -> None: @@ -107,9 +103,7 @@ def add_asks(self, liquidity_data: list[dict], row: pd.Series) -> None: prev_sqrt = self._get_pure_sqrt_ratio(prev_tick) next_sqrt = self._get_pure_sqrt_ratio(next_tick) - supply = abs( - ((glob_liq / prev_sqrt) - (glob_liq / next_sqrt)) / 10**self.token_a_decimal - ) + supply = abs(((glob_liq / prev_sqrt) - (glob_liq / next_sqrt)) / 10**self.token_a_decimal) price = self.tick_to_price(prev_tick) self.asks.append((price, supply)) @@ -125,8 +119,7 @@ def add_asks(self, liquidity_data: list[dict], row: pd.Series) -> None: next_sqrt = self._get_pure_sqrt_ratio(curr_tick) supply = abs( - ((glob_liq / prev_sqrt) - (glob_liq / next_sqrt)) - / 10**self.token_a_decimal + ((glob_liq / prev_sqrt) - (glob_liq / next_sqrt)) / 10**self.token_a_decimal ) price = self.tick_to_price(prev_tick) self.asks.append((price, supply)) @@ -149,9 +142,7 @@ def add_bids(self, liquidity_data: list[dict], row: pd.Series) -> None: prev_sqrt = self._get_pure_sqrt_ratio(prev_tick) next_sqrt = self._get_pure_sqrt_ratio(next_tick) - supply = abs( - ((glob_liq * prev_sqrt) - (glob_liq * next_sqrt)) / 10**self.token_b_decimal - ) + supply = abs(((glob_liq * prev_sqrt) - (glob_liq * next_sqrt)) / 10**self.token_b_decimal) price = self.tick_to_price(prev_tick) self.bids.append((price, supply)) @@ -166,8 +157,7 @@ def add_bids(self, liquidity_data: list[dict], row: pd.Series) -> None: next_sqrt = self._get_pure_sqrt_ratio(curr_tick) supply = ( - abs(((glob_liq * prev_sqrt) - (glob_liq * next_sqrt))) - / 10**self.token_b_decimal + abs(((glob_liq * prev_sqrt) - (glob_liq * next_sqrt))) / 10**self.token_b_decimal ) price = self.tick_to_price(prev_tick) self.bids.append((price, supply)) @@ -178,12 +168,11 @@ def _get_pure_sqrt_ratio(self, tick: Decimal) -> Decimal: :param tick: tick value :return: square root ratio """ - return Decimal("1.000001").sqrt() ** tick + return Decimal("1.000001").sqrt()**tick @staticmethod - def sort_ticks_by_asks_and_bids( - sorted_liquidity_data: list, current_tick: int - ) -> tuple[list, list]: + def sort_ticks_by_asks_and_bids(sorted_liquidity_data: list, + current_tick: int) -> tuple[list, list]: """ Sort tick by ask and bid :param sorted_liquidity_data: list - List of sorted liquidity data @@ -199,9 +188,7 @@ def sort_ticks_by_asks_and_bids( bid_data.append(sorted_data) return ask_data, bid_data - def calculate_liquidity_amount( - self, tick: Decimal, liquidity_pair_total: Decimal - ) -> Decimal: + def calculate_liquidity_amount(self, tick: Decimal, liquidity_pair_total: Decimal) -> Decimal: """ Calculate the liquidity amount based on the liquidity delta and sqrt ratio. :param tick: Decimal - The sqrt ratio. @@ -219,10 +206,10 @@ def tick_to_price(self, tick: Decimal) -> Decimal: :return: price by tick """ sqrt_ratio = self.get_sqrt_ratio(tick) - # calculate price by formula price = (sqrt_ratio / (2 ** 128)) ** 2 * 10 ** (token_a_decimal - token_b_decimal) - price = ((sqrt_ratio / (Decimal(2) ** 128)) ** 2) * 10 ** ( - self.token_a_decimal - self.token_b_decimal - ) + # calculate price by formula price = (sqrt_ratio / (2 ** 128)) ** 2 * 10 + # ** (token_a_decimal - token_b_decimal) + price = ((sqrt_ratio / + (Decimal(2)**128))**2) * 10**(self.token_a_decimal - self.token_b_decimal) return price diff --git a/apps/data_handler/handlers/order_books/haiko/__init__.py b/apps/data_handler/handlers/order_books/haiko/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/order_books/haiko/__init__.py +++ b/apps/data_handler/handlers/order_books/haiko/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/order_books/haiko/api_connector.py b/apps/data_handler/handlers/order_books/haiko/api_connector.py index b523c5d9..7efb9667 100644 --- a/apps/data_handler/handlers/order_books/haiko/api_connector.py +++ b/apps/data_handler/handlers/order_books/haiko/api_connector.py @@ -1,7 +1,9 @@ +""" This module contains API connectors for Haiko and Blast APIs. """ from data_handler.handlers.order_books.abstractions import AbstractionAPIConnector class HaikoAPIConnector(AbstractionAPIConnector): + """ This module contains API connectors for Haiko and Blast APIs. """ API_URL = "https://app.haiko.xyz/api/v1" @classmethod @@ -105,6 +107,7 @@ def get_usd_prices(cls, token_a_name, token_b_name) -> dict: class HaikoBlastAPIConnector(AbstractionAPIConnector): + """ This module contains API connectors for Haiko and Blast APIs. """ API_URL = "https://starknet-mainnet.blastapi.io" PROJECT_ID = "a419bd5a-ec9e-40a7-93a4-d16467fb79b3" diff --git a/apps/data_handler/handlers/order_books/haiko/logger.py b/apps/data_handler/handlers/order_books/haiko/logger.py index 2f8bf5d4..ba802482 100644 --- a/apps/data_handler/handlers/order_books/haiko/logger.py +++ b/apps/data_handler/handlers/order_books/haiko/logger.py @@ -1,3 +1,4 @@ +""" Logger for Haiko order book """ import logging from datetime import datetime from pathlib import Path @@ -18,9 +19,7 @@ def get_logger(path: str, echo: bool = False): file_handler = logging.FileHandler(log_path) file_handler.setLevel(logging.DEBUG) - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") file_handler.setFormatter(formatter) logger.addHandler(file_handler) diff --git a/apps/data_handler/handlers/order_books/haiko/main.py b/apps/data_handler/handlers/order_books/haiko/main.py index bc52470d..4588413a 100644 --- a/apps/data_handler/handlers/order_books/haiko/main.py +++ b/apps/data_handler/handlers/order_books/haiko/main.py @@ -1,3 +1,4 @@ +""" Haiko Order Book class implementation """ from decimal import Decimal from pathlib import Path @@ -11,6 +12,7 @@ class HaikoOrderBook(OrderBookBase): + """ Haiko Order Book class. """ DEX = "Haiko" def __init__(self, token_a, token_b, apply_filtering: bool = False): @@ -18,20 +20,19 @@ def __init__(self, token_a, token_b, apply_filtering: bool = False): Initialize the HaikoOrderBook object. :param token_a: baseToken hexadecimal address :param token_b: quoteToken hexadecimal address - :param apply_filtering: bool - If True apply min and max price filtering to the order book data + :param apply_filtering: bool - + If True apply min and max price filtering to the order book data """ super().__init__(token_a, token_b) self.haiko_connector = HaikoAPIConnector() self.blast_connector = HaikoBlastAPIConnector() self.apply_filtering = apply_filtering - self.logger = get_logger( - "Haiko", Path().resolve().joinpath("./logs"), echo=True - ) + self.logger = get_logger("Haiko", Path().resolve().joinpath("./logs"), echo=True) self.token_a_price = Decimal(0) self.token_b_price = Decimal(0) - self._decimals_diff = 10 ** ( + self._decimals_diff = 10**( self.token_a_decimal - self.token_b_decimal or self.token_a_decimal ) self._check_tokens_supported() @@ -62,9 +63,7 @@ def _set_usd_prices(self) -> None: def _check_tokens_supported(self) -> None: """Check if a pair of tokens is supported by Haiko""" - supported_tokens = self.haiko_connector.get_supported_tokens( - existing_only=False - ) + supported_tokens = self.haiko_connector.get_supported_tokens(existing_only=False) if isinstance(supported_tokens, dict) and supported_tokens.get("error"): raise RuntimeError(f"Unexpected error from API: {supported_tokens}") valid_tokens = self._get_valid_tokens_addresses() @@ -112,13 +111,9 @@ def fetch_price_and_liquidity(self) -> None: self._calculate_order_book(market_depth_list, Decimal(market["currPrice"])) self.sort_asks_bids() - self.current_price = max(tokens_markets, key=lambda x: Decimal(x["tvl"]))[ - "currPrice" - ] + self.current_price = max(tokens_markets, key=lambda x: Decimal(x["tvl"]))["currPrice"] - def _calculate_order_book( - self, market_ticks_liquidity: list, current_price: Decimal - ) -> None: + def _calculate_order_book(self, market_ticks_liquidity: list, current_price: Decimal) -> None: self.set_current_price(current_price) price_range = self.calculate_price_range() asks, bids = [], [] @@ -236,9 +231,7 @@ def calculate_liquidity_amount(self, tick, liquidity_pair_total) -> Decimal: return liquidity_delta / 10**self.token_a_decimal def tick_to_price(self, tick: Decimal) -> Decimal: - return Decimal("1.00001") ** tick * ( - 10 ** (self.token_a_decimal - self.token_b_decimal) - ) + return Decimal("1.00001")**tick * (10**(self.token_a_decimal - self.token_b_decimal)) def _filter_markets_data(self, all_markets_data: list) -> list: """ @@ -249,8 +242,8 @@ def _filter_markets_data(self, all_markets_data: list) -> list: token_a_valid, token_b_valid = self._get_valid_tokens_addresses() return list( filter( - lambda market: market["baseToken"]["address"] == token_a_valid - and market["quoteToken"]["address"] == token_b_valid, + lambda market: market["baseToken"]["address"] == token_a_valid and market[ + "quoteToken"]["address"] == token_b_valid, all_markets_data, ) ) diff --git a/apps/data_handler/handlers/order_books/haiko/report.py b/apps/data_handler/handlers/order_books/haiko/report.py index 56816747..45d6ea19 100644 --- a/apps/data_handler/handlers/order_books/haiko/report.py +++ b/apps/data_handler/handlers/order_books/haiko/report.py @@ -1,3 +1,4 @@ +""" This module is used to generate a report with order books data for all token pairs. """ import json import logging from datetime import datetime @@ -52,15 +53,11 @@ def get_report() -> dict: try: order_book = HaikoOrderBook(base_token, quote_token) except ValueError: - logging.log( - logging.ERROR, f"Pair of tokens: {base_token}-{quote_token}" - ) + logging.log(logging.ERROR, f"Pair of tokens: {base_token}-{quote_token}") logging.log(logging.ERROR, "One of the tokens isn't supported by Haiko") continue except RuntimeError as e: - logging.log( - logging.ERROR, f"Pair of tokens: {base_token}-{quote_token}" - ) + logging.log(logging.ERROR, f"Pair of tokens: {base_token}-{quote_token}") logging.log(logging.ERROR, e) continue @@ -78,9 +75,7 @@ def get_report() -> dict: report["empty_pairs"].append(token_pair) except KeyError: order_book.logger.error(f"Pair of tokens: {base_token}-{quote_token}") - order_book.logger.error( - "One of the tokens doesn't present in tokens mapping" - ) + order_book.logger.error("One of the tokens doesn't present in tokens mapping") except RuntimeError as e: order_book.logger.error(f"Pair of tokens: {base_token}-{quote_token}") order_book.logger.error(e) diff --git a/apps/data_handler/handlers/order_books/myswap/__init__.py b/apps/data_handler/handlers/order_books/myswap/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/order_books/myswap/__init__.py +++ b/apps/data_handler/handlers/order_books/myswap/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/order_books/myswap/api_connection/__init__.py b/apps/data_handler/handlers/order_books/myswap/api_connection/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/order_books/myswap/api_connection/__init__.py +++ b/apps/data_handler/handlers/order_books/myswap/api_connection/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/order_books/myswap/api_connection/api_connector.py b/apps/data_handler/handlers/order_books/myswap/api_connection/api_connector.py index 8b199b81..9c94e32e 100644 --- a/apps/data_handler/handlers/order_books/myswap/api_connection/api_connector.py +++ b/apps/data_handler/handlers/order_books/myswap/api_connection/api_connector.py @@ -1,7 +1,9 @@ +""" This module contains the MySwapAPIConnector class. """ from data_handler.handlers.order_books.abstractions import AbstractionAPIConnector class MySwapAPIConnector(AbstractionAPIConnector): + """ This module contains the MySwapAPIConnector class. """ API_URL = "https://myswap-cl-charts.s3.amazonaws.com" @classmethod diff --git a/apps/data_handler/handlers/order_books/myswap/main.py b/apps/data_handler/handlers/order_books/myswap/main.py index f8d5c7ae..a04faabd 100644 --- a/apps/data_handler/handlers/order_books/myswap/main.py +++ b/apps/data_handler/handlers/order_books/myswap/main.py @@ -1,3 +1,4 @@ +""" MySwap order book class. """ import asyncio import itertools import logging @@ -16,9 +17,7 @@ from data_handler.db.crud import DBConnector from data_handler.db.models import OrderBookModel -MYSWAP_CL_MM_ADDRESS = ( - "0x01114c7103e12c2b2ecbd3a2472ba9c48ddcbf702b1c242dd570057e26212111" -) +MYSWAP_CL_MM_ADDRESS = ("0x01114c7103e12c2b2ecbd3a2472ba9c48ddcbf702b1c242dd570057e26212111") # The maximum tick value available in liqmap.json.gz from MySwap Data Service MAX_MYSWAP_TICK = Decimal("1774532") @@ -29,9 +28,7 @@ class MySwapOrderBook(OrderBookBase): DEX = "MySwap" - def __init__( - self, base_token: str, quote_token: str, apply_filtering: bool = False - ): + def __init__(self, base_token: str, quote_token: str, apply_filtering: bool = False): """ Initialize the MySwap order book. :param base_token: str - The base token address in hexadecimal. @@ -42,12 +39,11 @@ def __init__( self.connector = MySwapAPIConnector() self.apply_filtering = apply_filtering self.logger = get_logger("MySwap", os.getcwd() + "/logs") - self._decimals_diff = Decimal( - 10 ** (self.token_a_decimal - self.token_b_decimal) - ) + self._decimals_diff = Decimal(10**(self.token_a_decimal - self.token_b_decimal)) def _get_clean_addresses(self) -> tuple[str, str]: - """Remove leading zeroes from token addresses. Raise Value Error if address can't be converted to int.""" + """Remove leading zeroes from token addresses. + Raise Value Error if address can't be converted to int.""" try: return hex(int(self.token_a, base=16)), hex(int(self.token_b, base=16)) except ValueError: @@ -76,8 +72,8 @@ def _filter_pools_data(self, all_pools: dict) -> list: base_token, quote_token = self._get_clean_addresses() return list( filter( - lambda pool: pool["token0"]["address"] == base_token - and pool["token1"]["address"] == quote_token, + lambda pool: pool["token0"]["address"] == base_token and pool["token1"]["address"] + == quote_token, all_pools["pools"], ) ) @@ -88,9 +84,7 @@ def _get_ticks_range(self) -> tuple[Decimal, Decimal]: return: tuple[Decimal, Decimal] - The minimum and maximum ticks. """ price_range_from, price_range_to = self.calculate_price_range() - return self._price_to_tick(price_range_from), self._price_to_tick( - price_range_to - ) + return self._price_to_tick(price_range_from), self._price_to_tick(price_range_to) def _price_to_tick(self, price: Decimal) -> Decimal: """ @@ -102,8 +96,8 @@ def _price_to_tick(self, price: Decimal) -> Decimal: Formula was derived from provided in tick_to_price. """ signed_tick = round( - Decimal(math.log(price / (Decimal(2**128) * self._decimals_diff))) - / Decimal(math.log(Decimal("1.0001"))) + Decimal(math.log(price / (Decimal(2**128) * self._decimals_diff))) / + Decimal(math.log(Decimal("1.0001"))) ) return Decimal(signed_tick) + MAX_MYSWAP_TICK @@ -166,9 +160,7 @@ def add_asks(self, pool_asks: pd.DataFrame, pool_liquidity: Decimal) -> None: y = self._get_token_amount( current_liq=Decimal(int(pool_asks.iloc[index - 1]["liq"])), current_sqrt=current_price.sqrt(), - next_sqrt=Decimal( - self.tick_to_price(pool_asks.iloc[index]["tick"].item()) - ).sqrt(), + next_sqrt=Decimal(self.tick_to_price(pool_asks.iloc[index]["tick"].item())).sqrt(), is_ask=False, ) local_asks.append((current_price, y)) @@ -199,9 +191,7 @@ def add_bids(self, pool_bids: pd.DataFrame) -> None: y = self._get_token_amount( current_liq=Decimal(int(pool_bids.iloc[index]["liq"])), current_sqrt=current_price.sqrt(), - next_sqrt=Decimal( - self.tick_to_price(pool_bids.iloc[index]["tick"].item()) - ).sqrt(), + next_sqrt=Decimal(self.tick_to_price(pool_bids.iloc[index]["tick"].item())).sqrt(), is_ask=False, ) local_bids.append((current_price, y)) @@ -233,14 +223,11 @@ def tick_to_price(self, tick: Decimal) -> Decimal: """ Convert tick value to price. :param tick: Decimal - Tick value - Formula derived from base Uniswap V3 formula - 1.0001 ** tick. Ticks in MySwap are unsigned values, + Formula derived from base Uniswap V3 formula - 1.0001 ** tick. + Ticks in MySwap are unsigned values, so we convert them to signed by subtracting max tick. """ - return ( - Decimal("1.0001") ** (tick - MAX_MYSWAP_TICK) - * Decimal(2**128) - * self._decimals_diff - ) + return (Decimal("1.0001")**(tick - MAX_MYSWAP_TICK) * Decimal(2**128) * self._decimals_diff) def calculate_liquidity_amount(self, tick, liquidity_pair_total) -> Decimal: sqrt_ratio = self.get_sqrt_ratio(tick) @@ -263,9 +250,7 @@ def calculate_liquidity_amount(self, tick, liquidity_pair_total) -> Decimal: order_book = MySwapOrderBook(base_token, quote_token, apply_filtering=True) order_book.fetch_price_and_liquidity() if order_book.asks or order_book.bids: - logging.info( - f"Pair processed successfully: {base_token} - {quote_token}" - ) + logging.info(f"Pair processed successfully: {base_token} - {quote_token}") serialized_data = order_book.serialize() connector.write_to_db(OrderBookModel(**serialized_data.model_dump())) except Exception as e: diff --git a/apps/data_handler/handlers/order_books/processing.py b/apps/data_handler/handlers/order_books/processing.py index 0717c5f5..243e644f 100644 --- a/apps/data_handler/handlers/order_books/processing.py +++ b/apps/data_handler/handlers/order_books/processing.py @@ -1,9 +1,18 @@ +""" +Module for processing order book data, allowing calculation of the quantity of a base token needed +to impact the price by a specified ratio on various DEX platforms. +""" from decimal import Decimal from data_handler.db.crud import DBConnector class OrderBookProcessor: + """ + Processes order book data for a specified DEX and token pair, enabling calculation of the + quantity needed to achieve a specified price change ratio. + """ + def __init__(self, dex: str, token_a: str, token_b: str): """ Initialize the order book processor. @@ -19,13 +28,12 @@ def calculate_price_change(self, price_change_ratio: Decimal) -> Decimal: """ Calculate quantity of `token_a` that can be bought to change the price by the given ratio. :param price_change_ratio: Decimal - The price change ratio. - :return: Decimal - Quantity that can be traded without moving price outside acceptable bound. + :return: Decimal - Quantity that can be traded without + moving price outside acceptable bound. """ # Fetch order book connector = DBConnector() - order_book = connector.get_latest_order_book( - self.dex, self.token_a, self.token_b - ) + order_book = connector.get_latest_order_book(self.dex, self.token_a, self.token_b) if not order_book: raise ValueError("No order book found for the given DEX and token pair.") diff --git a/apps/data_handler/handlers/order_books/uniswap_v2/__init__.py b/apps/data_handler/handlers/order_books/uniswap_v2/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/handlers/order_books/uniswap_v2/__init__.py +++ b/apps/data_handler/handlers/order_books/uniswap_v2/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/handlers/order_books/uniswap_v2/main.py b/apps/data_handler/handlers/order_books/uniswap_v2/main.py index daa06197..6c9de132 100644 --- a/apps/data_handler/handlers/order_books/uniswap_v2/main.py +++ b/apps/data_handler/handlers/order_books/uniswap_v2/main.py @@ -1,3 +1,4 @@ +""" This module contains the main class for the Uniswap V2 order book. """ import asyncio from decimal import Decimal from typing import Iterable @@ -8,6 +9,7 @@ class UniswapV2OrderBook(OrderBookBase): + """ This module contains the main class for the Uniswap V2 order book. """ DEX = "Starknet" def __init__(self, token_a: str, token_b: str): @@ -29,9 +31,7 @@ def _set_token_names(self) -> None: def _set_current_price(self) -> None: """Set the current price of the pair based on asks and bids.""" if not self.asks or not self.bids: - raise ValueError( - "Asks and bids are required to calculate the current price." - ) + raise ValueError("Asks and bids are required to calculate the current price.") max_bid_price = max(self.bids, key=lambda x: x[0])[0] min_ask_price = min(self.asks, key=lambda x: x[0])[0] self.current_price = (max_bid_price + min_ask_price) / Decimal("2") @@ -68,9 +68,7 @@ def get_prices_range(self, current_price: Decimal) -> Iterable[Decimal]: collateral_tokens = ("ETH", "wBTC", "STRK") if self.token_a in collateral_tokens: return get_collateral_token_range(self.token_a, current_price) - return get_range( - Decimal(0), current_price * Decimal("1.3"), Decimal(current_price / 100) - ) + return get_range(Decimal(0), current_price * Decimal("1.3"), Decimal(current_price / 100)) def _calculate_order_book(self) -> None: token_a_reserves = Decimal(self._pool.tokens[0].balance_converted) @@ -83,18 +81,14 @@ def _calculate_order_book(self) -> None: self._set_current_price() self.block = 0 - def add_quantities_data( - self, prices_range: Iterable[Decimal], current_price: Decimal - ) -> None: + def add_quantities_data(self, prices_range: Iterable[Decimal], current_price: Decimal) -> None: """ Add bids and asks data to the order book. :param prices_range: Iterable[Decimal] - The prices range to get quantities for. :param current_price: Decimal - The current pair price. """ if current_price == 0: - raise ValueError( - "Provide valid prices range and current price for analysis." - ) + raise ValueError("Provide valid prices range and current price for analysis.") for price in prices_range: supply = self._pool.supply_at_price(price) if price < current_price: @@ -102,18 +96,15 @@ def add_quantities_data( else: self.asks.append((price, supply)) - def calculate_liquidity_amount( - self, tick: Decimal, liquidity_pair_total: Decimal - ) -> Decimal: + def calculate_liquidity_amount(self, tick: Decimal, liquidity_pair_total: Decimal) -> Decimal: sqrt_ratio = self.get_sqrt_ratio(tick) liquidity_delta = liquidity_pair_total / (sqrt_ratio / Decimal(2**128)) return liquidity_delta / 10**self.token_a_decimal def tick_to_price(self, tick: Decimal) -> Decimal: sqrt_ratio = self.get_sqrt_ratio(tick) - price = ((sqrt_ratio / (Decimal(2) ** 128)) ** 2) * 10 ** ( - self.token_a_decimal - self.token_b_decimal - ) + price = ((sqrt_ratio / + (Decimal(2)**128))**2) * 10**(self.token_a_decimal - self.token_b_decimal) return price diff --git a/apps/data_handler/handlers/order_books/uniswap_v2/swap_amm.py b/apps/data_handler/handlers/order_books/uniswap_v2/swap_amm.py index 94aa7a67..576d0699 100644 --- a/apps/data_handler/handlers/order_books/uniswap_v2/swap_amm.py +++ b/apps/data_handler/handlers/order_books/uniswap_v2/swap_amm.py @@ -1,3 +1,7 @@ +""" +Module for handling Swap AMM pools, including Pool, Pair, +and MySwapPool classes for liquidity and balance operations. +""" from dataclasses import dataclass from decimal import Decimal from typing import Optional @@ -10,13 +14,22 @@ class Pair: + """ + Utility class for handling token pairs in pools. + """ @staticmethod def tokens_to_id(base_token, quote_token): + """ + Generate a unique pool ID by combining two token symbols. + """ (first, second) = tuple(sorted((base_token, quote_token))) return f"{first}/{second}" class Pool(Pair): + """ + Represents a liquidity pool, inheriting from Pair and providing balance and supply operations. + """ def __init__(self, symbol1, symbol2, addresses, myswap_id): self.id = Pair.tokens_to_id(symbol1, symbol2) self.addresses = addresses @@ -36,6 +49,9 @@ def __init__(self, symbol1, symbol2, addresses, myswap_id): self.myswap_id = myswap_id async def get_balance(self): + """ + Asynchronously fetches and updates token balances in the pool. + """ myswap_pool = None if self.myswap_id is not None: myswap_pool = await get_myswap_pool(self.myswap_id) @@ -49,32 +65,41 @@ async def get_balance(self): token.balance_converted = Decimal(balance) / token.decimal_factor def update_converted_balance(self): + """ + Update the converted balance for each token based on its decimal factor. + """ for token in self.tokens: token.balance_converted = Decimal(token.balance_base) / token.decimal_factor def supply_at_price(self, initial_price: Decimal): + """ + Calculate supply available at a specified price, assuming constant product function. + """ # assuming constant product function - constant = Decimal( - self.tokens[0].balance_converted * self.tokens[1].balance_converted - ) - return (initial_price * constant).sqrt() * ( - Decimal("1") - Decimal("0.95").sqrt() - ) + constant = Decimal(self.tokens[0].balance_converted * self.tokens[1].balance_converted) + return (initial_price * constant).sqrt() * (Decimal("1") - Decimal("0.95").sqrt()) class MySwapPool(Pool): """ - This class implements MySwap pools where Hashstack V1 users can spend their debt. To properly account for their - token holdings, we collect the total supply of LP tokens and the amounts of both tokens in the pool. + This class implements MySwap pools where Hashstack V1 + users can spend their debt. To properly account for their + token holdings, we collect the total supply of LP + tokens and the amounts of both tokens in the pool. """ def __init__(self, *args, **kwargs) -> None: + """ + Initialize pools with predefined tokens and addresses, and fetch balances. + """ + super().__init__(*args, **kwargs) self.total_lp_supply: Optional[Decimal] = None self.token_amounts: Optional[TokenValues] = None async def get_data(self) -> None: + """ Collects the total supply of LP tokens and the amounts of both tokens in the pool. """ self.total_lp_supply = Decimal( ( await func_call( @@ -85,7 +110,8 @@ async def get_data(self) -> None: )[0] ) self.token_amounts = TokenValues() - # The order of the values returned is: `name`, `token_a_address`, `token_a_reserves`, ``, `token_b_address`, + # The order of the values returned is: `name`, `token_a_address`, + # `token_a_reserves`, ``, `token_b_address`, # `token_b_reserves`, ``, `fee_percentage`, `cfmm_type`, `liq_token`. pool = await func_call( addr=self.addresses[-1], @@ -100,13 +126,17 @@ async def get_data(self) -> None: @dataclass class SwapAmmToken(TokenSettings): + """ This class represents a token in the Swap AMM, with balance and conversion operations. """ # TODO: Improve this. balance_base: Optional[float] = None balance_converted: Optional[float] = None class SwapAmm(Pair): + """ This class represents the Swap AMM, which contains pools and provides balance + and supply operations. """ async def init(self): + """ Initialize the SwapAmm with predefined pools and fetch balances. """ # TODO: Add AVNU self.pools = {} self.add_pool( @@ -229,10 +259,16 @@ async def init(self): await self.get_balance() async def get_balance(self): + """ + Asynchronously retrieves balances for all pools. + """ for pool in self.pools.values(): await pool.get_balance() def add_pool(self, base_token: str, quote_token, pool_addresses, myswap_id=None): + """ + Add a new pool to the SwapAmm with the specified tokens and addresses. + """ if myswap_id is None: pool = Pool(base_token, quote_token, pool_addresses, myswap_id) else: @@ -240,10 +276,14 @@ def add_pool(self, base_token: str, quote_token, pool_addresses, myswap_id=None) self.pools[pool.id] = pool def get_pool(self, base_token, quote_token): + """ + Retrieve a pool by base and quote token, raising an error if not found. + """ pools = self.pools.get(self.tokens_to_id(base_token, quote_token), None) if not pools: raise ValueError( - f"Trying to get pools that are not set: {self.tokens_to_id(base_token, quote_token)}" + f"Trying to get pools that are not set: " + f"{self.tokens_to_id(base_token, quote_token)}" ) return pools diff --git a/apps/data_handler/handlers/settings.py b/apps/data_handler/handlers/settings.py index e310f8a0..5bab9a09 100644 --- a/apps/data_handler/handlers/settings.py +++ b/apps/data_handler/handlers/settings.py @@ -1,18 +1,31 @@ +""" +This module defines configuration settings for token pools and trading pairs used in DeFi protocols +like JediSwap and MySwap. It includes settings for supported trading pairs, token details, and pool +addresses and identifiers for various token pairs across protocols. +""" import dataclasses from decimal import Decimal @dataclasses.dataclass class TokenSettings: + """ + Configuration for a token, including symbol, decimal factor, and blockchain address. + """ symbol: str # Source: Starkscan, e.g. - # https://starkscan.co/token/0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 for ETH. + # https://starkscan.co/token/0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 + # for ETH. decimal_factor: Decimal address: str @dataclasses.dataclass class JediSwapPoolSettings: + """ + Settings for a JediSwap pool, including symbol, address, + and token addresses. + """ symbol: str address: str token_1: str @@ -38,77 +51,92 @@ class JediSwapPoolSettings: } HASHSTACK_V1_ADDITIONAL_TOKEN_SETTINGS: dict[str, TokenSettings] = { - "JediSwap: DAI/ETH Pool": TokenSettings( + "JediSwap: DAI/ETH Pool": + TokenSettings( symbol="JediSwap: DAI/ETH Pool", decimal_factor=Decimal("1e18"), address="0x07e2a13b40fc1119ec55e0bcf9428eedaa581ab3c924561ad4e955f95da63138", ), - "JediSwap: DAI/USDC Pool": TokenSettings( + "JediSwap: DAI/USDC Pool": + TokenSettings( symbol="JediSwap: DAI/USDC Pool", decimal_factor=Decimal("1e18"), address="0x00cfd39f5244f7b617418c018204a8a9f9a7f72e71f0ef38f968eeb2a9ca302b", ), - "JediSwap: DAI/USDT Pool": TokenSettings( + "JediSwap: DAI/USDT Pool": + TokenSettings( symbol="JediSwap: DAI/USDT Pool", decimal_factor=Decimal("1e18"), address="0x00f0f5b3eed258344152e1f17baf84a2e1b621cd754b625bec169e8595aea767", ), - "JediSwap: ETH/USDC Pool": TokenSettings( + "JediSwap: ETH/USDC Pool": + TokenSettings( symbol="JediSwap: ETH/USDC Pool", decimal_factor=Decimal("1e18"), address="0x04d0390b777b424e43839cd1e744799f3de6c176c7e32c1812a41dbd9c19db6a", ), - "JediSwap: ETH/USDT Pool": TokenSettings( + "JediSwap: ETH/USDT Pool": + TokenSettings( symbol="JediSwap: ETH/USDT Pool", decimal_factor=Decimal("1e18"), address="0x045e7131d776dddc137e30bdd490b431c7144677e97bf9369f629ed8d3fb7dd6", ), - "JediSwap: USDC/USDT Pool": TokenSettings( + "JediSwap: USDC/USDT Pool": + TokenSettings( symbol="JediSwap: USDC/USDT Pool", decimal_factor=Decimal("1e18"), address="0x05801bdad32f343035fb242e98d1e9371ae85bc1543962fedea16c59b35bd19b", ), - "JediSwap: WBTC/ETH Pool": TokenSettings( + "JediSwap: WBTC/ETH Pool": + TokenSettings( symbol="JediSwap: WBTC/ETH Pool", decimal_factor=Decimal("1e18"), address="0x0260e98362e0949fefff8b4de85367c035e44f734c9f8069b6ce2075ae86b45c", ), - "JediSwap: WBTC/USDC Pool": TokenSettings( + "JediSwap: WBTC/USDC Pool": + TokenSettings( symbol="JediSwap: WBTC/USDC Pool", decimal_factor=Decimal("1e18"), address="0x005a8054e5ca0b277b295a830e53bd71a6a6943b42d0dbb22329437522bc80c8", ), - "JediSwap: WBTC/USDT Pool": TokenSettings( + "JediSwap: WBTC/USDT Pool": + TokenSettings( symbol="JediSwap: WBTC/USDT Pool", decimal_factor=Decimal("1e18"), address="0x044d13ad98a46fd2322ef2637e5e4c292ce8822f47b7cb9a1d581176a801c1a0", ), - "mySwap: DAI/ETH Pool": TokenSettings( + "mySwap: DAI/ETH Pool": + TokenSettings( symbol="mySwap: DAI/ETH Pool", decimal_factor=Decimal("1e18"), address="0x07c662b10f409d7a0a69c8da79b397fd91187ca5f6230ed30effef2dceddc5b3", ), - "mySwap: DAI/USDC Pool": TokenSettings( + "mySwap: DAI/USDC Pool": + TokenSettings( symbol="mySwap: DAI/USDC Pool", decimal_factor=Decimal("1e12"), address="0x0611e8f4f3badf1737b9e8f0ca77dd2f6b46a1d33ce4eed951c6b18ac497d505", ), - "mySwap: ETH/USDC Pool": TokenSettings( + "mySwap: ETH/USDC Pool": + TokenSettings( symbol="mySwap: ETH/USDC Pool", decimal_factor=Decimal("1e12"), address="0x022b05f9396d2c48183f6deaf138a57522bcc8b35b67dee919f76403d1783136", ), - "mySwap: ETH/USDT Pool": TokenSettings( + "mySwap: ETH/USDT Pool": + TokenSettings( symbol="mySwap: ETH/USDT Pool", decimal_factor=Decimal("1e12"), address="0x041f9a1e9a4d924273f5a5c0c138d52d66d2e6a8bee17412c6b0f48fe059ae04", ), - "mySwap: USDC/USDT Pool": TokenSettings( + "mySwap: USDC/USDT Pool": + TokenSettings( symbol="mySwap: USDC/USDT Pool", decimal_factor=Decimal("1e6"), address="0x01ea237607b7d9d2e9997aa373795929807552503683e35d8739f4dc46652de1", ), - "mySwap: WBTC/USDC Pool": TokenSettings( + "mySwap: WBTC/USDC Pool": + TokenSettings( symbol="mySwap: WBTC/USDC Pool", decimal_factor=Decimal("1e7"), address="0x025b392609604c75d62dde3d6ae98e124a31b49123b8366d7ce0066ccb94f696", @@ -116,55 +144,64 @@ class JediSwapPoolSettings: } JEDISWAP_POOL_SETTINGS: dict[str, JediSwapPoolSettings] = { - "JediSwap: DAI/ETH Pool": JediSwapPoolSettings( + "JediSwap: DAI/ETH Pool": + JediSwapPoolSettings( symbol="JediSwap: DAI/ETH Pool", address="0x07e2a13b40fc1119ec55e0bcf9428eedaa581ab3c924561ad4e955f95da63138", token_1="0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3", token_2="0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", ), - "JediSwap: DAI/USDC Pool": JediSwapPoolSettings( + "JediSwap: DAI/USDC Pool": + JediSwapPoolSettings( symbol="JediSwap: DAI/USDC Pool", address="0x00cfd39f5244f7b617418c018204a8a9f9a7f72e71f0ef38f968eeb2a9ca302b", token_1="0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3", token_2="0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", ), - "JediSwap: DAI/USDT Pool": JediSwapPoolSettings( + "JediSwap: DAI/USDT Pool": + JediSwapPoolSettings( symbol="JediSwap: DAI/USDT Pool", address="0x00f0f5b3eed258344152e1f17baf84a2e1b621cd754b625bec169e8595aea767", token_1="0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3", token_2="0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", ), - "JediSwap: ETH/USDC Pool": JediSwapPoolSettings( + "JediSwap: ETH/USDC Pool": + JediSwapPoolSettings( symbol="JediSwap: ETH/USDC Pool", address="0x04d0390b777b424e43839cd1e744799f3de6c176c7e32c1812a41dbd9c19db6a", token_1="0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", token_2="0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", ), - "JediSwap: ETH/USDT Pool": JediSwapPoolSettings( + "JediSwap: ETH/USDT Pool": + JediSwapPoolSettings( symbol="JediSwap: ETH/USDT Pool", address="0x045e7131d776dddc137e30bdd490b431c7144677e97bf9369f629ed8d3fb7dd6", token_1="0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", token_2="0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", ), - "JediSwap: USDC/USDT Pool": JediSwapPoolSettings( + "JediSwap: USDC/USDT Pool": + JediSwapPoolSettings( symbol="JediSwap: USDC/USDT Pool", address="0x05801bdad32f343035fb242e98d1e9371ae85bc1543962fedea16c59b35bd19b", token_1="0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", token_2="0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", ), - "JediSwap: WBTC/ETH Pool": JediSwapPoolSettings( + "JediSwap: WBTC/ETH Pool": + JediSwapPoolSettings( symbol="JediSwap: WBTC/ETH Pool", address="0x0260e98362e0949fefff8b4de85367c035e44f734c9f8069b6ce2075ae86b45c", token_1="0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac", token_2="0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", ), - "JediSwap: WBTC/USDC Pool": JediSwapPoolSettings( + "JediSwap: WBTC/USDC Pool": + JediSwapPoolSettings( symbol="JediSwap: WBTC/USDC Pool", address="0x005a8054e5ca0b277b295a830e53bd71a6a6943b42d0dbb22329437522bc80c8", token_1="0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac", token_2="0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", ), - "JediSwap: WBTC/USDT Pool": JediSwapPoolSettings( + "JediSwap: WBTC/USDT Pool": + JediSwapPoolSettings( symbol="JediSwap: WBTC/USDT Pool", address="0x044d13ad98a46fd2322ef2637e5e4c292ce8822f47b7cb9a1d581176a801c1a0", token_1="0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac", @@ -175,6 +212,8 @@ class JediSwapPoolSettings: @dataclasses.dataclass class MySwapPoolSettings: + """Settings for a MySwap pool: symbol, address, + ID, and token addresses.""" symbol: str address: str myswap_id: int @@ -183,42 +222,48 @@ class MySwapPoolSettings: MYSWAP_POOL_SETTINGS: dict[str, MySwapPoolSettings] = { - "mySwap: DAI/ETH Pool": MySwapPoolSettings( + "mySwap: DAI/ETH Pool": + MySwapPoolSettings( symbol="mySwap: DAI/ETH Pool", address="0x010884171baf1914edc28d7afb619b40a4051cfae78a094a55d230f19e944a28", myswap_id=2, token_1="0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3", token_2="0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", ), - "mySwap: DAI/USDC Pool": MySwapPoolSettings( + "mySwap: DAI/USDC Pool": + MySwapPoolSettings( symbol="mySwap: DAI/USDC Pool", address="0x010884171baf1914edc28d7afb619b40a4051cfae78a094a55d230f19e944a28", myswap_id=6, token_1="0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3", token_2="0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", ), - "mySwap: ETH/USDC Pool": MySwapPoolSettings( + "mySwap: ETH/USDC Pool": + MySwapPoolSettings( symbol="mySwap: ETH/USDC Pool", address="0x010884171baf1914edc28d7afb619b40a4051cfae78a094a55d230f19e944a28", myswap_id=1, token_1="0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", token_2="0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", ), - "mySwap: ETH/USDT Pool": MySwapPoolSettings( + "mySwap: ETH/USDT Pool": + MySwapPoolSettings( symbol="mySwap: ETH/USDT Pool", address="0x010884171baf1914edc28d7afb619b40a4051cfae78a094a55d230f19e944a28", myswap_id=4, token_1="0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", token_2="0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", ), - "mySwap: USDC/USDT Pool": MySwapPoolSettings( + "mySwap: USDC/USDT Pool": + MySwapPoolSettings( symbol="mySwap: USDC/USDT Pool", address="0x010884171baf1914edc28d7afb619b40a4051cfae78a094a55d230f19e944a28", myswap_id=5, token_1="0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", token_2="0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", ), - "mySwap: WBTC/USDC Pool": MySwapPoolSettings( + "mySwap: WBTC/USDC Pool": + MySwapPoolSettings( symbol="mySwap: WBTC/USDC Pool", address="0x010884171baf1914edc28d7afb619b40a4051cfae78a094a55d230f19e944a28", myswap_id=3, diff --git a/apps/data_handler/handlers/state.py b/apps/data_handler/handlers/state.py index a283db66..fd62212e 100644 --- a/apps/data_handler/handlers/state.py +++ b/apps/data_handler/handlers/state.py @@ -1,3 +1,6 @@ +""" +Token settings for Nostra Alpha, including collateral, debt factors, and liquidation fees. +""" import dataclasses import decimal @@ -7,9 +10,14 @@ @dataclasses.dataclass class NostraAlphaSpecificTokenSettings(TokenSettings): + """ + Settings for a Nostra Alpha token, including collateral and debt factors, + liquidation fees, protocol fee, and token address. + """ # TODO: Load these via chain calls? # Source: Starkscan, e.g. - # https://starkscan.co/call/0x06f619127a63ddb5328807e535e56baa1e244c8923a3b50c123d41dcbed315da_1_1 for ETH. + # https://starkscan.co/call/0x06f619127a63ddb5328807e535e56baa1e244c8923a3b50c123d41dcbed315da_1_1 + # for ETH. collateral_factor: decimal.Decimal # TODO: Add source. debt_factor: decimal.Decimal @@ -21,7 +29,8 @@ class NostraAlphaSpecificTokenSettings(TokenSettings): NOSTRA_ALPHA_SPECIFIC_TOKEN_SETTINGS: dict[str, NostraAlphaSpecificTokenSettings] = { - "ETH": NostraAlphaSpecificTokenSettings( + "ETH": + NostraAlphaSpecificTokenSettings( symbol="ETH", decimal_factor=decimal.Decimal("1e18"), address="0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", @@ -32,7 +41,8 @@ class NostraAlphaSpecificTokenSettings(TokenSettings): protocol_fee=decimal.Decimal("0.02"), protocol_token_address="0x04f89253e37ca0ab7190b2e9565808f105585c9cacca6b2fa6145553fa061a41", ), - "wBTC": NostraAlphaSpecificTokenSettings( + "wBTC": + NostraAlphaSpecificTokenSettings( symbol="wBTC", decimal_factor=decimal.Decimal("1e8"), address="0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac", @@ -43,7 +53,8 @@ class NostraAlphaSpecificTokenSettings(TokenSettings): protocol_fee=decimal.Decimal("0.02"), protocol_token_address="0x07788bc687f203b6451f2a82e842b27f39c7cae697dace12edfb86c9b1c12f3d", ), - "USDC": NostraAlphaSpecificTokenSettings( + "USDC": + NostraAlphaSpecificTokenSettings( symbol="USDC", decimal_factor=decimal.Decimal("1e6"), address="0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", @@ -54,7 +65,8 @@ class NostraAlphaSpecificTokenSettings(TokenSettings): protocol_fee=decimal.Decimal("0.02"), protocol_token_address="0x05327df4c669cb9be5c1e2cf79e121edef43c1416fac884559cd94fcb7e6e232", ), - "DAI": NostraAlphaSpecificTokenSettings( + "DAI": + NostraAlphaSpecificTokenSettings( symbol="DAI", decimal_factor=decimal.Decimal("1e18"), address="0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3", @@ -65,7 +77,8 @@ class NostraAlphaSpecificTokenSettings(TokenSettings): protocol_fee=decimal.Decimal("0.02"), protocol_token_address="0x02ea39ba7a05f0c936b7468d8bc8d0e1f2116916064e7e163e7c1044d95bd135", ), - "USDT": NostraAlphaSpecificTokenSettings( + "USDT": + NostraAlphaSpecificTokenSettings( symbol="USDT", decimal_factor=decimal.Decimal("1e6"), address="0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", @@ -77,7 +90,8 @@ class NostraAlphaSpecificTokenSettings(TokenSettings): protocol_token_address="0x040375d0720245bc0d123aa35dc1c93d14a78f64456eff75f63757d99a0e6a83", ), # TODO: These (`wstETH`, `LORDS`, and `STRK`) are actually Nostra Mainnet tokens. - "wstETH": NostraAlphaSpecificTokenSettings( + "wstETH": + NostraAlphaSpecificTokenSettings( symbol="wstETH", decimal_factor=decimal.Decimal("1e18"), address="0x042b8f0484674ca266ac5d08e4ac6a3fe65bd3129795def2dca5c34ecc5f96d2", @@ -88,7 +102,8 @@ class NostraAlphaSpecificTokenSettings(TokenSettings): protocol_fee=decimal.Decimal("0.02"), protocol_token_address="", ), - "LORDS": NostraAlphaSpecificTokenSettings( + "LORDS": + NostraAlphaSpecificTokenSettings( symbol="LORDS", decimal_factor=decimal.Decimal("1e18"), address="0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", @@ -99,7 +114,8 @@ class NostraAlphaSpecificTokenSettings(TokenSettings): protocol_fee=decimal.Decimal("0"), # TODO: Not observed yet. protocol_token_address="", ), - "STRK": NostraAlphaSpecificTokenSettings( + "STRK": + NostraAlphaSpecificTokenSettings( symbol="STRK", decimal_factor=decimal.Decimal("1e18"), address="0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", @@ -115,43 +131,42 @@ class NostraAlphaSpecificTokenSettings(TokenSettings): @dataclasses.dataclass class SpecificTokenSettings: + """ + Basic token settings with collateral and debt factors. + """ collateral_factor: decimal.Decimal debt_factor: decimal.Decimal @dataclasses.dataclass class TokenSettings(SpecificTokenSettings, TokenSettings): + """ + Extends SpecificTokenSettings to include additional token attributes. + """ pass LOAN_ENTITY_SPECIFIC_TOKEN_SETTINGS: dict[str, SpecificTokenSettings] = { - "ETH": SpecificTokenSettings( - collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") - ), - "wBTC": SpecificTokenSettings( - collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") - ), - "USDC": SpecificTokenSettings( - collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") - ), - "DAI": SpecificTokenSettings( - collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") - ), - "USDT": SpecificTokenSettings( - collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") - ), - "wstETH": SpecificTokenSettings( - collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") - ), - "LORDS": SpecificTokenSettings( - collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") - ), - "STRK": SpecificTokenSettings( - collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1") - ), + "ETH": + SpecificTokenSettings(collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1")), + "wBTC": + SpecificTokenSettings(collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1")), + "USDC": + SpecificTokenSettings(collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1")), + "DAI": + SpecificTokenSettings(collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1")), + "USDT": + SpecificTokenSettings(collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1")), + "wstETH": + SpecificTokenSettings(collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1")), + "LORDS": + SpecificTokenSettings(collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1")), + "STRK": + SpecificTokenSettings(collateral_factor=decimal.Decimal("1"), debt_factor=decimal.Decimal("1")), } TOKEN_SETTINGS: dict[str, TokenSettings] = { - token: TokenSettings( + token: + TokenSettings( symbol=TOKEN_SETTINGS[token].symbol, decimal_factor=TOKEN_SETTINGS[token].decimal_factor, address=TOKEN_SETTINGS[token].address, diff --git a/apps/data_handler/main.py b/apps/data_handler/main.py index 7c62b71a..ead81d5a 100644 --- a/apps/data_handler/main.py +++ b/apps/data_handler/main.py @@ -1,3 +1,8 @@ +""" +Defines FastAPI endpoints for querying loan states, interest rates, +user health ratios, and order book records, with a 10 requests/second rate +limit per endpoint. +""" import logging from typing import List, Optional @@ -107,10 +112,8 @@ def get_last_interest_rate_by_block( raise HTTPException(status_code=400, detail="Invalid protocol ID") last_record = ( - db.query(InterestRate) - .filter(InterestRate.protocol_id == protocol) - .order_by(InterestRate.block.desc()) - .first() + db.query(InterestRate).filter(InterestRate.protocol_id == protocol + ).order_by(InterestRate.block.desc()).first() ) return last_record @@ -120,12 +123,8 @@ def get_last_interest_rate_by_block( @app.get("/health-ratio-per-user/{protocol}/") async def get_health_ratio_per_user( request: Request, - protocol: str = Path( - ..., - ), - user_id: str = Query( - ..., - ), + protocol: str = Path(..., ), + user_id: str = Query(..., ), db: Session = Depends(get_database), ): """ @@ -143,18 +142,13 @@ async def get_health_ratio_per_user( raise HTTPException(status_code=400, detail="Invalid protocol ID") if not user_id: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail="User ID is required" - ) + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="User ID is required") row = ( - db.query(HealthRatioLevel) - .filter( + db.query(HealthRatioLevel).filter( HealthRatioLevel.protocol_id == protocol, HealthRatioLevel.user_id == user_id, - ) - .order_by(desc(HealthRatioLevel.timestamp)) - .first() + ).order_by(desc(HealthRatioLevel.timestamp)).first() ) if not row: @@ -181,14 +175,11 @@ def get_orderbook( logger.info(f"Fetching order book records for {base_token}/{quote_token} on {dex}") records = ( - db.query(OrderBookModel) - .filter( + db.query(OrderBookModel).filter( OrderBookModel.token_a == base_token, OrderBookModel.token_b == quote_token, OrderBookModel.dex == dex, - ) - .order_by(OrderBookModel.timestamp.desc()) - .first() + ).order_by(OrderBookModel.timestamp.desc()).first() ) print(f"Found {records} order book records") logger.info(f"Found {records} order book records") diff --git a/apps/data_handler/migrations/__init__.py b/apps/data_handler/migrations/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/migrations/__init__.py +++ b/apps/data_handler/migrations/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/migrations/env.py b/apps/data_handler/migrations/env.py index e9017e1d..9a3e6c91 100644 --- a/apps/data_handler/migrations/env.py +++ b/apps/data_handler/migrations/env.py @@ -1,3 +1,4 @@ +""" Alembic environment configuration file. """ from logging.config import fileConfig from alembic import context diff --git a/apps/data_handler/migrations/versions/31430d42d46a_merge_heads_into_a_single_branch.py b/apps/data_handler/migrations/versions/31430d42d46a_merge_heads_into_a_single_branch.py index ba48ee8c..cd869e8a 100644 --- a/apps/data_handler/migrations/versions/31430d42d46a_merge_heads_into_a_single_branch.py +++ b/apps/data_handler/migrations/versions/31430d42d46a_merge_heads_into_a_single_branch.py @@ -19,8 +19,12 @@ def upgrade() -> None: + """Function to apply the upgrade migrations.""" pass def downgrade() -> None: + """ + Reverts the database schema changes applied in the upgrade. + """ pass diff --git a/apps/data_handler/migrations/versions/4280b8a75614_add_collateral_debt_zklend_model.py b/apps/data_handler/migrations/versions/4280b8a75614_add_collateral_debt_zklend_model.py index 1bcda5f3..0e7f1433 100644 --- a/apps/data_handler/migrations/versions/4280b8a75614_add_collateral_debt_zklend_model.py +++ b/apps/data_handler/migrations/versions/4280b8a75614_add_collateral_debt_zklend_model.py @@ -20,6 +20,7 @@ def upgrade() -> None: + """ Base class for collectors. """ # Check if the table already exists conn = op.get_bind() inspector = Inspector.from_engine(conn) @@ -51,6 +52,7 @@ def upgrade() -> None: def downgrade() -> None: + """ Base classes for ORM models. """ # Drop the index and table if it exists conn = op.get_bind() inspector = Inspector.from_engine(conn) diff --git a/apps/data_handler/migrations/versions/509baf9251f2_add_health_ratio_level_model.py b/apps/data_handler/migrations/versions/509baf9251f2_add_health_ratio_level_model.py index 0f5bdbb4..f9993009 100644 --- a/apps/data_handler/migrations/versions/509baf9251f2_add_health_ratio_level_model.py +++ b/apps/data_handler/migrations/versions/509baf9251f2_add_health_ratio_level_model.py @@ -23,6 +23,7 @@ def upgrade() -> None: + """ Base class for collectors. """ # Check if the table already exists conn = op.get_bind() inspector = Inspector.from_engine(conn) @@ -57,15 +58,12 @@ def upgrade() -> None: def downgrade() -> None: + """ Base classes for ORM models. """ # Drop the indices and table if it exists conn = op.get_bind() inspector = Inspector.from_engine(conn) if inspector.has_table("health_ratio_level"): - op.drop_index( - op.f("ix_health_ratio_level_user_id"), table_name="health_ratio_level" - ) - op.drop_index( - op.f("ix_health_ratio_level_timestamp"), table_name="health_ratio_level" - ) + op.drop_index(op.f("ix_health_ratio_level_user_id"), table_name="health_ratio_level") + op.drop_index(op.f("ix_health_ratio_level_timestamp"), table_name="health_ratio_level") op.drop_table("health_ratio_level") diff --git a/apps/data_handler/migrations/versions/593bb0a7d06b_add_order_book_and_interest_rate.py b/apps/data_handler/migrations/versions/593bb0a7d06b_add_order_book_and_interest_rate.py index e0c88a42..9d4a7726 100644 --- a/apps/data_handler/migrations/versions/593bb0a7d06b_add_order_book_and_interest_rate.py +++ b/apps/data_handler/migrations/versions/593bb0a7d06b_add_order_book_and_interest_rate.py @@ -22,6 +22,7 @@ def upgrade() -> None: + """ Base classes for ORM models. """ # ### commands auto generated by Alembic - please adjust! ### bind = op.get_bind() inspector = sa.inspect(bind) @@ -40,9 +41,7 @@ def upgrade() -> None: sa.Column("id", sa.UUID(), nullable=False), sa.PrimaryKeyConstraint("id"), ) - op.create_index( - op.f("ix_interest_rate_block"), "interest_rate", ["block"], unique=False - ) + op.create_index(op.f("ix_interest_rate_block"), "interest_rate", ["block"], unique=False) op.create_index( op.f("ix_interest_rate_timestamp"), "interest_rate", @@ -63,16 +62,14 @@ def upgrade() -> None: sa.PrimaryKeyConstraint("id"), ) op.create_index(op.f("ix_orderbook_dex"), "orderbook", ["dex"], unique=False) - op.create_index( - op.f("ix_orderbook_token_a"), "orderbook", ["token_a"], unique=False - ) - op.create_index( - op.f("ix_orderbook_token_b"), "orderbook", ["token_b"], unique=False - ) + op.create_index(op.f("ix_orderbook_token_a"), "orderbook", ["token_a"], unique=False) + op.create_index(op.f("ix_orderbook_token_b"), "orderbook", ["token_b"], unique=False) # ### end Alembic commands ### def downgrade() -> None: + """ Base classes for ORM models. """ + # ### commands auto generated by Alembic - please adjust! ### op.drop_index(op.f("ix_orderbook_token_b"), table_name="orderbook") op.drop_index(op.f("ix_orderbook_token_a"), table_name="orderbook") diff --git a/apps/data_handler/migrations/versions/64a870953fa5_add_constraint_for_loan_state.py b/apps/data_handler/migrations/versions/64a870953fa5_add_constraint_for_loan_state.py index eaecf71f..8450b97c 100644 --- a/apps/data_handler/migrations/versions/64a870953fa5_add_constraint_for_loan_state.py +++ b/apps/data_handler/migrations/versions/64a870953fa5_add_constraint_for_loan_state.py @@ -20,6 +20,7 @@ def upgrade() -> None: + """ Base class for collectors. """ # ### commands auto generated by Alembic - please adjust! ### op.create_unique_constraint( "loan_state_protocol_id_user_key", "loan_state", ["protocol_id", "user"] @@ -28,6 +29,7 @@ def upgrade() -> None: def downgrade() -> None: + """ Base classes for ORM models. """ # ### commands auto generated by Alembic - please adjust! ### op.drop_constraint("loan_state_protocol_id_user_key", "loan_state", type_="unique") # ### end Alembic commands ### diff --git a/apps/data_handler/migrations/versions/70353336c9ef_.py b/apps/data_handler/migrations/versions/70353336c9ef_.py index 2e805189..fe1ba61a 100644 --- a/apps/data_handler/migrations/versions/70353336c9ef_.py +++ b/apps/data_handler/migrations/versions/70353336c9ef_.py @@ -19,8 +19,10 @@ def upgrade() -> None: + """ Function to apply the upgrade migrations. """ pass def downgrade() -> None: + """ Function to revert the upgrade migrations. """ pass diff --git a/apps/data_handler/migrations/versions/71a8b872296d_add_event_models.py b/apps/data_handler/migrations/versions/71a8b872296d_add_event_models.py index e6f0b511..fa7f99d7 100644 --- a/apps/data_handler/migrations/versions/71a8b872296d_add_event_models.py +++ b/apps/data_handler/migrations/versions/71a8b872296d_add_event_models.py @@ -6,6 +6,7 @@ """ + from typing import Sequence, Union import sqlalchemy as sa @@ -18,21 +19,20 @@ # revision identifiers, used by Alembic. revision: str = "71a8b872296d" down_revision: Union[str, None] = "64a870953fa5" +revision: str = "71a8b872296d" +down_revision: Union[str, None] = "64a870953fa5" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: + """ Base class for collectors. """ # ### commands auto generated by Alembic - please adjust! ### op.create_table( "accumulators_sync_event_data", sa.Column("token", sa.String(), nullable=False), - sa.Column( - "lending_accumulator", sa.Numeric(precision=38, scale=18), nullable=False - ), - sa.Column( - "debt_accumulator", sa.Numeric(precision=38, scale=18), nullable=False - ), + sa.Column("lending_accumulator", sa.Numeric(precision=38, scale=18), nullable=False), + sa.Column("debt_accumulator", sa.Numeric(precision=38, scale=18), nullable=False), sa.Column("event_name", sa.String(), nullable=False), sa.Column("block_number", sa.Integer(), nullable=False), sa.Column( @@ -60,16 +60,10 @@ def upgrade() -> None: sa.Column("liquidator", sa.String(), nullable=False), sa.Column("user", sa.String(), nullable=False), sa.Column("debt_token", sa.String(), nullable=False), - sa.Column( - "debt_raw_amount", sa.Numeric(precision=38, scale=18), nullable=False - ), - sa.Column( - "debt_face_amount", sa.Numeric(precision=38, scale=18), nullable=False - ), + sa.Column("debt_raw_amount", sa.Numeric(precision=38, scale=18), nullable=False), + sa.Column("debt_face_amount", sa.Numeric(precision=38, scale=18), nullable=False), sa.Column("collateral_token", sa.String(), nullable=False), - sa.Column( - "collateral_amount", sa.Numeric(precision=38, scale=18), nullable=False - ), + sa.Column("collateral_amount", sa.Numeric(precision=38, scale=18), nullable=False), sa.Column("event_name", sa.String(), nullable=False), sa.Column("block_number", sa.Integer(), nullable=False), sa.Column( @@ -112,6 +106,7 @@ def upgrade() -> None: def downgrade() -> None: + """ Base classes for ORM models. """ # ### commands auto generated by Alembic - please adjust! ### op.drop_index( op.f("ix_liquidation_event_data_event_name"), diff --git a/apps/data_handler/migrations/versions/aecc94d73ee6_add_new_event_models.py b/apps/data_handler/migrations/versions/aecc94d73ee6_add_new_event_models.py index e35c4e2e..06a37b04 100644 --- a/apps/data_handler/migrations/versions/aecc94d73ee6_add_new_event_models.py +++ b/apps/data_handler/migrations/versions/aecc94d73ee6_add_new_event_models.py @@ -21,6 +21,7 @@ def upgrade() -> None: + """ Upgrade the database """ conn = op.get_bind() @@ -180,6 +181,8 @@ def upgrade() -> None: def downgrade() -> None: + """ Downgrade the database """ + conn = op.get_bind() if conn.engine.dialect.has_table(conn, "withdrawal_event"): diff --git a/apps/data_handler/migrations/versions/c65693d53658_add_veriosn_field_for_.py b/apps/data_handler/migrations/versions/c65693d53658_add_veriosn_field_for_.py index c2e0ca2d..365645f2 100644 --- a/apps/data_handler/migrations/versions/c65693d53658_add_veriosn_field_for_.py +++ b/apps/data_handler/migrations/versions/c65693d53658_add_veriosn_field_for_.py @@ -19,10 +19,9 @@ def upgrade() -> None: + """ Function to apply the upgrade migrations. """ # ### Step 1: Add the new column as nullable ### - op.add_column( - "hashtack_collateral_debt", sa.Column("version", sa.Integer(), nullable=True) - ) + op.add_column("hashtack_collateral_debt", sa.Column("version", sa.Integer(), nullable=True)) # ### Step 2: Set default value (0) for existing records ### op.execute("UPDATE hashtack_collateral_debt SET version = 0") @@ -41,6 +40,7 @@ def upgrade() -> None: def downgrade() -> None: + """ Function to revert the upgrade migrations. """ # ### commands auto generated by Alembic - please adjust! ### op.drop_index( op.f("ix_hashtack_collateral_debt_version"), diff --git a/apps/data_handler/migrations/versions/d2fa8201b04a_add_loanstate_model.py b/apps/data_handler/migrations/versions/d2fa8201b04a_add_loanstate_model.py index 68843c80..1c73d8d2 100644 --- a/apps/data_handler/migrations/versions/d2fa8201b04a_add_loanstate_model.py +++ b/apps/data_handler/migrations/versions/d2fa8201b04a_add_loanstate_model.py @@ -22,6 +22,7 @@ def upgrade() -> None: + """ Base classes for ORM models. """ # Check if the table 'loan_state' already exists bind = op.get_bind() inspector = sa.inspect(bind) @@ -43,21 +44,16 @@ def upgrade() -> None: sa.Column("id", sa.UUID(), nullable=False), sa.PrimaryKeyConstraint("id"), ) - op.create_index( - op.f("ix_loan_state_block"), "loan_state", ["block"], unique=False - ) - op.create_index( - op.f("ix_loan_state_timestamp"), "loan_state", ["timestamp"], unique=False - ) - op.create_index( - op.f("ix_loan_state_user"), "loan_state", ["user"], unique=False - ) + op.create_index(op.f("ix_loan_state_block"), "loan_state", ["block"], unique=False) + op.create_index(op.f("ix_loan_state_timestamp"), "loan_state", ["timestamp"], unique=False) + op.create_index(op.f("ix_loan_state_user"), "loan_state", ["user"], unique=False) ### end Alembic commands ### else: print("Table 'loan_state' already exists, skipping creation.") def downgrade() -> None: + """ Base classes for ORM models. """ # ### commands auto generated by Alembic - please adjust! ### op.drop_index(op.f("ix_loan_state_user"), table_name="loan_state") op.drop_index(op.f("ix_loan_state_timestamp"), table_name="loan_state") diff --git a/apps/data_handler/migrations/versions/e4c7f75ff173_add_liquidable_debt_model.py b/apps/data_handler/migrations/versions/e4c7f75ff173_add_liquidable_debt_model.py index a0af8252..03ad9a35 100644 --- a/apps/data_handler/migrations/versions/e4c7f75ff173_add_liquidable_debt_model.py +++ b/apps/data_handler/migrations/versions/e4c7f75ff173_add_liquidable_debt_model.py @@ -21,6 +21,7 @@ def upgrade() -> None: + """Creates the 'liquidable_debt' table if it does not exist.""" # ### commands auto generated by Alembic - please adjust! ### bind = op.get_bind() inspector = sa.inspect(bind) @@ -43,6 +44,7 @@ def upgrade() -> None: def downgrade() -> None: + """Drops the 'liquidable_debt' table.""" # ### commands auto generated by Alembic - please adjust! ### op.drop_table("liquidable_debt") # ### end Alembic commands ### diff --git a/apps/data_handler/migrations/versions/e813bfbd573f_fix_merge_conflict.py b/apps/data_handler/migrations/versions/e813bfbd573f_fix_merge_conflict.py index e4dd8e5c..7990de39 100644 --- a/apps/data_handler/migrations/versions/e813bfbd573f_fix_merge_conflict.py +++ b/apps/data_handler/migrations/versions/e813bfbd573f_fix_merge_conflict.py @@ -22,14 +22,13 @@ def upgrade() -> None: + """ Function to apply the upgrade migrations. """ # Get the current database bind and create an inspector object bind = op.get_bind() inspector = sa.inspect(bind) # Check if columns already exist before adding them - existing_columns = [ - column["name"] for column in inspector.get_columns("liquidable_debt") - ] + existing_columns = [column["name"] for column in inspector.get_columns("liquidable_debt")] if "protocol_name" not in existing_columns: op.add_column( @@ -51,9 +50,7 @@ def upgrade() -> None: sa.Column("collateral_token", sa.String(), nullable=False), ) if "debt_token" not in existing_columns: - op.add_column( - "liquidable_debt", sa.Column("debt_token", sa.String(), nullable=False) - ) + op.add_column("liquidable_debt", sa.Column("debt_token", sa.String(), nullable=False)) # Drop columns if they exist if "protocol" in existing_columns: @@ -72,23 +69,18 @@ def upgrade() -> None: op.drop_column("liquidable_debt", "risk_adjusted_collateral") # Drop the index if it exists - existing_indexes = [ - index["name"] for index in inspector.get_indexes("liquidable_debt") - ] + existing_indexes = [index["name"] for index in inspector.get_indexes("liquidable_debt")] if "ix_liquidable_debt_user" in existing_indexes: op.drop_index("ix_liquidable_debt_user", table_name="liquidable_debt") # Add a new column to another table - existing_orderbook_columns = [ - column["name"] for column in inspector.get_columns("orderbook") - ] + existing_orderbook_columns = [column["name"] for column in inspector.get_columns("orderbook")] if "current_price" not in existing_orderbook_columns: - op.add_column( - "orderbook", sa.Column("current_price", sa.DECIMAL(), nullable=True) - ) + op.add_column("orderbook", sa.Column("current_price", sa.DECIMAL(), nullable=True)) def downgrade() -> None: + """ Function to apply the downgrade migrations. """ # ### commands auto generated by Alembic - please adjust! ### op.drop_column("orderbook", "current_price") op.add_column( @@ -132,13 +124,9 @@ def downgrade() -> None: ) op.add_column( "liquidable_debt", - sa.Column( - "protocol", sa.VARCHAR(length=255), autoincrement=False, nullable=False - ), - ) - op.create_index( - "ix_liquidable_debt_user", "liquidable_debt", ["user"], unique=False + sa.Column("protocol", sa.VARCHAR(length=255), autoincrement=False, nullable=False), ) + op.create_index("ix_liquidable_debt_user", "liquidable_debt", ["user"], unique=False) op.drop_column("liquidable_debt", "debt_token") op.drop_column("liquidable_debt", "collateral_token") op.drop_column("liquidable_debt", "collateral_token_price") diff --git a/apps/data_handler/migrations/versions/efd02f93572b_health_ratio_model_adjusted.py b/apps/data_handler/migrations/versions/efd02f93572b_health_ratio_model_adjusted.py index bda8cef9..45a39697 100644 --- a/apps/data_handler/migrations/versions/efd02f93572b_health_ratio_model_adjusted.py +++ b/apps/data_handler/migrations/versions/efd02f93572b_health_ratio_model_adjusted.py @@ -23,6 +23,8 @@ def upgrade() -> None: + """ Function to apply the upgrade migrations. """ + # ### commands auto generated by Alembic - please adjust! ### # Reflect the current state of the table @@ -43,6 +45,7 @@ def upgrade() -> None: def downgrade() -> None: + """ Function to revert the upgrade migrations. """ # ### commands auto generated by Alembic - please adjust! ### op.drop_column("health_ratio_level", "protocol_id") # ### end Alembic commands ### diff --git a/apps/data_handler/migrations/versions/fafbe0720bc8_add_hashtack_collateral_debt_model.py b/apps/data_handler/migrations/versions/fafbe0720bc8_add_hashtack_collateral_debt_model.py index 4bfa9ee7..b580963a 100644 --- a/apps/data_handler/migrations/versions/fafbe0720bc8_add_hashtack_collateral_debt_model.py +++ b/apps/data_handler/migrations/versions/fafbe0720bc8_add_hashtack_collateral_debt_model.py @@ -20,6 +20,7 @@ def upgrade() -> None: + """Creates the 'hashtack_collateral_debt' table if it does not exist.""" # Check if the table already exists conn = op.get_bind() inspector = Inspector.from_engine(conn) @@ -47,6 +48,7 @@ def upgrade() -> None: def downgrade() -> None: + """Drops the 'hashtack_collateral_debt' table.""" # Drop the index and table if it exists conn = op.get_bind() inspector = Inspector.from_engine(conn) diff --git a/apps/data_handler/tests/__init__.py b/apps/data_handler/tests/__init__.py index e69de29b..b70cc3df 100644 --- a/apps/data_handler/tests/__init__.py +++ b/apps/data_handler/tests/__init__.py @@ -0,0 +1,2 @@ +"""Module docstring placeholder.""" + diff --git a/apps/data_handler/tests/db_test/test_db_connector.py b/apps/data_handler/tests/db_test/test_db_connector.py index 28f7d9d8..8df2803b 100644 --- a/apps/data_handler/tests/db_test/test_db_connector.py +++ b/apps/data_handler/tests/db_test/test_db_connector.py @@ -49,8 +49,7 @@ def sample_batch_loan_states(): timestamp=1000000, block=12345, deposit=10.0, - ) - for i in range(3) + ) for i in range(3) ] return batch_loan_states @@ -120,9 +119,7 @@ def test_get_object_positive(mock_db_connector, sample_loan_state): mock_db_connector.get_object.return_value = sample_loan_state result = mock_db_connector.get_object(LoanState, sample_loan_state.id) assert result.user == "test_user" - mock_db_connector.get_object.assert_called_once_with( - LoanState, sample_loan_state.id - ) + mock_db_connector.get_object.assert_called_once_with(LoanState, sample_loan_state.id) def test_get_object_not_found(mock_db_connector): @@ -145,9 +142,7 @@ def test_delete_object_positive(mock_db_connector, sample_loan_state): """ mock_db_connector.delete_object.return_value = None mock_db_connector.delete_object(LoanState, sample_loan_state.id) - mock_db_connector.delete_object.assert_called_once_with( - LoanState, sample_loan_state.id - ) + mock_db_connector.delete_object.assert_called_once_with(LoanState, sample_loan_state.id) def test_get_latest_block_loans(mock_db_connector): @@ -211,9 +206,7 @@ def test_get_last_interest_rate_record_by_protocol_id(mock_db_connector): mock_db_connector.get_last_interest_rate_record_by_protocol_id.return_value = ( mock_interest_rate ) - result = mock_db_connector.get_last_interest_rate_record_by_protocol_id( - ProtocolIDs.ZKLEND - ) + result = mock_db_connector.get_last_interest_rate_record_by_protocol_id(ProtocolIDs.ZKLEND) assert result.protocol_id == ProtocolIDs.ZKLEND.value assert result.block == 12345 @@ -226,9 +219,7 @@ def test_write_batch_to_db(mock_db_connector, sample_batch_loan_states): :return: None """ mock_db_connector.write_batch_to_db(sample_batch_loan_states) - mock_db_connector.write_batch_to_db.assert_called_once_with( - sample_batch_loan_states - ) + mock_db_connector.write_batch_to_db.assert_called_once_with(sample_batch_loan_states) def test_get_loans(mock_db_connector, sample_batch_loan_states): @@ -253,9 +244,7 @@ def test_get_last_hashstack_loan_state(mock_db_connector, sample_hashstack_loan_ :param sample_hashstack_loan_state: Sample hashstack loan state :return: None """ - mock_db_connector.get_last_hashstack_loan_state.return_value = ( - sample_hashstack_loan_state - ) + mock_db_connector.get_last_hashstack_loan_state.return_value = (sample_hashstack_loan_state) result = mock_db_connector.get_last_hashstack_loan_state("test_user") assert result.user_id == "test_user" assert result.loan_id == 1 diff --git a/apps/data_handler/tests/db_test/test_initializer_db_connector.py b/apps/data_handler/tests/db_test/test_initializer_db_connector.py index 8f9075bf..ce83cf26 100644 --- a/apps/data_handler/tests/db_test/test_initializer_db_connector.py +++ b/apps/data_handler/tests/db_test/test_initializer_db_connector.py @@ -1,3 +1,4 @@ +""" This module contains tests for the InitializerDBConnector class. """ import pytest from data_handler.db.models import HashtackCollateralDebt, ZkLendCollateralDebt from sqlalchemy.exc import SQLAlchemyError @@ -38,9 +39,7 @@ def sample_hashtack_collateral_debt(): return hashtack_collateral_debt -def test_get_zklend_by_user_ids( - mock_initializer_db_connector, sample_zklend_collateral_debt -): +def test_get_zklend_by_user_ids(mock_initializer_db_connector, sample_zklend_collateral_debt): """ Test the get_zklend_by_user_ids method. :param mock_initializer_db_connector: Mock InitializerDBConnector @@ -55,9 +54,7 @@ def test_get_zklend_by_user_ids( assert result[0].user_id == "test_user" -def test_get_hashtack_by_loan_ids( - mock_initializer_db_connector, sample_hashtack_collateral_debt -): +def test_get_hashtack_by_loan_ids(mock_initializer_db_connector, sample_hashtack_collateral_debt): """ Test the get_hashtack_by_loan_ids method. :param mock_initializer_db_connector: Mock InitializerDBConnector diff --git a/apps/data_handler/tests/fetch_zklend_specific_token_settings.py b/apps/data_handler/tests/fetch_zklend_specific_token_settings.py index 116a5163..7c78353a 100644 --- a/apps/data_handler/tests/fetch_zklend_specific_token_settings.py +++ b/apps/data_handler/tests/fetch_zklend_specific_token_settings.py @@ -1,3 +1,4 @@ +""" Test the fetch_zklend_specific_token_settings.py """ import asyncio import decimal @@ -10,10 +11,21 @@ # Mock response for the on chain call (func_call) class MockBlockchainCall: + """Mock response for the on chain call (func_call)""" + async def func_call(self, addr, selector, calldata): + """ Mock response for the on chain call (func_call) """ return [ - {"name": "enabled", "type": "core::bool", "value": "1"}, - {"name": "decimals", "type": "core::felt252", "value": "18"}, + { + "name": "enabled", + "type": "core::bool", + "value": "1" + }, + { + "name": "decimals", + "type": "core::felt252", + "value": "18" + }, { "name": "z_token_address", "type": "core::starknet::contract_address::ContractAddress", @@ -88,51 +100,60 @@ async def func_call(self, addr, selector, calldata): class ProtocolAddresses: + """ Protocol addresses for the ZkLend protocol. """ + def __init__(self): self.ZKLEND_MARKET_ADDRESSES = "0xMarketAddress" src = type("src", (), {"blockchain_call": MockBlockchainCall()}) - # Mock TOKEN_SETTINGS dict TOKEN_SETTINGS: dict[str, TokenSettings] = { - "ETH": TokenSettings( + "ETH": + TokenSettings( symbol="ETH", decimal_factor=decimal.Decimal("1e18"), address="0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", ), - "wBTC": TokenSettings( + "wBTC": + TokenSettings( symbol="wBTC", decimal_factor=decimal.Decimal("1e8"), address="0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac", ), - "USDC": TokenSettings( + "USDC": + TokenSettings( symbol="USDC", decimal_factor=decimal.Decimal("1e6"), address="0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", ), - "DAI": TokenSettings( + "DAI": + TokenSettings( symbol="DAI", decimal_factor=decimal.Decimal("1e18"), address="0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3", ), - "USDT": TokenSettings( + "USDT": + TokenSettings( symbol="USDT", decimal_factor=decimal.Decimal("1e6"), address="0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", ), - "wstETH": TokenSettings( + "wstETH": + TokenSettings( symbol="wstETH", decimal_factor=decimal.Decimal("1e18"), address="0x042b8f0484674ca266ac5d08e4ac6a3fe65bd3129795def2dca5c34ecc5f96d2", ), - "LORDS": TokenSettings( + "LORDS": + TokenSettings( symbol="LORDS", decimal_factor=decimal.Decimal("1e18"), address="0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", ), - "STRK": TokenSettings( + "STRK": + TokenSettings( symbol="STRK", decimal_factor=decimal.Decimal("1e18"), address="0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", @@ -142,6 +163,7 @@ def __init__(self): # Function to call the mock of func_call async def get_token_reserve_data(token_setting_address: str): + """ Get the reserve data for a token setting address """ reserve_data = await src.blockchain_call.func_call( addr=ProtocolAddresses().ZKLEND_MARKET_ADDRESSES, selector="get_reserve_data", @@ -169,6 +191,7 @@ async def fetch_zklend_specific_token_settings(): # Test the functionality async def main(): + """ Test the functionality """ new_settings = await fetch_zklend_specific_token_settings() for symbol, settings in new_settings.items(): print(f"{symbol}: {settings}") diff --git a/apps/data_handler/tests/test_api_connector.py b/apps/data_handler/tests/test_api_connector.py index 0978ec74..2839b0e2 100644 --- a/apps/data_handler/tests/test_api_connector.py +++ b/apps/data_handler/tests/test_api_connector.py @@ -1,3 +1,4 @@ +""" This module contains tests for the DeRiskAPIConnector class. """ import os import unittest from unittest.mock import MagicMock, patch @@ -10,6 +11,7 @@ class TestDeRiskAPIConnector(unittest.TestCase): + """ Test class for the DeRiskAPIConnector class. """ DERISK_API_URL = "https://api.derisk.io" @patch.dict(os.environ, {"DERISK_API_URL": DERISK_API_URL}) @@ -29,9 +31,7 @@ def test_api_connector_raises_value_error_exception(self): def test_get_data_success(self, mock_get): """Test that DeRiskAPIConnector.get_data() returns data on a successful request.""" json_data = {"status": "success", "data": "some_data"} - mock_get.return_value = MagicMock( - status_code=200, json=MagicMock(return_value=json_data) - ) + mock_get.return_value = MagicMock(status_code=200, json=MagicMock(return_value=json_data)) connector = DeRiskAPIConnector() result = connector.get_data( @@ -44,7 +44,8 @@ def test_get_data_success(self, mock_get): mock_get.assert_called_once_with( self.DERISK_API_URL, params={ - "from_address": "0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05", + "from_address": + "0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05", "min_block_number": 630000, "max_block_number": 631000, }, diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..39dfce97 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[pycodestyle] +max_line_length = 100 +aggressive = 2